Add new `git update` tool
[git] / git-update.sh
1 #!/bin/sh
2 #
3 # Copyright (C) 2014 Felipe Contreras
4 # Copyright (C) 2005 Junio C Hamano
5 #
6 # Fetch upstream and update the current branch.
7
8 USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]ff] [--[no-]rebase|--rebase=preserve] [-s strategy]...'
9 LONG_USAGE='Fetch upstram and update the current branch.'
10 SUBDIRECTORY_OK=Yes
11 OPTIONS_SPEC=
12 . git-sh-setup
13 . git-sh-i18n
14 set_reflog_action "update${1+ $*}"
15 require_work_tree_exists
16 cd_to_toplevel
17
18
19 warn () {
20         printf >&2 'warning: %s\n' "$*"
21 }
22
23 die_conflict () {
24         git diff-index --cached --name-status -r --ignore-submodules HEAD --
25         die "$(gettext "Update is not possible because you have instaged files.")"
26 }
27
28 die_merge () {
29         die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")"
30 }
31
32 test -z "$(git ls-files -u)" || die_conflict
33 test -f "$GIT_DIR/MERGE_HEAD" && die_merge
34
35 bool_or_string_config () {
36         git config --bool "$1" 2>/dev/null || git config "$1"
37 }
38
39 strategy_args= diffstat= no_commit= no_ff= ff_only=
40 log_arg= verbosity= progress= recurse_submodules= verify_signatures=
41 merge_args= edit= rebase_args=
42 curr_branch=$(git symbolic-ref -q HEAD)
43 curr_branch_short="${curr_branch#refs/heads/}"
44 mode=$(git config branch.${curr_branch_short}.updatemode)
45 if test -z "$mode"
46 then
47         mode=$(git config update.mode)
48 fi
49 case "$mode" in
50 merge|rebase|ff-only|'')
51         ;;
52 rebase-preserve)
53         mode="rebase"
54         rebase_args="--preserve-merges"
55         ;;
56 *)
57         echo "Invalid value for 'mode'"
58         usage
59         exit 1
60         ;;
61 esac
62 # compatibility with pull configuration
63 if test -z "$mode"
64 then
65         rebase=$(bool_or_string_config branch.$curr_branch_short.rebase)
66         if test -z "$rebase"
67         then
68                 rebase=$(bool_or_string_config pull.rebase)
69         fi
70 fi
71 test -z "$mode" && mode=ff-only
72 dry_run=
73 while :
74 do
75         case "$1" in
76         -q|--quiet)
77                 verbosity="$verbosity -q" ;;
78         -v|--verbose)
79                 verbosity="$verbosity -v" ;;
80         --progress)
81                 progress=--progress ;;
82         --no-progress)
83                 progress=--no-progress ;;
84         -n|--no-stat|--no-summary)
85                 diffstat=--no-stat ;;
86         --stat|--summary)
87                 diffstat=--stat ;;
88         --log|--no-log)
89                 log_arg=$1 ;;
90         -e|--edit)
91                 edit=--edit ;;
92         --no-edit)
93                 edit=--no-edit ;;
94         --no-commit)
95                 no_commit=--no-commit ;;
96         --commit)
97                 no_commit=--commit ;;
98         --ff)
99                 no_ff=--ff ;;
100         --no-ff)
101                 no_ff=--no-ff ;;
102         --ff-only)
103                 ff_only=--ff-only ;;
104         -s=*|--strategy=*|-s|--strategy)
105                 case "$#,$1" in
106                 *,*=*)
107                         strategy=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;;
108                 1,*)
109                         usage ;;
110                 *)
111                         strategy="$2"
112                         shift ;;
113                 esac
114                 strategy_args="${strategy_args}-s $strategy "
115                 ;;
116         -X*)
117                 case "$#,$1" in
118                 1,-X)
119                         usage ;;
120                 *,-X)
121                         xx="-X $(git rev-parse --sq-quote "$2")"
122                         shift ;;
123                 *,*)
124                         xx=$(git rev-parse --sq-quote "$1") ;;
125                 esac
126                 merge_args="$merge_args$xx "
127                 ;;
128         -r=*|--rebase=*)
129                 rebase="${1#*=}"
130                 ;;
131         -r|--rebase)
132                 mode=rebase
133                 ;;
134         -m|--merge)
135                 mode=merge
136                 ;;
137         --recurse-submodules)
138                 recurse_submodules=--recurse-submodules
139                 ;;
140         --recurse-submodules=*)
141                 recurse_submodules="$1"
142                 ;;
143         --no-recurse-submodules)
144                 recurse_submodules=--no-recurse-submodules
145                 ;;
146         --verify-signatures)
147                 verify_signatures=--verify-signatures
148                 ;;
149         --no-verify-signatures)
150                 verify_signatures=--no-verify-signatures
151                 ;;
152         --d|--dry-run)
153                 dry_run=--dry-run
154                 ;;
155         -h|--help-all)
156                 usage
157                 ;;
158         *)
159                 # Pass thru anything that may be meant for fetch.
160                 break
161                 ;;
162         esac
163         shift
164 done
165
166 if test -n "$rebase"
167 then
168         case "$rebase" in
169         true)
170                 mode="rebase"
171                 ;;
172         false)
173                 mode="merge"
174                 ;;
175         preserve)
176                 mode="rebase"
177                 rebase_args=--preserve-merges
178                 ;;
179         *)
180                 echo "Invalid value for --rebase, should be true, false, or preserve"
181                 usage
182                 exit 1
183                 ;;
184         esac
185 fi
186
187 test -z "$curr_branch" &&
188         die "$(gettext "You are not currently on a branch.")"
189
190 get_remote_branch () {
191         local ref
192
193         ref=$(git rev-parse --symbolic-full-name $1)
194
195         git config --get-regexp 'remote.*.fetch' | while read key value
196         do
197                 remote=${key#remote.}
198                 remote=${remote%.fetch}
199                 right=${value#*:}
200                 case $ref in
201                 $right)
202                         prefix=${right%\*}
203                         branch=${ref#$prefix}
204                         echo "remote=$remote branch=$branch"
205                         break
206                         ;;
207                 esac
208         done
209 }
210
211 case $# in
212 0)
213         branch=$(git config "branch.$curr_branch_short.merge")
214         remote=$(git config "branch.$curr_branch_short.remote")
215
216         test -z "$branch" && branch=$curr_branch
217         test -z "$remote" && remote="origin"
218         ;;
219 1)
220         branch=$1
221         remote=.
222
223         eval $(get_remote_branch $1)
224         ;;
225 *)
226         usage
227         exit 1
228         ;;
229 esac
230
231 branch="${branch#refs/heads/}"
232
233 test "$mode" = rebase && {
234         require_clean_work_tree "update with rebase" "Please commit or stash them."
235         oldremoteref=$(git merge-base --fork-point $branch $curr_branch 2>/dev/null)
236 }
237 orig_head=$(git rev-parse -q --verify HEAD)
238
239 if test $remote = "."
240 then
241         extra=--quiet
242 fi
243
244 git fetch $verbosity $progress $dry_run $recurse_submodules $remote $branch $extra || exit 1
245
246 test -z "$dry_run" || exit 0
247
248 merge_head=$(sed -e '/  not-for-merge   /d' -e 's/      .*//' "$GIT_DIR"/FETCH_HEAD)
249
250 test -z "$merge_head" &&
251         die "$(gettext "Couldnot fetch branch '${branch#refs/heads/}'.")"
252
253 if test "$mode" = 'ff-only' && test -z "$no_ff$ff_only"
254 then
255         # check if a non-fast-forward merge would be needed
256         if ! git merge-base --is-ancestor "$orig_head" "$merge_head" &&
257                 ! git merge-base --is-ancestor "$merge_head" "$orig_head"
258         then
259                 die "$(gettext "The update was not fast-forward, please either merge or rebase.
260 If unsure, run 'git update --merge'.")"
261         fi
262         ff_only=--ff-only
263 fi
264
265 if test "$mode" = rebase
266 then
267         o=$(git show-branch --merge-base $curr_branch $merge_head $oldremoteref)
268         test "$oldremoteref" = "$o" && unset oldremoteref
269 fi
270
271 build_msg () {
272         if test "$curr_branch_short" = "$branch"
273         then
274                 echo "Update branch '$curr_branch_short'"
275         else
276                 msg="Merge"
277                 msg="${msg} branch '$curr_branch_short'"
278                 test "$branch" != "master" && msg="${msg} into $branch"
279                 echo "$msg"
280         fi
281 }
282
283 fix_msg () {
284         build_msg
285         sed -e '1 d'
286 }
287
288 case "$mode" in
289 rebase)
290         eval="git-rebase $diffstat $strategy_args $merge_args $rebase_args $verbosity"
291         eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
292         eval "exec $eval"
293         ;;
294 *)
295         merge_msg=$(git fmt-merge-msg $log_arg < "$GIT_DIR"/FETCH_HEAD | fix_msg) || exit
296         eval="git-merge $diffstat $no_commit $verify_signatures $edit $no_ff $ff_only"
297         eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress"
298         eval="$eval --reverse-parents -m \"\$merge_msg\" $merge_head"
299         eval "exec $eval"
300         ;;
301 esac