tg-annihilate.sh: autostash and support --stash and --no-stash
[topgit/pro.git] / hooks / pre-commit.sh
blobf4d6ed8dc99810b084264bacb5fca30d5288d8f3
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
5 # All rights reserved.
6 # GPLv2
8 ## Set up all the tg machinery
10 set -e
11 tg__include=1
12 tg_util() {
13 . "@bindir@"/tg
14 tg_use_alt_odb=
16 tg_util
19 ## Generally have fun
21 # Don't do anything on a non-TopGit branch
22 if head_=$(git symbolic-ref -q HEAD); then
23 case "$head_" in
24 refs/heads/*)
25 head_="${head_#refs/heads/}"
26 git rev-parse -q --verify "refs/$topbases/$head_^0" -- >/dev/null || exit 0;;
28 exit 0;;
29 esac
31 else
32 exit 0;
35 check_topfile()
37 _tree=$1
38 _file=$2
39 _zerook="$3"
41 _ls_line="$(git ls-tree --long "$_tree" "$_file")" ||
42 die "cannot ls tree for $_file"
44 [ -n "$_ls_line" ] ||
45 die "$_file is missing"
47 # check for type and size
48 set -- $_ls_line
49 _type=$2
50 _size=$4
52 # check file is of type blob (file)
53 [ "x$_type" = "xblob" ] ||
54 die "$_file is not a file (i.e. not a 'blob')"
56 # check for positive size
57 [ -n "$_zerook" -o "$_size" -gt 0 ] ||
58 die "$_file has empty size"
61 tree=$(git write-tree) ||
62 die "cannot write tree"
64 check_topfile "$tree" ".topdeps" 1
65 check_topfile "$tree" ".topmsg"
67 # Don't do anything more if neither .topdeps nor .topmsg is changing
68 changedeps=
69 changemsg=
70 mode=modify
71 headrev="$(git rev-parse --quiet --verify HEAD -- || :)"
72 tab="$(printf '\t.')" && tab="${tab%?}"
73 prefix="[A-Z][0-9]*$tab"
74 if [ -n "$headrev" ]; then
75 headtree="$headrev^{tree}"
76 else
77 headtree="$(: | git mktree)"
79 while read -r status fn; do case "$fn" in
80 ".topdeps")
81 changedeps=1
82 case "$status" in "A"*) mode=create; esac
84 ".topmsg")
85 case "$status" in "A"*) mode=create; esac
86 changemsg=1
88 esac; done <<-EOT
89 $(git diff-index --cached --name-status "$headtree" | grep -e "^$prefix\\.topdeps\$" -e "^$prefix\\.topmsg\$")
90 EOT
91 [ -n "$changedeps" -o -n "$changemsg" ] || exit 0
93 check_cycle_name()
95 [ "$head_" != "$_dep" ] ||
96 die "TopGit dependencies form a cycle: perpetrator is $_name"
99 check_topdeps()
101 # we only need to check newly added deps and for these if a path exists to the
102 # current HEAD
103 check_status
104 base_remote=
105 [ -z "$tg_topmerge" ] || [ ! -s "$git_dir/tg-update/remote" ] ||
106 IFS= read -r base_remote <"$git_dir/tg-update/remote" || :
107 git diff --cached "$root_dir/.topdeps" | diff_added_lines |
108 while read newly_added; do
109 ref_exists "refs/heads/$newly_added" ||
110 { [ -n "$tg_topmerge" ] && auto_create_local_remote "$newly_added"; } ||
111 die "invalid branch as dependent: $newly_added"
113 # check for self as dep
114 [ "$head_" != "$newly_added" ] ||
115 die "cannot have myself as dependent"
117 # deps can be non-tgish but we can't run recurse_deps() on them
118 ref_exists "refs/$topbases/$newly_added" ||
119 continue
121 # recurse_deps uses dfs but takes the .topdeps from the tree,
122 # therefore no endless loop in the cycle-check
123 no_remotes=1 recurse_deps check_cycle_name "$newly_added"
124 done
125 test $? -eq 0
127 # check for repetitions of deps
128 depdir="$(get_temp tg-depdir -d)" ||
129 die "cannot check for multiple occurrences of dependents"
130 cat_file "$head_:.topdeps" -i |
131 while read dep; do
132 [ ! -d "$depdir/$dep" ] ||
133 die "multiple occurrences of the same dependent: $dep"
134 mkdir -p "$depdir/$dep" ||
135 die "cannot check for multiple occurrences of dependents"
136 done
137 test $? -eq 0
140 # Only check .topdeps if it's been changed otherwise the assumption is it's been checked
141 [ -z "$changedeps" ] || check_topdeps
143 # If we are not sequestering TopGit files or the commit is changing only TopGit files we're done
144 [ -z "$tgnosequester" ] || exit 0
145 [ $(( ${changedeps:-0} + ${changemsg:-0} )) -ne $(git diff-index --cached --name-only "$headtree" | wc -l) ] || exit 0
147 # Sequester the TopGit-specific file changes into their own commit and notify the user we did so
148 tg_index="$git_dir/tg-index"
149 if [ -n "$headrev" ]; then
150 GIT_INDEX_FILE="$tg_index" git read-tree "$headrev^{tree}"
151 else
152 GIT_INDEX_FILE="$tg_index" git read-tree --empty
154 prefix="100[0-9][0-9][0-9] $octet20 0$tab"
156 printf '%s\n' "0 $nullsha$tab.topdeps"
157 printf '%s\n' "0 $nullsha$tab.topmsg"
158 git ls-files --cached -s --full-name | grep -e "^$prefix\\.topdeps\$" -e "^$prefix\\.topmsg\$"
159 } | GIT_INDEX_FILE="$tg_index" git update-index --index-info
160 newtree="$(GIT_INDEX_FILE="$tg_index" git write-tree)"
161 rm -f "$tg_index"
162 files=
163 if [ -n "$changedeps" -a -n "$changemsg" ]; then
164 files=".topdeps and .topmsg"
165 elif [ -n "$changedeps" ]; then
166 files=".topdeps"
167 else
168 files=".topmsg"
170 newcommit="$(git commit-tree -m "tg: $mode $files" ${headrev:+-p $headrev} "$newtree")"
171 git update-ref -m "tg: sequester $files changes into their own preliminary commit" HEAD "$newcommit"
172 warn "sequestered $files changes into their own preliminary commit"
173 info "run the same \`git commit\` command again to commit the remaining changes" >&2
174 exit 1