Skip to contentSkip to conversation

A utility to perform merges between Subversion branches using git

PATCH In review Steven Grimm 2 participants 3 messages Jun 15, 2016

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 A utility to perform merges between Subversion branches using git

---
 contrib/svn/README       |    8 +++
 contrib/svn/git-svnmerge |  139 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 147 insertions(+), 0 deletions(-)
 create mode 100644 contrib/svn/README
 create mode 100755 contrib/svn/git-svnmerge
contrib/svn/README new file +8 -0
new file mode 100644
index 0000000..f3627ab
@@ -0,0 +1,8 @@
+This directory contains tools useful for a mixed git/Subversion environment.
+They will likely all depend on git-svnimport or git-svn, which are the main
+Subversion bridges in git.
+
+git-svnmerge
+	Use git to make up for svn's lack of merge tracking. This tool lets
+	you merge between Subversion branches in your git-svn repository.
+
contrib/svn/git-svnmerge new file +139 -0

139 lines not on this page · raw message

Signature · 1 line
1.5.2.rc0.35.gf41c8
#1 lore raw
Quoted 16 lines · Steven Grimm
contrib/svn/git-svnmerge new file +10 -0
new file mode 100755
index 0000000..e7ed45d
@@ -0,0 +1,139 @@
+#!/bin/sh
+#
+# Handles svn merges using git-svn.
+#
+# Usage: git-svnmerge [-m commit-message] other-branch-name
+#
+# This script pulls the changes from another svn branch into the current branch
+# and checks the result into svn. Then it updates the grafts file so that the
+# merge is known to git. That means that subsequent runs of the script will
+# automatically know which changes to apply.

contrib/svn/git-svnmerge line 20

Quoted 10 lines
contrib/svn/git-svnmerge new file +10 -0
+#
+# The user is assumed to have already updated the repository using git-svn.
+#
+# Exit codes:
+#   0 Merge successfully committed to svn.
+#   1 Merge has conflicts that need to be resolved.
+#   2 Encountered an error.
+#
+# Author: Steven Grimm <[email protected]>
+#

This is contrib stuff, so I'll limit the comments mostly to style.

contrib/svn/git-svnmerge line 23

Quoted 3 lines
contrib/svn/git-svnmerge new file +3 -0
+
+die() {
+	echo "$@" 1>&2

Many of our scripts do redirection at the beginning, like:

	echo >&2 ...

Also omitting 1 when redirecting to stderr is an idiom.

contrib/svn/git-svnmerge line 25

Quoted 2 lines
contrib/svn/git-svnmerge new file +2 -0
+	exit 2
+}

Does exit value 2 have any significance, or just any non-zero value would do?

contrib/svn/git-svnmerge line 29

Quoted 4 lines
contrib/svn/git-svnmerge new file +4 -0
+
+store_status() {
+	echo $Status $OldHead $RevToMerge $CommitMessage > "$StatusFile"
+}

We do not seem to do CamelCase on shell variable names. This is minor, as it is mostly taste.

contrib/svn/git-svnmerge line 31

Quoted 2 lines
contrib/svn/git-svnmerge new file +2 -0
+
+while test $# -gt 1; do

while <<command>>
do

without semicolon.

contrib/svn/git-svnmerge line 33

Quoted 2 lines
contrib/svn/git-svnmerge new file +2 -0
+	case "$1" in
+		-m)

Align case arms with "case/esac", just like you would do "switch" and "case" in C.

contrib/svn/git-svnmerge line 40

Quoted 3 lines
contrib/svn/git-svnmerge new file +3 -0
+		*)
+			die "Unknown argument $1"
+	esac

... and do not omit ';;' just like you do not omit "break" in the last case arm in switch in C.

contrib/svn/git-svnmerge line 43

Quoted 3 lines
contrib/svn/git-svnmerge new file +3 -0
+done
+
+if test $# -lt 1; then
	if <<command>>
        then

without semicolon.

contrib/svn/git-svnmerge line 47

Quoted 4 lines
contrib/svn/git-svnmerge new file +4 -0
+	die "Usage: $0 [-m commit-message] other-branch-name"
+fi
+
+GitDir="`git rev-parse --git-dir`"

GIT_DIR?

Quoted 10 lines
contrib/svn/git-svnmerge new file +10 -0
+if test -z "$GitDir"; then
+	die "Not a git repository."
+fi
+StatusFile="$GitDir/svnmerge-status"
+
+# We might be resuming a previous run, so get the old state if any.
+Status="start"
+if test -f "$StatusFile"; then
+	read Status OldHead RevToMerge CommitMessage < "$StatusFile"
+fi

contrib/svn/git-svnmerge line 67

Quoted 10 lines
contrib/svn/git-svnmerge new file +10 -0
+
+if test "$Status" = "start"; then
+	# Make sure there aren't uncommitted (to svn) changes here.
+	if test -n "`git svn dcommit -n`"; then
+		die "Can't merge to dirty branch"
+	fi
+
+	# Record the revisions we're merging. We'll use them in the
+	# grafts file later.
+	OldHead="`git rev-list --max-count=1 HEAD`"

OldHead=git rev-parse --verify HEAD

contrib/svn/git-svnmerge line 71

Quoted 4 lines
contrib/svn/git-svnmerge new file +4 -0
+	RevToMerge="`git rev-list --max-count=1 $1`"
+	if test -z "$RevToMerge"; then
+		die "Can't merge nonexistent branch."
+	fi

Wouldn't it possible that $1 might have $IFS in it here, would it?

	RevToMerge=`git rev-parse --verify "$1"`

If you are expecting "$1" to be only an existing branch, not just an arbitrary rev, you might want to be more strict by saying:

	RevToMerge=`git show-ref -s refs/heads/"$1"` ||
        die "Can't merge nonexistent branch."
Quoted 22 lines
contrib/svn/git-svnmerge new file +22 -0
+
+	# Do the actual merge.
+	git merge --squash "$RevToMerge" || \
+		MergeFailed=1
+
+	# Did we actually merge anything?
+	if git status > /dev/null; then
+		:
+	else
+		echo "No changes to merge. Have you fetched from svn?"
+		exit 0
+	fi
+
+	Status=merged
+	store_status
+
+	# If there are conflicts, bail out.
+	if test -n "$MergeFailed`git ls-files -u`"; then
+		echo "Please resolve conflicts and run again to continue."
+		exit 1
+	fi
+fi

contrib/svn/git-svnmerge line 103

Quoted 10 lines
contrib/svn/git-svnmerge new file +10 -0
+
+if test "$Status" = "merged"; then
+	if test -n "`git ls-files -u`"; then
+		echo "There are still conflicts; can't continue."
+		exit 1
+	fi
+
+	SvnRevision="`git log --pretty=format:%b -n 1 $RevToMerge | \
+			egrep '^git-svn-id:' | \
+			sed 's:.*/\([^/]*\)@\([0-9]*\) [0-9a-f].*:\1 revision \2:'`"

Piping grep to sed often makes your script look amateurish. How about...

        script='s|^git-svn-id:.*/\([^/]*\)@\([0-9]*\) [0-9a-f].*|\1 revision \2|p'
        git show -s --pretty=format:%b $RevToMerge |
        sed -n -e "$script"
Quoted 14 lines
contrib/svn/git-svnmerge new file +14 -0
+	echo "$SvnRevision merged. Committing."
+
+	if test -z "$CommitMessage"; then
+		CommitMessage="Merge from $SvnRevision"
+	fi
+
+	git commit -m "$CommitMessage" || \
+		die "Commit failed"
+
+	Status=committed
+	store_status
+fi
+
+# Since git-svn dcommit can fail for reasons having nothing to do with the

contrib/svn/git-svnmerge line 127

Quoted 10 lines
contrib/svn/git-svnmerge new file +10 -0
+# local repository (e.g. the svn server is down), we allow the user to retry
+# the dcommit by running this command again.
+if test "$Status" = "committed"; then
+	git svn dcommit || \
+		die "Can't commit to Subversion"
+
+	# dcommit will update our current HEAD to point to the newly committed
+	# svn revision. Update grafts file to tell git that it's a merge.
+	NewRevision="`git rev-list --max-count=1 HEAD`"
+	echo "$NewRevision $OldHead $RevToMerge" >> $GitDir/info/grafts

This graft look very yucky, and would not scale.

Presumably, the tree in HEAD commit before dcommit match the one in NewRevision, right? I wonder if there is a way to convince "git-svn" after dcommit that the commit you made yourself above is where it should stay.

Quoted 14 lines
contrib/svn/git-svnmerge new file +12 -0
+
+	rm "$StatusFile"
+	echo "Successfully merged $SvnRevision."
+
+	Status=""
+fi
+
+if test -n "$Status"; then
+	die "Unknown status $Status in status file!"
+fi
+
+exit 0
Signature · 1 line
1.5.2.rc0.35.gf41c8
#2 lore raw
Quoted 10 lines · Junio C Hamano

contrib/svn/git-svnmerge line 127

Quoted 8 lines
contrib/svn/git-svnmerge new file +8 -0
+if test "$Status" = "committed"; then
+	git svn dcommit || \
+		die "Can't commit to Subversion"
+
+	# dcommit will update our current HEAD to point to the newly committed
+	# svn revision. Update grafts file to tell git that it's a merge.
+	NewRevision="`git rev-list --max-count=1 HEAD`"
+	echo "$NewRevision $OldHead $RevToMerge" >> $GitDir/info/grafts

This graft look very yucky, and would not scale.

Style issues aside...

I do not know what dcommit is supposed to do, but I wonder if that graft part can be folded into it. Even if git-svn cannot deal with merges that happen on the svn side, assuming if your grafting solution works, it indicates that git-svn itself does not have trouble seeing a merge commit on git side, as its invocation of "git-rev-list --parents" would show that the commit you grafted an extra parent to has two parents and git-svn needs to be able to deal with it.

The command sequence in your contrib script is:

- make a commit on top of git-svn managed head by hand, recording what should have happened if git-svn knew how to merge two svn branches;

- make svn dcommit to record it to svn side, and let it make an extra commit to mark the synchronization between git-svn managed head and svn repository;

- pretend that the synchronization commit svn dcommit made has an extra commit, which is what you created by hand;

So perhaps you can add an option "git-svn dcommit --merge $OldHead $RevToMerge" that does whatever it usually does and record the resulting commit as a merge between these two commits?