[svnmerge] Bidirectional merging

Raman Gupta rocketraman at fastmail.fm
Sun Aug 21 11:26:49 PDT 2005


I am posting to both the new list at orcaware (for continued discussion)
as well as the old list (in case interested parties have not subscribed
to the orcaware list yet).

> Archie Cobbs wrote:
>
> Yes, this is a "known problem".

Hmm, too bad. I think that "bidirectional merges" are a no-go for all
but trivial cases until this is resolved.

> Let's call changes that make a round trip through svnmerge
> "reflected changes" (these can only happen when there are cycles
> in the graph of merge tracked branches).
> 
> In my testing, which was very simplistic, svn's merging algorithm
> always properly handled reflected merges, i.e., it always realized
> that the reflected merge had already been applied and so, as desired,
> the merge operation became a no-op.
> 
> So first of all I'm interested in understanding precisely what situations
> lead to svn NOT merging them properly.
> 
> That is: under what conditions will svn generate a conflict when merging
> in reflected changes? More importantly, under what conditions are these
> generated conflicts actually "bogus"? And define "bogus" :-)

Chapter 4 of the svn book states "When patching a file, Subversion
typically notices if the file already has the change, and does nothing.
But if the already-existing change has been modified in any way, you'll
get a conflict."

So, taking that statement as a guide, the basic problem is pretty easy
to demonstrate:

1) Initiate bi-directional merging between two trees (call them trunk
and branch).

2) Make a change, say change A, to the branch.

3) Merge change to trunk.

4) Make change B to change A on the trunk.

5) Attempt to merge trunk to branch.

Because svnmerge will attempt to apply both change A, and change B back
to the branch at step 5, we will get a conflict, which is clearly
spurious (bogus). For true bi-directional merging, we need to merge only
change B back to the branch, because change A came from the branch in
the first place (i.e. it is a reflected change).

I have attached a "testrepo.zip" repository that has been "initialized"
to step #4. The following commands will execute step 5, and demonstrate
the bogus conflict:

svn co file:///path/to/testrepo testsrc
cd testsrc/branch
svnmerge merge -f commit.txt -r $(svnmerge avail) \
  -S file:///path/to/testrepo/trunk

> As for solving the problem, there are a few different approaches,
> none of which are very elegant...

I feel your pain. I think what needs to be done is to store the revision
numbers of merges (or merge-points) somewhere, so that we can exclude
those revision(s) when merging back.

I guess the problem with doing this with subversion properties is that
the revision number is not generated until commit time, so it can't be
set as a property.

One solution that is not too inelegant, might be to create a "dot-file"
that contains the revision numbers of merges by using the $Revision$
keyword on each merge operation. The file would be named with the branch
name:

.svnmerge-merged.<branch-name>

On each merge from the <branch-name> source branch, the revision number
that is in that file (if any) would be made permanent by taking it from
the expanded keyword and prepending it. For example, in the testrepo
example above, the file would be created and the contents after the
merge is committed on the trunk at rev 6 would be:

# .svnmerged-merged.branch
merge-points: $Revision: 6$

The next merge would take the revision value from the keyword and add it
as a fixed value. The $Revision$ keyword can be left as is to be updated
on commit (say rev 10):

merge-points: 6, $Revision: 10$

Now, when merging from trunk to branch, svnmerge looks at the
svnmerge-integrated property to get the available revisions, and
subtracts from the available revisions the contents of the
".svnmerged-merged.branch" file on the source branch (trunk in this
case) -- this would be done by parsing the results of the "svn cat URL"
command.

As a hand example of how this would work, take my testrepo above. Back
on the branch, we execute "svnmerge merge". Svnmerge looks at the
"svn-integrated" property as it currently does. In my testrepo, this
property on the branch is:

svnmerge-integrated : /trunk:1

However, svnmerge also looks at the results of "svn cat
file:///path/to/testrepo/trunk/.svnmerged-merged.branch" and determines
that revision 6 was a merge-point on the trunk for this branch.

So now, svnmerge knows that rev 6 on the trunk came from the branch so
it should not be reflected. It therefore merges trunk revisions 2-5
(-r1:6) and 7 (-r6:7), which are the only remaining ranges on the trunk.
This results in the correct merge with no conflicts.

It also creates on the branch another .svnmerge-merged file, which after
the commit of the merged revision 8 would contain:

# .svnmerged-merged.trunk
merge-points: $Revision: 8$

Lets simulate one more step. Say we have a bunch more commits on the
branch and then go back to the trunk and execute an "svnmerge merge". We
see that /branch:1-5 has already been integrated via the
svnmerge-integrated property. We also see, based on the
.svnmerged-merged.trunk file on the branch that rev 8 on the branch was
a merge point. Therefore, the available revisions would be 6-7
inclusive, and 9-HEAD inclusive. This again results in the correct
merge. And of course, the .svnmerged-merged.branch file is updated
appropriately.

Did I miss any use cases?

Is creating a dot-file in someone's repository acceptable?  Personally,
I would gladly live with it if it gives me bi-directional functionality.

Cheers,
Raman
-------------- next part --------------
A non-text attachment was scrubbed...
Name: testrepo.zip
Type: application/x-zip-compressed
Size: 21354 bytes
Desc: not available
Url : /pipermail/svnmerge/attachments/20050821/dacf7d5c/attachment.bin 


More information about the Svnmerge mailing list