3 # topgit_recurse - TopGit awk utility script used by tg--awksome
4 # Copyright (C) 2017 Kyle J. McKay <mackyle@gmail.com>
10 # variable arguments (-v):
12 # brfile if non-empty, read TopGit branch names from here
13 # rmbr if true run system rm on brfile (after reading) if non-empty brfile
14 # anfile if non-empty, annihilated branch names are read from here
15 # rman if true run system rm on anfile (after reading) if non-empty anfile
16 # hdfile if non-empty, existing head names are read from here
17 # rmhd if true run system rm on hdfile (after reading) if non-empty hdfile
18 # cuthd if true extract and cut cuthd field of each "refs/heads/" line
19 # rtfile if non-empty, read non-annihilated remote tg branch names from here
20 # rmrt if true run system rm on rtfile (after reading) if non-empty rtfile
21 # usermt if non-empty output remote base lines using this prefix if in rtfile
22 # withan if true, include L == 2 output lines
23 # withbr if true, output a line for the top-level branch
24 # preord if true, output the branch line before its .topdeps instead of after
25 # exclbr whitespace separated list of names to exclude
26 # inclbr whitespace separated list of names to include
27 # startb whitespace separated list of start branch plus extra path items
28 # multib if "1" startb is list of multiple start nodes (with no extra path)
29 # filter if 1 or 2 output dependency instead of recurse lines (see below)
30 # once if > 0 nodes when 1st visited only if < 0 deps on 1st visit only
31 # leaves if true omit output lines where L != 1 (withbr recommended if set)
32 # tgonly if true only T != 0 (or M == 1) lines are output
33 # showlp if true output a :loop: line for any loops
35 # in multi start mode (multib is true) duplicate start names are ignored
36 # (using a true value for "multib" other than "1" may have undefined behavior)
38 # if inclbr is non-empty a branch name must be listed to appear on stdout
40 # if a branch name appears in exclbr it is omitted from stdout trumping inclbr
42 # except for branches removed entirely from consideration by exclbr/inclbr
43 # or !withan and being in anfile, any other branch found to be missing
44 # (i.e. no hdfile entry) will generate a missing (M=1) line regardless of
45 # any !withbr or leaves or tgonly settings
47 # annihilated branches listed in anfile may also appear in brfile without harm
48 # but they do not need to for correct results (i.e. same results either way)
50 # Note that if non-empty, usermt must be the FULL prefix for remote base ref
51 # names for example "refs/remotes/origin/top-bases" works (if that's the
52 # correct top-bases location of course)
54 # if a branch is excluded (either by not being in a non-empty inclbr list or
55 # by being listed in the exclbr list) then it will not be recursed into either
57 # to get accurate output, brfile, anfile and hdfile must all be provided and,
58 # obviously, if remote information is needed rtfile as well (usermt will be
59 # effectively ignored unless rtfile is provided)
61 # input is a list of edges as output by run_awk_topgit_deps
63 # using the startb starting point the graph is walked outputting one line for
64 # each visited node with the following format:
66 # M T L <node> [<parent> <branch> <chain> <names>]
68 # where M T L are single numeric digits with the following meanings:
70 # M=0 branch actually exists (i.e. it's NOT missing)
71 # M=1 branch does not exist but was named in a .topdeps or startb (if withbr)
73 # T=0 branch is NOT tgish or NOT local (missing and remotes are always 0)
74 # T=1 branch IS local tgish (annihilated branches are always 1)
75 # T=2 branch IS local tgish and has a non-annihilated remote tgish branch
77 # L=0 not a leaf node (missing and remotes are always 0)
78 # L=1 IS a leaf node (might or might NOT be tgish)
79 # L=2 an annihilated tgish branch (they can never be leaves anyway)
81 # contrary to the non-awk code this replaces, L == 1 IS possible with preord
83 # note that <node> will always be present and non-empty
84 # unless withbr is true then <parent> will also always be present and non-empty
85 # even if withbr IS true <parent> will be non-empty if extra path items were
86 # provided (the first path item becomes the parent and the rest the chain)
87 # the rest of the path items show the link chain from <node> up to <startb>
88 # with any extra path items output on the end
90 # An output line might look like this:
92 # 0 1 1 t/foo/leaf t/foo/int t/stage
94 # L=2 "a leaf" means any node that is either not a TopGit branch or is a
95 # non-annihilated TopGit branch with NO non-annihilated dependencies (that
96 # means NO non-tgish dependencies either)
98 # loops are detected and avoided (the link causing the loop is dropped) and
99 # if showlp is true a line like the following will be output whenever one is
102 # :loop: t/foo/int t/foo/leaf t/foo/int t/stage
104 # the two branch names immediately after LOOP show the link that was dropped
105 # to avoid the loop and the rest of the path is the normal branch chain
107 # in filter mode (filter is 1 or 2) the output is a list of deps (1) or
108 # edges (2) instead of recursion lines so the above sample output line would
109 # end up being this when filter mode is 1:
113 # to get the "patch series" list used for navigation use withbr=1 once=1 and
116 # and just this output when filter mode is 2:
118 # t/foo/int t/foo/leaf
120 # the first two node items from the recursion line are reversed (if withbr
121 # is active the single node name will be doubled to make an edge to itself)
122 # because the edge format is "<node-with-topdeps-line> <for-this-node>" whereas
123 # the normal recursion lines have the opposite order.
125 # extra items in startb are ignored in filter mode unless multib is "1" in
126 # which case they're then treated as additional starting nodes (just like
127 # normal multib=1 mode does).
129 # when filter mode is active the rtfile file and usermt settings are ignored
130 # (but rmrt will still work) while preord does work it's mostly pointless
131 # in filter mode. the leaves option still works exactly the same way as it's
132 # just the final format of the output line that's affected by filter mode not
133 # which lines (other than omitting remote lines) are output. Lines for
134 # missing (M == 1) items are, however, totally suppressed in filter mode
135 # since they're just not meaningful in that case. loop lines will still be
136 # output in exactly the same format if showlp is true.
138 # filter mode can be helpful when the intent is to ultimately run
139 # awk_topgit_navigate on a subset of the TopGit dependency tree
142 BEGIN { exitcode =
"" }
143 function exitnow
(e
) { exitcode=e
; exit e
}
144 END { if (exitcode
!= "") exit exitcode
}
148 cnt =
split(inclbr
, scratch
, " ")
151 for (i =
1; i
<= cnt
; ++i
) incnames
[scratch
[i
]] =
1
153 cnt =
split(exclbr
, scratch
, " ")
154 for (i =
1; i
<= cnt
; ++i
) excnames
[scratch
[i
]] =
1
155 cnt =
split(startb
, scratch
, " ")
159 for (i =
1; i
<= cnt
; ++i
) {
160 if (!seenstartbr
[scratch
[i
]]) {
161 startbr
[++startcnt
] = scratch
[i
]
162 extrabr
[startcnt
] =
""
163 seenstartr
[scratch
[i
]] =
1
167 startbr
[1] = scratch
[1]
169 for (i =
2; i
<= cnt
; ++i
) xtrapth = xtrapth
" " scratch
[i
]
170 extrabr
[1] = xtrapth
;
173 sub(/\
/+$
/, "", usermt
)
174 if (usermt
!= "") usermt = usermt
"/"
175 if (filter
!= "" && filter
!= 0 && filter
!= 1 && filter
!= 2) exitnow
(2)
178 function quotevar
(v
) {
179 gsub(/\047/, "\047\\\047\047", v
)
180 return "\047" v
"\047"
183 function init
(abranch
, _e
) {
186 while ((_e =
(getline abranch
<brfile
)) > 0) {
187 if (abranch
!= "") tgish
[abranch
] =
1
190 if (_e
< 0) exitnow
(2)
191 if (rmbr
) rmlist = rmlist
" " quotevar
(brfile
)
194 while ((_e =
(getline abranch
<anfile
)) > 0) {
195 if (abranch
!= "") ann
[abranch
] =
1
198 if (_e
< 0) exitnow
(2)
199 if (rman
) rmlist = rmlist
" " quotevar
(anfile
)
204 if (cuthd ~
/^
[1-9][0-9]*$
/) fno =
0 + cuthd
206 while ((_e =
(getline abranch
<hdfile
)) > 0) {
208 if (split(abranch
, scratch
, " ") < fno
|| length(scratch
[fno
]) < 12 ||
209 substr(scratch
[fno
], 1, 11) != "refs/heads/") continue
210 abranch =
substr(scratch
[fno
], 12)
211 sub(/[~
:^
].
*$
/, "", abranch
)
213 if (abranch
!= "") heads
[abranch
] =
1
216 if (_e
< 0) exitnow
(2)
217 if (rmhd
) rmlist = rmlist
" " quotevar
(hdfile
)
221 while ((_e =
(getline abranch
<rtfile
)) > 0) {
222 if (abranch
!= "") tgishr
[abranch
] =
1
225 if (_e
< 0) exitnow
(2)
227 if (rmrt
) rmlist = rmlist
" " quotevar
(rtfile
)
229 if (rmlist
!= "") system("rm -f" rmlist
)
232 function included
(abranch
) {
233 return (!inconly
|| incnames
[abranch
]) && !excnames
[abranch
]
238 NF ==
2 && $
1 != "" && $
2 != "" && $
1 != $
2 &&
239 included
($
1) && included
($
2) && !ann
[$
1] && (withan
|| !ann
[$
2]) {
242 if (index(" " linkval
" ", " " $
2 " ")) next
243 links
[$
1] = linkval
" " $
2
247 if (withan
&& !ann
[$
2]) {
248 # when using withan, linksx is the tree !withan would generate
249 # (it eXcludes all annihilated links)
250 # no need for it if !withan in effect as it would match links
252 if (linkval
!= "") linksx
[$
1] = linkval
" " $
2
257 function walktree
(node
, trail
, level
,
258 oncenodes
, istgish
, isleaf
, children
, childcnt
, parent
, child
, visited
, i
)
260 if (once
> 0 && (node in oncenodes
)) return
262 if (!filter
) print "1 0 0 " node trail
263 if (once
) oncenodes
[node
] =
1
267 parent =
substr(trail
, 2)
268 sub(/ .
*$
/, "", parent
)
269 if (parent ==
"") parent = node
277 } else if (tgish
[node
]) {
278 istgish = tgishr
[node
] ?
2 : 1
280 if (isleaf
!= 2) isleaf = !istgish
|| (withan?linksx
[node
]:links
[node
]) ==
""
281 if (preord
&& (level
> 0 || withbr
) && (!leaves
|| isleaf ==
1) && (!tgonly
|| istgish
)) {
282 if (filter
) print parent node
283 else print "0 " istgish
" " isleaf
" " node trail
285 if (!once
|| !
(node in oncenodes
)) {
286 if (istgish ==
2 && usermt
&& !leaves
&& !tgonly
) print "0 0 0 " usermt node
" " node trail
287 if ((childcnt =
split(links
[node
], children
, " "))) {
288 visited =
" " node trail
" "
289 for (i =
1; i
<= childcnt
; ++i
) {
291 if (index(visited
, " " child
" ")) {
292 if (showlp
) print ":loop: " child
" " node trail
295 walktree
(child
, " " node trail
, level
+ 1, oncenodes
)
299 if (!preord
&& (level
> 0 || withbr
) && (!leaves
|| isleaf ==
1) && (!tgonly
|| istgish
)) {
300 if (filter
) print parent node
301 else print "0 " istgish
" " isleaf
" " node trail
303 if (once
) oncenodes
[node
] =
1
307 for (startidx =
1; startidx
<= startcnt
; ++startidx
) {
308 astart = startbr
[startidx
]
309 if (included
(astart
) && (!heads
[astart
] || withan
|| !ann
[astart
]))
310 walktree
(astart
, extrabr
[startidx
], 0)