16

I have read numerous SO questions and blogs on git-svn and merging. Some of them (notably git-svn man page) warns against using merge anywhere near the branch I plan to dcommit from.

Others, like this SO answer, state that merging is fine as long as I use one-off feature branches that I delete before dcommitting.

I have a setup with two long-living SVN (and git) branches:

  • trunk (git-svn: svn/trunk, git: master) - the stable branch
  • branches/devel (git-svn: svn/devel, git: devel) - the main development branch from which I fork off feature branches (and I can live with rebasing them back into devel instead of merging).

Snapshot of current state in Source Tree

In the picture above, (1) shows past SVN history, where branches/devel has been merged into trunk.

(2) shows that I have successfully dcommitted my current development back into SVN.

The question: can I use git-svn to merge two SVN branches so that history shows the merge point (as SVN itself can do)? In other words: what happens if I were to dcommit from master as shown in (3)?

Will this mess up my SVN (or git) history? Or will this just plain forget that devel was merged into master at all?

EDIT: If git just forgets that devel was merged into master, is there a difference between dcommiting from point (3) and applying all the commits as a single patch?

Community
  • 1
  • 1
Laas
  • 5,978
  • 33
  • 52

2 Answers2

14

Digging some more into the matter, I found out that git supports setting svn:mergeinfo property on the SVN branch when dcommit'ing:

git svn dcommit --mergeinfo "/branches/somebranch:1-3"

NB! the svn:mergeinfo is overwritten with whatever is given on the command-line, so be careful to list previous merges too.

While more recent git version added the config parameter to automatically set this property:

config key: svn.pushmergeinfo

I had some troubles with the automatic mergeinfo - for one reason or the other GIT calculated it wrong and I couldn't get it to work.

SOLUTION: git-merge-svn

To automate the process, I wrote a shell script git-merge-svn which can be used to merge two SVN branches with correct svn:mergeinfo set on the dcommit.

The script handles both situations:

  • the branch is not merged in git - will do git merge beforehand
  • the branches have been already merged in git (but not in SVN) - will traverse until previous ancestor for the merged commit revisions.

With this script I was able to produce these merges solely on git-side and retain the merge info so that GIT graph shows the log nicely:

git-merge-svn result

Example usage:

  1. Make some commits on devel6
  2. dcommit devel6 to SVN (required to get SVN revision numbers for the commits)
  3. check out testtunk6 - yes, I know I made a typo in the name ;-)
  4. git merge-svn devel6

The last commant outputs:

% git merge-svn devel6
About to do an SVN merge: devel6 -> testtunk6

* NEW MERGE COMMIT
|\
| * devel6 [7b71187] (r102)
* | testtunk6 [0682a45] (r101)
 \|
  * [273d6d6] (r100)


STEP 1: GIT merge
Executing:
  git merge devel6

Continue? (y/n) [n]: y
Merge made by the 'recursive' strategy.
 testfile | 1 +
 1 file changed, 1 insertion(+)

STEP 2: SVN dcommit

executing:
git svn dcommit --mergeinfo
/idp/branches/devel:9-32,35-41 /idp/branches/devel6:89 /idp/branches/devel6:94 /idp/branches/devel6:93 /idp/branches/devel6:96 /idp/branches/devel6:97 /idp/branches/devel6:99 /idp/branches/devel6:100 /idp/branches/devel6:102

Continue? (y/n) [n]: y
Committing to https://my.svn.host/svn/auth/idp/branches/testtunk6 ...
  M testfile
Committed r103
  M testfile
Found merge parent (svn:mergeinfo prop): 7b71187fc371d3f86658c5850009e63be88157ac
r103 = 87759323cbadd38bac78087d57b6870a926287e7 (refs/remotes/svn/testtunk6)
No changes between 3fb2168cfbbe605fbd810d76513443203a85a549 and refs/remotes/svn/testtunk6
Resetting to the latest refs/remotes/svn/testtunk6
Laas
  • 5,978
  • 33
  • 52
  • Is there any way to get this to work: `git-merge-svn master mybranch` followed immediately by `git-merge-svn mybranch master`? The 2nd merge attempts to dcommit to mybranch, and if you try again, it says it is already merged (a lie probably due to git master being merged with a sha hash that corresponds to the sha of mybranch despite not being committed to svn -- though the git-svn-id line is there as it was git merged to a commit that was dcommitted from mybranch to svn). – Shadow Man Aug 23 '13 at 20:47
  • @ShadowCreeper - actually, the message is probably true - SVN reports that the commits are already present in `mybranch`, which they are. Your expected result would be that `mybranch` and `master` point to same SHA1, but this is impossible with SVN - it's branches are essentially folders and two branches must point to different folders. So, in SVN all is at it's best, while in Git you can't make use of full functionality - this is how it is. – Laas Aug 26 '13 at 17:21
  • 1
    In short - any Git branch that you "push" to SVN will stay as separate branch and can not be "fast-forwarded" into another branch in Git sense, only merged in regular SVN-sense. – Laas Aug 26 '13 at 17:27
  • @Laas No, my expected result is that SVN sees `mybranch` and `master` as having been merged, and that the changes in `mybranch` are committed to `master`. The merge need not be fast-forwarded, it can be rebased. But the merge needs to happen. – Shadow Man Aug 28 '13 at 18:16
  • 1
    ...perhaps a `--rebase` or `--no-ff` option to `git-merge-svn`? – Shadow Man Aug 28 '13 at 18:18
  • It might be too long since I used SVN, but I'm not quite following you. The above script should do exactly that - merge commits from `mybranch` into `master` and record `mergeinfo` in the process so that both SVN and Git see it as merge. Your second run of the command with reversed arguments is unnecessary. Or am I missing some SVN-specific stuff here? – Laas Aug 29 '13 at 05:57
  • It's just to give `mybranch` and `master` an identical code base. We first merge `master` into `mybranch` to make sure no changes from `master` (since the initial branch or last merge) break things in `mybranch`. Then we merge the changes from `mybranch` into `master` after we've tested them. Now the code base in both `mybranch` and `master` are identical, although git may see them as 2 separate SHAs (which is fine). – Shadow Man Apr 07 '14 at 18:55
  • Since this script cannot merge things this way, I have ended up just not bothering with svn branches. Instead I just edit the code without `dcommit`. When I'm finished, `git svn fetch; git svn rebase` then test, then `git svn dcommit`. However, it would be nicer to have my changes in an svn branch (in case my hard drive crashes, or whatever) before I finish and commit to `trunk`. – Shadow Man Apr 07 '14 at 18:58
  • OK, it finally got to my thick head what you are trying to do. I reviewed my script and checked some command outputs and it seems that the script should allow both `master mybranch` followed by `mybranch master`. Where do you get your error? Does the script try to merge/dcommit and fail or does it not try at all? – Laas Apr 08 '14 at 07:49
  • BTW, @ShadowCreeper, If you would fork the Gist and patch it, I would happily merge your changes. I got as far as thinking through that the script should verify that ANCESTOR is not FROM/TO and take a step back (FROM~1 or TO~1) if that happened, but ran out of time before I could finish this. – Laas Apr 25 '14 at 08:55
  • If I understand correctly, what you are doing is correctly composing the merginfo for the git-svn dcommit command. If this is so, and if you thoroughly tested your solution, maybe you should do a pull request to the git developers, so that your code gets integrated, or the composition of the merginfo corrected. – Fazi May 22 '15 at 16:12
  • I'm afraid your last if won't hold true. This is probably an edge case anyway and definitely not thoroughly enough tested to put into somebody's toolbox w/o them explicitly knowing it. – Laas May 24 '15 at 19:23
4

Git and Svn have different data structure to keep a history.

Svn uses a simple tree. I.e. each commit has only one parent, but one commit could have several childs (branches). So, Svn doesn't support merges. The thing they call as "merge" is actually changeset porting, the closest analogy in git is rebase or maybe cherry-pick. From version 1.5 Svn supports a metainfo property svn:mergeinfo which helps somehow to track what was "merged", however it looks mostly as a workaround, that explains why branches in Svn are so hard to use. Merge point is a commit which squashes all merged changesets and conflict resolution into one changeset, probably annotated with the svn:mergeinfo property.

Git uses Directed acyclic graph which is very natural way to describe history merging and branching. A merge point is just another commit with two (or more!) parents. So, your (3) on the picture is a merge commit. So, all history looks natural, it has all commits reachable on a history graph from the current point.

Git-Svn bridge tries do the best to handle Svn design drawback, before doing dcommit it usually rebases all merged commits on the top of the current Svn branch. Asof ~2 years ago, git-svn was not able to supply svn:mergeinfo, so the thing you are asking is impossible. Moreover, when you are dcommiting, git creates a "twin" commit "hardly" linked to a svn commit, so it rewrites original commit.

kan
  • 28,279
  • 7
  • 71
  • 101
  • Thanks for the explanation. I ended up with workflow that I `rebase -i` all one-off branches to `devel` and at the release point, I just create a patch and apply that as a single commit to the `master`. This way `master` only consists of release-commits and dev-history is in the `devel` branch only. Good enough solution. – Laas Apr 19 '13 at 08:34
  • @Laas Do you mean you squash all commits into one using `rebase -i`? Yeah... could work, and quite easy to do, however it screws history, but looks like it is the best you could achieve using svn. – kan Apr 19 '13 at 08:57
  • 2
    @Laas Or alternative solution would be cherry-picking range of commits with `-x` flag, at least it will keep commits separately implicitly linking them by the "cherry picked from commit" comment. This comment is processed by `git log` and some other commands, which would simplify some stuff. – kan Apr 19 '13 at 09:00
  • sorry, but as I found an exact answer to my question myself, I had to unaccept your answer as incomplete. Still, your answer led me in right direction. Thanks. – Laas Apr 23 '13 at 09:29