topgit: version 0.19.13
[topgit/pro.git] / tg-push.sh
blobe3c887b63d93e068e83856ecf6e8bedb8b8c8cd1
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # GPLv2
5 ## Parse options
7 recurse_deps=1
8 tgish_deps_only=
9 dry_run=
10 force=
11 push_all=
12 outofdateok=
13 branches=
14 remote=
15 signedopt=
16 atomicopt=
17 tagsopt=
18 opt4=
19 opt6=
21 while [ -n "$1" ]; do
22 arg="$1"; shift
23 case "$arg" in
24 --no-deps)
25 recurse_deps=;;
26 --dry-run)
27 dry_run=--dry-run;;
28 -f|--force)
29 force=--force;;
30 --signed|--signed=*)
31 signedopt="$arg";;
32 --atomic)
33 atomicopt="$arg";;
34 --follow-tags|--no-follow-tags)
35 tagsopt="$arg";;
36 -4|--ipv4)
37 opt4="$arg";;
38 -6|--ipv6)
39 opt6="$arg";;
40 --tgish-only|--tgish)
41 tgish_deps_only=1;;
42 -a|--all)
43 push_all=1;;
44 --allow-outdated)
45 outofdateok=1;;
46 -h|--help)
47 echo "Usage: ${tgname:-tg} [...] push [--dry-run] [--force] [--no-deps] [--tgish-only] [-r <pushRemote>] [-a | --all | <branch>...]"
48 exit 0;;
49 -r)
50 remote="$1"
51 shift
54 v_strip_ref arg "$arg"
55 branches="${branches:+$branches }$arg";;
56 esac
57 done
58 [ -z "$push_all" ] || [ -z "$branches" ] || die "branch names not allowed with --all"
60 [ -n "$remote" ] || remote="$(git config topgit.pushremote 2>/dev/null)" || :
61 [ -n "$remote" ] || remote="$base_remote"
63 if [ -z "$remote" ]; then
64 warn "either use -r <pushRemote> argument or set topgit.[push]remote"
65 die "no push remote location given"
68 [ -n "$branches$push_all" ] || branches="HEAD"
69 if [ -n "$push_all" ]; then
70 branches="$(non_annihilated_branches | paste -s -d " " -)"
71 else
72 oldbranches="$branches"
73 branches=
74 while read name && [ -n "$name" ]; do
75 if [ "$name" = "HEAD" ] || [ "$name" = "@" ]; then
76 sr="$(git symbolic-ref --quiet HEAD)" || :
77 [ -n "$sr" ] || die "cannot push a detached HEAD"
78 case "$sr" in refs/heads/*);;*)
79 die "HEAD is a symref to other than refs/heads/..."
80 esac
81 ref_exists "$sr" || die "HEAD ($sr) is unborn"
82 b="${sr#refs/heads/}"
83 else
84 ref_exists "refs/heads/$name" || die "no such ref: refs/heads/$name"
85 b="$name"
87 case " $branches " in *" $b "*);;*)
88 branches="${branches:+$branches }$b"
89 esac
90 done <<-EOT
91 $(sed 'y/ /\n/' <<-LIST
92 $oldbranches
93 LIST
95 EOT
96 unset oldbranches
99 _listfile="$(get_temp tg-push-listfile)"
100 _inclfile="$(get_temp tg-push-inclfile)"
102 push_branch()
104 # FIXME should we abort on missing dependency?
105 [ -z "$_dep_missing" ] || return 0
107 # if so desired omit non tgish deps
108 [ -z "$tgish_deps_only" ] || [ -n "$_dep_is_tgish" ] || return 0
110 # filter out plain SHA1s. These don't need to be pushed explicitly as
111 # the patches that depend on the sha1 have it already in their ancestry.
112 ! is_sha1 "$_dep" || return 0
114 echol "refs/heads/$_dep" >> "$_listfile"
115 [ -z "$_dep_is_tgish" ] ||
116 echo "refs/$topbases/$_dep" >> "$_listfile"
119 if [ -z "$outofdateok" ]; then
120 needs_update_check_clear
121 needs_update_check $branches
122 if [ -n "$needs_update_behind" ]; then
123 printf 'branch not up-to-date: %s\n' $needs_update_behind >&2
124 die "all branches to be pushed must be up-to-date (try --allow-outdated)"
128 no_remotes=1
129 while read name && [ -n "$name" ]; do
130 # current branch
131 # re-use push_branch, which expects some pre-defined variables
132 _dep="$name"
133 _dep_is_tgish=1
134 _dep_missing=
135 ref_exists "refs/$topbases/$_dep" ||
136 _dep_is_tgish=
137 push_branch "$name"
139 # deps but only if branch is tgish
140 [ -z "$recurse_deps" ] || [ -z "$_dep_is_tgish" ] ||
141 recurse_deps push_branch "$name"
142 done <<EOT
143 $(sed 'y/ /\n/' <<LIST
144 $branches
145 LIST
149 [ -s "$_listfile" ] || die "nothing to push"
151 # find a suitable temporary remote name to use
152 _rmtbase="tg-push-$(date '+%Y%m%d_%H%M%S')" || :
153 _rmttemp="$(git config --name-only --get-regexp '^remote\.[^.][^.]*\.' |
154 awk -v b="$_rmtbase" '
155 {sub(/^remote\./,"");sub(/\.[^.]*$/,"");if($0!="")r[$0]=1}
156 END {
157 if(b=="") exit 1
158 if(!r[b]) {print b;exit 0}
160 while (r[b"-"x]) ++x
161 print b"-"x
162 exit 0
164 ')" || die "unable to create temporary remote name"
166 # attempt to allow specifying a URL as an explicit push remote
167 # first see if there's a pushurl or url setting for the given
168 # remote and if not, use it as-is thereby treating it as a URL
169 _rmtnm=1
170 _rmturl="$(git config --get "remote.$remote.pushurl" 2>/dev/null)" || :
171 [ -n "$_rmturl" ] ||
172 _rmturl="$(git config --get "remote.$remote.url" 2>/dev/null)" || :
173 [ -n "$_rmturl" ] || { _rmtnm=; _rmturl="$remote"; } # use it as-is
175 # if we have a real remote name, check to see whether or not there is
176 # a fetch spec configured for the remote TopGit branches and/or bases
177 # and if so add suitable fetch specs for both the branches and bases
178 # to the temporary remote in order for the opportunistic ref updates
179 # to take place that would have if a temporary remote was not in use.
180 _rmtftc=
182 test -n "$_rmtnm" &&
183 git config --get-all "remote.$remote.fetch" 2>/dev/null |
184 awk -v r="$remote" -v bl="$topbases" -v br="${topbases#heads/}" '
185 BEGIN {
186 x=""
187 sl0="refs/heads/*:refs/remotes/"r"/*"; sl1="+"sl0
188 sr0="refs/"bl"/*:refs/remotes/"r"/"br"/*"; sr1="+"sr0
190 function exitnow(c) {x=c;exit x}
191 END {if(x!="")exit x}
192 $0 == sl0 || $0 == sl1 || $0 == sr0 || $0 == sr1 {exitnow(0)}
193 END {exit 1}'
194 then
195 _rmtftc=1
198 # remove multiple occurrences of the same branch and create
199 # an include file with a temporary remote listing all of them
200 # as push specs thereby avoiding any command line length limit
201 # and keeping the push entirely atomic if desired no matter how
202 # many branches may be involved
203 sort -u "$_listfile" | awk -v r="$_rmttemp" -v u="$_rmturl" -v f="$_rmtftc" \
204 -v fr="$remote" -v bl="$topbases" -v br="${topbases#heads/}" '
205 function q(s) { gsub(/[\\"]/,"\\\\&",s); return "\""s"\""; }
206 BEGIN {
207 print "[remote "q(r)"]"
208 print "\turl = "q(u)
209 if (f) {
210 sl="+refs/heads/*:refs/remotes/"fr"/*"
211 sr="+refs/"bl"/*:refs/remotes/"fr"/"br"/*"
212 print "\tfetch = "q(sl)
213 print "\tfetch = "q(sr)
216 { print "\tpush = "q($0":"$0) }
217 ' > "$_inclfile" || die "unable to create temporary remote include file"
219 # be careful to make sure the shell doesn't chain to git and clean up the
220 # temporary file via the EXIT trap before Git's had a chance to read it
221 ec=0
222 git -c "include.path=$_inclfile" push $opt4 $opt6 $dry_run $force $atomicopt $tagsopt $signedopt "$_rmttemp" || ec=$?
223 tmpdir_cleanup || :
224 exit ${ec:-0}