various: use "command" instead of "subcommand"
[topgit/pro.git] / hooks / pre-commit.sh
blobd6c1fcd732ae01f22ca2099edffcc5ff53ffc8d3
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # Copyright (C) 2008 Petr Baudis <pasky@suse.cz>
4 # Copyright (C) 2015,2017,2021 Kyle J. McKay <mackyle@gmail.com>
5 # All rights reserved
6 # GPLv2
8 ## Set up all the tg machinery
10 : "${TG_INST_BINDIR:=@bindir@}"
12 set -e
13 tg__include=1
14 tg_util() {
15 . "$TG_INST_BINDIR"/tg
16 tg_use_alt_odb=
18 tg_util
21 ## Generally have fun
23 # Don't do anything on a non-TopGit branch
24 if head_=$(git symbolic-ref -q HEAD); then
25 case "$head_" in
26 refs/heads/*)
27 head_="${head_#refs/heads/}"
28 git rev-parse -q --verify "refs/$topbases/$head_^0" -- >/dev/null || exit 0;;
30 exit 0;;
31 esac
33 else
34 exit 0
37 # status 0 iff HEAD exists and has neither .topdeps nor .topmsg entries
38 is_bare_branch()
40 _tbbtree="$(git rev-parse --quiet --verify HEAD^{tree} --)" || return 1
41 _tbbls="$(git ls-tree --full-tree "$_tbbtree" .topdeps .topmsg)" || return 1
42 test -z "$_tbbls"
45 # Input:
46 # $1 => variable name (set to "" for 0 return otherwise message)
47 # $2 => tree to inspect
48 # $3 => file name to look for
49 # $4 => if non-empty allow a zero-length file
50 # Output:
51 # 0: specified blob exists and meets $4 condition and eval "$1="
52 # 1: ls-tree gave no result for that file and eval "$1='some error message'"
53 # 2: file is of type other than blob and eval "$1='some error message'"
54 # 3: file is a zero length blob and $4 is empty and eval "$1='error message'"
55 v_check_topfile()
57 _var="$1"
58 shift
59 eval "$_var="
60 _tree="$1"
61 _file="$2"
62 _zerook="$3"
64 _ls_line="$(git ls-tree --long --full-tree "$_tree" "$_file")" || {
65 eval "$_var="'"cannot ls tree for $_file"'
66 return 1
69 [ -n "$_ls_line" ] || {
70 eval "$_var="'"$_file is missing"'
71 return 1
74 # check for type and size
75 set -- $_ls_line
76 _type="$2"
77 _size="$4"
79 # check file is of type blob (file)
80 [ "x$_type" = "xblob" ] || {
81 eval "$_var=\"\$_file is not a file (i.e. not a 'blob')\""
82 return 2
85 # check for positive size
86 [ -n "$_zerook" ] || [ "$_size" -gt 0 ] || {
87 eval "$_var="'"$_file has empty (i.e. 0) size"'
88 return 3
91 return 0
94 tree=$(git write-tree) ||
95 die "cannot write tree"
97 ed=0 && v_check_topfile msg1 "$tree" ".topdeps" 1 || ed=$?
98 em=0 && v_check_topfile msg2 "$tree" ".topmsg" || em=$?
99 [ $ed -ne 1 ] || [ $em -ne 1 ] || ! is_bare_branch || exit 0
100 [ -z "$msg1" ] || fatal "$msg1"
101 [ -z "$msg2" ] || fatal "$msg2"
102 [ $ed -eq 0 ] && [ $em -eq 0 ] || exit 1
104 # Don't do anything more if neither .topdeps nor .topmsg is changing
105 changedeps=
106 changemsg=
107 mode=modify
108 headrev="$(git rev-parse --quiet --verify HEAD --)" || :
109 tab=" " # one tab in there
110 prefix="[A-Z][0-9]*$tab"
111 if [ -n "$headrev" ]; then
112 headtree="$headrev^{tree}"
113 else
114 headtree="$(git mktree < /dev/null)"
116 while read -r status fn; do case "$fn" in
117 ".topdeps")
118 changedeps=1
119 case "$status" in "A"*) mode=create; esac
121 ".topmsg")
122 case "$status" in "A"*) mode=create; esac
123 changemsg=1
125 esac; done <<-EOT
126 $(git diff-index --cached --name-status "$headtree" | grep -e "^$prefix\\.topdeps\$" -e "^$prefix\\.topmsg\$")
128 [ -n "$changedeps" ] || [ -n "$changemsg" ] || exit 0
130 check_cycle_name()
132 [ "$head_" != "$_dep" ] ||
133 die "TopGit dependencies form a cycle: perpetrator is $_name"
136 check_topdeps()
138 # we only need to check newly added deps and for these if a path exists to the
139 # current HEAD
140 check_status
141 base_remote=
142 [ -z "$tg_topmerge" ] || [ ! -s "$git_dir/tg-state/remote" ] ||
143 IFS= read -r base_remote <"$git_dir/tg-state/remote" || :
144 git diff --cached --ignore-space-at-eol -- "$root_dir/.topdeps" | diff_added_lines |
145 while read newly_added; do
146 ref_exists "refs/heads/$newly_added" ||
147 { [ -n "$tg_topmerge" ] && auto_create_local_remote "$newly_added"; } ||
148 die "invalid branch as dependent: $newly_added"
150 # check for self as dep
151 [ "$head_" != "$newly_added" ] ||
152 die "cannot have myself as dependent"
154 # deps can be non-tgish but we can't run recurse_deps() on them
155 ref_exists "refs/$topbases/$newly_added" ||
156 continue
158 # recurse_deps uses dfs but takes the .topdeps from the tree,
159 # therefore no endless loop in the cycle-check
160 no_remotes=1 recurse_deps check_cycle_name "$newly_added"
161 done
162 test $? -eq 0
164 # check for repetitions of deps
165 depdir="$(get_temp tg-depdir -d)" ||
166 die "cannot check for multiple occurrences of dependents"
167 git cat-file blob ":0:.topdeps" 2>/dev/null |
168 while read -r dep || [ -n "$dep" ]; do
169 [ ! -d "$depdir/$dep" ] ||
170 die "multiple occurrences of the same dependent: $dep"
171 mkdir -p "$depdir/$dep" ||
172 die "cannot check for multiple occurrences of dependents"
173 done
174 test $? -eq 0
177 # Only check .topdeps if it's been changed otherwise the assumption is it's been checked
178 [ -z "$changedeps" ] || check_topdeps
180 # If we are not sequestering TopGit files or the commit is changing only TopGit files we're done
181 [ -z "$tgnosequester" ] || exit 0
182 [ $(( ${changedeps:-0} + ${changemsg:-0} )) -ne $(git diff-index --cached --name-only "$headtree" | wc -l) ] || exit 0
184 # Sequester the TopGit-specific file changes into their own commit and notify the user we did so
185 tg_index="$git_dir/tg-index"
186 if [ -n "$headrev" ]; then
187 GIT_INDEX_FILE="$tg_index" git read-tree "$headrev^{tree}"
188 else
189 GIT_INDEX_FILE="$tg_index" git read-tree --empty
191 prefix="100[0-9][0-9][0-9] $octet20$hexch* 0$tab"
193 printf '%s\n' "0 $nullsha$tab.topdeps"
194 printf '%s\n' "0 $nullsha$tab.topmsg"
195 git ls-files --cached -s --full-name | grep -e "^$prefix\\.topdeps\$" -e "^$prefix\\.topmsg\$"
196 } | GIT_INDEX_FILE="$tg_index" git update-index --index-info
197 newtree="$(GIT_INDEX_FILE="$tg_index" git write-tree)"
198 rm -f "$tg_index"
199 files=
200 upd=
201 if [ -n "$changedeps" ] && [ -n "$changemsg" ]; then
202 upd=":/.topdeps :/.topmsg"
203 files=".topdeps and .topmsg"
204 elif [ -n "$changedeps" ]; then
205 upd=":/.topdeps"
206 files=".topdeps"
207 else
208 upd=":/.topmsg"
209 files=".topmsg"
211 newcommit="$(git commit-tree -m "tg: $mode $files" ${headrev:+-p} ${headrev:+"$headrev"} "$newtree")"
212 git update-ref -m "tg: sequester $files changes into their own preliminary commit" HEAD "$newcommit"
213 { unset GIT_PREFIX; } >/dev/null 2>&1 || :
214 GIT_INDEX_FILE="$git_dir/index" && export GIT_INDEX_FILE
215 [ ! -e "$GIT_INDEX_FILE.lock" ] || rm -f "$GIT_INDEX_FILE.lock" >/dev/null 2>&1 || :
216 [ -z "$upd" ] || eval "git reset $newtree -- $upd" >/dev/null 2>&1 || :
217 warn "sequestered $files changes into their own preliminary commit"
218 info "run the same \`git commit\` command again to commit the remaining changes" >&2
219 exit 1