summaryrefslogtreecommitdiff
path: root/.html/dev/merging-structural-changes.html
blob: e5c8795883a0b187b0360c24a104f78d9df1906a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<!DOCTYPE html>
<html>
<head>
	<title>
		The Codex » 
		Merging Structural Changes
	</title>

	<link
		rel='stylesheet'
		type='text/css'
		href='http://fonts.googleapis.com/css?family=Buenard:400,700&amp;subset=latin,latin-ext'>
	<link
		rel="stylesheet"
		type="text/css"
		href="../media/css/reset.css">
	<link
		rel="stylesheet"
		type="text/css"
		href="../media/css/grimoire.css">
</head>
<body>

<div id="shell">
	
		<ol id="breadcrumbs">
			
				<li class="crumb-0 not-last">
					
						<a href="../">index</a>
					
				</li>
			
				<li class="crumb-1 not-last">
					
						<a href="./">dev</a>
					
				</li>
			
				<li class="crumb-2 last">
					
						merging-structural-changes
					
				</li>
			
		</ol>
	

	
	<div id="article">
		<h1 id="merging-structural-changes">Merging Structural Changes</h1>
<p>In 2008, a project I was working on set out to reinvent their build process,
migrating from a mass of poorly-written Ant scripts to Maven and reorganizing
their source tree in the process. The development process was based on having
a branch per client, so there was a lot of ongoing development on the original
layout for clients that hadn't been migrated yet. We discovered that our
version control tool, <a href="http://subversion.tigris.org/">Subversion</a>, was unable
to merge the changes between client branches on the old structure and the
trunk on the new structure automatically.</p>
<p>Curiousity piqued, I cooked up a script that reproduces the problem and
performs the merge from various directions to examine the results. Subversion,
sadly, performed dismally: none of the merge scenarios tested retained content
changes when merging structural changes to the same files.</p>
<h2 id="the-preferred-outcome">The Preferred Outcome</h2>
<p><img alt="Both changes survive the
merge." src="/media/dev/merging-structural-changes/ideal-merge-results"></p>
<p>The diagram above shows a very simple source tree with one directory, <code>dir-a</code>,
containing one file with two lines in it. On one branch, the file is modified
to have a third line; on another branch, the directory is renamed to <code>dir-b</code>.
Then, both branches are merged, and the resulting tree contains both sets of
changes: the file has three lines, and the directory has a new name.</p>
<p>This is the preferred outcome, as no changes are lost or require manual
merging.</p>
<h2 id="subversion">Subversion</h2>
<p><img alt="Subversion loses the content
change." src="/media/dev/merging-structural-changes/subversion-merge-results"></p>
<p>There are two merge scenarios in this diagram, with almost the same outcome.
On the left, a working copy of the branch where the file's content changed is
checked out, then the changes from the branch where the structure changed are
merged in. On the right, a working copy of the branch where the structure
changed is checked out, then the changes from the branch where the content
changed are merged in. In both cases, the result of the merge has the new
directory name, and the original file contents. In one case, the merge
triggers a rather opaque warning about a “missing file”; in the other, the
merge silently ignores the content changes.</p>
<p>This is a consequence of the way Subversion implements renames and copies.
When Subversion assembles a changeset for committing to the repository, it
comes up with a list of primitive operations that reproduce the change. There
is no primitive that says “this object was moved,” only primitives which say
“this object was deleted” or “this object was added, as a copy of that
object.” When you move a file in Subversion, those two operations are
scheduled. Later, when Subversion goes to merge content changes to the
original file, all it sees is that the file has been deleted; it's completely
unaware that there is a new name for the same file.</p>
<p>This would be fairly easy to remedy by adding a “this object was moved to that
object” primitive to the changeset language, and <a href="http://subversion.tigris.org/issues/show_bug.cgi?id=898">a bug report for just such a
feature</a> was filed in
2002. However, by that time Subversion's repository and changeset formats had
essentially frozen, as Subversion was approaching a 1.0 release and more
important bugs <em>without</em> workarounds were a priority.</p>
<p>There is some work going on in Subversion 1.6 to handle tree conflicts (the
kind of conflicts that come from this kind of structural change) more
sensibly, which will cause the two merges above to generate a Conflict result,
which is not as good as automatically merging it but far better than silently
ignoring changes.</p>
<h2 id="mercurial">Mercurial</h2>
<p><img alt="Mercurial preserves the content
change." src="/media/dev/merging-structural-changes/mercurial-merge-results"></p>
<p>Interestingly, there are tools which get this merge scenario right: the
diagram above shows how <a href="http://www.selenic.com/mercurial/">Mercurial</a> handles
the same two tests. Since its changeset language does include an “object
moved” primitive, it's able to take a content change for <code>dir-a/file</code> and
apply it to <code>dir-b/file</code> if appropriate.</p>
<h2 id="git">Git</h2>
<p>Git also gets this scenario right, <em>usually</em>. Unlike Mercurial, Git does not
track file copies or renames in its commits at all, prefering to infer them by
content comparison every time it performs a move-aware operation, such as a
merge.</p>
	</div>


	
<div id="comments">
<div id="disqus_thread"></div>
<script type="text/javascript">
    /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
    var disqus_shortname = 'grimoire'; // required: replace example with your forum shortname

    /* * * DON'T EDIT BELOW THIS LINE * * */
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</div>


	
	<div id="footer">
		<p>
			
				The Codex —
			
			Powered by <a href="http://markdoc.org/">Markdoc</a>.
			
<a href="https://bitbucket.org/ojacobson/grimoire.ca/src/master/wiki/dev/merging-structural-changes.md">See this page on Bitbucket</a> (<a href="https://bitbucket.org/ojacobson/grimoire.ca/history-node/master/wiki/dev/merging-structural-changes.md">history</a>).

		</p>
	</div>
	
</div>
</body>
</html>