diff options
-rw-r--r-- | Makefile | 3 | ||||
-rwxr-xr-x | git-commit.sh | 2 | ||||
-rwxr-xr-x | git-merge-octopus.sh | 86 | ||||
-rwxr-xr-x | git-merge-resolve.sh | 80 | ||||
-rwxr-xr-x | git-merge.sh | 212 |
5 files changed, 381 insertions, 2 deletions
@@ -73,7 +73,8 @@ SCRIPT_SH = \ git-repack.sh git-request-pull.sh git-reset.sh \ git-resolve.sh git-revert.sh git-sh-setup.sh git-status.sh \ git-tag.sh git-verify-tag.sh git-whatchanged.sh git.sh \ - git-applymbox.sh git-applypatch.sh + git-applymbox.sh git-applypatch.sh \ + git-merge.sh git-merge-resolve.sh git-merge-octopus.sh SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ diff --git a/git-commit.sh b/git-commit.sh index 741444aa06..d8bfc3c254 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -158,7 +158,7 @@ if [ ! -r "$GIT_DIR/HEAD" ]; then PARENTS="" else if [ -f "$GIT_DIR/MERGE_HEAD" ]; then - PARENTS="-p HEAD -p MERGE_HEAD" + PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"` fi if test "$use_commit" != "" then diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh new file mode 100755 index 0000000000..aa1cd2f106 --- /dev/null +++ b/git-merge-octopus.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# +# Resolve two or more trees. +# + +# The first parameters up to -- are merge bases; the rest are heads. +bases= head= remotes= sep_seen= +for arg +do + case ",$sep_seen,$head,$arg," in + *,--,) + sep_seen=yes + ;; + ,yes,,*) + head=$arg + ;; + ,yes,*) + remotes="$remotes$arg " + ;; + *) + bases="$bases$arg " + ;; + esac +done + +# Reject if this is not an Octopus -- resolve should be used instead. +case "$remotes" in +?*' '?*) + ;; +*) + exit 2 ;; +esac + +# MRC is the current "merge reference commit" +# MRT is the current "merge result tree" + +MRC=$head MSG= PARENT="-p $head" +MRT=$(git-write-tree) +CNT=1 ;# counting our head +NON_FF_MERGE=0 +for SHA1 in $remotes +do + common=$(git-merge-base $MRC $SHA1) || + die "Unable to find common commit with $SHA1" + + if test "$common" = $SHA1 + then + echo "Already up-to-date with $SHA1" + continue + fi + + CNT=`expr $CNT + 1` + PARENT="$PARENT -p $SHA1" + + if test "$common,$NON_FF_MERGE" = "$MRC,0" + then + # The first head being merged was a fast-forward. + # Advance MRC to the head being merged, and use that + # tree as the intermediate result of the merge. + # We still need to count this as part of the parent set. + + echo "Fast forwarding to: $SHA1" + git-read-tree -u -m $head $SHA1 || exit + MRC=$SHA1 MRT=$(git-write-tree) + continue + fi + + NON_FF_MERGE=1 + + echo "Trying simple merge with $SHA1" + git-read-tree -u -m $common $MRT $SHA1 || exit 2 + next=$(git-write-tree 2>/dev/null) + if test $? -ne 0 + then + echo "Simple merge did not work, trying automatic merge." + git-merge-index -o git-merge-one-file -a || + exit 2 ; # Automatic merge failed; should not be doing Octopus + next=$(git-write-tree 2>/dev/null) + fi + MRC=$common + MRT=$next +done + +exit 0 diff --git a/git-merge-resolve.sh b/git-merge-resolve.sh new file mode 100755 index 0000000000..f0a19b4c8a --- /dev/null +++ b/git-merge-resolve.sh @@ -0,0 +1,80 @@ +#!/bin/sh +# +# Copyright (c) 2005 Linus Torvalds +# +# Resolve two trees. + +# The first parameters up to -- are merge bases; the rest are heads. +bases= head= remotes= sep_seen= +for arg +do + case ",$sep_seen,$head,$arg," in + *,--,) + sep_seen=yes + ;; + ,yes,,*) + head=$arg + ;; + ,yes,*) + remotes="$remotes$arg " + ;; + *) + bases="$bases$arg " + ;; + esac +done + +# Give up if we are given more than two remotes -- not handling octopus. +case "$remotes" in +?*' '?*) + exit 2 ;; +esac + +# Find an optimum merge base if there are more than one candidates. +case "$bases" in +?*' '?*) + echo "Trying to find the optimum merge base." + G=.tmp-index$$ + best= + best_cnt=-1 + for c in $bases + do + rm -f $G + GIT_INDEX_FILE=$G git-read-tree -m $c $head $remotes \ + 2>/dev/null || continue + # Count the paths that are unmerged. + cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l` + if test $best_cnt -le 0 -o $cnt -le $best_cnt + then + best=$c + best_cnt=$cnt + if test "$best_cnt" -eq 0 + then + # Cannot do any better than all trivial merge. + break + fi + fi + done + rm -f $G + common="$best" + ;; +*) + common="$bases" + ;; +esac + +git-update-index --refresh 2>/dev/null +git-read-tree -u -m $common $head $remotes || exit 2 +echo "Trying simple merge." +if result_tree=$(git-write-tree 2>/dev/null) +then + exit 0 +else + echo "Simple merge failed, trying Automatic merge." + if git-merge-index -o git-merge-one-file -a + then + exit 0 + else + exit 1 + fi +fi diff --git a/git-merge.sh b/git-merge.sh new file mode 100755 index 0000000000..9926e45d1a --- /dev/null +++ b/git-merge.sh @@ -0,0 +1,212 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +. git-sh-setup || die "Not a git archive" + +LF=' +' + +usage () { + die "git-merge [-n] [-s <strategy>]... <merge-message> <head> <remote>+" +} + +# all_strategies='resolve multibase fredrik octopus' + +all_strategies='resolve multibase octopus' +default_strategies='resolve multibase octopus' +use_strategies= + +dropheads() { + rm -f -- "$GIT_DIR/MERGE_HEAD" || exit 1 +} + +summary() { + case "$no_summary" in + '') + git-diff-tree -p -M $head "$1" | + git-apply --stat --summary + ;; + esac +} + +while case "$#" in 0) break ;; esac +do + case "$1" in + -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\ + --no-summa|--no-summar|--no-summary) + no_summary=t ;; + -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ + --strateg=*|--strategy=*|\ + -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) + case "$#,$1" in + *,*=*) + strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;; + 0,*) + usage ;; + *) + strategy="$2" + shift ;; + esac + case " $all_strategies " in + *" $strategy "*) + use_strategies="$use_strategies$strategy " ;; + *) + die "available strategies are: $all_strategies" ;; + esac + ;; + -*) usage ;; + *) break ;; + esac + shift +done + +case "$use_strategies" in +'') + use_strategies=$default_strategies + ;; +esac +test "$#" -le 2 && usage ;# we need at least two heads. + +merge_msg="$1" +shift +head=$(git-rev-parse --verify "$1"^0) || usage +shift + +# All the rest are remote heads +for remote +do + git-rev-parse --verify "$remote"^0 >/dev/null || + die "$remote - not something we can merge" +done + +common=$(git-show-branch --merge-base $head "$@") +echo "$head" >"$GIT_DIR/ORIG_HEAD" + +case "$#,$common" in +*,'') + die "Unable to find common commit between $head and $*" + ;; +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 + echo "Already up-to-date. Yeeah!" + dropheads + exit 0 + ;; +1,"$head") + # Again the most common case of merging one remote. + echo "Updating from $head to $1." + git-update-index --refresh 2>/dev/null + git-read-tree -u -m $head "$1" || exit 1 + git-rev-parse --verify "$1^0" > "$GIT_DIR/HEAD" + summary "$1" + dropheads + exit 0 + ;; +1,*) + # We are not doing octopus and not fast forward. Need a + # real merge. + ;; +*) + # An octopus. If we can reach all the remote we are up to date. + up_to_date=t + for remote + do + common_one=$(git-merge-base $head $remote) + if test "$common_one" != "$remote" + then + up_to_date=f + break + fi + done + if test "$up_to_date" = t + then + echo "Already up-to-date. Yeeah!" + dropheads + exit 0 + fi + ;; +esac + +# At this point we need a real merge. Require that the tree matches +# exactly our head. + +git-update-index --refresh && +test '' = "`git-diff-index --cached --name-only $head`" || { + die "Need real merge but the working tree has local changes." +} + +result_tree= best_cnt=-1 best_strategy= wt_strategy= +for strategy in $use_strategies +do + test "$wt_strategy" = '' || { + echo "Rewinding the tree to pristine..." + git reset --hard $head + } + echo "Trying merge strategy $strategy..." + wt_strategy=$strategy + git-merge-$strategy $common -- $head "$@" || { + + # The backend exits with 1 when conflicts are left to be resolved, + # with 2 when it does not handle the given merge at all. + + exit=$? + if test "$exit" -eq 1 + then + cnt=`{ + git-diff-files --name-only + git-ls-files --unmerged + } | wc -l` + if test $best_cnt -le 0 -o $cnt -le $best_cnt + then + best_strategy=$strategy + best_cnt=$cnt + fi + fi + continue + } + + # Automerge succeeded. + result_tree=$(git-write-tree) && break +done + +# If we have a resulting tree, that means the strategy module +# auto resolved the merge cleanly. +if test '' != "$result_tree" +then + parents="-p $head" + for remote + do + parents="$parents -p $remote" + done + result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) + echo "Committed merge $result_commit, made by $wt_strategy." + echo $result_commit >"$GIT_DIR/HEAD" + summary $result_commit + dropheads + exit 0 +fi + +# Pick the result from the best strategy and have the user fix it up. +case "$best_strategy" in +'') + git reset --hard $head + die "No merge strategy handled the merge." + ;; +"$wt_strategy") + # We already have its result in the working tree. + ;; +*) + echo "Rewinding the tree to pristine..." + git reset --hard $head + echo "Using the $best_strategy to prepare resolving by hand." + git-merge-$best_strategy $common -- $head "$@" + ;; +esac +for remote +do + echo $remote +done >"$GIT_DIR/MERGE_HEAD" +die "Automatic merge failed; fix up by hand" |