README_DOCS.rst: wordsmith some of the commentary
[topgit/pro.git] / tg-info.sh
blob7093570839b75e67753622415aeb127d92c53a97
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # Copyright (C) Petr Baudis <pasky@suse.cz> 2008
4 # Copyright (C) Kyle J. McKay <mackyle@gmail.com> 2015, 2016, 2017, 2018, 2021
5 # GPLv2
7 USAGE="\
8 Usage: ${tgname:-tg} [...] info [-i | -w] [--heads | --leaves | --series[=<head>]] [<name>]
9 Or: ${tgname:-tg} [...] info [-i | -w] [--deps | --dependencies | --dependents] [<name>]
10 Options:
11 -i use TopGit metadata from index instead of HEAD branch
12 -w use metadata from working directory instead of branch"
14 usage()
16 if [ "${1:-0}" != 0 ]; then
17 printf '%s\n' "$USAGE" >&2
18 else
19 printf '%s\n' "$USAGE"
21 exit ${1:-0}
24 ## Parse options
26 datamode=
27 heads=
28 leaves=
29 deps=
30 dependents=
31 series=
32 serieshead=
33 verbose=
34 head_from=
36 while [ $# -gt 0 ]; do case "$1" in
37 -h|--help)
38 usage
40 -i|-w)
41 [ -z "$head_from" ] || die "-i and -w are mutually exclusive"
42 head_from="$1"
44 --heads)
45 heads=1 datamode=1
47 --leaves)
48 leaves=1 datamode=1
50 --deps|--dependencies)
51 deps=1 datamode=1
53 --dependents)
54 dependents=1 datamode=1
56 --series)
57 series=1 datamode=1
59 --series=*)
60 series=1 datamode=1
61 serieshead="${1#--series=}"
63 -v|--verbose)
64 verbose=$(( ${verbose:-0} + 1 ))
66 -vv|-vvv|-vvvv|-vvvvv)
67 verbose=$(( ${verbose:-0} + ${#1} - 1 ))
69 -?*)
70 echo "Unknown option: $1" >&2
71 usage 1
74 break
76 esac; shift; done
77 [ -z "$datamode" ] || [ "$heads$leaves$deps$dependents$series" = "1" ] ||
78 die "mutually exclusive options: --series --deps --heads --leaves --dependents"
79 [ $# -gt 0 ] || set -- HEAD
80 [ $# -eq 1 ] || die "name already specified ($1)"
81 name="$1"
83 process_dep()
85 if [ -n "$_dep_is_tgish" ] && [ -z "$_dep_missing$_dep_annihilated" ]; then
86 printf '%s\n' "$_dep ${_depchain##* }"
90 if [ -n "$heads" ]; then
91 no_remotes=1
92 base_remote=
93 validate=
94 v_verify_topgit_branch tgbranch "${name:-HEAD}" -f || tgbranch=
95 [ -n "$tgbranch" ] || validate=1 tgbranch="$(cat_file HEAD:.topdeps $head_from 2>/dev/null | paste -d ' ' -s -)" || :
96 v_get_tdopt with_deps_opts "$head_from"
97 if [ -n "$tgbranch" ]; then
98 # faster version with known TopGit branch name(s)
99 [ -z "$validate" ] || validate="$(get_temp results)"
100 eval navigate_deps "$with_deps_opts" -s=-1 -1 -- '"$tgbranch"' | eval sort "${validate:+>\"\$validate\"}"
101 [ -n "$validate" ] || exit 0
103 hash="$(git rev-parse --verify --quiet "${name:-HEAD}^0" --)" || die "no such commit-ish: ${name:-HEAD}"
104 if [ -n "$validate" ] && [ -n "$tgbranch" ] && [ -s "$validate" ]; then
105 # If we were on a detached (or non-TopGit) HEAD the shortcut might produce answers that
106 # do not actually contain the HEAD commit and which are meaningless for an orphan branch
107 # (hence the requirement that HEAD actually point to an existing commit to get here)
108 ansok=
109 while read -r abranch; do
110 contained_by "$hash" "refs/heads/$abranch" || continue
111 ansok=1
112 printf '%s\n' "$abranch"
113 done <"$validate"
114 [ -z "$ansok" ] || exit 0
115 # There might still be an answer, but not via the shortcut
117 depslist="$(get_temp depslist)"
118 eval tg --no-pager summary $head_from --topgit-heads |
119 while read -r onetghead; do
120 printf '%s %s\n' "$onetghead" "$onetghead"
121 recurse_deps process_dep "$onetghead"
122 done | sort -u >"$depslist"
123 fer_branch_contains "$hash" | sed 's,^refs/heads/,,' | sort |
124 join -o 2.2 - "$depslist" |
125 sort -u
126 exit 0
129 v_cntargs() { eval "$1=$(( $# - 1 ))"; }
130 if [ -n "$series" ]; then
131 v_get_tdopt with_deps_opts "$head_from"
132 if [ -z "$serieshead" ]; then
133 v_verify_topgit_branch name "${name:-HEAD}"
134 heads="$(eval navigate_deps "$with_deps_opts" -s=-1 -1 -- '"$name"' | sort | paste -d ' ' -s -)" || heads="$name"
135 v_cntargs headcnt $heads
136 if [ "$headcnt" -gt 1 ]; then
137 err "multiple heads found"
138 info "use the --series=<head> option on one of them:" >&2
139 for ahead in $heads; do
140 info "$tab$ahead" >&2
141 done
142 die "--series requires exactly one head"
144 [ "$headcnt" = 1 ] || die "programmer bug"
145 serieshead="$heads"
146 else
147 v_verify_topgit_branch serieshead "$serieshead"
148 v_verify_topgit_branch name "${name:-HEAD}" -f || name=
150 seriesf="$(get_temp series)"
151 recurse_deps_internal --series -- "$serieshead" | awk '{print $0 " " NR}' | sort >"$seriesf"
152 refslist=
153 [ -z "$tg_read_only" ] || [ -z "$tg_ref_cache" ] || ! [ -s "$tg_ref_cache" ] ||
154 refslist="-r=\"$tg_ref_cache\""
155 flagname=
156 [ -z "$name" ] || [ "$serieshead" = "$name" ] || flagname="$name"
157 output() {
158 v_get_tmopt tm_opt "$head_from"
159 eval run_awk_topgit_msg -n "-nokind${tm_opt:+ $tm_opt}" "$refslist" '"refs/$topbases"' | sort |
160 join "$seriesf" - | sort -k2,2n | awk -v "flag=$flagname" '
162 bn = $1
163 mark = ""
164 if (flag != "") mark = (bn == flag) ? "* " : " "
165 bn = mark bn
166 desc = $0
167 sub(/^[^ ]+ [^ ]+ /, "", desc)
168 printf "%-39s\t%s\n", bn, desc
171 } && page output
172 exit 0
175 v_verify_topgit_branch name "${name:-HEAD}"
177 if [ -n "$leaves" ]; then
178 v_get_tdopt with_deps_opts "$head_from"
179 find_leaves "$name"
180 exit 0
183 if [ -n "$deps$dependents" ]; then
184 alldeps="$(get_temp alldeps)"
185 tg --no-pager summary $head_from --tgish-only --deps >"$alldeps" || die "tg summary --deps failed"
186 if [ -n "$deps" ]; then
187 awk -v annb="$name" 'NF == 2 && $2 != "" && $1 == annb { print $2 }' <"$alldeps"
188 else
189 awk -v annb="$name" 'NF == 2 && $1 != "" && $2 == annb { print $1 }' <"$alldeps"
191 exit 0
194 base_rev="$(git rev-parse --short --verify "refs/$topbases/$name^0" -- 2>/dev/null)" ||
195 die "not a TopGit-controlled branch"
197 measure="$(measure_branch "refs/heads/$name" "$base_rev")"
199 echo "Topic Branch: $name ($measure)"
201 noskipiw=
202 [ -z "$head_from" ] || ! v_verify_topgit_branch tghead HEAD -f || [ "$tghead" != "$name" ] || noskipiw=1
203 [ -z "$noskipiw" ] || v_get_tmopt tm_opt "$head_from"
204 read -r bkind subj <<EOT
205 $(eval run_awk_topmsg_header "-kind${tm_opt:+ $tm_opt}" '"$name"')
207 [ "$bkind" = "3" ] || printf "Subject: %s\n" "$subj"
209 if [ "${verbose:-0}" -ge 1 ]; then
210 scratch="$(get_temp scratch)"
211 printf '%s\n' "$name" >"$scratch"
212 dependents="$(get_temp dependents_list)"
213 tg --no-pager summary $head_from --tgish-only --deps | sort -k2,2 | join -1 2 - "$scratch" | cut -d ' ' -f 2 | sort -u >"$dependents"
214 if ! [ -s "$dependents" ]; then
215 echo "Dependents: [none]"
216 else
217 if [ "${verbose:-0}" -le 1 ]; then
218 sed '1{ s/^/Dependents: /; n; }; s/^/ /;' <"$dependents"
219 else
220 minwidth=0
221 while read -r endent; do
222 [ ${#endent} -le $minwidth ] || minwidth=${#endent}
223 done <"$dependents"
224 prefix="Dependents:"
225 while read -r endent; do
226 ood=
227 contained_by "refs/heads/$name" "refs/$topbases/$endent^0" || ood=1
228 if [ -n "$ood" ]; then
229 printf '%s %-*s [needs merge]\n' "$prefix" $minwidth "$endent"
230 else
231 printf '%s %s\n' "$prefix" "$endent"
233 prefix=" "
234 done <"$dependents"
239 if [ "$bkind" = "3" ]; then
240 echo "* No commits."
241 exit 0
244 echo "Base: $base_rev"
245 branch_contains "refs/heads/$name" "refs/$topbases/$name" ||
246 echo "* Base is newer than head! Please run \`$tgdisplay update\`."
248 rhood=
249 if has_remote "$name"; then
250 echo "Remote Mate: $base_remote/$name"
251 # has_remote only checks the single ref it's passed therefore
252 # check to see if the remote base is present especially since remote
253 # bases in the old location do not automatically fetched by default
254 if ref_exists "refs/remotes/$base_remote/${topbases#heads/}/$name"; then
255 branch_contains "refs/$topbases/$name" "refs/remotes/$base_remote/${topbases#heads/}/$name" ||
256 echo "* Local base is out of date wrt. the remote base."
257 else
258 echo "* Remote base ($base_remote/${topbases#heads/}/$name) is missing."
260 branch_contains "refs/heads/$name" "refs/remotes/$base_remote/$name" || {
261 rhood=1
262 echo "* Local head is out of date wrt. the remote head."
264 branch_contains "refs/remotes/$base_remote/$name" "refs/heads/$name" ||
265 echo "* Local head is ahead of the remote head."
268 # annihilated, empty and bare branches do not logically have any dependencies
269 # but we might be about to commit on an empty branch and with -i or -w it could
271 [ "$bkind" = "0" ] || [ "$bkind" = "1" ] || {
272 [ -n "$noskipiw" ] && [ -n "$head_from" ]
274 then
275 anflag=
276 [ "${verbose:-0}" -lt 2 ] || anflag=1
277 depslist="$(get_temp topdeps)"
278 cat_file "refs/heads/$name:.topdeps" ${noskipiw:+$head_from} >"$depslist" 2>/dev/null
279 eval run_awk_topgit_msg "$tm_opt" '"refs/$topbases"' "$(awk -v p="refs/$topbases/" <"$depslist" '
280 function sq(x) {
281 gsub(/\047/, "\047\\\047\047", x)
282 return "\047" x "\047"
284 {sub(/\r$/, "")}
285 NF == 1 && $0 != "" && $0 !~ /[ \t\r\n*?:[^~\\]/ {printf "%s ", sq(p $0)}
286 ')" | awk -v an="$anflag" -v df="$depslist" '
287 NF >= 2 && $1 != "" && $2 ~ /^[0-4]$/ {bt[$1] = 0 + $2}
288 END {
289 prefix = "Depends: "
290 while ((e = (getline adep <df)) > 0) {
291 sub(/\r$/, "", adep)
292 if (adep == "" || adep ~ /[ \t\r\n*?:[^~\\]/) continue
293 if (!an && bt[adep] == 2) continue
294 suffix = ""
295 if (bt[adep] == 2) suffix = " (annihilated)"
296 print prefix adep suffix
297 prefix = " "
299 close(df)
300 if (e < 0) exit 2;
305 depcheck="$(get_temp tg-depcheck)"
306 missing_deps=
307 v_get_tdopt with_deps_opts "$head_from"
308 needs_update "$name" >"$depcheck" || :
309 [ -z "$rhood" ] || echo ":refs/remotes/$base_remote/$name $name" >>"$depcheck"
310 if [ -n "$missing_deps" ]; then
311 echo "MISSING: $missing_deps"
313 depcheck2="$(get_temp tg-depcheck2)"
314 sed '/^!/d' <"$depcheck" >"$depcheck2"
315 if [ -s "$depcheck2" ]; then
316 echo "Needs update from:"
317 # 's/ [^ ]* *$//' -- last is $name
318 # 's/^[:] /::/' -- don't distinguish base updates
319 <"$depcheck2" sed -e 's/ [^ ]* *$//' -e 's/^[:] /:/' |
320 while read dep chain; do
321 extradep=
322 case "$dep" in
323 ::*)
324 dep="${dep#::}"
325 fulldep="refs/heads/$dep"
326 extradep="refs/$topbases/$dep"
329 dep="${dep#:}"
330 fulldep="$dep"
333 fulldep="refs/heads/$dep"
335 esac
336 printf '%s' "$dep "
337 [ -z "$chain" ] || printf '%s' "(<= $(echol "$chain" | sed 's/ / <= /')) "
338 printf '%s' "($(eval measure_branch '"$fulldep"' '"refs/heads/$name"' ${extradep:+\"\$extradep\"}))"
339 echo
340 done | sed 's/^/ /'
341 else
342 echo "Up-to-date${missing_deps:+ (except for missing dependencies)}."