t4000: add bare branch sanity checks
[topgit/pro.git] / tg-contains.sh
blob8b7ffcffa4675f33cd065e0938b0364f90116649
1 #!/bin/sh
2 # TopGit contains command
3 # (C) 2017 Kyle J. McKay <mackyle@gmail.com>
4 # All rights reserved
5 # GPLv2
7 USAGE="\
8 Usage: ${tgname:-tg} [...] contains [-v] [-r] [--ann] [--no-strict] [--] <committish>"
10 usage()
12 if [ "${1:-0}" != 0 ]; then
13 printf '%s\n' "$USAGE" >&2
14 else
15 printf '%s\n' "$USAGE"
17 exit ${1:-0}
20 verbose=
21 remotes=
22 strict=1
23 annok=
25 while [ $# -gt 0 ]; do case "$1" in
26 -h|--help)
27 usage
29 -r|--remotes)
30 remotes=1
32 --ann|--annihilated|--annihilated-ok|--annihilated-okay)
33 annok=1
35 --heads)
36 echo "Did you mean --verbose (-v) instead of --heads?" >&2
37 usage 1
39 -v|--verbose)
40 verbose=$(( ${verbose:-0} + 1 ))
42 -vv|-vvv|-vvvv|-vvvvv)
43 verbose=$(( ${verbose:-0} + ${#1} - 1 ))
45 --strict)
46 strict=1
48 --no-strict)
49 strict=
51 --)
52 shift
53 break
55 -?*)
56 echo "Unknown option: $1" >&2
57 usage 1
60 break
62 esac; shift; done
63 [ $# = 1 ] || usage 1
64 [ "$1" != "@" ] || set -- HEAD
66 set -e
67 findrev="$(git rev-parse --verify "$1"^0 --)" || exit 1
69 # $1 => return correct $topbases value in here on success
70 # $2 => remote name
71 # $3 => remote branch name
72 # succeeds if both refs/remotes/$2/$3 and refs/remotes/$2/${$1#heads/}/$3 exist
73 v_is_remote_tgbranch()
75 git rev-parse --quiet --verify "refs/remotes/$2/$3^0" -- >/dev/null || return 1
76 if git rev-parse --quiet --verify "refs/remotes/$2/${topbases#heads/}/$3^0" -- >/dev/null; then
77 [ -z "$1" ] || eval "$1="'"$topbases"'
78 return 0
80 git rev-parse --quiet --verify "refs/remotes/$2/${oldbases#heads/}/$3^0" -- >/dev/null || return 1
81 if [ -z "$annok" ]; then
82 rmb="$(git merge-base "refs/remotes/$2/${oldbases#heads/}/$3^0" "refs/remotes/$2/$3^0" 2>/dev/null)" || :
83 if [ -n "$rmb" ]; then
84 rmbtree="$(git rev-parse --quiet --verify "$rmb^{tree}" --)" || :
85 rbrtree=
86 [ -z "$rmbtree" ] ||
87 rbrtree="$(git rev-parse --quiet --verify "refs/remotes/$2/$3^{tree}" --)" || :
88 [ -z "$rmbtree" ] || [ -z "$rbrtree" ] || [ "$rmbtree" != "$rbrtree" ] || return 1
91 [ -z "$1" ] || eval "$1="'"$oldbases"'
94 process_dep()
96 if [ -n "$_dep_is_tgish" ] && [ -z "$_dep_missing$_dep_annihilated" ]; then
97 printf '%s\n' "$_dep ${_depchain##* }"
101 depslist=
102 make_deps_list()
104 no_remotes=1
105 base_remote=
106 depslist="$(get_temp depslist)"
107 tg summary --topgit-heads |
108 while read -r onetghead; do
109 printf '%s %s\n' "$onetghead" "$onetghead"
110 recurse_deps process_dep "$onetghead"
111 done | sort -u >"$depslist"
114 localcnt=
115 remotecnt=
116 localb="$(get_temp localb)"
117 localwide=0
118 remoteb=
119 remotewide=0
120 [ -z "$remotes" ] || remoteb="$(get_temp remoteb)"
121 while IFS= read -r branch && [ -n "$branch" ]; do
122 branch="${branch#??}"
123 if v_verify_topgit_branch "" "$branch" -f; then
124 [ -n "$annok" ] || ! branch_annihilated "$branch" || continue
125 if contained_by "$findrev" "refs/$topbases/$branch"; then
126 [ -z "$strict" ] || continue
127 depth="$(git rev-list --count --ancestry-path "refs/$topbases/$branch" --not "$findrev")"
128 depth=$(( ${depth:-0} + 1 ))
129 else
130 depth=0
132 localcnt=$(( ${localcnt:-0} + 1 ))
133 [ ${#branch} -le $localwide ] || localwide=${#branch}
134 printf '%s %s\n' "$depth" "$branch" >>"$localb"
135 remotecnt=
136 else
137 [ -n "$remotes" ] && [ -z "$localcnt" ] && [ "${branch#remotes/}" != "$branch" ] || continue
138 rbranch="${branch#remotes/}"
139 rremote="${rbranch%%/*}"
140 rbranch="${rbranch#*/}"
141 [ "remotes/$rremote/$rbranch" = "$branch" ] || continue
142 v_is_remote_tgbranch rtopbases "$rremote" "$rbranch" || continue
143 if contained_by "$findrev" "refs/remotes/$rremote/${rtopbases#heads/}/$rbranch"; then
144 [ -z "$strict" ] || continue
145 depth="$(git rev-list --count --ancestry-path "refs/remotes/$rremote/${rtopbases#heads/}/$rbranch" --not "$findrev")"
146 depth=$(( ${depth:-0} + 1 ))
147 else
148 depth=0
150 remotecnt=$(( ${remotecnt:-0} + 1 ))
151 [ ${#branch} -le $remotewide ] || remotewide=${#branch}
152 [ -n "$remoteb" ] || remoteb="$(get_temp remoteb)"
153 printf '%s %s\n' "$depth" "remotes/$rremote/$rbranch" >>"$remoteb"
155 done <<EOT
156 $(git branch ${remotes:+-a} --contains "$findrev")
158 [ -n "$localcnt$remotecnt" ] || exit 1
159 [ -z "$localcnt" ] || [ ${verbose:-0} -le 0 ] || make_deps_list
160 if [ -n "$localcnt" ]; then
161 process="$localb"
162 minwide=$localwide
163 else
164 process="$remoteb"
165 minwide=$remotewide
168 sort -k1,1n "$process" |
169 while read -r depth ref; do
170 [ -n "$mindepth" ] || mindepth="$depth"
171 [ $depth -le $mindepth ] || continue
172 printf '%s\n' "$ref"
173 done | sort -u |
174 while read -r oneresult; do
175 headinfo=
176 isann=
177 [ -z "$annok" ] || [ -z "$depslist" ] || ! branch_annihilated "$oneresult" || isann=1
178 [ -z "$depslist" ] || [ -n "$isann" ] ||
179 headinfo="$(printf '%s\n' "$oneresult" | join -o 2.2 - "$depslist" |
180 sort -u | paste -d , -s - | sed -e 's/,/, /g')"
181 [ -z "$annok" ] || [ -z "$depslist" ] || [ -z "$isann" ] || headinfo=":annihilated:"
182 if [ -z "$headinfo" ]; then
183 printf '%s\n' "$oneresult"
184 else
185 printf '%-*s [%s]\n' $minwide "$oneresult" "$headinfo"
187 done
188 exit 0