git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: Trying to sync two svn repositories with git-svn (repost)

From
Avery Pennarun <apenwarr@gmail.com>
Date
May 14, 2009, 06:35 UTC
Message-ID
<32541b130905132335t3cbd0e7wf29577ee15ba0bac@mail.gmail.com>
In-Reply-To
<20090513222243.GQ15420@raven.wolf.lan>
On Wed, May 13, 2009 at 6:22 PM, Josef Wolf <jw@raven.inka.de> wrote:
> Thanks for your patience, Avery!  I would be completely lost here without
> your help..
Okay, well, I think I've been making things worse instead of better :(

Fundamentally, my claim that merging symmetrically between two svn branches "ought to be easy" was incorrect. The problem is that when git does a merge from branch A to B and then back to A, it really *really* wants the two branches to end up identical. All of git's merge machinery works on the assumption that this is what you want.

Now, you can still bypass this by using various clever tricks. The solution we were working with turned out to be *almost* right, and I did get it working in my test environment, but it got so convoluted that I couldn't even explain to myself why it was correct. That's usually a bad sign. So I threw that one away.

By far the sanest thing you could possibly do is to create a central "public" branch that contains all the common commits, then merge from that public branch to the site-specific branches, but never merge in the opposite direction. In case you happen to make some changes on the site-specific branches that you want to share, you can just cherry-pick them; the resulting conflicts when merging back are likely to be fairly minor. This would be entirely consistent with git's normal operations, and would be easy:

   git checkout public
   git cherry-pick stuff   # as rarely as possible; do the work
directly on public if you can
   git checkout svn-1
   git merge --no-ff public
   git svn dcommit
   git checkout svn-2
   git merge --no-ff public
   git svn dcommit
No criss-cross merges, no insanity, no question about whether it's correct.

More as an academic exercise than anything, I did find a way that will let you do criss-cross merging of all changes on A and B. I still don't *really* recommend you use it, because it's extremely error prone, and there are lots of places where you could get merge conflicts and then end up in trouble. (The above simple method, in contrast, might get conflicts sometimes, but you can just fix them as you encounter them and be done with it.)

The script below demonstrates how to take branches remote-ab and remote-ac, and auto-pick their changes (as they happen) into a new (automatically managed) branch public. Then it merges public back into each branch, while avoiding conflicts. The magic itself happens in extract() and crossmerge().

If nothing else, this method makes the gitk output far more sane than the original method. This is because it doesn't include the history of 'public' in the site-specific branches. That was the fundamental flaw in the method I had identified originally. You can trick that original method into working too, but it's stunningly complex. This is much more sane, albeit still not really sane.

Enjoy!
Have fun,
Avery

P.S. Sorry for the mess. I suppose I should have broken down and written (or asked for :)) a minimal test case earlier, as it quickly revealed the problem.

#!/bin/bash -x set -e rm -rf tt mkdir tt cd tt git init

count=100
newfile()
{
	count=$(($count + 1))
	echo $count >$1
	git add $1
	git commit -m "$1"
}

newfile .gitignore git checkout -b public newfile a git checkout -b remote-ab public newfile b git checkout -b remote-ac public newfile c

# We've simulated two remote branches (perhaps svn repositories), remote-ab # and remote-ac. They contain one identical file (a) and one different file # (b vs. c). We've arranged for the common part to end up in 'public'.

git tag -f remote-ab-lastmerge remote-ab git tag -f remote-ac-lastmerge remote-ac

extract()
{
	last_public="$(git merge-base $1-lastmerge public)"
	if false; then
		# use this if you want each patch separately
		git branch -f $1-public $1
		git checkout $1-public
		git rebase --onto "$last_public" $1-lastmerge
	else
		# use this if you want changes squashed into one patch
		git branch -f $1-public "$last_public"
		git checkout $1-public
		git diff --binary $1-lastmerge $1 -- | git apply --index
		(
			echo "merged from $1"
			echo
			git log $1-lastmerge..$1
		) | git commit -F -
	fi
	
	git checkout $1
	git merge -s ours -m 'no-op' $1-public
}
crossmerge()
{
	branches="remote-ab remote-ac"
	
	for b in $branches; do
		# extract the most recent changes from $b-lastmerge..$b
		# The changes can be found as public..$b-public
		extract $b
		
		# Merge those completed changes into public
		git checkout public
		git merge $b-public
		git branch -d $b-public
		git tag -f $b-lastmerge $b  # to reduce problems if this script dies
halfway through
	done
	
	# merge changes from public back into each branch.
	# changes that originated in each branch won't be re-merged, because
	# we already merged back $b-public into each $b.
	for b in $branches; do
		git checkout $b
		git merge --no-ff public
		git tag -f $b-lastmerge $b
	done
}
iterate()
{
	# Some changes have arrived in the remote repos:
	#git svn fetch ab
	#git svn fetch ac
	git checkout remote-ab
	newfile x$1
	git checkout remote-ac
	newfile y$1
	crossmerge
}

iterate 1 git tag remote-ab-1 remote-ab git tag remote-ac-1 remote-ac

iterate 2 git tag remote-ab-2 remote-ab git tag remote-ac-2 remote-ac

iterate 3
gitk --all
Previous: Josef WolfNext: Josef Wolf
Message 25 of 31 in “Trying to sync two svn repositories with git-svn (repost)”
  1. Josef WolfApr 27, 2009
  2. Josef WolfApr 28, 2009
  3. Avery PennarunApr 28, 2009
  4. Josef WolfApr 28, 2009
  5. Avery PennarunApr 29, 2009
  6. Josef WolfApr 29, 2009
  7. Avery PennarunApr 29, 2009
  8. Josef WolfApr 29, 2009
  9. Avery PennarunApr 30, 2009
  10. Josef WolfApr 30, 2009
  11. Avery PennarunApr 30, 2009
  12. Josef WolfMay 1, 2009
  13. Avery PennarunMay 1, 2009
  14. Josef WolfMay 2, 2009
  15. Avery PennarunMay 4, 2009
  16. Josef WolfMay 4, 2009
  17. Josef WolfMay 6, 2009
  18. Avery PennarunMay 6, 2009
  19. Josef WolfMay 6, 2009
  20. Avery PennarunMay 8, 2009
  21. Josef WolfMay 8, 2009
  22. Josef WolfMay 13, 2009
  23. Avery PennarunMay 13, 2009
  24. Josef WolfMay 13, 2009
  25. Avery PennarunMay 14, 2009
  26. Josef WolfMay 14, 2009
  27. Avery PennarunMay 14, 2009
  28. Josef WolfMay 15, 2009
  29. Avery PennarunMay 15, 2009
  30. Josef WolfMay 17, 2009
  31. Josef WolfMay 20, 2009

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.