tg-annihilate.sh: autostash and support --stash and --no-stash
[topgit/pro.git] / tg--awksome.sh
blob05aff7e68de256b56ec6b94148f507230e096565
1 #!/bin/sh
2 # TopGit awk scripts and related utility functions
3 # Copyright (C) 2017 Kyle J. McKay <mackyle@gmail.com>
4 # All rights reserved.
5 # License GPLv2
7 ## Several awk scripts are used to quickly process TopGit branches and
8 ## their associated .topdeps files and with the exception of $gcfbo
9 ## (which can be left unset) and the get_temp function, can be used
10 ## independently; a suitable and simple get_temp function can be just:
12 ## get_temp() { mktemp "${TMPDIR:-/tmp}/${1:-temp}-XXXXXX"; }
14 ## The utility functions all require use of the '%(rest)' atom format
15 ## introduced with Git v1.8.5 for use with git cat-file --batch and
16 ## --batch-check options. Although it would be possible to simulate
17 ## that functionality it would require a lot of extra processing that
18 ## use of '%(rest)' eliminates so no attempt is made to support earlier
19 ## versions of git.
21 ## Note that the $gcfbopt variable may contain the value "--buffer"
22 ## if Git is version 2.6.0 or later for increased acceleration
23 ## (Git Cat-File Buffer OPTion)
25 ## NOTE: This file contains only function definitions, variable assignments
26 # and checks for the existence of the corresponding script files but
27 # has no other non-function shell code than that
29 [ -n "$TG_INST_AWKDIR" ] || {
30 TG_INST_AWKDIR="${TG_INST_CMDDIR%/}"
31 [ -n "$TG_INST_AWKDIR" ] ||
32 case "$0" in *"/"*[!/]) TG_INST_AWKDIR="${0%/*}"; esac
33 [ -z "$TG_INST_AWKDIR" ] || TG_INST_AWKDIR="$TG_INST_AWKDIR/"
34 TG_INST_AWKDIR="${TG_INST_AWKDIR}awk"
37 # die with a fatal programmer error bug
38 run_awk_bug()
40 printf 'fatal: [BUG] programmer error\n%s%s\n' \
41 'fatal: [BUG] ' "$*" >&2
42 exit 2
45 # verify an awk script exists, is a file and is readable or die
46 run_awk_require()
48 while [ $# -gt 0 ]; do
49 [ -f "$TG_INST_AWKDIR/$1" ] && [ -r "$TG_INST_AWKDIR/$1" ] || {
50 printf '%s\n' "fatal: awksome: missing awk script '$TG_INST_AWKDIR/$1'" >&2
51 exit 2
53 shift
54 done
57 # run_awk_ref_match [opts] [<for-each-ref-pattern>...]
59 # --sort=<key> sort key (repeatable) last is primary same names as --format
60 # --count=<max> stop after this many matches
61 # --format=<fmt> for-each-ref style format but only objname and refname ok
62 # -p input is packed-refs style rather than "<ref> <hash>"
64 # input is one ref + hash table entry per line either packed-refs style or
65 # ref first then hash where nonsensical lines are ignored
67 # output is one "format expansion" per match but stopping after --count (if given)
69 # the <fmt> works like for-each-ref except that only the "refname" and "objectname"
70 # (and %-escapes) get expanded; well, actually, %(objecttype) is expeanded too, but
71 # always to the constant string "object" (unquoted) rather than a real type
73 # the sort keys work like for-each-ref too except that keys other than refname
74 # and/or objectname are ignored but reverse (leading "-") IS supported
76 run_awk_require "ref_match"
77 run_awk_ref_match()
79 _ra_pckdrefs=
80 _ra_sortkey=
81 _ra_maxout=
82 _ra_matchfmt=
83 while [ $# -gt 0 ]; do case "$1" in
84 -p) _ra_pckdrefs=1;;
85 --sort=*) _ra_sortkey="${_ra_sortkey:+$_ra_sortkey,}${1#--sort=}";;
86 --count=*) _ra_maxout="${1#--count=}";;
87 --format=*) _ra_matchfmt="${1#--format=}";;
88 --) shift; break;;
89 -?*) run_awk_bug "unknown run_awk_ref_match option: $1";;
90 *) break;;
91 esac; shift; done
92 awk -f "$TG_INST_AWKDIR/ref_match" \
93 -v "pckdrefs=$_ra_pckdrefs" \
94 -v "sortkey=$_ra_sortkey" \
95 -v "maxout=$_ra_maxout" \
96 -v "matchfmt=$_ra_matchfmt" \
97 -v "patterns=$*"
100 # run_awk_ref_prefixes [opts] <prefix1> <prefix2> [<prefixh>]
102 # -p input is packed-refs format rather than full ref in first field
103 # -e on error when both prefixes present use the default (prefix1) instead
104 # -n no default instead error out
106 # input is one fully qualified ref name per line either in the first
107 # field (default) or in packed-refs format (second field) with -p
108 # (input need not be sorted in any particular order even with a third argument)
110 # on success output is <prefix1> or <prefix2) (with trailing slashes stripped)
111 # on error (both prefixes found and status 65) there's no output but -e will
112 # change that to be success with <prefix1> instead; using -n will cause an
113 # exit with status 66 if neither prefix is found rather than using <prefix1>
115 run_awk_require "ref_prefixes"
116 run_awk_ref_prefixes()
118 _ra_pckdrefs=
119 _ra_noerr=
120 _ra_nodef=
121 while [ $# -gt 0 ]; do case "$1" in
122 -p) _ra_pckdrefs=1;;
123 -e) _ra_noerr=1;;
124 -n) _ra_nodef=1;;
125 --) shift; break;;
126 -?*) run_awk_bug "unknown run_awk_ref_prefixes option: $1";;
127 *) break;;
128 esac; shift; done
129 [ $# -ge 2 ] && [ $# -le 3 ] ||
130 run_awk_bug "run_awk_ref_prefixes requires exactly two or three non-option arguments"
131 awk -f "$TG_INST_AWKDIR/ref_prefixes" \
132 -v "pckdrefs=$_ra_pckdrefs" \
133 -v "noerr=$_ra_noerr" \
134 -v "nodef=$_ra_nodef" \
135 -v "prefix1=$1" \
136 -v "prefix2=$2" \
137 -v "prefixh=$3"
140 # run_awk_topgit_branches [opts] <bases-prefix> [<for-each-ref-pattern>...]
142 # -n omit annihilated branches from output and -b=<file> (but not -a=<file>)
143 # -h=<head> full ref prefix of heads location (default usually works fine)
144 # -a=<file> write the TopGit branch names one per line of annihilated branches
145 # -b=<file> write a copy of the stdout into here
146 # -p=<file> write a copy of the ref prepare output into here
147 # -r=<file> read substitute refs list from here
148 # -p refs list is in packed-refs format rather than "<ref> <hash>"
149 # -rmr remove -r= <file> after it's been read (convenience knob)
150 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
151 # -x=<exbr> whitespace separated list of branch names to exclude
152 # -db enable output of .topdeps blob lines
153 # -mb enable output of .topmsg blob lines
154 # -td=<tdbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topdeps
155 # -tm=<tmbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topmsg
157 # If given, the -a and -b files are always truncated if there are no matching
158 # branches for them
160 # input is a full top-bases prefix (i.e. "refs/top-bases" or
161 # "refs/remotes/origin/top-bases" or similar) and zero or more for-each-ref
162 # patterns the default being <bases-prefix> if no patterns are given
164 # note that <bases-prefix> is required because this file is independent and
165 # there is no universal default available
167 # output is one TopGit branch name per line (not output until after any -a or -b
168 # files have been fully written and closed)
170 # if -n is used annihilated branches will be omitted from the output (and any
171 # -b=<file>)
173 # If -b=<file> is used it will get a copy of the output being truncated if the
174 # output is empty
176 # If -a=<file> is used it will get a list of annihilated branches (regardless of
177 # whether or not -n is used) and will be truncated if there are none
179 # Note that since git for-each-ref sorts its results the output will always be
180 # sorted by branch name (including the -a=<file> output)
182 # Note that this function does NOT call get_temp
184 run_awk_require "ref_prepare" "topgit_branches"
185 run_awk_topgit_branches()
187 _ra_noann=
188 _ra_headbase=
189 _ra_anfile=
190 _ra_brfile=
191 _ra_inclbr=
192 _ra_exclbr=
193 _ra_depsblob=
194 _ra_msgblob=
195 _ra_topdeps=
196 _ra_topmsg=
197 _ra_teeout=
198 _ra_pckdrefs=
199 _ra_refsfile=
200 _ra_rmrf=
201 while [ $# -gt 0 ]; do case "$1" in
202 -n) _ra_noann=1;;
203 -p) _ra_pckdrefs=1;;
204 -h=*) _ra_headbase="${1#-h=}";;
205 -a=*) _ra_anfile="${1#-a=}";;
206 -b=*) _ra_brfile="${1#-b=}";;
207 -i=*) _ra_inclbr="${1#-i=}";;
208 -x=*) _ra_exclbr="${1#-x=}";;
209 -db) _ra_depsblob=1;;
210 -mb) _ra_msgblob=1;;
211 -td=*) _ra_topdeps="${1#-td=}";;
212 -tm=*) _ra_topmsg="${1#-tm=}";;
213 -p=*) _ra_teeout="${1#-p=}";;
214 -r=*) _ra_refsfile="${1#-r=}";;
215 -rmr) _ra_rmrf=1;;
216 --) shift; break;;
217 -?*) run_awk_bug "unknown run_awk_topgit_branches option: $1";;
218 *) break;;
219 esac; shift; done
220 [ -n "$1" ] || run_awk_bug "run_awk_topgit_branches missing <bases-prefix>"
221 _ra_topbases="$1"
222 shift
223 [ $# -gt 0 ] || set -- "$_ra_topbases"
225 if [ -n "$_ra_refsfile" ]; then
226 run_awk_ref_match ${_ra_pckdrefs:+-p} <"$_ra_refsfile" \
227 --format="%(refname)" "$@"
228 else
229 git for-each-ref --format="%(refname)" "$@"
232 awk -f "$TG_INST_AWKDIR/ref_prepare" \
233 -v "topbases=$_ra_topbases" \
234 -v "headbase=$_ra_headbase" \
235 -v "rmrf=$_ra_rmrf" \
236 -v "refsfile=$_ra_refsfile" \
237 -v "depsblob=$_ra_depsblob" \
238 -v "msgblob=$_ra_msgblob" \
239 -v "topdeps=$_ra_topdeps" \
240 -v "topmsg=$_ra_topmsg" \
241 -v "pckdrefs=$_ra_pckdrefs" \
242 -v "teeout=$_ra_teeout" |
243 git cat-file $gcfbopt --batch-check='%(objectname) %(objecttype) %(rest)' |
244 awk -f "$TG_INST_AWKDIR/topgit_branches" \
245 -v "noann=$_ra_noann" \
246 -v "anfile=$_ra_anfile" \
247 -v "brfile=$_ra_brfile" \
248 -v "inclbr=$_ra_inclbr" \
249 -v "exclbr=$_ra_exclbr"
252 # run_awk_topgit_msg [opts] <bases-prefix> [<for-each-ref-pattern>...]
254 # -c do simple column formatting if more than one output column
255 # -n omit annihilated branches from output and -b=<file> (but not -a=<file>)
256 # -nokind omit the kind column from the output
257 # -mt=<mt> empty branch treatment ("" = like ann; true = include; false not)
258 # -h=<head> full ref prefix of heads location (default usually works fine)
259 # -a=<file> write the TopGit branch names one per line of annihilated branches
260 # -b=<file> write the TopGit branch names one per line into here
261 # -p=<file> write a copy of the ref prepare output into here
262 # -r=<file> read substitute refs list from here
263 # -p refs list is in packed-refs format rather than "<ref> <hash>"
264 # -rmr remove -r= <file> after it's been read (convenience knob)
265 # -m=<hash> use this hash for missing .topmsg files (use empty blob if given)
266 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
267 # -x=<exbr> whitespace separated list of branch names to exclude
268 # -db enable output of .topdeps blob lines
269 # -mb ignored (output of .topmsg blob lines is always enabled)
270 # -td=<tdbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topdeps
271 # -tm=<tmbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topmsg
272 # --list a convenience macro that sets -c -n -mt=1 -nokind
274 # If given, the -a and -b files are always truncated if there are no matching
275 # branches for them
277 # input is a full top-bases prefix (i.e. "refs/top-bases" or
278 # "refs/remotes/origin/top-bases" or similar) and zero or more for-each-ref
279 # patterns the default being <bases-prefix> if no patterns are given
281 # note that <bases-prefix> is required because this file is independent and
282 # there is no universal default available
284 # output is one TopGit branch edge (i.e. two space-separated TopGit branch
285 # names) per line (not output until after any -a or -b files have been fully
286 # written and closed)
288 # if -n is used annihilated branches will be omitted from the output (and any
289 # -b=<file> if used), but if -a=<file> is NOT used then get_temp WILL be called
290 # but the file will be subsequently removed before returning
292 # Note that using -a=<file> does NOT exclude annihilated branches (but does
293 # put them into that file); only the -n option will exclude annihilated branches
295 # If -b=<file> is used it will get a copy of the output being truncated if the
296 # output is empty
298 # If -a=<file> is used it will get a list of annihilated branches (regardless of
299 # whether or not -n is used) and will be truncated if there are none
301 # Note that since git for-each-ref sorts its results the output will always be
302 # sorted by branch name (always for the -a=<file> and -b=<file> output) but
303 # that will only be the case for the stdout results for the first (second with
304 # -r) branch name on each stdout line
306 # With -s a "self" link is output for each branch after all the .topdeps
307 # entries (before with -r) have been output for that branch and the entries are
308 # output in .topdeps order (reverse order with -r)
310 # Note that using -s and omitting -n is NOT enough to make a self link for
311 # annihilated branches appear in the output; in order for them to appear (and
312 # also any TopGit branches lacking a .topdeps file) the -m= option must be
313 # used and the value passed must be the empty blob's hash
315 # Each "link" line output is <branch_name> <.topdeps_entry> (reversed with -r)
317 # Note that this function WILL call get_temp if -a= is NOT given AND -n is used
318 # (but it will remove the temp file before returning)
320 run_awk_require "ref_prepare" "topgit_msg_prepare" "topgit_msg"
321 run_awk_topgit_msg()
323 _ra_colfmt=
324 _ra_nokind=
325 _ra_withan=1
326 _ra_withmt=
327 _ra_headbase=
328 _ra_anfile=
329 _ra_rman=
330 _ra_brfile=
331 _ra_rmbr=
332 _ra_missing=
333 _ra_inclbr=
334 _ra_exclbr=
335 _ra_depsblob=
336 _ra_topdeps=
337 _ra_topmsg=
338 _ra_teeout=
339 _ra_pckdrefs=
340 _ra_refsfile=
341 _ra_rmrf=
342 while [ $# -gt 0 ]; do case "$1" in
343 -c) _ra_colfmt=1;;
344 -n) _ra_withan=;;
345 -p) _ra_pckdrefs=1;;
346 -mt=*) _ra_withmt="${1#-mt=}";;
347 -nokind) _ra_nokind=1;;
348 -h=*) _ra_headbase="${1#-h=}";;
349 -a=*) _ra_anfile="${1#-a=}";;
350 -b=*) _ra_brfile="${1#-b=}";;
351 -r=*) _ra_refsfile="${1#-r=}";;
352 -rmr) _ra_rmrf=1;;
353 -m=*) _ra_missing="${1#-m=}";;
354 -i=*) _ra_inclbr="${1#-i=}";;
355 -x=*) _ra_exclbr="${1#-x=}";;
356 -db) _ra_depsblob=1;;
357 -mb) ;;
358 -tm=*) _ra_topmsg="${1#-tm=}";;
359 -td=*) _ra_topdeps="${1#-td=}";;
360 -p=*) _ra_teeout="${1#-p=}";;
361 --list)
362 _ra_colfmt=1
363 _ra_withan=
364 _ra_withmt=1
365 _ra_nokind=1
367 --) shift; break;;
368 -?*) run_awk_bug "unknown run_awk_topgit_msg option: $1";;
369 *) break;;
370 esac; shift; done
371 [ -n "$1" ] || run_awk_bug "run_awk_topgit_msg missing <bases-prefix>"
372 _ra_topbases="$1"
373 shift
374 [ $# -gt 0 ] || set -- "$_ra_topbases"
375 if [ -z "$_ra_withan" ] && [ -z "$_ra_anfile" ]; then
376 _ra_anfile="$(get_temp rawk_annihilated)" || return 2
379 if [ -n "$_ra_refsfile" ]; then
380 run_awk_ref_match ${_ra_pckdrefs:+-p} <"$_ra_refsfile" \
381 --format="%(refname)" "$@"
382 else
383 git for-each-ref --format="%(refname)" "$@"
386 awk -f "$TG_INST_AWKDIR/ref_prepare" \
387 -v "topbases=$_ra_topbases" \
388 -v "headbase=$_ra_headbase" \
389 -v "rmrf=$_ra_rmrf" \
390 -v "refsfile=$_ra_refsfile" \
391 -v "depsblob=$_ra_depsblob" \
392 -v "msgblob=1" \
393 -v "topdeps=$_ra_topdeps" \
394 -v "topmsg=$_ra_topmsg" \
395 -v "pckdrefs=$_ra_pckdrefs" \
396 -v "teeout=$_ra_teeout" |
397 git cat-file $gcfbopt --batch-check='%(objectname) %(objecttype) %(rest)' |
398 awk -f "$TG_INST_AWKDIR/topgit_msg_prepare" \
399 -v "withan=$_ra_withan" \
400 -v "withmt=$_ra_withmt" \
401 -v "depsblob=$_ra_depsblob" \
402 -v "anfile=$_ra_anfile" \
403 -v "brfile=$_ra_brfile" \
404 -v "missing=$_ra_missing" |
405 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)' | tr '\0' '\27' |
406 awk -f "$TG_INST_AWKDIR/topgit_msg" \
407 -v "withan=$_ra_withan" \
408 -v "anfile=$_ra_anfile" \
409 -v "rman=$_ra_rman" \
410 -v "withmt=$_ra_withmt" \
411 -v "brfile=$_ra_brfile" \
412 -v "rmbr=$_ra_rmbr" \
413 -v "nokind=$_ra_nokind" \
414 -v "colfmt=$_ra_colfmt" \
415 -v "inclbr=$_ra_inclbr" \
416 -v "exclbr=$_ra_exclbr"
419 # run_awk_topmsg_header [-r=<regex>] [--stdin] [opts] <branch_name> [blobish]
421 # -kind include the kind field just before the description (normally omitted)
422 # -name include the branch name field as a first field (normally omitted)
424 # with --stdin, the contents of a .topmsg file should be on stdin
425 # otherwise if blobish is given it provides the input otherwise
426 # refs/heads/<branch_name>:.topdeps^{blob} does
428 # with -r=<regex> a case-insensitive keyword regular expression (always
429 # has to match the entire keyword name) instead of "Subject" can be used
430 # to extract other headers ("Subject" is the default though)
432 # output will be the subject (or a suitable description if there is no .topmsg
433 # or blobish or the input is empty); for non-subject keywords if the header
434 # is not present just the empty string is output use -r="(Subject)" to do the
435 # same thing for the subject header and avoid descriptive "missing" text
437 # "<branch_name>" is always required because it's used in the output message
438 # whenever the "Subject:" header line is not present (for whatever reason)
440 run_awk_require "topgit_msg"
441 run_awk_topmsg_header()
443 _usestdin=
444 _ra_kwregex=
445 _ra_colfmt=
446 _ra_nokind=1
447 _ra_noname=1
448 while [ $# -gt 0 ]; do case "$1" in
449 -c) _ra_colfmt=1;;
450 -kind) _ra_nokind=;;
451 -name) _ra_noname=;;
452 -r=*) _ra_kwregex="${1#-r=}";;
453 --stdin) _usestdin=1;;
454 --) shift; break;;
455 -?*) run_awk_bug "unknown run_awk_topmsg_header option: $1";;
456 *) break;;
457 esac; shift; done
458 if [ -n "$_usestdin" ]; then
459 [ $# -eq 1 ] || run_awk_bug "run_awk_topmsg_header --stdin requires exactly 1 arg"
460 [ -n "$1" ] || run_awk_bug "run_awk_topmsg_header --stdin requires a branch name"
461 else
462 [ $# -le 2 ] || run_awk_bug "run_awk_topmsg_header allows at most 2 args"
463 [ $# -ge 1 ] && [ -n "$1" ] ||
464 run_awk_bug "run_awk_topmsg_header requires a branch name"
466 if [ -n "$_usestdin" ]; then
467 printf 'blob 32767 0 %s\n' "$1"
468 tr '\0' '\27'
469 else
470 printf '%s 0 %s\n' "${2:-refs/heads/$1:.topmsg}" "$1" |
471 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)' |
472 tr '\0' '\27' | awk -v "bn=$1" \
473 '1 == NR && $2 == "missing" {printf "blob 0 1 %s\n\n", bn; next}{print}'
474 fi |
475 awk -f "$TG_INST_AWKDIR/topgit_msg" \
476 -v "noname=$_ra_noname" \
477 -v "nokind=$_ra_nokind" \
478 -v "kwregex=$_ra_kwregex" \
479 -v "only1=1" \
480 -v "colfmt=$_ra_colfmt"
483 # run_awk_topgit_deps [opts] <bases-prefix> [<for-each-ref-pattern>...]
485 # -n omit annihilated branches from output and -b=<file> (but not -a=<file>)
486 # -t only output tgish deps (i.e. dep is listed in -b=<file>)
487 # -r reverse the dependency graph
488 # -s include a link to itself for each branch
489 # -h=<head> full ref prefix of heads location (default usually works fine)
490 # -a=<file> write the TopGit branch names one per line of annihilated branches
491 # -b=<file> write the TopGit branch names one per line into here
492 # -p=<file> write a copy of the ref prepare output into here
493 # -r=<file> read substitute refs list from here
494 # -p refs list is in packed-refs format rather than "<ref> <hash>"
495 # -rmr remove -r= <file> after it's been read (convenience knob)
496 # -m=<hash> use this hash for missing .topdeps files (use empty blob if given)
497 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
498 # -x=<exbr> whitespace separated list of branch names to exclude
499 # -db ignored (output of .topdeps blob lines is always enabled)
500 # -mb enable output of .topmsg blob lines
501 # -td=<tdbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topdeps
502 # -tm=<tmbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topmsg
504 # If given, the -a and -b files are always truncated if there are no matching
505 # branches for them
507 # input is a full top-bases prefix (i.e. "refs/top-bases" or
508 # "refs/remotes/origin/top-bases" or similar) and zero or more for-each-ref
509 # patterns the default being <bases-prefix> if no patterns are given
511 # note that <bases-prefix> is required because this file is independent and
512 # there is no universal default available
514 # output is one TopGit branch edge (i.e. two space-separated TopGit branch
515 # names) per line (not output until after any -a or -b files have been fully
516 # written and closed)
518 # if -n is used annihilated branches will be omitted from the output (and any
519 # -b=<file> if used), but if -a=<file> is NOT used then get_temp WILL be called
520 # but the file will be subsequently removed before returning
522 # Note that using -a=<file> does NOT exclude annihilated branches (but does
523 # put them into that file); only the -n option will exclude annihilated branches
525 # If -b=<file> is used it will get a copy of the output being truncated if the
526 # output is empty
528 # If -a=<file> is used it will get a list of annihilated branches (regardless of
529 # whether or not -n is used) and will be truncated if there are none
531 # Note that since git for-each-ref sorts its results the output will always be
532 # sorted by branch name (always for the -a=<file> and -b=<file> output) but
533 # that will only be the case for the stdout results for the first (second with
534 # -r) branch name on each stdout line
536 # With -s a "self" link is output for each branch after all the .topdeps
537 # entries (before with -r) have been output for that branch and the entries are
538 # output in .topdeps order (reverse order with -r)
540 # Note that using -s and omitting -n is NOT enough to make a self link for
541 # annihilated branches appear in the output; in order for them to appear (and
542 # also any TopGit branches lacking a .topdeps file) the -m= option must be
543 # used and the value passed must be the empty blob's hash
545 # Each "link" line output is <branch_name> <.topdeps_entry> (reversed with -r)
547 # Note that this function WILL call get_temp if -a= is NOT given AND -n is used
548 # OR -b= is NOT given AND -t is used
549 # (but it will remove the temp file(s) before returning)
551 run_awk_require "ref_prepare" "topgit_deps_prepare" "topgit_deps"
552 run_awk_topgit_deps()
554 _ra_noann=
555 _ra_tgonly=
556 _ra_rev=
557 _ra_withbr=
558 _ra_withan=
559 _ra_headbase=
560 _ra_anfile=
561 _ra_rman=
562 _ra_brfile=
563 _ra_rmbr=
564 _ra_missing=
565 _ra_inclbr=
566 _ra_exclbr=
567 _ra_msgblob=
568 _ra_topdeps=
569 _ra_topmsg=
570 _ra_teeout=
571 _ra_pckdrefs=
572 _ra_refsfile=
573 _ra_rmrf=
574 while [ $# -gt 0 ]; do case "$1" in
575 -n) _ra_noann=1;;
576 -t) _ra_tgonly=1;;
577 -r) _ra_rev=1;;
578 -s) _ra_withbr=1;;
579 -p) _ra_pckdrefs=1;;
580 -h=*) _ra_headbase="${1#-h=}";;
581 -a=*) _ra_anfile="${1#-a=}";;
582 -b=*) _ra_brfile="${1#-b=}";;
583 -r=*) _ra_refsfile="${1#-r=}";;
584 -rmr) _ra_rmrf=1;;
585 -m=*) _ra_missing="${1#-m=}";;
586 -i=*) _ra_inclbr="${1#-i=}";;
587 -x=*) _ra_exclbr="${1#-x=}";;
588 -db) ;;
589 -mb) _ra_msgblob=1;;
590 -tm=*) _ra_topmsg="${1#-tm=}";;
591 -td=*) _ra_topdeps="${1#-td=}";;
592 -p=*) _ra_teeout="${1#-p=}";;
593 --) shift; break;;
594 -?*) run_awk_bug "unknown run_awk_topgit_deps option: $1";;
595 *) break;;
596 esac; shift; done
597 [ -n "$1" ] || run_awk_bug "run_awk_topgit_deps missing <bases-prefix>"
598 _ra_topbases="$1"
599 shift
600 [ $# -gt 0 ] || set -- "$_ra_topbases"
601 if [ -n "$_ra_noann" ] && [ -z "$_ra_anfile" ]; then
602 _ra_rman=1
603 _ra_anfile="$(get_temp rawk_annihilated)" || return 2
605 if [ -n "$_ra_tgonly" ] && [ -z "$_ra_brfile" ]; then
606 _ra_rmbr=1
607 _ra_brfile="$(get_temp rawk_branches)" || return 2
609 [ -n "$_ra_noann" ] || _ra_withan=1
611 if [ -n "$_ra_refsfile" ]; then
612 run_awk_ref_match ${_ra_pckdrefs:+-p} <"$_ra_refsfile" \
613 --format="%(refname)" "$@"
614 else
615 git for-each-ref --format="%(refname)" "$@"
618 awk -f "$TG_INST_AWKDIR/ref_prepare" \
619 -v "topbases=$_ra_topbases" \
620 -v "headbase=$_ra_headbase" \
621 -v "rmrf=$_ra_rmrf" \
622 -v "refsfile=$_ra_refsfile" \
623 -v "depsblob=1" \
624 -v "msgblob=$_ra_msgblob" \
625 -v "topdeps=$_ra_topdeps" \
626 -v "topmsg=$_ra_topmsg" \
627 -v "pckdrefs=$_ra_pckdrefs" \
628 -v "teeout=$_ra_teeout" |
629 git cat-file $gcfbopt --batch-check='%(objectname) %(objecttype) %(rest)' |
630 awk -f "$TG_INST_AWKDIR/topgit_deps_prepare" \
631 -v "noann=$_ra_noann" \
632 -v "anfile=$_ra_anfile" \
633 -v "brfile=$_ra_brfile" \
634 -v "missing=$_ra_missing" |
635 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)' | tr '\0' '\27' |
636 awk -f "$TG_INST_AWKDIR/topgit_deps" \
637 -v "withan=$_ra_withan" \
638 -v "anfile=$_ra_anfile" \
639 -v "rman=$_ra_rman" \
640 -v "withbr=$_ra_withbr" \
641 -v "brfile=$_ra_brfile" \
642 -v "rmbr=$_ra_rmbr" \
643 -v "tgonly=$_ra_tgonly" \
644 -v "rev=$_ra_rev" \
645 -v "inclbr=$_ra_inclbr" \
646 -v "exclbr=$_ra_exclbr"
649 # run_awk_topgit_recurse [opts] <branch> [<name>...]
651 # -d output ":loop: some bad branch chain path" for any detected loops
652 # -n omit annihilated branches (L == 2) from output
653 # -f output branch first before dependencies (i.e. preorder not post)
654 # -s include a line for the starting node (recommended when using -m)
655 # -l only output L == 1 lines (leaves)
656 # -t only output T != 0 lines (tgish)
657 # -m activate multimode, any [<name>...] are more branches to process
658 # -e=<type> emit only dependency graph edges (2) or deps (1) (aka filter mode)
659 # -o=<once> only output node on 1st visit (1) or visit its deps only 1st (-1)
660 # -u=<remt> prefix for -r=<file> branch names (e.g. "refs/remotes/x/top-bases")
661 # -c=<hfld> use only field <hfld> from -h= and cut off leading "refs/heads/"
662 # -b=<file> file with list of TopGit branches (one per line, e.g. "t/foo")
663 # -rmb remove -b= <file> after it's been read (convenience knob)
664 # -a=<file> file with list of annihilated TopGit branches
665 # -rma remove -a= <file> after it's been read (convenience knob)
666 # -h=<file> file with list of existing "refs/heads/..." refs (see -c=)
667 # -rmh remove -h= <file> after it's been read (convenience knob)
668 # -r=<file> file with list of TopGit branches with a remote (one per line)
669 # -rmr remove -r= <file> after it's been read (convenience knob)
670 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
671 # -x=<exbr> whitespace separated list of branch names to exclude
672 # --series a convenience macro that sets -e=1 -o=1 -s -n -t options
674 # REQUIRED OPTIONS: -a=<file> -b=<file>
676 # But, if -h=<file> is omitted, get_temp WILL be called!
678 # Note that it is guaranteed to be safe to use the same name for the -a and -b
679 # options of a run_awk_topgit_deps command that is piping output into this
680 # command because they are guaranteed to be fully written and closed by that
681 # command before it outputs the first line and this command is guaranteed to
682 # not start reading them until after it receives the first line
684 # if the -c=<hfld> option is NOT used then the -h= <file> must contain only the
685 # branch name (e.g. "t/foo") on each line; but if -c=<hfld> IS used then the
686 # chosen field (use -c=2 for packed-refs) must be a full ref starting with the
687 # "refs/heads/" prefix; a file with one full refname per line (i.e. starts with
688 # "refs/heads/") instead of just the branch name should use -c=1 for it to work
690 # note that no matter what options are used, missing lines (i.e. M == 1) are
691 # always output when applicable (any branch name not found in the -h= <file>
692 # will always generate a missing line)
694 # if -r=<file> is omitted no remote lines will ever be generated and the
695 # -u=<remt> option will be ignored; if -r=<file> is used but -u=<remt> is
696 # omitted then remote base lines will never be generated, but any branch
697 # names in both -b=<file> AND -r=<file> but NOT -a=<file> will still have a
698 # T == 2 value in their output lines
700 # With -s a "starting" line is output for <branch> (and each additional <name>
701 # when using -m) either before (with -f) or after (the default) recursively
702 # processing all of that branch's dependencies
704 # if both -s AND -m are used AND at least two nodes are specified the effect
705 # on the output is as though a virtual starting node was passed that had a
706 # .topdeps file consisting of the nodes listed on the command line (in command
707 # line order) AND the -s flag was than not passed when recursing on that
708 # fictional virtual node; if -m and more than one node is used without the
709 # -s option it will not generally be possible to determine where the output
710 # from one node's recursion ends and the next begins in the output
712 # input is the output of run_awk_topgit_deps and despite the availability of
713 # the -n option here, if it was used on the run_awk_topgit_deps command line
714 # the links to annihilated branches will not be present in the input so the
715 # only possible L == 2 line in that case (if -n is NOT used) would be for a
716 # starting <branch> and then only if -s IS used; this is usually fine because
717 # in most cases the annihilated branches need to disappear anyway
719 # output lines look like this:
721 # M T L <node> [<parent> <branch> <chain> <names>]
723 # where (see awk_topgit_recurse) M is 0 (not) or 1 (missing), T is 0 (not)
724 # or 1 (tgish) or 2 (with remote) and L is 0 (not) or 1 (leaf) or 2 (annihilated)
725 # so the following is an example of a possible output line:
727 # 0 1 1 t/foo/leaf t/foo/int t/stage
729 # Note that unlike most of the other run_awk_... functions, this function is
730 # primarily just a convenience wrapper around the awk_topgit_recurse script
731 # (although it will provide the required -h=<file> if necessary) and
732 # really does not provide any additional and/or changed behavior so the script
733 # may also be used directly (by running awk) instead of via this function
735 # however, if "-o" mode is active instead of the above example output line
736 # this line would be output instead:
738 # t/foo/int t/foo/leaf
740 # note the reversed ordering as the first branch name is the branch with the
741 # .topdeps file that contains the the second branch name
743 # Note that this function WILL call get_temp if -h=<file> is NOT given
745 run_awk_require "topgit_recurse"
746 run_awk_topgit_recurse()
748 _ra_showlp=
749 _ra_withan=1
750 _ra_preord=
751 _ra_withbr=
752 _ra_leaves=
753 _ra_tgonly=
754 _ra_multib=
755 _ra_filter=
756 _ra_once=
757 _ra_usermt=
758 _ra_cuthd=
759 _ra_brfile=
760 _ra_anfile=
761 _ra_hdfile=
762 _ra_rtfile=
763 _ra_rmbr=
764 _ra_rman=
765 _ra_rmhd=
766 _ra_rmrt=
767 _ra_inclbr=
768 _ra_exclbr=
769 while [ $# -gt 0 ]; do case "$1" in
770 -d) _ra_showlp=1;;
771 -n) _ra_withan=;;
772 -f) _ra_preord=1;;
773 -s) _ra_withbr=1;;
774 -l) _ra_leaves=1;;
775 -t) _ra_tgonly=1;;
776 -m) _ra_multib=1;;
777 --filter=*) _ra_filter="${1#--filter=}";;
778 --emit=*) _ra_filter="${1#--emit=}";;
779 -e=*) _ra_filter="${1#-e=}";;
780 -o=*) _ra_once="${1#-o=}";;
781 -u=*) _ra_usermt="${1#-u=}";;
782 -c=*) _ra_cuthd="${1#-c=}";;
783 -b=*) _ra_brfile="${1#-b=}";;
784 -a=*) _ra_anfile="${1#-a=}";;
785 -h=*) _ra_hdfile="${1#-h=}";;
786 -r=*) _ra_rtfile="${1#-r=}";;
787 -rmb) _ra_rmbr=1;;
788 -rma) _ra_rman=1;;
789 -rmh) _ra_rmhd=1;;
790 -rmr) _ra_rmrt=1;;
791 -i=*) _ra_inclbr="${1#-i=}";;
792 -x=*) _ra_exclbr="${1#-x=}";;
793 --series|--patch-series)
794 _ra_filter=1
795 _ra_once=1
796 _ra_withbr=1
797 _ra_withan=
798 _ra_tgonly=1
800 --) shift; break;;
801 -?*) run_awk_bug "unknown run_awk_topgit_recurse option: $1";;
802 *) break;;
803 esac; shift; done
804 [ -z "${_ra_filter#[12]}" ] ||
805 run_awk_bug "run_awk_topgit_recurse -e filter value must be 1 or 2"
806 [ -z "${_ra_once#[1]}" ] || [ "$_ra_once" = "-1" ] ||
807 run_awk_bug "run_awk_topgit_recurse -o value must be 1 or -1"
808 [ -n "$_ra_brfile" ] || run_awk_bug "run_awk_topgit_recurse missing required -b=<file>"
809 [ -n "$_ra_anfile" ] || run_awk_bug "run_awk_topgit_recurse missing required -a=<file>"
810 [ -n "$1" ] || run_awk_bug "run_awk_topgit_recurse missing <branch>"
811 if [ -z "$_ra_hdfile" ]; then
812 _ra_hdfile="$(get_temp rawk_heads)" || return 2
813 _ra_rmhd=1
814 git for-each-ref --format="%(refname)" "refs/heads" >"$_ra_hdfile" || {
815 err=$?
816 rm -f "$ra_hdfile"
817 exit $err
819 _ra_cuthd=1
821 awk -f "$TG_INST_AWKDIR/topgit_recurse" \
822 -v "showlp=$_ra_showlp" \
823 -v "withan=$_ra_withan" \
824 -v "preord=$_ra_preord" \
825 -v "withbr=$_ra_withbr" \
826 -v "leaves=$_ra_leaves" \
827 -v "multib=$_ra_multib" \
828 -v "usermt=$_ra_usermt" \
829 -v "cuthd=$_ra_cuthd" \
830 -v "brfile=$_ra_brfile" \
831 -v "anfile=$_ra_anfile" \
832 -v "hdfile=$_ra_hdfile" \
833 -v "rtfile=$_ra_rtfile" \
834 -v "rmbr=$_ra_rmbr" \
835 -v "rman=$_ra_rman" \
836 -v "rmhd=$_ra_rmhd" \
837 -v "rmrt=$_rt_rmrt" \
838 -v "tgonly=$_ra_tgonly" \
839 -v "inclbr=$_ra_inclbr" \
840 -v "exclbr=$_ra_exclbr" \
841 -v "startb=$*" \
842 -v "filter=$_ra_filter" \
843 -v "once=$_ra_once"
846 # run_awk_topgit_navigate [opts] <branch> [<head>...]
848 # -d do loop detection even when not strictly necessary
849 # -n omit annihilated branches (default)
850 # -N do NOT omit annihilated branches from output
851 # -r reverse direction for movement
852 # -k keep last node when running off end
853 # -t include only TopGit branches in the output (ineffective without -b=)
854 # -1 omit all but the first field of each output line
855 # -s=<steps> how many steps to move
856 # -b=<file> file with list of TopGit branches (one per line, e.g. "t/foo")
857 # -rmb remove -b= <file> after it's been read (convenience knob)
858 # -a=<file> file with list of annihilated TopGit branches
859 # -rma remove -a= <file> after it's been read (convenience knob)
860 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
861 # -x=<exbr> whitespace separated list of branch names to exclude
863 # The [<head>...] list is actually a tree pruning list of nodes similar to
864 # git rev-list except that an isolated "^" replaces the "--not" functionality
865 # and if there are no positive nodes listed all nodes are considered positive
866 # before applying the negative node references
868 # input is the output of run_awk_topgit_deps and despite the availability of
869 # the -N option here, if -n was used on the run_awk_topgit_deps command line
870 # the links to annihilated branches will not be present in the input so they
871 # cannot be output in that case; to avoid missing hermit nodes
872 # run_awk_topgit_deps should be run with the -s otherwise those nodes will be
873 # invisible to navigation (which is occasionally useful)
875 # output nodes are written one per line in this format:
877 # <result_branch_name> <containing_topgit_head_branch_name>...
879 # where the second field will be empty only if navigating 1 or a negative
880 # number of steps from no node (i.e. ""); note that pruning IS allowed
881 # with the "no node" value but then the "no node" must be explicitly given
882 # as "" in order to avoid interpreting the first pruning ref as the starting
883 # node; and it's also possible to provide multiple space-separated starting
884 # nodes provided they are quoted together into the first argument
887 run_awk_require "topgit_navigate"
888 run_awk_topgit_navigate()
890 _ra_chklps=
891 _ra_withan=
892 _ra_tgonly=
893 _ra_fldone=
894 _ra_brfile=
895 _ra_anfile=
896 _ra_rmbr=
897 _ra_rman=
898 _ra_rev=
899 _ra_steps=
900 _ra_pin=
901 _ra_inclbr=
902 _ra_exclbr=
903 while [ $# -gt 0 ]; do case "$1" in
904 -d) _ra_chklps=1;;
905 -n) _ra_withan=;;
906 -N) _ra_withan=1;;
907 -r) _ra_rev=1;;
908 -k|--pin) _ra_pin=1;;
909 -t) _ra_tgonly=1;;
910 -1) _ra_fldone=1;;
911 -s=*) _ra_steps="${1#-s=}";;
912 -b=*) _ra_brfile="${1#-b=}";;
913 -a=*) _ra_anfile="${1#-a=}";;
914 -rmb) _ra_rmbr=1;;
915 -rma) _ra_rman=1;;
916 -i=*) _ra_inclbr="${1#-i=}";;
917 -x=*) _ra_exclbr="${1#-x=}";;
918 --) shift; break;;
919 -?*) run_awk_bug "unknown run_awk_topgit_navigate option: $1";;
920 *) break;;
921 esac; shift; done
922 [ -n "$_ra_steps" ] ||
923 run_awk_bug "run_awk_topgit_navigate -s steps value must not be empty"
924 badsteps=1
925 case "$_ra_steps" in -[0-9]*|[0-9]*)
926 badsteps= _t="${_ra_steps#-}" && [ "$_t" = "${_t%%[!0-9]*}" ] || badsteps=1
927 esac
928 [ -z "$badsteps" ] ||
929 run_awk_bug "run_awk_topgit_navigate -s steps value must be empty an integer"
930 _ra_startb=
931 [ $# -eq 0 ] || { _ra_startb="$1"; shift; }
932 awk -f "$TG_INST_AWKDIR/topgit_navigate" \
933 -v "chklps=$_ra_chklps" \
934 -v "withan=$_ra_withan" \
935 -v "rev=$_ra_rev" \
936 -v "steps=$_ra_steps" \
937 -v "brfile=$_ra_brfile" \
938 -v "anfile=$_ra_anfile" \
939 -v "rmbr=$_ra_rmbr" \
940 -v "rman=$_ra_rman" \
941 -v "tgonly=$_ra_tgonly" \
942 -v "fldone=$_ra_fldone" \
943 -v "pin=$_ra_pin" \
944 -v "inclbr=$_ra_inclbr" \
945 -v "exclbr=$_ra_exclbr" \
946 -v "startb=$_ra_startb" \
947 -v "pruneb=$*"