#!/bin/bash
LF='
'
die () {
    echo >&2 "$*"
    exit 1
}
# Copyright (c) 2005 Junio C Hamano
#
# Resolve two or more trees.
#

merge-octopus-fork () { 
	# 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

	# MRC is the current "merge reference commit"
	# MRT is the current "merge result tree"

	MRC=$(git rev-parse --verify -q $head)
	MRT=$(git write-tree)
	NON_FF_MERGE=0
	OCTOPUS_FAILURE=0
	for SHA1 in $remotes
	do
		case "$OCTOPUS_FAILURE" in
		1)
			# We allow only last one to have a hand-resolvable
			# conflicts.  Last round failed and we still had
			# a head to merge.
			echo "Automated merge did not work."
			echo "Should not be doing an Octopus."
			return 2
		esac

		eval pretty_name=\${GITHEAD_$SHA1:-$SHA1}
		if test "$SHA1" = "$pretty_name"
		then
			SHA1_UP="$(echo "$SHA1" | tr a-z A-Z)"
			eval pretty_name=\${GITHEAD_$SHA1_UP:-$pretty_name}
		fi
		common=$(git merge-base --all $SHA1 $MRC) ||
			die "Unable to find common commit with $pretty_name"

		case "$LF$common$LF" in
		*"$LF$SHA1$LF"*)
			echo "Already up-to-date with $pretty_name"
			continue
			;;
		esac

		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: $pretty_name"
			git read-tree -u -m $head $SHA1 || return 0
			MRC=$SHA1 MRT=$(git write-tree)
			continue
		fi

		NON_FF_MERGE=1

		echo "Trying simple merge with $pretty_name"
		git read-tree -u -m --aggressive  $common $MRT $SHA1 || return 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

			if test $? -ne 0
			then
				git apply-conflict-resolution || OCTOPUS_FAILURE=1
			fi

			next=$(git write-tree 2>/dev/null)
		fi

		MRC="$MRC $SHA1"
		MRT=$next
	done

	return "$OCTOPUS_FAILURE"
}
usage() {
cat <<EOF
usage: git octopus [options] [<pattern>...]

    -n     leaves the repository back to HEAD
    -c     Commit the resulting merge in the current branch.
    -s <n> do the octopus by chunk of n branches.
    -e <p> exclude branches matching the pattern.
    -v     prints the version of git-octopus
EOF
exit
}

removeAllFrom() {
    from=$1
    remove=$2
    for i in $remove; do
        from=${from/$i/}
    done
    echo "$from"
}

line_break() {
    echo "-----------------------------------------------------------"
}

# Save the current state of the repository in $triggeredBranch
triggeredBranch=$(git symbolic-ref --short HEAD 2> /dev/null) ||
    triggeredBranch=$(git rev-parse HEAD)
# We save the current HEAD in case of an octopus by chunk
triggeredSha1=$(git rev-parse --verify HEAD)


signalHandler() {
    echo
    line_break
    echo "Stoping..."
    echo "HEAD -> $triggeredBranch"
    git reset -q --hard $triggeredSha1
    git checkout -q $triggeredBranch
    git clean -d -f
    exit 1
}

doCommit=$(git config octopus.commit)
splitByChunk=false
while getopts "nhvcs:e:" opt; do
  case "$opt" in
    h)
      usage
      ;;
    n)
      doCommit=false
      ;;
    c)
      doCommit=true
      ;;
    v)
      echo "1.4"
      exit 0
      ;;
    s)
      [[ $OPTARG =~ ^-?[0-9]+$ ]] || die "-s argument must be a postive number"
      splitByChunk=true
      chunkSize=$OPTARG
      ;;
    e)
      exclude+=" $OPTARG"
      ;;
    \?)
      exit 1
      ;;
  esac
done

[[ -z $(git status --porcelain) ]] || die "The repository has to be clean"

trap signalHandler SIGINT SIGQUIT

#Shift all options in order to iterate over refspec-patterns
shift $(expr $OPTIND - 1)

#Retrive patterns written in the conf
patterns=$(git config --get-all octopus.pattern)
excludePatterns=$(git config --get-all octopus.excludePattern)

#Overriding the conf with the patterns given as parameters
if [[ -n "$@" ]] ; then
    patterns=$@
fi
if [[ -n "$exclude" ]]; then
    excludePatterns=${exclude:1}
fi

#Exit code 0 if nothing to merge
if [[ -z "$patterns" ]] ; then
    exit 0
fi

branches=$(git ls-remote . $patterns | cut -d $'\t' -f 2)
#Get nothing if excludePatterns is empty
[[ -n "$excludePatterns" ]] && excludedBranches=$(git ls-remote . $excludePatterns | cut -d $'\t' -f 2)
branches=$(removeAllFrom "$branches" "$excludedBranches")

[[ -z "$excludedBranches" ]] || echo "Excluding branches :"
for branch in $excludedBranches ; do
    echo $'\t'$branch
done

if [ -z "$branches" ]; then
  echo "No branch matching $patterns were found"
  exit 0
fi

echo "Branches beeing merged :"
for branch in $branches ; do
    echo $'\t'$branch
done

line_break

mergeBases= sha1s= octopusMessage= i=0
for branch in $branches
do
    sha1=$(git rev-parse --verify "$branch")
    sha1s[$i]="$sha1"
    eval GITHEAD_$sha1='"$branch"'
    export GITHEAD_$sha1

    # merges bases are not used in the octopus stategy so we don't need to compute them
    # mergeBases="$mergeBases`git merge-base --all HEAD $branch` "

    octopusMessage[$i]="$branch"
    ((i++))
done
$splitByChunk || chunkSize=${#sha1s[@]}

$splitByChunk && echo "Will merge ${#sha1s[@]} branches by chunks of $chunkSize"
for ((i=0; $i < ${#sha1s[@]}; i+=$chunkSize))
do
    if $splitByChunk; then
      upperChunk=$(($i + $chunkSize))
      [ $upperChunk -gt ${#sha1s[@]} ] && upperChunk=${#sha1s[@]}
      echo "Merging chunks $i to $upperChunk (out of ${#sha1s[@]})"
    fi
    sha1sChunk=" ${sha1s[@]:$i:$chunkSize}"

    alreadyUpToDate=true
    for sha1 in ${sha1sChunk[@]}
    do
      git merge-base --is-ancestor $sha1 HEAD || alreadyUpToDate=false
    done

    # This prevents git-octopus to create a commit when there's nothing to merge,
    # i.e. no feature branches but only master.
    $alreadyUpToDate && octopusStatus=0 && echo "Already up to date" && continue

    merge-octopus-fork "$mergeBases" -- HEAD $sha1sChunk
    octopusStatus=$?
    if [ $octopusStatus -eq 0 ]
    then
        if [[ $doCommit || $splitByChunk ]]; then
          tree=$(git write-tree)
          head=$(git rev-parse --verify HEAD)
          octopusMessageChunk="${octopusMessage[@]:$i:$chunkSize} "
          commit=$(git commit-tree -p $head ${sha1sChunk// / -p } -m "${octopusMessageChunk// /$LF}" $tree)
          git update-ref HEAD $commit
          $splitByChunk && echo "Chunk success"
        fi
    else
        $splitByChunk && echo "Chunk failed"
        break
    fi
done

if [ $octopusStatus -eq 0 ]
then
    if ! $doCommit; then
      git reset -q --hard $triggeredSha1
    fi
    line_break
    echo "OCTOPUS SUCCESS"
else
    # Octopus merge failed, starting to run the analysis sequence ...
    line_break

    git reset -q --hard $triggeredSha1

    echo "Testing merges one by one with $triggeredBranch..."
    echo

    tmpFile=

    # Will perform a simple merge from the current branch with each branches one by one.
    for branch in $branches
    do
        if [[ $(git rev-parse $branch) != $(git rev-parse $triggeredBranch) ]]
        then
            echo -n "merging $branch ... "

            # merge base is not used in the octopus strategy
            # mergeBase=$(git merge-base --all HEAD $branch)
            mergeBase=

            sha1=$(git rev-parse --verify "$branch")

            tmpFile=$(merge-octopus-fork "$mergeBase" -- HEAD $sha1 2>&1)

            if [ $? -eq 0 ]
            then
                echo "SUCCESS"
            else
                echo "FAILED"
                echo "$tmpFile"
                git diff
                conflicts+="$branch "
            fi
            git reset -q --hard
        fi
    done
    
    line_break

    if [ -z "$conflicts" ]; then
        echo "No conflicts found between $triggeredBranch and the rest of the branches"
    else
        echo "$triggeredBranch has conflicts with :"
        for branch in $conflicts
        do
            echo $'\t'$branch
        done
    fi

    echo "OCTOPUS FAILED"
    exit 1
fi
