tg.sh: next version is 0.19.13
[topgit/pro.git] / tg--awksome.sh
blob21e09de30ab4449a8899b560cd8d782405b77663
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 [ -n "$mtblob" ] || run_awk_bug "mtblob must be set before sourcing awksome"
59 # run_awk_ref_match [opts] [<for-each-ref-pattern>...]
61 # --sort=<key> sort key (repeatable) last is primary same names as --format
62 # --count=<max> stop after this many matches
63 # --format=<fmt> for-each-ref style format but only objname and refname ok
64 # -p input is packed-refs style rather than "<ref> <hash>"
66 # input is one ref + hash table entry per line either packed-refs style or
67 # ref first then hash where nonsensical lines are ignored
69 # output is one "format expansion" per match but stopping after --count (if given)
71 # the <fmt> works like for-each-ref except that only the "refname" and "objectname"
72 # (and %-escapes) get expanded; well, actually, %(objecttype) is expeanded too, but
73 # always to the constant string "object" (unquoted) rather than a real type
75 # the sort keys work like for-each-ref too except that keys other than refname
76 # and/or objectname are ignored but reverse (leading "-") IS supported
78 run_awk_require "ref_match"
79 run_awk_ref_match()
81 _ra_pckdrefs=
82 _ra_sortkey=
83 _ra_maxout=
84 _ra_matchfmt=
85 while [ $# -gt 0 ]; do case "$1" in
86 -p) _ra_pckdrefs=1;;
87 --sort=*) _ra_sortkey="${_ra_sortkey:+$_ra_sortkey,}${1#--sort=}";;
88 --count=*) _ra_maxout="${1#--count=}";;
89 --format=*) _ra_matchfmt="${1#--format=}";;
90 --) shift; break;;
91 -?*) run_awk_bug "unknown run_awk_ref_match option: $1";;
92 *) break;;
93 esac; shift; done
94 awk -f "$TG_INST_AWKDIR/ref_match" \
95 -v "pckdrefs=$_ra_pckdrefs" \
96 -v "sortkey=$_ra_sortkey" \
97 -v "maxout=$_ra_maxout" \
98 -v "matchfmt=$_ra_matchfmt" \
99 -v "patterns=$*"
102 # run_awk_ref_prefixes [opts] <prefix1> <prefix2> [<prefixh>]
104 # -p input is packed-refs format rather than full ref in first field
105 # -e on error when both prefixes present use the default (prefix1) instead
106 # -n no default instead error out
108 # input is one fully qualified ref name per line either in the first
109 # field (default) or in packed-refs format (second field) with -p
110 # (input need not be sorted in any particular order even with a third argument)
112 # on success output is <prefix1> or <prefix2) (with trailing slashes stripped)
113 # on error (both prefixes found and status 65) there's no output but -e will
114 # change that to be success with <prefix1> instead; using -n will cause an
115 # exit with status 66 if neither prefix is found rather than using <prefix1>
117 run_awk_require "ref_prefixes"
118 run_awk_ref_prefixes()
120 _ra_pckdrefs=
121 _ra_noerr=
122 _ra_nodef=
123 while [ $# -gt 0 ]; do case "$1" in
124 -p) _ra_pckdrefs=1;;
125 -e) _ra_noerr=1;;
126 -n) _ra_nodef=1;;
127 --) shift; break;;
128 -?*) run_awk_bug "unknown run_awk_ref_prefixes option: $1";;
129 *) break;;
130 esac; shift; done
131 [ $# -ge 2 ] && [ $# -le 3 ] ||
132 run_awk_bug "run_awk_ref_prefixes requires exactly two or three non-option arguments"
133 awk -f "$TG_INST_AWKDIR/ref_prefixes" \
134 -v "pckdrefs=$_ra_pckdrefs" \
135 -v "noerr=$_ra_noerr" \
136 -v "nodef=$_ra_nodef" \
137 -v "prefix1=$1" \
138 -v "prefix2=$2" \
139 -v "prefixh=$3"
142 # run_awk_topgit_branches [opts] <bases-prefix> [<for-each-ref-pattern>...]
144 # -n omit annihilated branches from output and -b=<file> (but not -a=<file>)
145 # -h=<head> full ref prefix of heads location (default usually works fine)
146 # -a=<file> write the TopGit branch names one per line of annihilated branches
147 # -b=<file> write a copy of the stdout into here
148 # -p=<file> write a copy of the ref prepare output into here
149 # -r=<file> read substitute refs list from here
150 # -p refs list is in packed-refs format rather than "<ref> <hash>"
151 # -rmr remove -r= <file> after it's been read (convenience knob)
152 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
153 # -x=<exbr> whitespace separated list of branch names to exclude
154 # -db enable output of .topdeps blob lines
155 # -mb enable output of .topmsg blob lines
156 # -td=<tdbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topdeps
157 # -tm=<tmbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topmsg
159 # If given, the -a and -b files are always truncated if there are no matching
160 # branches for them
162 # input is a full top-bases prefix (i.e. "refs/top-bases" or
163 # "refs/remotes/origin/top-bases" or similar) and zero or more for-each-ref
164 # patterns the default being <bases-prefix> if no patterns are given
166 # note that <bases-prefix> is required because this file is independent and
167 # there is no universal default available
169 # output is one TopGit branch name per line (not output until after any -a or -b
170 # files have been fully written and closed)
172 # if -n is used annihilated branches will be omitted from the output (and any
173 # -b=<file>)
175 # If -b=<file> is used it will get a copy of the output being truncated if the
176 # output is empty
178 # If -a=<file> is used it will get a list of annihilated branches (regardless of
179 # whether or not -n is used) and will be truncated if there are none
181 # Note that since git for-each-ref sorts its results the output will always be
182 # sorted by branch name (including the -a=<file> output)
184 # Note that this function does NOT call get_temp
186 run_awk_require "ref_prepare" "topgit_branches"
187 run_awk_topgit_branches()
189 _ra_noann=
190 _ra_headbase=
191 _ra_anfile=
192 _ra_brfile=
193 _ra_inclbr=
194 _ra_exclbr=
195 _ra_depsblob=
196 _ra_msgblob=
197 _ra_topdeps=
198 _ra_topmsg=
199 _ra_teeout=
200 _ra_pckdrefs=
201 _ra_refsfile=
202 _ra_rmrf=
203 while [ $# -gt 0 ]; do case "$1" in
204 -n) _ra_noann=1;;
205 -p) _ra_pckdrefs=1;;
206 -h=*) _ra_headbase="${1#-h=}";;
207 -a=*) _ra_anfile="${1#-a=}";;
208 -b=*) _ra_brfile="${1#-b=}";;
209 -i=*) _ra_inclbr="${1#-i=}";;
210 -x=*) _ra_exclbr="${1#-x=}";;
211 -db) _ra_depsblob=1;;
212 -mb) _ra_msgblob=1;;
213 -td=*) _ra_topdeps="${1#-td=}";;
214 -tm=*) _ra_topmsg="${1#-tm=}";;
215 -p=*) _ra_teeout="${1#-p=}";;
216 -r=*) _ra_refsfile="${1#-r=}";;
217 -rmr) _ra_rmrf=1;;
218 --) shift; break;;
219 -?*) run_awk_bug "unknown run_awk_topgit_branches option: $1";;
220 *) break;;
221 esac; shift; done
222 [ -n "$1" ] || run_awk_bug "run_awk_topgit_branches missing <bases-prefix>"
223 _ra_topbases="$1"
224 shift
225 [ $# -gt 0 ] || set -- "$_ra_topbases"
227 if [ -n "$_ra_refsfile" ]; then
228 run_awk_ref_match ${_ra_pckdrefs:+-p} <"$_ra_refsfile" \
229 --format="%(refname)" "$@"
230 else
231 git for-each-ref --format="%(refname)" "$@"
234 awk -f "$TG_INST_AWKDIR/ref_prepare" \
235 -v "topbases=$_ra_topbases" \
236 -v "headbase=$_ra_headbase" \
237 -v "rmrf=$_ra_rmrf" \
238 -v "refsfile=$_ra_refsfile" \
239 -v "chkblob=$mtblob" \
240 -v "depsblob=$_ra_depsblob" \
241 -v "msgblob=$_ra_msgblob" \
242 -v "topdeps=$_ra_topdeps" \
243 -v "topmsg=$_ra_topmsg" \
244 -v "pckdrefs=$_ra_pckdrefs" \
245 -v "teeout=$_ra_teeout" |
246 git cat-file $gcfbopt --batch-check='%(objectname) %(objecttype) %(rest)' |
247 awk -f "$TG_INST_AWKDIR/topgit_branches" \
248 -v "noann=$_ra_noann" \
249 -v "anfile=$_ra_anfile" \
250 -v "brfile=$_ra_brfile" \
251 -v "inclbr=$_ra_inclbr" \
252 -v "exclbr=$_ra_exclbr"
255 # run_awk_topgit_msg [opts] <bases-prefix> [<for-each-ref-pattern>...]
257 # -c do simple column formatting if more than one output column
258 # -n omit annihilated branches from output and -b=<file> (but not -a=<file>)
259 # -nokind omit the kind column from the output
260 # -mt=<mt> empty branch treatment ("" = like ann; true = include; false not)
261 # -h=<head> full ref prefix of heads location (default usually works fine)
262 # -a=<file> write the TopGit branch names one per line of annihilated branches
263 # -b=<file> write the TopGit branch names one per line into here
264 # -p=<file> write a copy of the ref prepare output into here
265 # -r=<file> read substitute refs list from here
266 # -p refs list is in packed-refs format rather than "<ref> <hash>"
267 # -rmr remove -r= <file> after it's been read (convenience knob)
268 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
269 # -x=<exbr> whitespace separated list of branch names to exclude
270 # -db ignored (output of .topdeps blob lines is always enabled)
271 # -mb ignored (output of .topmsg blob lines is always enabled)
272 # -td=<tdbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topdeps
273 # -tm=<tmbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topmsg
274 # --list a convenience macro that sets -c -n -mt=1 -nokind
276 # If given, the -a and -b files are always truncated if there are no matching
277 # branches for them
279 # input is a full top-bases prefix (i.e. "refs/top-bases" or
280 # "refs/remotes/origin/top-bases" or similar) and zero or more for-each-ref
281 # patterns the default being <bases-prefix> if no patterns are given
283 # note that <bases-prefix> is required because this file is independent and
284 # there is no universal default available
286 # output is 0 or more branch lines with .topmsg "Subject:" descriptions
287 # in the same order they appear on the input in this format:
289 # <TopGit_branch_name> K description of the TopGit branch
291 # But if nokind is true the "K" field will be omitted; K can take these values:
293 # 0 non-annihilated, non-empty branch WITH a .topmsg file
294 # 1 non-annihilated, non-empty branch WITHOUT a .topmsg file
295 # 2 annihilated branch
296 # 3 empty branch (an empty branch has the same branch and base commit hash)
297 # 4 bare branch (same as 1 AND also WITHOUT a .topdeps file)
299 # output does not appear until after any -a or -b files have been fully written
300 # and closed first
302 # if -n is used annihilated branches will be omitted from the output (and any
303 # -b=<file> if used), but if -a=<file> is NOT used then get_temp WILL be called
304 # but the file will be subsequently removed before returning
306 # Note that using -a=<file> does NOT exclude annihilated branches (but does
307 # put them into that file); only the -n option will exclude annihilated branches
309 # If -b=<file> is used it will get a copy of the output being truncated if the
310 # output is empty
312 # If -a=<file> is used it will get a list of annihilated branches (regardless of
313 # whether or not -n is used) and will be truncated if there are none
315 # Note that since git for-each-ref sorts its results the output will always be
316 # sorted by branch name (always for the -a=<file> and -b=<file> output) but
317 # that will only be the case for the stdout results for the first (second with
318 # -r) branch name on each stdout line
320 # Note that this function WILL call get_temp if -a= is NOT given AND -n is used
321 # (but it will remove the temp file before returning)
323 run_awk_require "ref_prepare" "topgit_msg_prepare" "topgit_msg"
324 run_awk_topgit_msg()
326 _ra_colfmt=
327 _ra_nokind=
328 _ra_withan=1
329 _ra_withmt=
330 _ra_headbase=
331 _ra_anfile=
332 _ra_rman=
333 _ra_brfile=
334 _ra_rmbr=
335 _ra_inclbr=
336 _ra_exclbr=
337 _ra_topdeps=
338 _ra_topmsg=
339 _ra_teeout=
340 _ra_pckdrefs=
341 _ra_refsfile=
342 _ra_rmrf=
343 while [ $# -gt 0 ]; do case "$1" in
344 -c) _ra_colfmt=1;;
345 -n) _ra_withan=;;
346 -p) _ra_pckdrefs=1;;
347 -mt=*) _ra_withmt="${1#-mt=}";;
348 -nokind) _ra_nokind=1;;
349 -h=*) _ra_headbase="${1#-h=}";;
350 -a=*) _ra_anfile="${1#-a=}";;
351 -b=*) _ra_brfile="${1#-b=}";;
352 -r=*) _ra_refsfile="${1#-r=}";;
353 -rmr) _ra_rmrf=1;;
354 -i=*) _ra_inclbr="${1#-i=}";;
355 -x=*) _ra_exclbr="${1#-x=}";;
356 -db) ;;
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
378 _ra_misscmd=
379 if [ -n "$tgbin" ] && [ -x "$tgbin" ]; then
380 TG_BIN_ABS="$tgbin" && export TG_BIN_ABS
381 _ra_misscmd='eval "$TG_BIN_ABS" --make-empty-blob'
384 if [ -n "$_ra_refsfile" ]; then
385 run_awk_ref_match ${_ra_pckdrefs:+-p} <"$_ra_refsfile" \
386 --format="%(refname)" "$@"
387 else
388 git for-each-ref --format="%(refname)" "$@"
391 awk -f "$TG_INST_AWKDIR/ref_prepare" \
392 -v "topbases=$_ra_topbases" \
393 -v "headbase=$_ra_headbase" \
394 -v "rmrf=$_ra_rmrf" \
395 -v "refsfile=$_ra_refsfile" \
396 -v "chkblob=$mtblob" \
397 -v "depsblob=1" \
398 -v "msgblob=1" \
399 -v "topdeps=$_ra_topdeps" \
400 -v "topmsg=$_ra_topmsg" \
401 -v "pckdrefs=$_ra_pckdrefs" \
402 -v "teeout=$_ra_teeout" |
403 git cat-file $gcfbopt --batch-check='%(objectname) %(objecttype) %(rest)' |
404 awk -f "$TG_INST_AWKDIR/topgit_msg_prepare" \
405 -v "withan=$_ra_withan" \
406 -v "withmt=$_ra_withmt" \
407 -v "depsblob=2" \
408 -v "anfile=$_ra_anfile" \
409 -v "brfile=$_ra_brfile" \
410 -v "missing=$mtblob" \
411 -v "misscmd=$_ra_misscmd" |
412 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)' | tr '\0' '\27' |
413 awk -f "$TG_INST_AWKDIR/topgit_msg" \
414 -v "withan=$_ra_withan" \
415 -v "anfile=$_ra_anfile" \
416 -v "rman=$_ra_rman" \
417 -v "withmt=$_ra_withmt" \
418 -v "brfile=$_ra_brfile" \
419 -v "rmbr=$_ra_rmbr" \
420 -v "nokind=$_ra_nokind" \
421 -v "colfmt=$_ra_colfmt" \
422 -v "inclbr=$_ra_inclbr" \
423 -v "exclbr=$_ra_exclbr"
426 # run_awk_topmsg_header [-r=<regex>] [--stdin] [opts] <branch_name> [blobish]
428 # -kind include the kind field just before the description (normally omitted)
429 # -name include the branch name field as a first field (normally omitted)
430 # -c do simple column formatting if more than one output column
431 # -r=<regx> auto-anchored case-insenstive keyword match (default is "subject")
432 # --stdin fake stdin being a blob and use that
433 # -tm=<blb> blob to use as branch's .topmsg (any leading <foo>: is stripped off)
435 # with --stdin, the contents of a .topmsg file should be on stdin
436 # otherwise if blobish is given it provides the input otherwise
437 # refs/heads/<branch_name>:.topdeps^{blob} does (or -tm=<blb> if used)
439 # --stdin and -tm= are incompatible
441 # with -r=<regex> a case-insensitive keyword regular expression (always
442 # has to match the entire keyword name) instead of "Subject" can be used
443 # to extract other headers ("Subject" is the default though) and if it starts
444 # with a "+" all matches including their "pretty" keyword prefix will be output
446 # output will be the subject (or a suitable description if there is no .topmsg
447 # or blobish or the input is empty); for non-subject keywords if the header
448 # is not present just the empty string is output use -r="(Subject)" to do the
449 # same thing for the subject header and avoid descriptive "missing" text
451 # see run_awk_topgit_msg description for description of output when -kind,
452 # -name and/or -c are used
454 # "<branch_name>" is always required because it's used in the output message
455 # whenever the "Subject:" header line is not present (for whatever reason)
457 run_awk_require "topgit_msg_prepare" "topgit_msg"
458 run_awk_topmsg_header()
460 _usestdin=
461 _ra_kwregex=
462 _ra_colfmt=
463 _ra_nokind=1
464 _ra_noname=1
465 _ra_topdeps=
466 _ra_topmsg=
467 _ra_depsblob=
468 while [ $# -gt 0 ]; do case "$1" in
469 -c) _ra_colfmt=1;;
470 -kind) _ra_nokind=;;
471 -name) _ra_noname=;;
472 -r=*) _ra_kwregex="${1#-r=}";;
473 --stdin) _usestdin=1;;
474 -tm=*) _ra_topmsg="${1#-tm=}"; _ra_topmsg="${_ra_topmsg#*:}";;
475 --) shift; break;;
476 -?*) run_awk_bug "unknown run_awk_topmsg_header option: $1";;
477 *) break;;
478 esac; shift; done
479 if [ -n "$_usestdin" ]; then
480 [ $# -eq 1 ] || run_awk_bug "run_awk_topmsg_header --stdin requires exactly 1 arg"
481 [ -n "$1" ] || run_awk_bug "run_awk_topmsg_header --stdin requires a branch name"
482 [ -z "$_ra_topmsg" ] || run_awk_bug "run_awk_topmsg_header --stdin prohibits -tm=<blb>"
483 else
484 [ $# -le 2 ] || run_awk_bug "run_awk_topmsg_header allows at most 2 args"
485 [ $# -ge 1 ] && [ -n "$1" ] ||
486 run_awk_bug "run_awk_topmsg_header requires a branch name"
488 if [ -n "$_usestdin" ]; then
489 printf 'blob 32767 0 %s\n' "$1"
490 else
491 _ra_misscmd=
492 if [ -n "$tgbin" ] && [ -x "$tgbin" ]; then
493 TG_BIN_ABS="$tgbin" && export TG_BIN_ABS
494 _ra_misscmd='eval "$TG_BIN_ABS" --make-empty-blob'
496 if [ -n "$_ra_topmsg" ]; then
497 _ra_topmsg="$_ra_topmsg^{blob}"
498 else
499 _ra_depsblob=2
500 _ra_topdeps='"refs/heads/$1^{tree}:.topdeps"'
501 _ra_topmsg="refs/heads/$1^{tree}:.topmsg"
503 eval printf '"%s\n"' '"$mtblob^{blob} check ?"' \
504 '"refs/$topbases/$1 $1 :" "refs/$topbases/$1^0"' \
505 '"refs/heads/$1^0" "refs/$topbases/$1^{tree}"' \
506 '"refs/heads/$1^{tree}"' "$_ra_topdeps" '"$_ra_topmsg"' |
507 git cat-file --batch-check='%(objectname) %(objecttype) %(rest)' |
508 awk -f "$TG_INST_AWKDIR/topgit_msg_prepare" \
509 -v "depsblob=$_ra_depsblob" \
510 -v "withan=1" \
511 -v "missing=$mtblob" \
512 -v "misscmd=$_ra_misscmd" |
513 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)'
514 fi | tr '\0' '\27' |
515 awk -f "$TG_INST_AWKDIR/topgit_msg" \
516 -v "noname=$_ra_noname" \
517 -v "nokind=$_ra_nokind" \
518 -v "kwregex=$_ra_kwregex" \
519 -v "only1=1" \
520 -v "withan=1" \
521 -v "colfmt=$_ra_colfmt"
524 # run_awk_topgit_deps [opts] <bases-prefix> [<for-each-ref-pattern>...]
526 # -n omit annihilated branches from output and -b=<file> (but not -a=<file>)
527 # -t only output tgish deps (i.e. dep is listed in -b=<file>)
528 # -r reverse the dependency graph
529 # -s include a link to itself for each branch
530 # -h=<head> full ref prefix of heads location (default usually works fine)
531 # -a=<file> write the TopGit branch names one per line of annihilated branches
532 # -b=<file> write the TopGit branch names one per line into here
533 # -p=<file> write a copy of the ref prepare output into here
534 # -r=<file> read substitute refs list from here
535 # -p refs list is in packed-refs format rather than "<ref> <hash>"
536 # -rmr remove -r= <file> after it's been read (convenience knob)
537 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
538 # -x=<exbr> whitespace separated list of branch names to exclude
539 # -db ignored (output of .topdeps blob lines is always enabled)
540 # -mb enable output of .topmsg blob lines
541 # -td=<tdbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topdeps
542 # -tm=<tmbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topmsg
544 # If given, the -a and -b files are always truncated if there are no matching
545 # branches for them
547 # input is a full top-bases prefix (i.e. "refs/top-bases" or
548 # "refs/remotes/origin/top-bases" or similar) and zero or more for-each-ref
549 # patterns the default being <bases-prefix> if no patterns are given
551 # note that <bases-prefix> is required because this file is independent and
552 # there is no universal default available
554 # output is one TopGit branch edge (i.e. two space-separated TopGit branch
555 # names) per line (not output until after any -a or -b files have been fully
556 # written and closed)
558 # if -n is used annihilated branches will be omitted from the output (and any
559 # -b=<file> if used), but if -a=<file> is NOT used then get_temp WILL be called
560 # but the file will be subsequently removed before returning
562 # Note that using -a=<file> does NOT exclude annihilated branches (but does
563 # put them into that file); only the -n option will exclude annihilated branches
565 # If -b=<file> is used it will get a copy of the output being truncated if the
566 # output is empty
568 # If -a=<file> is used it will get a list of annihilated branches (regardless of
569 # whether or not -n is used) and will be truncated if there are none
571 # Note that since git for-each-ref sorts its results the output will always be
572 # sorted by branch name (always for the -a=<file> and -b=<file> output) but
573 # that will only be the case for the stdout results for the first (second with
574 # -r) branch name on each stdout line
576 # With -s a "self" link is output for each branch after all the .topdeps
577 # entries (before with -r) have been output for that branch and the entries are
578 # output in .topdeps order (reverse order with -r)
580 # Note that using -s and omitting -n is enough to make a self link for
581 # annihilated branches appear in the output because the empty blob will be
582 # automatically used (and first created if necessary) for any missing .topdeps
583 # files.
585 # Each "link" line output is <branch_name> <.topdeps_entry> (reversed with -r)
587 # Note that this function WILL call get_temp if -a= is NOT given AND -n is used
588 # OR -b= is NOT given AND -t is used
589 # (but it will remove the temp file(s) before returning)
591 run_awk_require "ref_prepare" "topgit_deps_prepare" "topgit_deps"
592 run_awk_topgit_deps()
594 _ra_noann=
595 _ra_tgonly=
596 _ra_rev=
597 _ra_withbr=
598 _ra_withan=
599 _ra_headbase=
600 _ra_anfile=
601 _ra_rman=
602 _ra_brfile=
603 _ra_rmbr=
604 _ra_inclbr=
605 _ra_exclbr=
606 _ra_msgblob=
607 _ra_topdeps=
608 _ra_topmsg=
609 _ra_teeout=
610 _ra_pckdrefs=
611 _ra_refsfile=
612 _ra_rmrf=
613 while [ $# -gt 0 ]; do case "$1" in
614 -n) _ra_noann=1;;
615 -t) _ra_tgonly=1;;
616 -r) _ra_rev=1;;
617 -s) _ra_withbr=1;;
618 -p) _ra_pckdrefs=1;;
619 -h=*) _ra_headbase="${1#-h=}";;
620 -a=*) _ra_anfile="${1#-a=}";;
621 -b=*) _ra_brfile="${1#-b=}";;
622 -r=*) _ra_refsfile="${1#-r=}";;
623 -rmr) _ra_rmrf=1;;
624 -i=*) _ra_inclbr="${1#-i=}";;
625 -x=*) _ra_exclbr="${1#-x=}";;
626 -db) ;;
627 -mb) _ra_msgblob=1;;
628 -tm=*) _ra_topmsg="${1#-tm=}";;
629 -td=*) _ra_topdeps="${1#-td=}";;
630 -p=*) _ra_teeout="${1#-p=}";;
631 --) shift; break;;
632 -?*) run_awk_bug "unknown run_awk_topgit_deps option: $1";;
633 *) break;;
634 esac; shift; done
635 [ -n "$1" ] || run_awk_bug "run_awk_topgit_deps missing <bases-prefix>"
636 _ra_topbases="$1"
637 shift
638 [ $# -gt 0 ] || set -- "$_ra_topbases"
639 if [ -n "$_ra_noann" ] && [ -z "$_ra_anfile" ]; then
640 _ra_rman=1
641 _ra_anfile="$(get_temp rawk_annihilated)" || return 2
643 if [ -n "$_ra_tgonly" ] && [ -z "$_ra_brfile" ]; then
644 _ra_rmbr=1
645 _ra_brfile="$(get_temp rawk_branches)" || return 2
647 [ -n "$_ra_noann" ] || _ra_withan=1
648 _ra_misscmd=
649 if [ -n "$tgbin" ] && [ -x "$tgbin" ]; then
650 TG_BIN_ABS="$tgbin" && export TG_BIN_ABS
651 _ra_misscmd='eval "$TG_BIN_ABS" --make-empty-blob'
654 if [ -n "$_ra_refsfile" ]; then
655 run_awk_ref_match ${_ra_pckdrefs:+-p} <"$_ra_refsfile" \
656 --format="%(refname)" "$@"
657 else
658 git for-each-ref --format="%(refname)" "$@"
661 awk -f "$TG_INST_AWKDIR/ref_prepare" \
662 -v "topbases=$_ra_topbases" \
663 -v "headbase=$_ra_headbase" \
664 -v "rmrf=$_ra_rmrf" \
665 -v "refsfile=$_ra_refsfile" \
666 -v "chkblob=$mtblob" \
667 -v "depsblob=1" \
668 -v "msgblob=$_ra_msgblob" \
669 -v "topdeps=$_ra_topdeps" \
670 -v "topmsg=$_ra_topmsg" \
671 -v "pckdrefs=$_ra_pckdrefs" \
672 -v "teeout=$_ra_teeout" |
673 git cat-file $gcfbopt --batch-check='%(objectname) %(objecttype) %(rest)' |
674 awk -f "$TG_INST_AWKDIR/topgit_deps_prepare" \
675 -v "noann=$_ra_noann" \
676 -v "anfile=$_ra_anfile" \
677 -v "brfile=$_ra_brfile" \
678 -v "missing=$mtblob" \
679 -v "misscmd=$_ra_misscmd" |
680 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)' | tr '\0' '\27' |
681 awk -f "$TG_INST_AWKDIR/topgit_deps" \
682 -v "withan=$_ra_withan" \
683 -v "anfile=$_ra_anfile" \
684 -v "rman=$_ra_rman" \
685 -v "withbr=$_ra_withbr" \
686 -v "brfile=$_ra_brfile" \
687 -v "rmbr=$_ra_rmbr" \
688 -v "tgonly=$_ra_tgonly" \
689 -v "rev=$_ra_rev" \
690 -v "inclbr=$_ra_inclbr" \
691 -v "exclbr=$_ra_exclbr"
694 # run_awk_topgit_recurse [opts] <branch> [<name>...]
696 # -d output ":loop: some bad branch chain path" for any detected loops
697 # -n omit annihilated branches (L == 2) from output
698 # -f output branch first before dependencies (i.e. preorder not post)
699 # -s include a line for the starting node (recommended when using -m)
700 # -l only output L == 1 lines (leaves)
701 # -t only output T != 0 lines (tgish)
702 # -m activate multimode, any [<name>...] are more branches to process
703 # -e=<type> emit only dependency graph edges (2) or deps (1) (aka filter mode)
704 # -o=<once> only output node on 1st visit (1) or visit its deps only 1st (-1)
705 # -u=<remt> prefix for -r=<file> branch names (e.g. "refs/remotes/x/top-bases")
706 # -c=<hfld> use only field <hfld> from -h= and cut off leading "refs/heads/"
707 # -b=<file> file with list of TopGit branches (one per line, e.g. "t/foo")
708 # -rmb remove -b= <file> after it's been read (convenience knob)
709 # -a=<file> file with list of annihilated TopGit branches
710 # -rma remove -a= <file> after it's been read (convenience knob)
711 # -h=<file> file with list of existing "refs/heads/..." refs (see -c=)
712 # -rmh remove -h= <file> after it's been read (convenience knob)
713 # -r=<file> file with list of TopGit branches with a remote (one per line)
714 # -rmr remove -r= <file> after it's been read (convenience knob)
715 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
716 # -x=<exbr> whitespace separated list of branch names to exclude
717 # --series a convenience macro that sets -e=1 -o=1 -s -n -t options
719 # REQUIRED OPTIONS: -a=<file> -b=<file>
721 # But, if -h=<file> is omitted, get_temp WILL be called!
723 # Note that it is guaranteed to be safe to use the same name for the -a and -b
724 # options of a run_awk_topgit_deps command that is piping output into this
725 # command because they are guaranteed to be fully written and closed by that
726 # command before it outputs the first line and this command is guaranteed to
727 # not start reading them until after it receives the first line
729 # if the -c=<hfld> option is NOT used then the -h= <file> must contain only the
730 # branch name (e.g. "t/foo") on each line; but if -c=<hfld> IS used then the
731 # chosen field (use -c=2 for packed-refs) must be a full ref starting with the
732 # "refs/heads/" prefix; a file with one full refname per line (i.e. starts with
733 # "refs/heads/") instead of just the branch name should use -c=1 for it to work
735 # note that no matter what options are used, missing lines (i.e. M == 1) are
736 # always output when applicable (any branch name not found in the -h= <file>
737 # will always generate a missing line)
739 # if -r=<file> is omitted no remote lines will ever be generated and the
740 # -u=<remt> option will be ignored; if -r=<file> is used but -u=<remt> is
741 # omitted then remote base lines will never be generated, but any branch
742 # names in both -b=<file> AND -r=<file> but NOT -a=<file> will still have a
743 # T == 2 value in their output lines
745 # With -s a "starting" line is output for <branch> (and each additional <name>
746 # when using -m) either before (with -f) or after (the default) recursively
747 # processing all of that branch's dependencies
749 # if both -s AND -m are used AND at least two nodes are specified the effect
750 # on the output is as though a virtual starting node was passed that had a
751 # .topdeps file consisting of the nodes listed on the command line (in command
752 # line order) AND the -s flag was than not passed when recursing on that
753 # fictional virtual node; if -m and more than one node is used without the
754 # -s option it will not generally be possible to determine where the output
755 # from one node's recursion ends and the next begins in the output
757 # input is the output of run_awk_topgit_deps and despite the availability of
758 # the -n option here, if it was used on the run_awk_topgit_deps command line
759 # the links to annihilated branches will not be present in the input so the
760 # only possible L == 2 line in that case (if -n is NOT used) would be for a
761 # starting <branch> and then only if -s IS used; this is usually fine because
762 # in most cases the annihilated branches need to disappear anyway
764 # output lines look like this:
766 # M T L V <node> [<parent> <branch> <chain> <names>]
768 # where (see awk_topgit_recurse) M is 0 (not) or 1 (missing), T is 0 (not)
769 # or 1 (tgish) or 2 (with remote) and L is 0 (not) or 1 (leaf) or 2 (annihilated)
770 # and V is a non-negative integer number of excess visits to the node
771 # so the following is an example of a possible output line:
773 # 0 1 1 0 t/foo/leaf t/foo/int t/stage
775 # Note that unlike most of the other run_awk_... functions, this function is
776 # primarily just a convenience wrapper around the awk_topgit_recurse script
777 # (although it will provide the required -h=<file> if necessary) and
778 # really does not provide any additional and/or changed behavior so the script
779 # may also be used directly (by running awk) instead of via this function
781 # however, if "-o" mode is active instead of the above example output line
782 # this line would be output instead:
784 # t/foo/int t/foo/leaf
786 # note the reversed ordering as the first branch name is the branch with the
787 # .topdeps file that contains the the second branch name
789 # Note that this function WILL call get_temp if -h=<file> is NOT given
791 run_awk_require "topgit_recurse"
792 run_awk_topgit_recurse()
794 _ra_showlp=
795 _ra_withan=1
796 _ra_preord=
797 _ra_withbr=
798 _ra_leaves=
799 _ra_tgonly=
800 _ra_multib=
801 _ra_filter=
802 _ra_once=
803 _ra_usermt=
804 _ra_cuthd=
805 _ra_brfile=
806 _ra_anfile=
807 _ra_hdfile=
808 _ra_rtfile=
809 _ra_rmbr=
810 _ra_rman=
811 _ra_rmhd=
812 _ra_rmrt=
813 _ra_inclbr=
814 _ra_exclbr=
815 while [ $# -gt 0 ]; do case "$1" in
816 -d) _ra_showlp=1;;
817 -n) _ra_withan=;;
818 -f) _ra_preord=1;;
819 -s) _ra_withbr=1;;
820 -l) _ra_leaves=1;;
821 -t) _ra_tgonly=1;;
822 -m) _ra_multib=1;;
823 --filter=*) _ra_filter="${1#--filter=}";;
824 --emit=*) _ra_filter="${1#--emit=}";;
825 -e=*) _ra_filter="${1#-e=}";;
826 -o=*) _ra_once="${1#-o=}";;
827 -u=*) _ra_usermt="${1#-u=}";;
828 -c=*) _ra_cuthd="${1#-c=}";;
829 -b=*) _ra_brfile="${1#-b=}";;
830 -a=*) _ra_anfile="${1#-a=}";;
831 -h=*) _ra_hdfile="${1#-h=}";;
832 -r=*) _ra_rtfile="${1#-r=}";;
833 -rmb) _ra_rmbr=1;;
834 -rma) _ra_rman=1;;
835 -rmh) _ra_rmhd=1;;
836 -rmr) _ra_rmrt=1;;
837 -i=*) _ra_inclbr="${1#-i=}";;
838 -x=*) _ra_exclbr="${1#-x=}";;
839 --series|--patch-series)
840 _ra_filter=1
841 _ra_once=1
842 _ra_withbr=1
843 _ra_withan=
844 _ra_tgonly=1
846 --) shift; break;;
847 -?*) run_awk_bug "unknown run_awk_topgit_recurse option: $1";;
848 *) break;;
849 esac; shift; done
850 [ -z "${_ra_filter#[12]}" ] ||
851 run_awk_bug "run_awk_topgit_recurse -e filter value must be 1 or 2"
852 [ -z "${_ra_once#[1]}" ] || [ "$_ra_once" = "-1" ] ||
853 run_awk_bug "run_awk_topgit_recurse -o value must be 1 or -1"
854 [ -n "$_ra_brfile" ] || run_awk_bug "run_awk_topgit_recurse missing required -b=<file>"
855 [ -n "$_ra_anfile" ] || run_awk_bug "run_awk_topgit_recurse missing required -a=<file>"
856 [ -n "$1" ] || run_awk_bug "run_awk_topgit_recurse missing <branch>"
857 if [ -z "$_ra_hdfile" ]; then
858 _ra_hdfile="$(get_temp rawk_heads)" || return 2
859 _ra_rmhd=1
860 git for-each-ref --format="%(refname)" "refs/heads" >"$_ra_hdfile" || {
861 err=$?
862 rm -f "$ra_hdfile"
863 exit $err
865 _ra_cuthd=1
867 awk -f "$TG_INST_AWKDIR/topgit_recurse" \
868 -v "showlp=$_ra_showlp" \
869 -v "withan=$_ra_withan" \
870 -v "preord=$_ra_preord" \
871 -v "withbr=$_ra_withbr" \
872 -v "leaves=$_ra_leaves" \
873 -v "multib=$_ra_multib" \
874 -v "usermt=$_ra_usermt" \
875 -v "cuthd=$_ra_cuthd" \
876 -v "brfile=$_ra_brfile" \
877 -v "anfile=$_ra_anfile" \
878 -v "hdfile=$_ra_hdfile" \
879 -v "rtfile=$_ra_rtfile" \
880 -v "rmbr=$_ra_rmbr" \
881 -v "rman=$_ra_rman" \
882 -v "rmhd=$_ra_rmhd" \
883 -v "rmrt=$_rt_rmrt" \
884 -v "tgonly=$_ra_tgonly" \
885 -v "inclbr=$_ra_inclbr" \
886 -v "exclbr=$_ra_exclbr" \
887 -v "startb=$*" \
888 -v "filter=$_ra_filter" \
889 -v "once=$_ra_once"
892 # run_awk_topgit_navigate [opts] <branch> [<head>...]
894 # -d do loop detection even when not strictly necessary
895 # -n omit annihilated branches (default)
896 # -N do NOT omit annihilated branches from output
897 # -r reverse direction for movement
898 # -k keep last node when running off end
899 # -t include only TopGit branches in the output (ineffective without -b=)
900 # -1 omit all but the first field of each output line
901 # -s=<steps> how many steps to move
902 # -b=<file> file with list of TopGit branches (one per line, e.g. "t/foo")
903 # -rmb remove -b= <file> after it's been read (convenience knob)
904 # -a=<file> file with list of annihilated TopGit branches
905 # -rma remove -a= <file> after it's been read (convenience knob)
906 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
907 # -x=<exbr> whitespace separated list of branch names to exclude
909 # The [<head>...] list is actually a tree pruning list of nodes similar to
910 # git rev-list except that an isolated "^" replaces the "--not" functionality
911 # and if there are no positive nodes listed all nodes are considered positive
912 # before applying the negative node references
914 # input is the output of run_awk_topgit_deps and despite the availability of
915 # the -N option here, if -n was used on the run_awk_topgit_deps command line
916 # the links to annihilated branches will not be present in the input so they
917 # cannot be output in that case; to avoid missing hermit nodes
918 # run_awk_topgit_deps should be run with the -s otherwise those nodes will be
919 # invisible to navigation (which is occasionally useful)
921 # output nodes are written one per line in this format:
923 # <result_branch_name> <containing_topgit_head_branch_name>...
925 # where the second field will be empty only if navigating 1 or a negative
926 # number of steps from no node (i.e. ""); note that pruning IS allowed
927 # with the "no node" value but then the "no node" must be explicitly given
928 # as "" in order to avoid interpreting the first pruning ref as the starting
929 # node; and it's also possible to provide multiple space-separated starting
930 # nodes provided they are quoted together into the first argument
933 run_awk_require "topgit_navigate"
934 run_awk_topgit_navigate()
936 _ra_chklps=
937 _ra_withan=
938 _ra_tgonly=
939 _ra_fldone=
940 _ra_brfile=
941 _ra_anfile=
942 _ra_rmbr=
943 _ra_rman=
944 _ra_rev=
945 _ra_steps=
946 _ra_pin=
947 _ra_inclbr=
948 _ra_exclbr=
949 while [ $# -gt 0 ]; do case "$1" in
950 -d) _ra_chklps=1;;
951 -n) _ra_withan=;;
952 -N) _ra_withan=1;;
953 -r) _ra_rev=1;;
954 -k|--pin) _ra_pin=1;;
955 -t) _ra_tgonly=1;;
956 -1) _ra_fldone=1;;
957 -s=*) _ra_steps="${1#-s=}";;
958 -b=*) _ra_brfile="${1#-b=}";;
959 -a=*) _ra_anfile="${1#-a=}";;
960 -rmb) _ra_rmbr=1;;
961 -rma) _ra_rman=1;;
962 -i=*) _ra_inclbr="${1#-i=}";;
963 -x=*) _ra_exclbr="${1#-x=}";;
964 --) shift; break;;
965 -?*) run_awk_bug "unknown run_awk_topgit_navigate option: $1";;
966 *) break;;
967 esac; shift; done
968 [ -n "$_ra_steps" ] ||
969 run_awk_bug "run_awk_topgit_navigate -s steps value must not be empty"
970 badsteps=1
971 case "$_ra_steps" in -[0-9]*|[0-9]*)
972 badsteps= _t="${_ra_steps#-}" && [ "$_t" = "${_t%%[!0-9]*}" ] || badsteps=1
973 esac
974 [ -z "$badsteps" ] ||
975 run_awk_bug "run_awk_topgit_navigate -s steps value must be empty an integer"
976 _ra_startb=
977 [ $# -eq 0 ] || { _ra_startb="$1"; shift; }
978 awk -f "$TG_INST_AWKDIR/topgit_navigate" \
979 -v "chklps=$_ra_chklps" \
980 -v "withan=$_ra_withan" \
981 -v "rev=$_ra_rev" \
982 -v "steps=$_ra_steps" \
983 -v "brfile=$_ra_brfile" \
984 -v "anfile=$_ra_anfile" \
985 -v "rmbr=$_ra_rmbr" \
986 -v "rman=$_ra_rman" \
987 -v "tgonly=$_ra_tgonly" \
988 -v "fldone=$_ra_fldone" \
989 -v "pin=$_ra_pin" \
990 -v "inclbr=$_ra_inclbr" \
991 -v "exclbr=$_ra_exclbr" \
992 -v "startb=$_ra_startb" \
993 -v "pruneb=$*"