helper/needs_update_check.sh: allow test scripts access
[topgit/pro.git] / tg--awksome.sh
blobc736d4723590bef8c0b9e8604bbf9103a0cde922
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 enable output of .topdeps blob lines
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 one TopGit branch edge (i.e. two space-separated TopGit branch
287 # names) per line (not output until after any -a or -b files have been fully
288 # written and closed)
290 # if -n is used annihilated branches will be omitted from the output (and any
291 # -b=<file> if used), but if -a=<file> is NOT used then get_temp WILL be called
292 # but the file will be subsequently removed before returning
294 # Note that using -a=<file> does NOT exclude annihilated branches (but does
295 # put them into that file); only the -n option will exclude annihilated branches
297 # If -b=<file> is used it will get a copy of the output being truncated if the
298 # output is empty
300 # If -a=<file> is used it will get a list of annihilated branches (regardless of
301 # whether or not -n is used) and will be truncated if there are none
303 # Note that since git for-each-ref sorts its results the output will always be
304 # sorted by branch name (always for the -a=<file> and -b=<file> output) but
305 # that will only be the case for the stdout results for the first (second with
306 # -r) branch name on each stdout line
308 # With -s a "self" link is output for each branch after all the .topdeps
309 # entries (before with -r) have been output for that branch and the entries are
310 # output in .topdeps order (reverse order with -r)
312 # Note that using -s and omitting -n is NOT enough to make a self link for
313 # annihilated branches appear in the output; in order for them to appear (and
314 # also any TopGit branches lacking a .topdeps file) the -m= option must be
315 # used and the value passed must be the empty blob's hash
317 # Each "link" line output is <branch_name> <.topdeps_entry> (reversed with -r)
319 # Note that this function WILL call get_temp if -a= is NOT given AND -n is used
320 # (but it will remove the temp file before returning)
322 run_awk_require "ref_prepare" "topgit_msg_prepare" "topgit_msg"
323 run_awk_topgit_msg()
325 _ra_colfmt=
326 _ra_nokind=
327 _ra_withan=1
328 _ra_withmt=
329 _ra_headbase=
330 _ra_anfile=
331 _ra_rman=
332 _ra_brfile=
333 _ra_rmbr=
334 _ra_inclbr=
335 _ra_exclbr=
336 _ra_depsblob=
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) _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
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=$_ra_depsblob" \
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=$_ra_depsblob" \
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)
431 # with --stdin, the contents of a .topmsg file should be on stdin
432 # otherwise if blobish is given it provides the input otherwise
433 # refs/heads/<branch_name>:.topdeps^{blob} does
435 # with -r=<regex> a case-insensitive keyword regular expression (always
436 # has to match the entire keyword name) instead of "Subject" can be used
437 # to extract other headers ("Subject" is the default though)
439 # output will be the subject (or a suitable description if there is no .topmsg
440 # or blobish or the input is empty); for non-subject keywords if the header
441 # is not present just the empty string is output use -r="(Subject)" to do the
442 # same thing for the subject header and avoid descriptive "missing" text
444 # "<branch_name>" is always required because it's used in the output message
445 # whenever the "Subject:" header line is not present (for whatever reason)
447 run_awk_require "topgit_msg"
448 run_awk_topmsg_header()
450 _usestdin=
451 _ra_kwregex=
452 _ra_colfmt=
453 _ra_nokind=1
454 _ra_noname=1
455 while [ $# -gt 0 ]; do case "$1" in
456 -c) _ra_colfmt=1;;
457 -kind) _ra_nokind=;;
458 -name) _ra_noname=;;
459 -r=*) _ra_kwregex="${1#-r=}";;
460 --stdin) _usestdin=1;;
461 --) shift; break;;
462 -?*) run_awk_bug "unknown run_awk_topmsg_header option: $1";;
463 *) break;;
464 esac; shift; done
465 if [ -n "$_usestdin" ]; then
466 [ $# -eq 1 ] || run_awk_bug "run_awk_topmsg_header --stdin requires exactly 1 arg"
467 [ -n "$1" ] || run_awk_bug "run_awk_topmsg_header --stdin requires a branch name"
468 else
469 [ $# -le 2 ] || run_awk_bug "run_awk_topmsg_header allows at most 2 args"
470 [ $# -ge 1 ] && [ -n "$1" ] ||
471 run_awk_bug "run_awk_topmsg_header requires a branch name"
473 if [ -n "$_usestdin" ]; then
474 printf 'blob 32767 0 %s\n' "$1"
475 tr '\0' '\27'
476 else
477 printf '%s 0 %s\n' "${2:-refs/heads/$1:.topmsg}" "$1" |
478 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)' |
479 tr '\0' '\27' | awk -v "bn=$1" \
480 '1 == NR && $2 == "missing" {printf "blob 0 1 %s\n\n", bn; next}{print}'
481 fi |
482 awk -f "$TG_INST_AWKDIR/topgit_msg" \
483 -v "noname=$_ra_noname" \
484 -v "nokind=$_ra_nokind" \
485 -v "kwregex=$_ra_kwregex" \
486 -v "only1=1" \
487 -v "colfmt=$_ra_colfmt"
490 # run_awk_topgit_deps [opts] <bases-prefix> [<for-each-ref-pattern>...]
492 # -n omit annihilated branches from output and -b=<file> (but not -a=<file>)
493 # -t only output tgish deps (i.e. dep is listed in -b=<file>)
494 # -r reverse the dependency graph
495 # -s include a link to itself for each branch
496 # -h=<head> full ref prefix of heads location (default usually works fine)
497 # -a=<file> write the TopGit branch names one per line of annihilated branches
498 # -b=<file> write the TopGit branch names one per line into here
499 # -p=<file> write a copy of the ref prepare output into here
500 # -r=<file> read substitute refs list from here
501 # -p refs list is in packed-refs format rather than "<ref> <hash>"
502 # -rmr remove -r= <file> after it's been read (convenience knob)
503 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
504 # -x=<exbr> whitespace separated list of branch names to exclude
505 # -db ignored (output of .topdeps blob lines is always enabled)
506 # -mb enable output of .topmsg blob lines
507 # -td=<tdbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topdeps
508 # -tm=<tmbr> must be <branch>:<hash> to use <hash>^{blob} as <branch>'s .topmsg
510 # If given, the -a and -b files are always truncated if there are no matching
511 # branches for them
513 # input is a full top-bases prefix (i.e. "refs/top-bases" or
514 # "refs/remotes/origin/top-bases" or similar) and zero or more for-each-ref
515 # patterns the default being <bases-prefix> if no patterns are given
517 # note that <bases-prefix> is required because this file is independent and
518 # there is no universal default available
520 # output is one TopGit branch edge (i.e. two space-separated TopGit branch
521 # names) per line (not output until after any -a or -b files have been fully
522 # written and closed)
524 # if -n is used annihilated branches will be omitted from the output (and any
525 # -b=<file> if used), but if -a=<file> is NOT used then get_temp WILL be called
526 # but the file will be subsequently removed before returning
528 # Note that using -a=<file> does NOT exclude annihilated branches (but does
529 # put them into that file); only the -n option will exclude annihilated branches
531 # If -b=<file> is used it will get a copy of the output being truncated if the
532 # output is empty
534 # If -a=<file> is used it will get a list of annihilated branches (regardless of
535 # whether or not -n is used) and will be truncated if there are none
537 # Note that since git for-each-ref sorts its results the output will always be
538 # sorted by branch name (always for the -a=<file> and -b=<file> output) but
539 # that will only be the case for the stdout results for the first (second with
540 # -r) branch name on each stdout line
542 # With -s a "self" link is output for each branch after all the .topdeps
543 # entries (before with -r) have been output for that branch and the entries are
544 # output in .topdeps order (reverse order with -r)
546 # Note that using -s and omitting -n is NOT enough to make a self link for
547 # annihilated branches appear in the output; in order for them to appear (and
548 # also any TopGit branches lacking a .topdeps file) the -m= option must be
549 # used and the value passed must be the empty blob's hash
551 # Each "link" line output is <branch_name> <.topdeps_entry> (reversed with -r)
553 # Note that this function WILL call get_temp if -a= is NOT given AND -n is used
554 # OR -b= is NOT given AND -t is used
555 # (but it will remove the temp file(s) before returning)
557 run_awk_require "ref_prepare" "topgit_deps_prepare" "topgit_deps"
558 run_awk_topgit_deps()
560 _ra_noann=
561 _ra_tgonly=
562 _ra_rev=
563 _ra_withbr=
564 _ra_withan=
565 _ra_headbase=
566 _ra_anfile=
567 _ra_rman=
568 _ra_brfile=
569 _ra_rmbr=
570 _ra_inclbr=
571 _ra_exclbr=
572 _ra_msgblob=
573 _ra_topdeps=
574 _ra_topmsg=
575 _ra_teeout=
576 _ra_pckdrefs=
577 _ra_refsfile=
578 _ra_rmrf=
579 while [ $# -gt 0 ]; do case "$1" in
580 -n) _ra_noann=1;;
581 -t) _ra_tgonly=1;;
582 -r) _ra_rev=1;;
583 -s) _ra_withbr=1;;
584 -p) _ra_pckdrefs=1;;
585 -h=*) _ra_headbase="${1#-h=}";;
586 -a=*) _ra_anfile="${1#-a=}";;
587 -b=*) _ra_brfile="${1#-b=}";;
588 -r=*) _ra_refsfile="${1#-r=}";;
589 -rmr) _ra_rmrf=1;;
590 -i=*) _ra_inclbr="${1#-i=}";;
591 -x=*) _ra_exclbr="${1#-x=}";;
592 -db) ;;
593 -mb) _ra_msgblob=1;;
594 -tm=*) _ra_topmsg="${1#-tm=}";;
595 -td=*) _ra_topdeps="${1#-td=}";;
596 -p=*) _ra_teeout="${1#-p=}";;
597 --) shift; break;;
598 -?*) run_awk_bug "unknown run_awk_topgit_deps option: $1";;
599 *) break;;
600 esac; shift; done
601 [ -n "$1" ] || run_awk_bug "run_awk_topgit_deps missing <bases-prefix>"
602 _ra_topbases="$1"
603 shift
604 [ $# -gt 0 ] || set -- "$_ra_topbases"
605 if [ -n "$_ra_noann" ] && [ -z "$_ra_anfile" ]; then
606 _ra_rman=1
607 _ra_anfile="$(get_temp rawk_annihilated)" || return 2
609 if [ -n "$_ra_tgonly" ] && [ -z "$_ra_brfile" ]; then
610 _ra_rmbr=1
611 _ra_brfile="$(get_temp rawk_branches)" || return 2
613 [ -n "$_ra_noann" ] || _ra_withan=1
614 _ra_misscmd=
615 if [ -n "$tgbin" ] && [ -x "$tgbin" ]; then
616 TG_BIN_ABS="$tgbin" && export TG_BIN_ABS
617 _ra_misscmd='eval "$TG_BIN_ABS" --make-empty-blob'
620 if [ -n "$_ra_refsfile" ]; then
621 run_awk_ref_match ${_ra_pckdrefs:+-p} <"$_ra_refsfile" \
622 --format="%(refname)" "$@"
623 else
624 git for-each-ref --format="%(refname)" "$@"
627 awk -f "$TG_INST_AWKDIR/ref_prepare" \
628 -v "topbases=$_ra_topbases" \
629 -v "headbase=$_ra_headbase" \
630 -v "rmrf=$_ra_rmrf" \
631 -v "refsfile=$_ra_refsfile" \
632 -v "chkblob=$mtblob" \
633 -v "depsblob=1" \
634 -v "msgblob=$_ra_msgblob" \
635 -v "topdeps=$_ra_topdeps" \
636 -v "topmsg=$_ra_topmsg" \
637 -v "pckdrefs=$_ra_pckdrefs" \
638 -v "teeout=$_ra_teeout" |
639 git cat-file $gcfbopt --batch-check='%(objectname) %(objecttype) %(rest)' |
640 awk -f "$TG_INST_AWKDIR/topgit_deps_prepare" \
641 -v "noann=$_ra_noann" \
642 -v "anfile=$_ra_anfile" \
643 -v "brfile=$_ra_brfile" \
644 -v "missing=$mtblob" \
645 -v "misscmd=$_ra_misscmd" |
646 git cat-file $gcfbopt --batch='%(objecttype) %(objectsize) %(rest)' | tr '\0' '\27' |
647 awk -f "$TG_INST_AWKDIR/topgit_deps" \
648 -v "withan=$_ra_withan" \
649 -v "anfile=$_ra_anfile" \
650 -v "rman=$_ra_rman" \
651 -v "withbr=$_ra_withbr" \
652 -v "brfile=$_ra_brfile" \
653 -v "rmbr=$_ra_rmbr" \
654 -v "tgonly=$_ra_tgonly" \
655 -v "rev=$_ra_rev" \
656 -v "inclbr=$_ra_inclbr" \
657 -v "exclbr=$_ra_exclbr"
660 # run_awk_topgit_recurse [opts] <branch> [<name>...]
662 # -d output ":loop: some bad branch chain path" for any detected loops
663 # -n omit annihilated branches (L == 2) from output
664 # -f output branch first before dependencies (i.e. preorder not post)
665 # -s include a line for the starting node (recommended when using -m)
666 # -l only output L == 1 lines (leaves)
667 # -t only output T != 0 lines (tgish)
668 # -m activate multimode, any [<name>...] are more branches to process
669 # -e=<type> emit only dependency graph edges (2) or deps (1) (aka filter mode)
670 # -o=<once> only output node on 1st visit (1) or visit its deps only 1st (-1)
671 # -u=<remt> prefix for -r=<file> branch names (e.g. "refs/remotes/x/top-bases")
672 # -c=<hfld> use only field <hfld> from -h= and cut off leading "refs/heads/"
673 # -b=<file> file with list of TopGit branches (one per line, e.g. "t/foo")
674 # -rmb remove -b= <file> after it's been read (convenience knob)
675 # -a=<file> file with list of annihilated TopGit branches
676 # -rma remove -a= <file> after it's been read (convenience knob)
677 # -h=<file> file with list of existing "refs/heads/..." refs (see -c=)
678 # -rmh remove -h= <file> after it's been read (convenience knob)
679 # -r=<file> file with list of TopGit branches with a remote (one per line)
680 # -rmr remove -r= <file> after it's been read (convenience knob)
681 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
682 # -x=<exbr> whitespace separated list of branch names to exclude
683 # --series a convenience macro that sets -e=1 -o=1 -s -n -t options
685 # REQUIRED OPTIONS: -a=<file> -b=<file>
687 # But, if -h=<file> is omitted, get_temp WILL be called!
689 # Note that it is guaranteed to be safe to use the same name for the -a and -b
690 # options of a run_awk_topgit_deps command that is piping output into this
691 # command because they are guaranteed to be fully written and closed by that
692 # command before it outputs the first line and this command is guaranteed to
693 # not start reading them until after it receives the first line
695 # if the -c=<hfld> option is NOT used then the -h= <file> must contain only the
696 # branch name (e.g. "t/foo") on each line; but if -c=<hfld> IS used then the
697 # chosen field (use -c=2 for packed-refs) must be a full ref starting with the
698 # "refs/heads/" prefix; a file with one full refname per line (i.e. starts with
699 # "refs/heads/") instead of just the branch name should use -c=1 for it to work
701 # note that no matter what options are used, missing lines (i.e. M == 1) are
702 # always output when applicable (any branch name not found in the -h= <file>
703 # will always generate a missing line)
705 # if -r=<file> is omitted no remote lines will ever be generated and the
706 # -u=<remt> option will be ignored; if -r=<file> is used but -u=<remt> is
707 # omitted then remote base lines will never be generated, but any branch
708 # names in both -b=<file> AND -r=<file> but NOT -a=<file> will still have a
709 # T == 2 value in their output lines
711 # With -s a "starting" line is output for <branch> (and each additional <name>
712 # when using -m) either before (with -f) or after (the default) recursively
713 # processing all of that branch's dependencies
715 # if both -s AND -m are used AND at least two nodes are specified the effect
716 # on the output is as though a virtual starting node was passed that had a
717 # .topdeps file consisting of the nodes listed on the command line (in command
718 # line order) AND the -s flag was than not passed when recursing on that
719 # fictional virtual node; if -m and more than one node is used without the
720 # -s option it will not generally be possible to determine where the output
721 # from one node's recursion ends and the next begins in the output
723 # input is the output of run_awk_topgit_deps and despite the availability of
724 # the -n option here, if it was used on the run_awk_topgit_deps command line
725 # the links to annihilated branches will not be present in the input so the
726 # only possible L == 2 line in that case (if -n is NOT used) would be for a
727 # starting <branch> and then only if -s IS used; this is usually fine because
728 # in most cases the annihilated branches need to disappear anyway
730 # output lines look like this:
732 # M T L V <node> [<parent> <branch> <chain> <names>]
734 # where (see awk_topgit_recurse) M is 0 (not) or 1 (missing), T is 0 (not)
735 # or 1 (tgish) or 2 (with remote) and L is 0 (not) or 1 (leaf) or 2 (annihilated)
736 # and V is a non-negative integer number of excess visits to the node
737 # so the following is an example of a possible output line:
739 # 0 1 1 0 t/foo/leaf t/foo/int t/stage
741 # Note that unlike most of the other run_awk_... functions, this function is
742 # primarily just a convenience wrapper around the awk_topgit_recurse script
743 # (although it will provide the required -h=<file> if necessary) and
744 # really does not provide any additional and/or changed behavior so the script
745 # may also be used directly (by running awk) instead of via this function
747 # however, if "-o" mode is active instead of the above example output line
748 # this line would be output instead:
750 # t/foo/int t/foo/leaf
752 # note the reversed ordering as the first branch name is the branch with the
753 # .topdeps file that contains the the second branch name
755 # Note that this function WILL call get_temp if -h=<file> is NOT given
757 run_awk_require "topgit_recurse"
758 run_awk_topgit_recurse()
760 _ra_showlp=
761 _ra_withan=1
762 _ra_preord=
763 _ra_withbr=
764 _ra_leaves=
765 _ra_tgonly=
766 _ra_multib=
767 _ra_filter=
768 _ra_once=
769 _ra_usermt=
770 _ra_cuthd=
771 _ra_brfile=
772 _ra_anfile=
773 _ra_hdfile=
774 _ra_rtfile=
775 _ra_rmbr=
776 _ra_rman=
777 _ra_rmhd=
778 _ra_rmrt=
779 _ra_inclbr=
780 _ra_exclbr=
781 while [ $# -gt 0 ]; do case "$1" in
782 -d) _ra_showlp=1;;
783 -n) _ra_withan=;;
784 -f) _ra_preord=1;;
785 -s) _ra_withbr=1;;
786 -l) _ra_leaves=1;;
787 -t) _ra_tgonly=1;;
788 -m) _ra_multib=1;;
789 --filter=*) _ra_filter="${1#--filter=}";;
790 --emit=*) _ra_filter="${1#--emit=}";;
791 -e=*) _ra_filter="${1#-e=}";;
792 -o=*) _ra_once="${1#-o=}";;
793 -u=*) _ra_usermt="${1#-u=}";;
794 -c=*) _ra_cuthd="${1#-c=}";;
795 -b=*) _ra_brfile="${1#-b=}";;
796 -a=*) _ra_anfile="${1#-a=}";;
797 -h=*) _ra_hdfile="${1#-h=}";;
798 -r=*) _ra_rtfile="${1#-r=}";;
799 -rmb) _ra_rmbr=1;;
800 -rma) _ra_rman=1;;
801 -rmh) _ra_rmhd=1;;
802 -rmr) _ra_rmrt=1;;
803 -i=*) _ra_inclbr="${1#-i=}";;
804 -x=*) _ra_exclbr="${1#-x=}";;
805 --series|--patch-series)
806 _ra_filter=1
807 _ra_once=1
808 _ra_withbr=1
809 _ra_withan=
810 _ra_tgonly=1
812 --) shift; break;;
813 -?*) run_awk_bug "unknown run_awk_topgit_recurse option: $1";;
814 *) break;;
815 esac; shift; done
816 [ -z "${_ra_filter#[12]}" ] ||
817 run_awk_bug "run_awk_topgit_recurse -e filter value must be 1 or 2"
818 [ -z "${_ra_once#[1]}" ] || [ "$_ra_once" = "-1" ] ||
819 run_awk_bug "run_awk_topgit_recurse -o value must be 1 or -1"
820 [ -n "$_ra_brfile" ] || run_awk_bug "run_awk_topgit_recurse missing required -b=<file>"
821 [ -n "$_ra_anfile" ] || run_awk_bug "run_awk_topgit_recurse missing required -a=<file>"
822 [ -n "$1" ] || run_awk_bug "run_awk_topgit_recurse missing <branch>"
823 if [ -z "$_ra_hdfile" ]; then
824 _ra_hdfile="$(get_temp rawk_heads)" || return 2
825 _ra_rmhd=1
826 git for-each-ref --format="%(refname)" "refs/heads" >"$_ra_hdfile" || {
827 err=$?
828 rm -f "$ra_hdfile"
829 exit $err
831 _ra_cuthd=1
833 awk -f "$TG_INST_AWKDIR/topgit_recurse" \
834 -v "showlp=$_ra_showlp" \
835 -v "withan=$_ra_withan" \
836 -v "preord=$_ra_preord" \
837 -v "withbr=$_ra_withbr" \
838 -v "leaves=$_ra_leaves" \
839 -v "multib=$_ra_multib" \
840 -v "usermt=$_ra_usermt" \
841 -v "cuthd=$_ra_cuthd" \
842 -v "brfile=$_ra_brfile" \
843 -v "anfile=$_ra_anfile" \
844 -v "hdfile=$_ra_hdfile" \
845 -v "rtfile=$_ra_rtfile" \
846 -v "rmbr=$_ra_rmbr" \
847 -v "rman=$_ra_rman" \
848 -v "rmhd=$_ra_rmhd" \
849 -v "rmrt=$_rt_rmrt" \
850 -v "tgonly=$_ra_tgonly" \
851 -v "inclbr=$_ra_inclbr" \
852 -v "exclbr=$_ra_exclbr" \
853 -v "startb=$*" \
854 -v "filter=$_ra_filter" \
855 -v "once=$_ra_once"
858 # run_awk_topgit_navigate [opts] <branch> [<head>...]
860 # -d do loop detection even when not strictly necessary
861 # -n omit annihilated branches (default)
862 # -N do NOT omit annihilated branches from output
863 # -r reverse direction for movement
864 # -k keep last node when running off end
865 # -t include only TopGit branches in the output (ineffective without -b=)
866 # -1 omit all but the first field of each output line
867 # -s=<steps> how many steps to move
868 # -b=<file> file with list of TopGit branches (one per line, e.g. "t/foo")
869 # -rmb remove -b= <file> after it's been read (convenience knob)
870 # -a=<file> file with list of annihilated TopGit branches
871 # -rma remove -a= <file> after it's been read (convenience knob)
872 # -i=<inbr> whitespace separated list of branch names to include (unless in -x)
873 # -x=<exbr> whitespace separated list of branch names to exclude
875 # The [<head>...] list is actually a tree pruning list of nodes similar to
876 # git rev-list except that an isolated "^" replaces the "--not" functionality
877 # and if there are no positive nodes listed all nodes are considered positive
878 # before applying the negative node references
880 # input is the output of run_awk_topgit_deps and despite the availability of
881 # the -N option here, if -n was used on the run_awk_topgit_deps command line
882 # the links to annihilated branches will not be present in the input so they
883 # cannot be output in that case; to avoid missing hermit nodes
884 # run_awk_topgit_deps should be run with the -s otherwise those nodes will be
885 # invisible to navigation (which is occasionally useful)
887 # output nodes are written one per line in this format:
889 # <result_branch_name> <containing_topgit_head_branch_name>...
891 # where the second field will be empty only if navigating 1 or a negative
892 # number of steps from no node (i.e. ""); note that pruning IS allowed
893 # with the "no node" value but then the "no node" must be explicitly given
894 # as "" in order to avoid interpreting the first pruning ref as the starting
895 # node; and it's also possible to provide multiple space-separated starting
896 # nodes provided they are quoted together into the first argument
899 run_awk_require "topgit_navigate"
900 run_awk_topgit_navigate()
902 _ra_chklps=
903 _ra_withan=
904 _ra_tgonly=
905 _ra_fldone=
906 _ra_brfile=
907 _ra_anfile=
908 _ra_rmbr=
909 _ra_rman=
910 _ra_rev=
911 _ra_steps=
912 _ra_pin=
913 _ra_inclbr=
914 _ra_exclbr=
915 while [ $# -gt 0 ]; do case "$1" in
916 -d) _ra_chklps=1;;
917 -n) _ra_withan=;;
918 -N) _ra_withan=1;;
919 -r) _ra_rev=1;;
920 -k|--pin) _ra_pin=1;;
921 -t) _ra_tgonly=1;;
922 -1) _ra_fldone=1;;
923 -s=*) _ra_steps="${1#-s=}";;
924 -b=*) _ra_brfile="${1#-b=}";;
925 -a=*) _ra_anfile="${1#-a=}";;
926 -rmb) _ra_rmbr=1;;
927 -rma) _ra_rman=1;;
928 -i=*) _ra_inclbr="${1#-i=}";;
929 -x=*) _ra_exclbr="${1#-x=}";;
930 --) shift; break;;
931 -?*) run_awk_bug "unknown run_awk_topgit_navigate option: $1";;
932 *) break;;
933 esac; shift; done
934 [ -n "$_ra_steps" ] ||
935 run_awk_bug "run_awk_topgit_navigate -s steps value must not be empty"
936 badsteps=1
937 case "$_ra_steps" in -[0-9]*|[0-9]*)
938 badsteps= _t="${_ra_steps#-}" && [ "$_t" = "${_t%%[!0-9]*}" ] || badsteps=1
939 esac
940 [ -z "$badsteps" ] ||
941 run_awk_bug "run_awk_topgit_navigate -s steps value must be empty an integer"
942 _ra_startb=
943 [ $# -eq 0 ] || { _ra_startb="$1"; shift; }
944 awk -f "$TG_INST_AWKDIR/topgit_navigate" \
945 -v "chklps=$_ra_chklps" \
946 -v "withan=$_ra_withan" \
947 -v "rev=$_ra_rev" \
948 -v "steps=$_ra_steps" \
949 -v "brfile=$_ra_brfile" \
950 -v "anfile=$_ra_anfile" \
951 -v "rmbr=$_ra_rmbr" \
952 -v "rman=$_ra_rman" \
953 -v "tgonly=$_ra_tgonly" \
954 -v "fldone=$_ra_fldone" \
955 -v "pin=$_ra_pin" \
956 -v "inclbr=$_ra_inclbr" \
957 -v "exclbr=$_ra_exclbr" \
958 -v "startb=$_ra_startb" \
959 -v "pruneb=$*"