#!/bin/sh # # Copyright (c) 2005 Junio C Hamano # setglobal OPTIONS_KEEPDASHDASH = '' setglobal OPTIONS_SPEC = '"\ git merge [options] ... git merge [options] HEAD -- stat show a diffstat at the end of the merge n don't show a diffstat at the end of the merge summary (synonym to --stat) log add list of one-line log to merge commit message squash create a single commit instead of doing a merge commit perform a commit if the merge succeeds (default) ff allow fast-forward (default) ff-only abort if fast-forward is not possible rerere-autoupdate update index with any reused conflict resolution s,strategy= merge strategy to use X= option for selected merge strategy m,message= message to be used for the merge commit (if any) '" setglobal SUBDIRECTORY_OK = 'Yes' source git-sh-setup require_work_tree cd_to_toplevel test -z $[git ls-files -u] || die "Merge is not possible because you have unmerged files." ! test -e "$GIT_DIR/MERGE_HEAD" || die 'You have not concluded your merge (MERGE_HEAD exists).' setglobal LF = '' '' setglobal all_strategies = ''recur recursive octopus resolve stupid ours subtree'' setglobal all_strategies = ""$all_strategies recursive-ours recursive-theirs"" setglobal not_strategies = ''base file index tree'' setglobal default_twohead_strategies = ''recursive'' setglobal default_octopus_strategies = ''octopus'' setglobal no_fast_forward_strategies = ''subtree ours'' setglobal no_trivial_strategies = ''recursive recur subtree ours recursive-ours recursive-theirs'' setglobal use_strategies = '' setglobal xopt = '' setglobal allow_fast_forward = 't' setglobal fast_forward_only = '' setglobal allow_trivial_merge = 't' setglobal squash = '', no_commit = '', log_arg = '', rr_arg = '' proc dropsave { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ "$GIT_DIR/MERGE_STASH" "$GIT_DIR/MERGE_MODE" || exit 1 } proc savestate { # Stash away any local modifications. git stash create >"$GIT_DIR/MERGE_STASH" } proc restorestate { if test -f "$GIT_DIR/MERGE_STASH" { git reset --hard $head >/dev/null git stash apply $[cat "$GIT_DIR/MERGE_STASH] git update-index --refresh >/dev/null } } proc finish_up_to_date { match $squash { with t echo "$1 (nothing to squash)" with '' echo $1 } dropsave } proc squash_message { echo Squashed commit of the following: echo git log --no-merges --pretty=medium ^"$head" $remoteheads } proc finish { if test '' = $2 { setglobal rlogm = $GIT_REFLOG_ACTION } else { echo $2 setglobal rlogm = ""$GIT_REFLOG_ACTION: $2"" } match $squash { with t echo "Squash commit -- not updating HEAD" squash_message >"$GIT_DIR/SQUASH_MSG" with '' match $merge_msg { with '' echo "No merge message -- not updating HEAD" with * git update-ref -m $rlogm HEAD $1 $head || exit 1 git gc --auto } } match $1 { with '' with ?* if test $show_diffstat = t { # We want color (if set), but no pager env GIT_PAGER='' git diff --stat --summary -M $head $1 } } # Run a post-merge hook if test -x "$GIT_DIR"/hooks/post-merge { match $squash { with t "$GIT_DIR"/hooks/post-merge 1 with '' "$GIT_DIR"/hooks/post-merge 0 } } } proc merge_name { setglobal remote = $1 setglobal rh = $[git rev-parse --verify "$remote^0" !2 >/dev/null] || return if setglobal truname = $[expr $remote : '\(.*\)~[0-9]*$] && git show-ref -q --verify "refs/heads/$truname" !2 >/dev/null { echo "$rh branch '$truname' (early part) of ." return } if setglobal found_ref = $[git rev-parse --symbolic-full-name --verify \ $remote !2 >/dev/null] { setglobal expanded = $[git check-ref-format --branch $remote] || exit if test $(found_ref#refs/heads/) != $found_ref { echo "$rh branch '$expanded' of ." return } elif test $(found_ref#refs/remotes/) != $found_ref { echo "$rh remote branch '$expanded' of ." return } } if test $remote = "FETCH_HEAD" && test -r "$GIT_DIR/FETCH_HEAD" { sed -e 's/ not-for-merge / /' -e 1q \ "$GIT_DIR/FETCH_HEAD" return } echo "$rh commit '$remote'" } proc parse_config { while test $Argc != 0 { match $1 { with -n|--no-stat|--no-summary setglobal show_diffstat = 'false' with --stat|--summary setglobal show_diffstat = 't' with --log|--no-log setglobal log_arg = $1 with --squash test $allow_fast_forward = t || die "You cannot combine --squash with --no-ff." setglobal squash = 't', no_commit = 't' with --no-squash setglobal squash = '', no_commit = '' with --commit setglobal no_commit = '' with --no-commit setglobal no_commit = 't' with --ff setglobal allow_fast_forward = 't' with --no-ff test $squash != t || die "You cannot combine --squash with --no-ff." test $fast_forward_only != t || die "You cannot combine --ff-only with --no-ff." setglobal allow_fast_forward = 'f' with --ff-only test $allow_fast_forward != f || die "You cannot combine --ff-only with --no-ff." setglobal fast_forward_only = 't' with --rerere-autoupdate|--no-rerere-autoupdate setglobal rr_arg = $1 with -s|--strategy shift match " $all_strategies " { with *" $1 "* setglobal use_strategies = ""$use_strategies$1 "" with * match " $not_strategies " { with *" $1 "* false } && type "git-merge-$1" >/dev/null !2 > !1 || die "available strategies are: $all_strategies" setglobal use_strategies = ""$use_strategies$1 "" } with -X shift setglobal xopt = ""$(xopt:+$xopt )$[git rev-parse --sq-quote "--$1]"" with -m|--message shift setglobal merge_msg = $1 setglobal have_message = 't' with -- shift break with * usage } shift } setglobal args_left = $Argc } test $Argc != 0 || usage setglobal have_message = '' if setglobal branch = $[git-symbolic-ref -q HEAD] { setglobal mergeopts = $[git config "branch.$(branch#refs/heads/).mergeoptions] if test -n $mergeopts { parse_config $mergeopts -- } } parse_config @Argv while test $args_left -lt $Argc { shift; } if test -z $show_diffstat { test $[git config --bool merge.diffstat] = false && setglobal show_diffstat = 'false' test $[git config --bool merge.stat] = false && setglobal show_diffstat = 'false' test -z $show_diffstat && setglobal show_diffstat = 't' } # This could be traditional "merge HEAD ..." and the # way we can tell it is to see if the second token is HEAD, but some # people might have misused the interface and used a commit-ish that # is the same as HEAD there instead. Traditional format never would # have "-m" so it is an additional safety measure to check for it. if test -z $have_message && setglobal second_token = $[git rev-parse --verify "$2^0" !2 >/dev/null] && setglobal head_commit = $[git rev-parse --verify "HEAD" !2 >/dev/null] && test $second_token = $head_commit { setglobal merge_msg = $1 shift setglobal head_arg = $1 shift } elif ! git rev-parse --verify HEAD >/dev/null !2 > !1 { # If the merged head is a valid one there is no reason to # forbid "git merge" into a branch yet to be born. We do # the same for "git pull". if test 1 -ne $Argc { echo >&2 "Can merge only exactly one commit into empty head> !2 "Can merge only exactly one commit into empty head" exit 1 } test $squash != t || die "Squash commit into empty head not supported yet" test $allow_fast_forward = t || die "Non-fast-forward into an empty head does not make sense" setglobal rh = $[git rev-parse --verify "$1^0] || die "$1 - not something we can merge" git update-ref -m "initial pull" HEAD $rh "" && git read-tree --reset -u HEAD exit } else { # We are invoked directly as the first-class UI. setglobal head_arg = 'HEAD' # All the rest are the commits being merged; prepare # the standard merge summary message to be appended to # the given message. If remote is invalid we will die # later in the common codepath so we discard the error # in this loop. setglobal merge_msg = $[for remote in @Argv { merge_name $remote } | if test $have_message = t { git fmt-merge-msg -m $merge_msg $log_arg } else { git fmt-merge-msg $log_arg }] } setglobal head = $[git rev-parse --verify "$head_arg"^0] || usage # All the rest are remote heads test "$Argc" = 0 && usage ;# we need at least one remote head. set_reflog_action "merge $ifsjoin(Argv)" setglobal remoteheads = ''for remote in @Argv { setglobal remotehead = $[git rev-parse --verify "$remote"^0 !2 >/dev/null] || die "$remote - not something we can merge" setglobal remoteheads = ""$(remoteheads)$remotehead "" eval GITHEAD_$remotehead='"$remote"' export GITHEAD_$remotehead } set x $remoteheads ; shift match $use_strategies { with '' match "$Argc" { with 1 setglobal var = $[git config --get pull.twohead] if test -n $var { setglobal use_strategies = $var } else { setglobal use_strategies = $default_twohead_strategies } with * setglobal var = $[git config --get pull.octopus] if test -n $var { setglobal use_strategies = $var } else { setglobal use_strategies = $default_octopus_strategies } } } for s in [$use_strategies] { for ss in [$no_fast_forward_strategies] { match " $s " { with *" $ss "* setglobal allow_fast_forward = 'f' break } } for ss in [$no_trivial_strategies] { match " $s " { with *" $ss "* setglobal allow_trivial_merge = 'f' break } } } match "$Argc" { with 1 setglobal common = $[git merge-base --all $head @Argv] with * setglobal common = $[git merge-base --all --octopus $head @Argv] } echo $head >"$GIT_DIR/ORIG_HEAD" match "$allow_fast_forward,$Argc,$common,$no_commit" { with ?,*,'',* # No common ancestors found. We need a real merge. with ?,1,"$1",* # If head can reach all the merge then we are up to date. # but first the most common case of merging one remote. finish_up_to_date "Already up-to-date." exit 0 with t,1,"$head",* # Again the most common case of merging one remote. echo "Updating $[git rev-parse --short $head]..$[git rev-parse --short $1]" git update-index --refresh !2 >/dev/null setglobal msg = '"Fast-forward'" if test -n $have_message { setglobal msg = ""$msg (no commit created; -m option ignored)"" } setglobal new_head = $[git rev-parse --verify "$1^0] && git read-tree -v -m -u --exclude-per-directory=.gitignore $head $new_head && finish $new_head $msg || exit dropsave exit 0 with ?,1,?*"$LF"?*,* # We are not doing octopus and not fast-forward. Need a # real merge. with ?,1,*, # We are not doing octopus, not fast-forward, and have only # one common. git update-index --refresh !2 >/dev/null match "$allow_trivial_merge,$fast_forward_only" { with t, # See if it is really trivial. git var GIT_COMMITTER_IDENT >/dev/null || exit echo "Trying really trivial in-index merge..." if git read-tree --trivial -m -u -v $common $head $1 && setglobal result_tree = $[git write-tree] { echo "Wonderful." setglobal result_commit = $[ printf '%s\n' $merge_msg | git commit-tree $result_tree -p HEAD -p $1] || exit finish $result_commit "In-index merge" dropsave exit 0 } echo "Nope." } with * # An octopus. If we can reach all the remote we are up to date. setglobal up_to_date = 't'for remote in @Argv { setglobal common_one = $[git merge-base --all $head $remote] if test $common_one != $remote { setglobal up_to_date = 'f' break } } if test $up_to_date = t { finish_up_to_date "Already up-to-date. Yeeah!" exit 0 } } if test $fast_forward_only = t { die "Not possible to fast-forward, aborting." } # We are going to make a new commit. git var GIT_COMMITTER_IDENT >/dev/null || exit # At this point, we need a real merge. No matter what strategy # we use, it would operate on the index, possibly affecting the # working tree, and when resolved cleanly, have the desired tree # in the index -- this means that the index must be in sync with # the $head commit. The strategies are responsible to ensure this. match $use_strategies { with ?*' '?* # Stash away the local changes so that we can try more than one. savestate setglobal single_strategy = 'no' with * rm -f "$GIT_DIR/MERGE_STASH" setglobal single_strategy = 'yes' } setglobal result_tree = '', best_cnt = '-1', best_strategy = '', wt_strategy = '' setglobal merge_was_ok = '' for strategy in [$use_strategies] { test $wt_strategy = '' || do { echo "Rewinding the tree to pristine..." restorestate } match $single_strategy { with no echo "Trying merge strategy $strategy..." } # Remember which strategy left the state in the working tree setglobal wt_strategy = $strategy eval 'git-merge-$strategy '"$xopt"' $common -- "$head_arg" "$@"' setglobal exit = $Status if test $no_commit = t && test $exit = 0 { setglobal merge_was_ok = 't' setglobal exit = '1' ;# pretend it left conflicts. } test $exit = 0 || do { # The backend exits with 1 when conflicts are left to be resolved, # with 2 when it does not handle the given merge at all. if test $exit -eq 1 { setglobal cnt = $[do { git diff-files --name-only git ls-files --unmerged } | wc -l] if test $best_cnt -le 0 || test $cnt -le $best_cnt { setglobal best_strategy = $strategy setglobal best_cnt = $cnt } } continue } # Automerge succeeded. setglobal result_tree = $[git write-tree] && break } # If we have a resulting tree, that means the strategy module # auto resolved the merge cleanly. if test '' != $result_tree { if test $allow_fast_forward = "t" { setglobal parents = $[git merge-base --independent $head @Argv] } else { setglobal parents = $[git rev-parse $head @Argv] } setglobal parents = $[echo $parents | sed -e 's/^/-p /] setglobal result_commit = $[printf '%s\n' $merge_msg | git commit-tree $result_tree $parents] || exit finish $result_commit "Merge made by $wt_strategy." dropsave exit 0 } # Pick the result from the best strategy and have the user fix it up. match $best_strategy { with '' restorestate match $use_strategies { with ?*' '?* echo >&2 "No merge strategy handled the merge.> !2 "No merge strategy handled the merge." with * echo >&2 "Merge with strategy $use_strategies failed.> !2 "Merge with strategy $use_strategies failed." } exit 2 with "$wt_strategy" # We already have its result in the working tree. with * echo "Rewinding the tree to pristine..." restorestate echo "Using the $best_strategy to prepare resolving by hand." git-merge-$best_strategy $common -- $head_arg @Argv } if test $squash = t { finish } else {for remote in @Argv { echo $remote } >"$GIT_DIR/MERGE_HEAD" printf '%s\n' $merge_msg >"$GIT_DIR/MERGE_MSG" || die "Could not write to $GIT_DIR/MERGE_MSG" if test $allow_fast_forward != t { printf "%s" no-ff } else { : } >"$GIT_DIR/MERGE_MODE" || die "Could not write to $GIT_DIR/MERGE_MODE" } if test $merge_was_ok = t { echo >&2 \ "Automatic merge went well; stopped before committing as requested> !2 \ "Automatic merge went well; stopped before committing as requested" exit 0 } else { do { echo ' Conflicts: ' git ls-files --unmerged | sed -e 's/^[^ ]* / /' | uniq } >>"$GIT_DIR/MERGE_MSG" git rerere $rr_arg die "Automatic merge failed; fix conflicts and then commit the result." }