##// END OF EJS Templates
copies: handle a case when both merging csets are not descendant of merge base...
Sushil khanchi -
r42098:7694b685 default
parent child Browse files
Show More
@@ -1,1001 +1,1007 b''
1 # copies.py - copy detection for Mercurial
1 # copies.py - copy detection for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import heapq
11 import heapq
12 import os
12 import os
13
13
14 from .i18n import _
14 from .i18n import _
15
15
16 from . import (
16 from . import (
17 match as matchmod,
17 match as matchmod,
18 node,
18 node,
19 pathutil,
19 pathutil,
20 scmutil,
20 scmutil,
21 util,
21 util,
22 )
22 )
23 from .utils import (
23 from .utils import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27 def _findlimit(repo, ctxa, ctxb):
27 def _findlimit(repo, ctxa, ctxb):
28 """
28 """
29 Find the last revision that needs to be checked to ensure that a full
29 Find the last revision that needs to be checked to ensure that a full
30 transitive closure for file copies can be properly calculated.
30 transitive closure for file copies can be properly calculated.
31 Generally, this means finding the earliest revision number that's an
31 Generally, this means finding the earliest revision number that's an
32 ancestor of a or b but not both, except when a or b is a direct descendent
32 ancestor of a or b but not both, except when a or b is a direct descendent
33 of the other, in which case we can return the minimum revnum of a and b.
33 of the other, in which case we can return the minimum revnum of a and b.
34 """
34 """
35
35
36 # basic idea:
36 # basic idea:
37 # - mark a and b with different sides
37 # - mark a and b with different sides
38 # - if a parent's children are all on the same side, the parent is
38 # - if a parent's children are all on the same side, the parent is
39 # on that side, otherwise it is on no side
39 # on that side, otherwise it is on no side
40 # - walk the graph in topological order with the help of a heap;
40 # - walk the graph in topological order with the help of a heap;
41 # - add unseen parents to side map
41 # - add unseen parents to side map
42 # - clear side of any parent that has children on different sides
42 # - clear side of any parent that has children on different sides
43 # - track number of interesting revs that might still be on a side
43 # - track number of interesting revs that might still be on a side
44 # - track the lowest interesting rev seen
44 # - track the lowest interesting rev seen
45 # - quit when interesting revs is zero
45 # - quit when interesting revs is zero
46
46
47 cl = repo.changelog
47 cl = repo.changelog
48 wdirparents = None
48 wdirparents = None
49 a = ctxa.rev()
49 a = ctxa.rev()
50 b = ctxb.rev()
50 b = ctxb.rev()
51 if a is None:
51 if a is None:
52 wdirparents = (ctxa.p1(), ctxa.p2())
52 wdirparents = (ctxa.p1(), ctxa.p2())
53 a = node.wdirrev
53 a = node.wdirrev
54 if b is None:
54 if b is None:
55 assert not wdirparents
55 assert not wdirparents
56 wdirparents = (ctxb.p1(), ctxb.p2())
56 wdirparents = (ctxb.p1(), ctxb.p2())
57 b = node.wdirrev
57 b = node.wdirrev
58
58
59 side = {a: -1, b: 1}
59 side = {a: -1, b: 1}
60 visit = [-a, -b]
60 visit = [-a, -b]
61 heapq.heapify(visit)
61 heapq.heapify(visit)
62 interesting = len(visit)
62 interesting = len(visit)
63 limit = node.wdirrev
63 limit = node.wdirrev
64
64
65 while interesting:
65 while interesting:
66 r = -heapq.heappop(visit)
66 r = -heapq.heappop(visit)
67 if r == node.wdirrev:
67 if r == node.wdirrev:
68 parents = [pctx.rev() for pctx in wdirparents]
68 parents = [pctx.rev() for pctx in wdirparents]
69 else:
69 else:
70 parents = cl.parentrevs(r)
70 parents = cl.parentrevs(r)
71 if parents[1] == node.nullrev:
71 if parents[1] == node.nullrev:
72 parents = parents[:1]
72 parents = parents[:1]
73 for p in parents:
73 for p in parents:
74 if p not in side:
74 if p not in side:
75 # first time we see p; add it to visit
75 # first time we see p; add it to visit
76 side[p] = side[r]
76 side[p] = side[r]
77 if side[p]:
77 if side[p]:
78 interesting += 1
78 interesting += 1
79 heapq.heappush(visit, -p)
79 heapq.heappush(visit, -p)
80 elif side[p] and side[p] != side[r]:
80 elif side[p] and side[p] != side[r]:
81 # p was interesting but now we know better
81 # p was interesting but now we know better
82 side[p] = 0
82 side[p] = 0
83 interesting -= 1
83 interesting -= 1
84 if side[r]:
84 if side[r]:
85 limit = r # lowest rev visited
85 limit = r # lowest rev visited
86 interesting -= 1
86 interesting -= 1
87
87
88 # Consider the following flow (see test-commit-amend.t under issue4405):
88 # Consider the following flow (see test-commit-amend.t under issue4405):
89 # 1/ File 'a0' committed
89 # 1/ File 'a0' committed
90 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
90 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
91 # 3/ Move back to first commit
91 # 3/ Move back to first commit
92 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
92 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
93 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
93 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
94 #
94 #
95 # During the amend in step five, we will be in this state:
95 # During the amend in step five, we will be in this state:
96 #
96 #
97 # @ 3 temporary amend commit for a1-amend
97 # @ 3 temporary amend commit for a1-amend
98 # |
98 # |
99 # o 2 a1-amend
99 # o 2 a1-amend
100 # |
100 # |
101 # | o 1 a1
101 # | o 1 a1
102 # |/
102 # |/
103 # o 0 a0
103 # o 0 a0
104 #
104 #
105 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
105 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
106 # yet the filelog has the copy information in rev 1 and we will not look
106 # yet the filelog has the copy information in rev 1 and we will not look
107 # back far enough unless we also look at the a and b as candidates.
107 # back far enough unless we also look at the a and b as candidates.
108 # This only occurs when a is a descendent of b or visa-versa.
108 # This only occurs when a is a descendent of b or visa-versa.
109 return min(limit, a, b)
109 return min(limit, a, b)
110
110
111 def _chain(src, dst, a, b):
111 def _chain(src, dst, a, b):
112 """chain two sets of copies a->b"""
112 """chain two sets of copies a->b"""
113 t = a.copy()
113 t = a.copy()
114 for k, v in b.iteritems():
114 for k, v in b.iteritems():
115 if v in t:
115 if v in t:
116 # found a chain
116 # found a chain
117 if t[v] != k:
117 if t[v] != k:
118 # file wasn't renamed back to itself
118 # file wasn't renamed back to itself
119 t[k] = t[v]
119 t[k] = t[v]
120 if v not in dst:
120 if v not in dst:
121 # chain was a rename, not a copy
121 # chain was a rename, not a copy
122 del t[v]
122 del t[v]
123 if v in src:
123 if v in src:
124 # file is a copy of an existing file
124 # file is a copy of an existing file
125 t[k] = v
125 t[k] = v
126
126
127 for k, v in list(t.items()):
127 for k, v in list(t.items()):
128 # remove criss-crossed copies
128 # remove criss-crossed copies
129 if k in src and v in dst:
129 if k in src and v in dst:
130 del t[k]
130 del t[k]
131 # remove copies to files that were then removed
131 # remove copies to files that were then removed
132 elif k not in dst:
132 elif k not in dst:
133 del t[k]
133 del t[k]
134
134
135 return t
135 return t
136
136
137 def _tracefile(fctx, am, limit=node.nullrev):
137 def _tracefile(fctx, am, limit=node.nullrev):
138 """return file context that is the ancestor of fctx present in ancestor
138 """return file context that is the ancestor of fctx present in ancestor
139 manifest am, stopping after the first ancestor lower than limit"""
139 manifest am, stopping after the first ancestor lower than limit"""
140
140
141 for f in fctx.ancestors():
141 for f in fctx.ancestors():
142 if am.get(f.path(), None) == f.filenode():
142 if am.get(f.path(), None) == f.filenode():
143 return f
143 return f
144 if limit >= 0 and not f.isintroducedafter(limit):
144 if limit >= 0 and not f.isintroducedafter(limit):
145 return None
145 return None
146
146
147 def _dirstatecopies(repo, match=None):
147 def _dirstatecopies(repo, match=None):
148 ds = repo.dirstate
148 ds = repo.dirstate
149 c = ds.copies().copy()
149 c = ds.copies().copy()
150 for k in list(c):
150 for k in list(c):
151 if ds[k] not in 'anm' or (match and not match(k)):
151 if ds[k] not in 'anm' or (match and not match(k)):
152 del c[k]
152 del c[k]
153 return c
153 return c
154
154
155 def _computeforwardmissing(a, b, match=None):
155 def _computeforwardmissing(a, b, match=None):
156 """Computes which files are in b but not a.
156 """Computes which files are in b but not a.
157 This is its own function so extensions can easily wrap this call to see what
157 This is its own function so extensions can easily wrap this call to see what
158 files _forwardcopies is about to process.
158 files _forwardcopies is about to process.
159 """
159 """
160 ma = a.manifest()
160 ma = a.manifest()
161 mb = b.manifest()
161 mb = b.manifest()
162 return mb.filesnotin(ma, match=match)
162 return mb.filesnotin(ma, match=match)
163
163
164 def _committedforwardcopies(a, b, match):
164 def _committedforwardcopies(a, b, match):
165 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
165 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
166 # files might have to be traced back to the fctx parent of the last
166 # files might have to be traced back to the fctx parent of the last
167 # one-side-only changeset, but not further back than that
167 # one-side-only changeset, but not further back than that
168 repo = a._repo
168 repo = a._repo
169
169
170 if repo.ui.config('experimental', 'copies.read-from') == 'compatibility':
170 if repo.ui.config('experimental', 'copies.read-from') == 'compatibility':
171 return _changesetforwardcopies(a, b, match)
171 return _changesetforwardcopies(a, b, match)
172
172
173 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
173 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
174 dbg = repo.ui.debug
174 dbg = repo.ui.debug
175 if debug:
175 if debug:
176 dbg('debug.copies: looking into rename from %s to %s\n'
176 dbg('debug.copies: looking into rename from %s to %s\n'
177 % (a, b))
177 % (a, b))
178 limit = _findlimit(repo, a, b)
178 limit = _findlimit(repo, a, b)
179 if debug:
179 if debug:
180 dbg('debug.copies: search limit: %d\n' % limit)
180 dbg('debug.copies: search limit: %d\n' % limit)
181 am = a.manifest()
181 am = a.manifest()
182
182
183 # find where new files came from
183 # find where new files came from
184 # we currently don't try to find where old files went, too expensive
184 # we currently don't try to find where old files went, too expensive
185 # this means we can miss a case like 'hg rm b; hg cp a b'
185 # this means we can miss a case like 'hg rm b; hg cp a b'
186 cm = {}
186 cm = {}
187
187
188 # Computing the forward missing is quite expensive on large manifests, since
188 # Computing the forward missing is quite expensive on large manifests, since
189 # it compares the entire manifests. We can optimize it in the common use
189 # it compares the entire manifests. We can optimize it in the common use
190 # case of computing what copies are in a commit versus its parent (like
190 # case of computing what copies are in a commit versus its parent (like
191 # during a rebase or histedit). Note, we exclude merge commits from this
191 # during a rebase or histedit). Note, we exclude merge commits from this
192 # optimization, since the ctx.files() for a merge commit is not correct for
192 # optimization, since the ctx.files() for a merge commit is not correct for
193 # this comparison.
193 # this comparison.
194 forwardmissingmatch = match
194 forwardmissingmatch = match
195 if b.p1() == a and b.p2().node() == node.nullid:
195 if b.p1() == a and b.p2().node() == node.nullid:
196 filesmatcher = scmutil.matchfiles(a._repo, b.files())
196 filesmatcher = scmutil.matchfiles(a._repo, b.files())
197 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
197 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
198 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
198 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
199
199
200 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
200 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
201
201
202 if debug:
202 if debug:
203 dbg('debug.copies: missing file to search: %d\n' % len(missing))
203 dbg('debug.copies: missing file to search: %d\n' % len(missing))
204
204
205 for f in missing:
205 for f in missing:
206 if debug:
206 if debug:
207 dbg('debug.copies: tracing file: %s\n' % f)
207 dbg('debug.copies: tracing file: %s\n' % f)
208 fctx = b[f]
208 fctx = b[f]
209 fctx._ancestrycontext = ancestrycontext
209 fctx._ancestrycontext = ancestrycontext
210
210
211 if debug:
211 if debug:
212 start = util.timer()
212 start = util.timer()
213 ofctx = _tracefile(fctx, am, limit)
213 ofctx = _tracefile(fctx, am, limit)
214 if ofctx:
214 if ofctx:
215 if debug:
215 if debug:
216 dbg('debug.copies: rename of: %s\n' % ofctx._path)
216 dbg('debug.copies: rename of: %s\n' % ofctx._path)
217 cm[f] = ofctx.path()
217 cm[f] = ofctx.path()
218 if debug:
218 if debug:
219 dbg('debug.copies: time: %f seconds\n'
219 dbg('debug.copies: time: %f seconds\n'
220 % (util.timer() - start))
220 % (util.timer() - start))
221 return cm
221 return cm
222
222
223 def _changesetforwardcopies(a, b, match):
223 def _changesetforwardcopies(a, b, match):
224 if a.rev() == node.nullrev:
224 if a.rev() == node.nullrev:
225 return {}
225 return {}
226
226
227 repo = a.repo()
227 repo = a.repo()
228 children = {}
228 children = {}
229 cl = repo.changelog
229 cl = repo.changelog
230 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
230 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
231 for r in missingrevs:
231 for r in missingrevs:
232 for p in cl.parentrevs(r):
232 for p in cl.parentrevs(r):
233 if p == node.nullrev:
233 if p == node.nullrev:
234 continue
234 continue
235 if p not in children:
235 if p not in children:
236 children[p] = [r]
236 children[p] = [r]
237 else:
237 else:
238 children[p].append(r)
238 children[p].append(r)
239
239
240 roots = set(children) - set(missingrevs)
240 roots = set(children) - set(missingrevs)
241 # 'work' contains 3-tuples of a (revision number, parent number, copies).
241 # 'work' contains 3-tuples of a (revision number, parent number, copies).
242 # The parent number is only used for knowing which parent the copies dict
242 # The parent number is only used for knowing which parent the copies dict
243 # came from.
243 # came from.
244 work = [(r, 1, {}) for r in roots]
244 work = [(r, 1, {}) for r in roots]
245 heapq.heapify(work)
245 heapq.heapify(work)
246 while work:
246 while work:
247 r, i1, copies1 = heapq.heappop(work)
247 r, i1, copies1 = heapq.heappop(work)
248 if work and work[0][0] == r:
248 if work and work[0][0] == r:
249 # We are tracing copies from both parents
249 # We are tracing copies from both parents
250 r, i2, copies2 = heapq.heappop(work)
250 r, i2, copies2 = heapq.heappop(work)
251 copies = {}
251 copies = {}
252 ctx = repo[r]
252 ctx = repo[r]
253 p1man, p2man = ctx.p1().manifest(), ctx.p2().manifest()
253 p1man, p2man = ctx.p1().manifest(), ctx.p2().manifest()
254 allcopies = set(copies1) | set(copies2)
254 allcopies = set(copies1) | set(copies2)
255 # TODO: perhaps this filtering should be done as long as ctx
255 # TODO: perhaps this filtering should be done as long as ctx
256 # is merge, whether or not we're tracing from both parent.
256 # is merge, whether or not we're tracing from both parent.
257 for dst in allcopies:
257 for dst in allcopies:
258 if not match(dst):
258 if not match(dst):
259 continue
259 continue
260 if dst not in copies2:
260 if dst not in copies2:
261 # Copied on p1 side: mark as copy from p1 side if it didn't
261 # Copied on p1 side: mark as copy from p1 side if it didn't
262 # already exist on p2 side
262 # already exist on p2 side
263 if dst not in p2man:
263 if dst not in p2man:
264 copies[dst] = copies1[dst]
264 copies[dst] = copies1[dst]
265 elif dst not in copies1:
265 elif dst not in copies1:
266 # Copied on p2 side: mark as copy from p2 side if it didn't
266 # Copied on p2 side: mark as copy from p2 side if it didn't
267 # already exist on p1 side
267 # already exist on p1 side
268 if dst not in p1man:
268 if dst not in p1man:
269 copies[dst] = copies2[dst]
269 copies[dst] = copies2[dst]
270 else:
270 else:
271 # Copied on both sides: mark as copy from p1 side
271 # Copied on both sides: mark as copy from p1 side
272 copies[dst] = copies1[dst]
272 copies[dst] = copies1[dst]
273 else:
273 else:
274 copies = copies1
274 copies = copies1
275 if r == b.rev():
275 if r == b.rev():
276 return copies
276 return copies
277 for c in children[r]:
277 for c in children[r]:
278 childctx = repo[c]
278 childctx = repo[c]
279 if r == childctx.p1().rev():
279 if r == childctx.p1().rev():
280 parent = 1
280 parent = 1
281 childcopies = childctx.p1copies()
281 childcopies = childctx.p1copies()
282 else:
282 else:
283 assert r == childctx.p2().rev()
283 assert r == childctx.p2().rev()
284 parent = 2
284 parent = 2
285 childcopies = childctx.p2copies()
285 childcopies = childctx.p2copies()
286 if not match.always():
286 if not match.always():
287 childcopies = {dst: src for dst, src in childcopies.items()
287 childcopies = {dst: src for dst, src in childcopies.items()
288 if match(dst)}
288 if match(dst)}
289 childcopies = _chain(a, childctx, copies, childcopies)
289 childcopies = _chain(a, childctx, copies, childcopies)
290 heapq.heappush(work, (c, parent, childcopies))
290 heapq.heappush(work, (c, parent, childcopies))
291 assert False
291 assert False
292
292
293 def _forwardcopies(a, b, match=None):
293 def _forwardcopies(a, b, match=None):
294 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
294 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
295
295
296 match = a.repo().narrowmatch(match)
296 match = a.repo().narrowmatch(match)
297 # check for working copy
297 # check for working copy
298 if b.rev() is None:
298 if b.rev() is None:
299 if a == b.p1():
299 if a == b.p1():
300 # short-circuit to avoid issues with merge states
300 # short-circuit to avoid issues with merge states
301 return _dirstatecopies(b._repo, match)
301 return _dirstatecopies(b._repo, match)
302
302
303 cm = _committedforwardcopies(a, b.p1(), match)
303 cm = _committedforwardcopies(a, b.p1(), match)
304 # combine copies from dirstate if necessary
304 # combine copies from dirstate if necessary
305 return _chain(a, b, cm, _dirstatecopies(b._repo, match))
305 return _chain(a, b, cm, _dirstatecopies(b._repo, match))
306 return _committedforwardcopies(a, b, match)
306 return _committedforwardcopies(a, b, match)
307
307
308 def _backwardrenames(a, b, match):
308 def _backwardrenames(a, b, match):
309 if a._repo.ui.config('experimental', 'copytrace') == 'off':
309 if a._repo.ui.config('experimental', 'copytrace') == 'off':
310 return {}
310 return {}
311
311
312 # Even though we're not taking copies into account, 1:n rename situations
312 # Even though we're not taking copies into account, 1:n rename situations
313 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
313 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
314 # arbitrarily pick one of the renames.
314 # arbitrarily pick one of the renames.
315 # We don't want to pass in "match" here, since that would filter
315 # We don't want to pass in "match" here, since that would filter
316 # the destination by it. Since we're reversing the copies, we want
316 # the destination by it. Since we're reversing the copies, we want
317 # to filter the source instead.
317 # to filter the source instead.
318 f = _forwardcopies(b, a)
318 f = _forwardcopies(b, a)
319 r = {}
319 r = {}
320 for k, v in sorted(f.iteritems()):
320 for k, v in sorted(f.iteritems()):
321 if match and not match(v):
321 if match and not match(v):
322 continue
322 continue
323 # remove copies
323 # remove copies
324 if v in a:
324 if v in a:
325 continue
325 continue
326 r[v] = k
326 r[v] = k
327 return r
327 return r
328
328
329 def pathcopies(x, y, match=None):
329 def pathcopies(x, y, match=None):
330 """find {dst@y: src@x} copy mapping for directed compare"""
330 """find {dst@y: src@x} copy mapping for directed compare"""
331 repo = x._repo
331 repo = x._repo
332 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
332 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
333 if debug:
333 if debug:
334 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
334 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
335 % (x, y))
335 % (x, y))
336 if x == y or not x or not y:
336 if x == y or not x or not y:
337 return {}
337 return {}
338 a = y.ancestor(x)
338 a = y.ancestor(x)
339 if a == x:
339 if a == x:
340 if debug:
340 if debug:
341 repo.ui.debug('debug.copies: search mode: forward\n')
341 repo.ui.debug('debug.copies: search mode: forward\n')
342 return _forwardcopies(x, y, match=match)
342 return _forwardcopies(x, y, match=match)
343 if a == y:
343 if a == y:
344 if debug:
344 if debug:
345 repo.ui.debug('debug.copies: search mode: backward\n')
345 repo.ui.debug('debug.copies: search mode: backward\n')
346 return _backwardrenames(x, y, match=match)
346 return _backwardrenames(x, y, match=match)
347 if debug:
347 if debug:
348 repo.ui.debug('debug.copies: search mode: combined\n')
348 repo.ui.debug('debug.copies: search mode: combined\n')
349 return _chain(x, y, _backwardrenames(x, a, match=match),
349 return _chain(x, y, _backwardrenames(x, a, match=match),
350 _forwardcopies(a, y, match=match))
350 _forwardcopies(a, y, match=match))
351
351
352 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
352 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
353 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
353 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
354 and c2. This is its own function so extensions can easily wrap this call
354 and c2. This is its own function so extensions can easily wrap this call
355 to see what files mergecopies is about to process.
355 to see what files mergecopies is about to process.
356
356
357 Even though c1 and c2 are not used in this function, they are useful in
357 Even though c1 and c2 are not used in this function, they are useful in
358 other extensions for being able to read the file nodes of the changed files.
358 other extensions for being able to read the file nodes of the changed files.
359
359
360 "baselabel" can be passed to help distinguish the multiple computations
360 "baselabel" can be passed to help distinguish the multiple computations
361 done in the graft case.
361 done in the graft case.
362 """
362 """
363 u1 = sorted(addedinm1 - addedinm2)
363 u1 = sorted(addedinm1 - addedinm2)
364 u2 = sorted(addedinm2 - addedinm1)
364 u2 = sorted(addedinm2 - addedinm1)
365
365
366 header = " unmatched files in %s"
366 header = " unmatched files in %s"
367 if baselabel:
367 if baselabel:
368 header += ' (from %s)' % baselabel
368 header += ' (from %s)' % baselabel
369 if u1:
369 if u1:
370 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
370 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
371 if u2:
371 if u2:
372 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
372 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
373
373
374 return u1, u2
374 return u1, u2
375
375
376 def _makegetfctx(ctx):
376 def _makegetfctx(ctx):
377 """return a 'getfctx' function suitable for _checkcopies usage
377 """return a 'getfctx' function suitable for _checkcopies usage
378
378
379 We have to re-setup the function building 'filectx' for each
379 We have to re-setup the function building 'filectx' for each
380 '_checkcopies' to ensure the linkrev adjustment is properly setup for
380 '_checkcopies' to ensure the linkrev adjustment is properly setup for
381 each. Linkrev adjustment is important to avoid bug in rename
381 each. Linkrev adjustment is important to avoid bug in rename
382 detection. Moreover, having a proper '_ancestrycontext' setup ensures
382 detection. Moreover, having a proper '_ancestrycontext' setup ensures
383 the performance impact of this adjustment is kept limited. Without it,
383 the performance impact of this adjustment is kept limited. Without it,
384 each file could do a full dag traversal making the time complexity of
384 each file could do a full dag traversal making the time complexity of
385 the operation explode (see issue4537).
385 the operation explode (see issue4537).
386
386
387 This function exists here mostly to limit the impact on stable. Feel
387 This function exists here mostly to limit the impact on stable. Feel
388 free to refactor on default.
388 free to refactor on default.
389 """
389 """
390 rev = ctx.rev()
390 rev = ctx.rev()
391 repo = ctx._repo
391 repo = ctx._repo
392 ac = getattr(ctx, '_ancestrycontext', None)
392 ac = getattr(ctx, '_ancestrycontext', None)
393 if ac is None:
393 if ac is None:
394 revs = [rev]
394 revs = [rev]
395 if rev is None:
395 if rev is None:
396 revs = [p.rev() for p in ctx.parents()]
396 revs = [p.rev() for p in ctx.parents()]
397 ac = repo.changelog.ancestors(revs, inclusive=True)
397 ac = repo.changelog.ancestors(revs, inclusive=True)
398 ctx._ancestrycontext = ac
398 ctx._ancestrycontext = ac
399 def makectx(f, n):
399 def makectx(f, n):
400 if n in node.wdirfilenodeids: # in a working context?
400 if n in node.wdirfilenodeids: # in a working context?
401 if ctx.rev() is None:
401 if ctx.rev() is None:
402 return ctx.filectx(f)
402 return ctx.filectx(f)
403 return repo[None][f]
403 return repo[None][f]
404 fctx = repo.filectx(f, fileid=n)
404 fctx = repo.filectx(f, fileid=n)
405 # setup only needed for filectx not create from a changectx
405 # setup only needed for filectx not create from a changectx
406 fctx._ancestrycontext = ac
406 fctx._ancestrycontext = ac
407 fctx._descendantrev = rev
407 fctx._descendantrev = rev
408 return fctx
408 return fctx
409 return util.lrucachefunc(makectx)
409 return util.lrucachefunc(makectx)
410
410
411 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
411 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
412 """combine partial copy paths"""
412 """combine partial copy paths"""
413 remainder = {}
413 remainder = {}
414 for f in copyfrom:
414 for f in copyfrom:
415 if f in copyto:
415 if f in copyto:
416 finalcopy[copyto[f]] = copyfrom[f]
416 finalcopy[copyto[f]] = copyfrom[f]
417 del copyto[f]
417 del copyto[f]
418 for f in incompletediverge:
418 for f in incompletediverge:
419 assert f not in diverge
419 assert f not in diverge
420 ic = incompletediverge[f]
420 ic = incompletediverge[f]
421 if ic[0] in copyto:
421 if ic[0] in copyto:
422 diverge[f] = [copyto[ic[0]], ic[1]]
422 diverge[f] = [copyto[ic[0]], ic[1]]
423 else:
423 else:
424 remainder[f] = ic
424 remainder[f] = ic
425 return remainder
425 return remainder
426
426
427 def mergecopies(repo, c1, c2, base):
427 def mergecopies(repo, c1, c2, base):
428 """
428 """
429 The function calling different copytracing algorithms on the basis of config
429 The function calling different copytracing algorithms on the basis of config
430 which find moves and copies between context c1 and c2 that are relevant for
430 which find moves and copies between context c1 and c2 that are relevant for
431 merging. 'base' will be used as the merge base.
431 merging. 'base' will be used as the merge base.
432
432
433 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
433 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
434 files that were moved/ copied in one merge parent and modified in another.
434 files that were moved/ copied in one merge parent and modified in another.
435 For example:
435 For example:
436
436
437 o ---> 4 another commit
437 o ---> 4 another commit
438 |
438 |
439 | o ---> 3 commit that modifies a.txt
439 | o ---> 3 commit that modifies a.txt
440 | /
440 | /
441 o / ---> 2 commit that moves a.txt to b.txt
441 o / ---> 2 commit that moves a.txt to b.txt
442 |/
442 |/
443 o ---> 1 merge base
443 o ---> 1 merge base
444
444
445 If we try to rebase revision 3 on revision 4, since there is no a.txt in
445 If we try to rebase revision 3 on revision 4, since there is no a.txt in
446 revision 4, and if user have copytrace disabled, we prints the following
446 revision 4, and if user have copytrace disabled, we prints the following
447 message:
447 message:
448
448
449 ```other changed <file> which local deleted```
449 ```other changed <file> which local deleted```
450
450
451 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
451 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
452 "dirmove".
452 "dirmove".
453
453
454 "copy" is a mapping from destination name -> source name,
454 "copy" is a mapping from destination name -> source name,
455 where source is in c1 and destination is in c2 or vice-versa.
455 where source is in c1 and destination is in c2 or vice-versa.
456
456
457 "movewithdir" is a mapping from source name -> destination name,
457 "movewithdir" is a mapping from source name -> destination name,
458 where the file at source present in one context but not the other
458 where the file at source present in one context but not the other
459 needs to be moved to destination by the merge process, because the
459 needs to be moved to destination by the merge process, because the
460 other context moved the directory it is in.
460 other context moved the directory it is in.
461
461
462 "diverge" is a mapping of source name -> list of destination names
462 "diverge" is a mapping of source name -> list of destination names
463 for divergent renames.
463 for divergent renames.
464
464
465 "renamedelete" is a mapping of source name -> list of destination
465 "renamedelete" is a mapping of source name -> list of destination
466 names for files deleted in c1 that were renamed in c2 or vice-versa.
466 names for files deleted in c1 that were renamed in c2 or vice-versa.
467
467
468 "dirmove" is a mapping of detected source dir -> destination dir renames.
468 "dirmove" is a mapping of detected source dir -> destination dir renames.
469 This is needed for handling changes to new files previously grafted into
469 This is needed for handling changes to new files previously grafted into
470 renamed directories.
470 renamed directories.
471 """
471 """
472 # avoid silly behavior for update from empty dir
472 # avoid silly behavior for update from empty dir
473 if not c1 or not c2 or c1 == c2:
473 if not c1 or not c2 or c1 == c2:
474 return {}, {}, {}, {}, {}
474 return {}, {}, {}, {}, {}
475
475
476 narrowmatch = c1.repo().narrowmatch()
476 narrowmatch = c1.repo().narrowmatch()
477
477
478 # avoid silly behavior for parent -> working dir
478 # avoid silly behavior for parent -> working dir
479 if c2.node() is None and c1.node() == repo.dirstate.p1():
479 if c2.node() is None and c1.node() == repo.dirstate.p1():
480 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
480 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
481
481
482 copytracing = repo.ui.config('experimental', 'copytrace')
482 copytracing = repo.ui.config('experimental', 'copytrace')
483 boolctrace = stringutil.parsebool(copytracing)
483 boolctrace = stringutil.parsebool(copytracing)
484
484
485 # Copy trace disabling is explicitly below the node == p1 logic above
485 # Copy trace disabling is explicitly below the node == p1 logic above
486 # because the logic above is required for a simple copy to be kept across a
486 # because the logic above is required for a simple copy to be kept across a
487 # rebase.
487 # rebase.
488 if copytracing == 'heuristics':
488 if copytracing == 'heuristics':
489 # Do full copytracing if only non-public revisions are involved as
489 # Do full copytracing if only non-public revisions are involved as
490 # that will be fast enough and will also cover the copies which could
490 # that will be fast enough and will also cover the copies which could
491 # be missed by heuristics
491 # be missed by heuristics
492 if _isfullcopytraceable(repo, c1, base):
492 if _isfullcopytraceable(repo, c1, base):
493 return _fullcopytracing(repo, c1, c2, base)
493 return _fullcopytracing(repo, c1, c2, base)
494 return _heuristicscopytracing(repo, c1, c2, base)
494 return _heuristicscopytracing(repo, c1, c2, base)
495 elif boolctrace is False:
495 elif boolctrace is False:
496 # stringutil.parsebool() returns None when it is unable to parse the
496 # stringutil.parsebool() returns None when it is unable to parse the
497 # value, so we should rely on making sure copytracing is on such cases
497 # value, so we should rely on making sure copytracing is on such cases
498 return {}, {}, {}, {}, {}
498 return {}, {}, {}, {}, {}
499 else:
499 else:
500 return _fullcopytracing(repo, c1, c2, base)
500 return _fullcopytracing(repo, c1, c2, base)
501
501
502 def _isfullcopytraceable(repo, c1, base):
502 def _isfullcopytraceable(repo, c1, base):
503 """ Checks that if base, source and destination are all no-public branches,
503 """ Checks that if base, source and destination are all no-public branches,
504 if yes let's use the full copytrace algorithm for increased capabilities
504 if yes let's use the full copytrace algorithm for increased capabilities
505 since it will be fast enough.
505 since it will be fast enough.
506
506
507 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
507 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
508 number of changesets from c1 to base such that if number of changesets are
508 number of changesets from c1 to base such that if number of changesets are
509 more than the limit, full copytracing algorithm won't be used.
509 more than the limit, full copytracing algorithm won't be used.
510 """
510 """
511 if c1.rev() is None:
511 if c1.rev() is None:
512 c1 = c1.p1()
512 c1 = c1.p1()
513 if c1.mutable() and base.mutable():
513 if c1.mutable() and base.mutable():
514 sourcecommitlimit = repo.ui.configint('experimental',
514 sourcecommitlimit = repo.ui.configint('experimental',
515 'copytrace.sourcecommitlimit')
515 'copytrace.sourcecommitlimit')
516 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
516 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
517 return commits < sourcecommitlimit
517 return commits < sourcecommitlimit
518 return False
518 return False
519
519
520 def _fullcopytracing(repo, c1, c2, base):
520 def _fullcopytracing(repo, c1, c2, base):
521 """ The full copytracing algorithm which finds all the new files that were
521 """ The full copytracing algorithm which finds all the new files that were
522 added from merge base up to the top commit and for each file it checks if
522 added from merge base up to the top commit and for each file it checks if
523 this file was copied from another file.
523 this file was copied from another file.
524
524
525 This is pretty slow when a lot of changesets are involved but will track all
525 This is pretty slow when a lot of changesets are involved but will track all
526 the copies.
526 the copies.
527 """
527 """
528 # In certain scenarios (e.g. graft, update or rebase), base can be
528 # In certain scenarios (e.g. graft, update or rebase), base can be
529 # overridden We still need to know a real common ancestor in this case We
529 # overridden We still need to know a real common ancestor in this case We
530 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
530 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
531 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
531 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
532 # caller may not know if the revision passed in lieu of the CA is a genuine
532 # caller may not know if the revision passed in lieu of the CA is a genuine
533 # common ancestor or not without explicitly checking it, it's better to
533 # common ancestor or not without explicitly checking it, it's better to
534 # determine that here.
534 # determine that here.
535 #
535 #
536 # base.isancestorof(wc) is False, work around that
536 # base.isancestorof(wc) is False, work around that
537 _c1 = c1.p1() if c1.rev() is None else c1
537 _c1 = c1.p1() if c1.rev() is None else c1
538 _c2 = c2.p1() if c2.rev() is None else c2
538 _c2 = c2.p1() if c2.rev() is None else c2
539 # an endpoint is "dirty" if it isn't a descendant of the merge base
539 # an endpoint is "dirty" if it isn't a descendant of the merge base
540 # if we have a dirty endpoint, we need to trigger graft logic, and also
540 # if we have a dirty endpoint, we need to trigger graft logic, and also
541 # keep track of which endpoint is dirty
541 # keep track of which endpoint is dirty
542 dirtyc1 = not base.isancestorof(_c1)
542 dirtyc1 = not base.isancestorof(_c1)
543 dirtyc2 = not base.isancestorof(_c2)
543 dirtyc2 = not base.isancestorof(_c2)
544 graft = dirtyc1 or dirtyc2
544 graft = dirtyc1 or dirtyc2
545 tca = base
545 tca = base
546 if graft:
546 if graft:
547 tca = _c1.ancestor(_c2)
547 tca = _c1.ancestor(_c2)
548
548
549 limit = _findlimit(repo, c1, c2)
549 limit = _findlimit(repo, c1, c2)
550 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
550 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
551
551
552 m1 = c1.manifest()
552 m1 = c1.manifest()
553 m2 = c2.manifest()
553 m2 = c2.manifest()
554 mb = base.manifest()
554 mb = base.manifest()
555
555
556 # gather data from _checkcopies:
556 # gather data from _checkcopies:
557 # - diverge = record all diverges in this dict
557 # - diverge = record all diverges in this dict
558 # - copy = record all non-divergent copies in this dict
558 # - copy = record all non-divergent copies in this dict
559 # - fullcopy = record all copies in this dict
559 # - fullcopy = record all copies in this dict
560 # - incomplete = record non-divergent partial copies here
560 # - incomplete = record non-divergent partial copies here
561 # - incompletediverge = record divergent partial copies here
561 # - incompletediverge = record divergent partial copies here
562 diverge = {} # divergence data is shared
562 diverge = {} # divergence data is shared
563 incompletediverge = {}
563 incompletediverge = {}
564 data1 = {'copy': {},
564 data1 = {'copy': {},
565 'fullcopy': {},
565 'fullcopy': {},
566 'incomplete': {},
566 'incomplete': {},
567 'diverge': diverge,
567 'diverge': diverge,
568 'incompletediverge': incompletediverge,
568 'incompletediverge': incompletediverge,
569 }
569 }
570 data2 = {'copy': {},
570 data2 = {'copy': {},
571 'fullcopy': {},
571 'fullcopy': {},
572 'incomplete': {},
572 'incomplete': {},
573 'diverge': diverge,
573 'diverge': diverge,
574 'incompletediverge': incompletediverge,
574 'incompletediverge': incompletediverge,
575 }
575 }
576
576
577 # find interesting file sets from manifests
577 # find interesting file sets from manifests
578 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
578 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
579 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
579 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
580 bothnew = sorted(addedinm1 & addedinm2)
580 bothnew = sorted(addedinm1 & addedinm2)
581 if tca == base:
581 if tca == base:
582 # unmatched file from base
582 # unmatched file from base
583 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
583 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
584 u1u, u2u = u1r, u2r
584 u1u, u2u = u1r, u2r
585 else:
585 else:
586 # unmatched file from base (DAG rotation in the graft case)
586 # unmatched file from base (DAG rotation in the graft case)
587 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
587 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
588 baselabel='base')
588 baselabel='base')
589 # unmatched file from topological common ancestors (no DAG rotation)
589 # unmatched file from topological common ancestors (no DAG rotation)
590 # need to recompute this for directory move handling when grafting
590 # need to recompute this for directory move handling when grafting
591 mta = tca.manifest()
591 mta = tca.manifest()
592 u1u, u2u = _computenonoverlap(repo, c1, c2,
592 u1u, u2u = _computenonoverlap(repo, c1, c2,
593 m1.filesnotin(mta, repo.narrowmatch()),
593 m1.filesnotin(mta, repo.narrowmatch()),
594 m2.filesnotin(mta, repo.narrowmatch()),
594 m2.filesnotin(mta, repo.narrowmatch()),
595 baselabel='topological common ancestor')
595 baselabel='topological common ancestor')
596
596
597 for f in u1u:
597 for f in u1u:
598 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
598 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
599
599
600 for f in u2u:
600 for f in u2u:
601 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
601 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
602
602
603 copy = dict(data1['copy'])
603 copy = dict(data1['copy'])
604 copy.update(data2['copy'])
604 copy.update(data2['copy'])
605 fullcopy = dict(data1['fullcopy'])
605 fullcopy = dict(data1['fullcopy'])
606 fullcopy.update(data2['fullcopy'])
606 fullcopy.update(data2['fullcopy'])
607
607
608 if dirtyc1:
608 if dirtyc1:
609 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
609 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
610 incompletediverge)
610 incompletediverge)
611 else:
611 if dirtyc2:
612 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
612 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
613 incompletediverge)
613 incompletediverge)
614
614
615 renamedelete = {}
615 renamedelete = {}
616 renamedeleteset = set()
616 renamedeleteset = set()
617 divergeset = set()
617 divergeset = set()
618 for of, fl in list(diverge.items()):
618 for of, fl in list(diverge.items()):
619 if len(fl) == 1 or of in c1 or of in c2:
619 if len(fl) == 1 or of in c1 or of in c2:
620 del diverge[of] # not actually divergent, or not a rename
620 del diverge[of] # not actually divergent, or not a rename
621 if of not in c1 and of not in c2:
621 if of not in c1 and of not in c2:
622 # renamed on one side, deleted on the other side, but filter
622 # renamed on one side, deleted on the other side, but filter
623 # out files that have been renamed and then deleted
623 # out files that have been renamed and then deleted
624 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
624 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
625 renamedeleteset.update(fl) # reverse map for below
625 renamedeleteset.update(fl) # reverse map for below
626 else:
626 else:
627 divergeset.update(fl) # reverse map for below
627 divergeset.update(fl) # reverse map for below
628
628
629 if bothnew:
629 if bothnew:
630 repo.ui.debug(" unmatched files new in both:\n %s\n"
630 repo.ui.debug(" unmatched files new in both:\n %s\n"
631 % "\n ".join(bothnew))
631 % "\n ".join(bothnew))
632 bothdiverge = {}
632 bothdiverge = {}
633 bothincompletediverge = {}
633 bothincompletediverge = {}
634 remainder = {}
634 remainder = {}
635 both1 = {'copy': {},
635 both1 = {'copy': {},
636 'fullcopy': {},
636 'fullcopy': {},
637 'incomplete': {},
637 'incomplete': {},
638 'diverge': bothdiverge,
638 'diverge': bothdiverge,
639 'incompletediverge': bothincompletediverge
639 'incompletediverge': bothincompletediverge
640 }
640 }
641 both2 = {'copy': {},
641 both2 = {'copy': {},
642 'fullcopy': {},
642 'fullcopy': {},
643 'incomplete': {},
643 'incomplete': {},
644 'diverge': bothdiverge,
644 'diverge': bothdiverge,
645 'incompletediverge': bothincompletediverge
645 'incompletediverge': bothincompletediverge
646 }
646 }
647 for f in bothnew:
647 for f in bothnew:
648 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
648 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
649 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
649 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
650 if dirtyc1:
650 if dirtyc1 and dirtyc2:
651 remainder = _combinecopies(both2['incomplete'], both1['incomplete'],
652 copy, bothdiverge, bothincompletediverge)
653 remainder1 = _combinecopies(both1['incomplete'], both2['incomplete'],
654 copy, bothdiverge, bothincompletediverge)
655 remainder.update(remainder1)
656 elif dirtyc1:
651 # incomplete copies may only be found on the "dirty" side for bothnew
657 # incomplete copies may only be found on the "dirty" side for bothnew
652 assert not both2['incomplete']
658 assert not both2['incomplete']
653 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
659 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
654 bothincompletediverge)
660 bothincompletediverge)
655 elif dirtyc2:
661 elif dirtyc2:
656 assert not both1['incomplete']
662 assert not both1['incomplete']
657 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
663 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
658 bothincompletediverge)
664 bothincompletediverge)
659 else:
665 else:
660 # incomplete copies and divergences can't happen outside grafts
666 # incomplete copies and divergences can't happen outside grafts
661 assert not both1['incomplete']
667 assert not both1['incomplete']
662 assert not both2['incomplete']
668 assert not both2['incomplete']
663 assert not bothincompletediverge
669 assert not bothincompletediverge
664 for f in remainder:
670 for f in remainder:
665 assert f not in bothdiverge
671 assert f not in bothdiverge
666 ic = remainder[f]
672 ic = remainder[f]
667 if ic[0] in (m1 if dirtyc1 else m2):
673 if ic[0] in (m1 if dirtyc1 else m2):
668 # backed-out rename on one side, but watch out for deleted files
674 # backed-out rename on one side, but watch out for deleted files
669 bothdiverge[f] = ic
675 bothdiverge[f] = ic
670 for of, fl in bothdiverge.items():
676 for of, fl in bothdiverge.items():
671 if len(fl) == 2 and fl[0] == fl[1]:
677 if len(fl) == 2 and fl[0] == fl[1]:
672 copy[fl[0]] = of # not actually divergent, just matching renames
678 copy[fl[0]] = of # not actually divergent, just matching renames
673
679
674 if fullcopy and repo.ui.debugflag:
680 if fullcopy and repo.ui.debugflag:
675 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
681 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
676 "% = renamed and deleted):\n")
682 "% = renamed and deleted):\n")
677 for f in sorted(fullcopy):
683 for f in sorted(fullcopy):
678 note = ""
684 note = ""
679 if f in copy:
685 if f in copy:
680 note += "*"
686 note += "*"
681 if f in divergeset:
687 if f in divergeset:
682 note += "!"
688 note += "!"
683 if f in renamedeleteset:
689 if f in renamedeleteset:
684 note += "%"
690 note += "%"
685 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
691 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
686 note))
692 note))
687 del divergeset
693 del divergeset
688
694
689 if not fullcopy:
695 if not fullcopy:
690 return copy, {}, diverge, renamedelete, {}
696 return copy, {}, diverge, renamedelete, {}
691
697
692 repo.ui.debug(" checking for directory renames\n")
698 repo.ui.debug(" checking for directory renames\n")
693
699
694 # generate a directory move map
700 # generate a directory move map
695 d1, d2 = c1.dirs(), c2.dirs()
701 d1, d2 = c1.dirs(), c2.dirs()
696 # Hack for adding '', which is not otherwise added, to d1 and d2
702 # Hack for adding '', which is not otherwise added, to d1 and d2
697 d1.addpath('/')
703 d1.addpath('/')
698 d2.addpath('/')
704 d2.addpath('/')
699 invalid = set()
705 invalid = set()
700 dirmove = {}
706 dirmove = {}
701
707
702 # examine each file copy for a potential directory move, which is
708 # examine each file copy for a potential directory move, which is
703 # when all the files in a directory are moved to a new directory
709 # when all the files in a directory are moved to a new directory
704 for dst, src in fullcopy.iteritems():
710 for dst, src in fullcopy.iteritems():
705 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
711 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
706 if dsrc in invalid:
712 if dsrc in invalid:
707 # already seen to be uninteresting
713 # already seen to be uninteresting
708 continue
714 continue
709 elif dsrc in d1 and ddst in d1:
715 elif dsrc in d1 and ddst in d1:
710 # directory wasn't entirely moved locally
716 # directory wasn't entirely moved locally
711 invalid.add(dsrc)
717 invalid.add(dsrc)
712 elif dsrc in d2 and ddst in d2:
718 elif dsrc in d2 and ddst in d2:
713 # directory wasn't entirely moved remotely
719 # directory wasn't entirely moved remotely
714 invalid.add(dsrc)
720 invalid.add(dsrc)
715 elif dsrc in dirmove and dirmove[dsrc] != ddst:
721 elif dsrc in dirmove and dirmove[dsrc] != ddst:
716 # files from the same directory moved to two different places
722 # files from the same directory moved to two different places
717 invalid.add(dsrc)
723 invalid.add(dsrc)
718 else:
724 else:
719 # looks good so far
725 # looks good so far
720 dirmove[dsrc] = ddst
726 dirmove[dsrc] = ddst
721
727
722 for i in invalid:
728 for i in invalid:
723 if i in dirmove:
729 if i in dirmove:
724 del dirmove[i]
730 del dirmove[i]
725 del d1, d2, invalid
731 del d1, d2, invalid
726
732
727 if not dirmove:
733 if not dirmove:
728 return copy, {}, diverge, renamedelete, {}
734 return copy, {}, diverge, renamedelete, {}
729
735
730 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
736 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
731
737
732 for d in dirmove:
738 for d in dirmove:
733 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
739 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
734 (d, dirmove[d]))
740 (d, dirmove[d]))
735
741
736 movewithdir = {}
742 movewithdir = {}
737 # check unaccounted nonoverlapping files against directory moves
743 # check unaccounted nonoverlapping files against directory moves
738 for f in u1r + u2r:
744 for f in u1r + u2r:
739 if f not in fullcopy:
745 if f not in fullcopy:
740 for d in dirmove:
746 for d in dirmove:
741 if f.startswith(d):
747 if f.startswith(d):
742 # new file added in a directory that was moved, move it
748 # new file added in a directory that was moved, move it
743 df = dirmove[d] + f[len(d):]
749 df = dirmove[d] + f[len(d):]
744 if df not in copy:
750 if df not in copy:
745 movewithdir[f] = df
751 movewithdir[f] = df
746 repo.ui.debug((" pending file src: '%s' -> "
752 repo.ui.debug((" pending file src: '%s' -> "
747 "dst: '%s'\n") % (f, df))
753 "dst: '%s'\n") % (f, df))
748 break
754 break
749
755
750 return copy, movewithdir, diverge, renamedelete, dirmove
756 return copy, movewithdir, diverge, renamedelete, dirmove
751
757
752 def _heuristicscopytracing(repo, c1, c2, base):
758 def _heuristicscopytracing(repo, c1, c2, base):
753 """ Fast copytracing using filename heuristics
759 """ Fast copytracing using filename heuristics
754
760
755 Assumes that moves or renames are of following two types:
761 Assumes that moves or renames are of following two types:
756
762
757 1) Inside a directory only (same directory name but different filenames)
763 1) Inside a directory only (same directory name but different filenames)
758 2) Move from one directory to another
764 2) Move from one directory to another
759 (same filenames but different directory names)
765 (same filenames but different directory names)
760
766
761 Works only when there are no merge commits in the "source branch".
767 Works only when there are no merge commits in the "source branch".
762 Source branch is commits from base up to c2 not including base.
768 Source branch is commits from base up to c2 not including base.
763
769
764 If merge is involved it fallbacks to _fullcopytracing().
770 If merge is involved it fallbacks to _fullcopytracing().
765
771
766 Can be used by setting the following config:
772 Can be used by setting the following config:
767
773
768 [experimental]
774 [experimental]
769 copytrace = heuristics
775 copytrace = heuristics
770
776
771 In some cases the copy/move candidates found by heuristics can be very large
777 In some cases the copy/move candidates found by heuristics can be very large
772 in number and that will make the algorithm slow. The number of possible
778 in number and that will make the algorithm slow. The number of possible
773 candidates to check can be limited by using the config
779 candidates to check can be limited by using the config
774 `experimental.copytrace.movecandidateslimit` which defaults to 100.
780 `experimental.copytrace.movecandidateslimit` which defaults to 100.
775 """
781 """
776
782
777 if c1.rev() is None:
783 if c1.rev() is None:
778 c1 = c1.p1()
784 c1 = c1.p1()
779 if c2.rev() is None:
785 if c2.rev() is None:
780 c2 = c2.p1()
786 c2 = c2.p1()
781
787
782 copies = {}
788 copies = {}
783
789
784 changedfiles = set()
790 changedfiles = set()
785 m1 = c1.manifest()
791 m1 = c1.manifest()
786 if not repo.revs('%d::%d', base.rev(), c2.rev()):
792 if not repo.revs('%d::%d', base.rev(), c2.rev()):
787 # If base is not in c2 branch, we switch to fullcopytracing
793 # If base is not in c2 branch, we switch to fullcopytracing
788 repo.ui.debug("switching to full copytracing as base is not "
794 repo.ui.debug("switching to full copytracing as base is not "
789 "an ancestor of c2\n")
795 "an ancestor of c2\n")
790 return _fullcopytracing(repo, c1, c2, base)
796 return _fullcopytracing(repo, c1, c2, base)
791
797
792 ctx = c2
798 ctx = c2
793 while ctx != base:
799 while ctx != base:
794 if len(ctx.parents()) == 2:
800 if len(ctx.parents()) == 2:
795 # To keep things simple let's not handle merges
801 # To keep things simple let's not handle merges
796 repo.ui.debug("switching to full copytracing because of merges\n")
802 repo.ui.debug("switching to full copytracing because of merges\n")
797 return _fullcopytracing(repo, c1, c2, base)
803 return _fullcopytracing(repo, c1, c2, base)
798 changedfiles.update(ctx.files())
804 changedfiles.update(ctx.files())
799 ctx = ctx.p1()
805 ctx = ctx.p1()
800
806
801 cp = _forwardcopies(base, c2)
807 cp = _forwardcopies(base, c2)
802 for dst, src in cp.iteritems():
808 for dst, src in cp.iteritems():
803 if src in m1:
809 if src in m1:
804 copies[dst] = src
810 copies[dst] = src
805
811
806 # file is missing if it isn't present in the destination, but is present in
812 # file is missing if it isn't present in the destination, but is present in
807 # the base and present in the source.
813 # the base and present in the source.
808 # Presence in the base is important to exclude added files, presence in the
814 # Presence in the base is important to exclude added files, presence in the
809 # source is important to exclude removed files.
815 # source is important to exclude removed files.
810 filt = lambda f: f not in m1 and f in base and f in c2
816 filt = lambda f: f not in m1 and f in base and f in c2
811 missingfiles = [f for f in changedfiles if filt(f)]
817 missingfiles = [f for f in changedfiles if filt(f)]
812
818
813 if missingfiles:
819 if missingfiles:
814 basenametofilename = collections.defaultdict(list)
820 basenametofilename = collections.defaultdict(list)
815 dirnametofilename = collections.defaultdict(list)
821 dirnametofilename = collections.defaultdict(list)
816
822
817 for f in m1.filesnotin(base.manifest()):
823 for f in m1.filesnotin(base.manifest()):
818 basename = os.path.basename(f)
824 basename = os.path.basename(f)
819 dirname = os.path.dirname(f)
825 dirname = os.path.dirname(f)
820 basenametofilename[basename].append(f)
826 basenametofilename[basename].append(f)
821 dirnametofilename[dirname].append(f)
827 dirnametofilename[dirname].append(f)
822
828
823 for f in missingfiles:
829 for f in missingfiles:
824 basename = os.path.basename(f)
830 basename = os.path.basename(f)
825 dirname = os.path.dirname(f)
831 dirname = os.path.dirname(f)
826 samebasename = basenametofilename[basename]
832 samebasename = basenametofilename[basename]
827 samedirname = dirnametofilename[dirname]
833 samedirname = dirnametofilename[dirname]
828 movecandidates = samebasename + samedirname
834 movecandidates = samebasename + samedirname
829 # f is guaranteed to be present in c2, that's why
835 # f is guaranteed to be present in c2, that's why
830 # c2.filectx(f) won't fail
836 # c2.filectx(f) won't fail
831 f2 = c2.filectx(f)
837 f2 = c2.filectx(f)
832 # we can have a lot of candidates which can slow down the heuristics
838 # we can have a lot of candidates which can slow down the heuristics
833 # config value to limit the number of candidates moves to check
839 # config value to limit the number of candidates moves to check
834 maxcandidates = repo.ui.configint('experimental',
840 maxcandidates = repo.ui.configint('experimental',
835 'copytrace.movecandidateslimit')
841 'copytrace.movecandidateslimit')
836
842
837 if len(movecandidates) > maxcandidates:
843 if len(movecandidates) > maxcandidates:
838 repo.ui.status(_("skipping copytracing for '%s', more "
844 repo.ui.status(_("skipping copytracing for '%s', more "
839 "candidates than the limit: %d\n")
845 "candidates than the limit: %d\n")
840 % (f, len(movecandidates)))
846 % (f, len(movecandidates)))
841 continue
847 continue
842
848
843 for candidate in movecandidates:
849 for candidate in movecandidates:
844 f1 = c1.filectx(candidate)
850 f1 = c1.filectx(candidate)
845 if _related(f1, f2):
851 if _related(f1, f2):
846 # if there are a few related copies then we'll merge
852 # if there are a few related copies then we'll merge
847 # changes into all of them. This matches the behaviour
853 # changes into all of them. This matches the behaviour
848 # of upstream copytracing
854 # of upstream copytracing
849 copies[candidate] = f
855 copies[candidate] = f
850
856
851 return copies, {}, {}, {}, {}
857 return copies, {}, {}, {}, {}
852
858
853 def _related(f1, f2):
859 def _related(f1, f2):
854 """return True if f1 and f2 filectx have a common ancestor
860 """return True if f1 and f2 filectx have a common ancestor
855
861
856 Walk back to common ancestor to see if the two files originate
862 Walk back to common ancestor to see if the two files originate
857 from the same file. Since workingfilectx's rev() is None it messes
863 from the same file. Since workingfilectx's rev() is None it messes
858 up the integer comparison logic, hence the pre-step check for
864 up the integer comparison logic, hence the pre-step check for
859 None (f1 and f2 can only be workingfilectx's initially).
865 None (f1 and f2 can only be workingfilectx's initially).
860 """
866 """
861
867
862 if f1 == f2:
868 if f1 == f2:
863 return True # a match
869 return True # a match
864
870
865 g1, g2 = f1.ancestors(), f2.ancestors()
871 g1, g2 = f1.ancestors(), f2.ancestors()
866 try:
872 try:
867 f1r, f2r = f1.linkrev(), f2.linkrev()
873 f1r, f2r = f1.linkrev(), f2.linkrev()
868
874
869 if f1r is None:
875 if f1r is None:
870 f1 = next(g1)
876 f1 = next(g1)
871 if f2r is None:
877 if f2r is None:
872 f2 = next(g2)
878 f2 = next(g2)
873
879
874 while True:
880 while True:
875 f1r, f2r = f1.linkrev(), f2.linkrev()
881 f1r, f2r = f1.linkrev(), f2.linkrev()
876 if f1r > f2r:
882 if f1r > f2r:
877 f1 = next(g1)
883 f1 = next(g1)
878 elif f2r > f1r:
884 elif f2r > f1r:
879 f2 = next(g2)
885 f2 = next(g2)
880 else: # f1 and f2 point to files in the same linkrev
886 else: # f1 and f2 point to files in the same linkrev
881 return f1 == f2 # true if they point to the same file
887 return f1 == f2 # true if they point to the same file
882 except StopIteration:
888 except StopIteration:
883 return False
889 return False
884
890
885 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
891 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
886 """
892 """
887 check possible copies of f from msrc to mdst
893 check possible copies of f from msrc to mdst
888
894
889 srcctx = starting context for f in msrc
895 srcctx = starting context for f in msrc
890 dstctx = destination context for f in mdst
896 dstctx = destination context for f in mdst
891 f = the filename to check (as in msrc)
897 f = the filename to check (as in msrc)
892 base = the changectx used as a merge base
898 base = the changectx used as a merge base
893 tca = topological common ancestor for graft-like scenarios
899 tca = topological common ancestor for graft-like scenarios
894 remotebase = True if base is outside tca::srcctx, False otherwise
900 remotebase = True if base is outside tca::srcctx, False otherwise
895 limit = the rev number to not search beyond
901 limit = the rev number to not search beyond
896 data = dictionary of dictionary to store copy data. (see mergecopies)
902 data = dictionary of dictionary to store copy data. (see mergecopies)
897
903
898 note: limit is only an optimization, and provides no guarantee that
904 note: limit is only an optimization, and provides no guarantee that
899 irrelevant revisions will not be visited
905 irrelevant revisions will not be visited
900 there is no easy way to make this algorithm stop in a guaranteed way
906 there is no easy way to make this algorithm stop in a guaranteed way
901 once it "goes behind a certain revision".
907 once it "goes behind a certain revision".
902 """
908 """
903
909
904 msrc = srcctx.manifest()
910 msrc = srcctx.manifest()
905 mdst = dstctx.manifest()
911 mdst = dstctx.manifest()
906 mb = base.manifest()
912 mb = base.manifest()
907 mta = tca.manifest()
913 mta = tca.manifest()
908 # Might be true if this call is about finding backward renames,
914 # Might be true if this call is about finding backward renames,
909 # This happens in the case of grafts because the DAG is then rotated.
915 # This happens in the case of grafts because the DAG is then rotated.
910 # If the file exists in both the base and the source, we are not looking
916 # If the file exists in both the base and the source, we are not looking
911 # for a rename on the source side, but on the part of the DAG that is
917 # for a rename on the source side, but on the part of the DAG that is
912 # traversed backwards.
918 # traversed backwards.
913 #
919 #
914 # In the case there is both backward and forward renames (before and after
920 # In the case there is both backward and forward renames (before and after
915 # the base) this is more complicated as we must detect a divergence.
921 # the base) this is more complicated as we must detect a divergence.
916 # We use 'backwards = False' in that case.
922 # We use 'backwards = False' in that case.
917 backwards = not remotebase and base != tca and f in mb
923 backwards = not remotebase and base != tca and f in mb
918 getsrcfctx = _makegetfctx(srcctx)
924 getsrcfctx = _makegetfctx(srcctx)
919 getdstfctx = _makegetfctx(dstctx)
925 getdstfctx = _makegetfctx(dstctx)
920
926
921 if msrc[f] == mb.get(f) and not remotebase:
927 if msrc[f] == mb.get(f) and not remotebase:
922 # Nothing to merge
928 # Nothing to merge
923 return
929 return
924
930
925 of = None
931 of = None
926 seen = {f}
932 seen = {f}
927 for oc in getsrcfctx(f, msrc[f]).ancestors():
933 for oc in getsrcfctx(f, msrc[f]).ancestors():
928 of = oc.path()
934 of = oc.path()
929 if of in seen:
935 if of in seen:
930 # check limit late - grab last rename before
936 # check limit late - grab last rename before
931 if oc.linkrev() < limit:
937 if oc.linkrev() < limit:
932 break
938 break
933 continue
939 continue
934 seen.add(of)
940 seen.add(of)
935
941
936 # remember for dir rename detection
942 # remember for dir rename detection
937 if backwards:
943 if backwards:
938 data['fullcopy'][of] = f # grafting backwards through renames
944 data['fullcopy'][of] = f # grafting backwards through renames
939 else:
945 else:
940 data['fullcopy'][f] = of
946 data['fullcopy'][f] = of
941 if of not in mdst:
947 if of not in mdst:
942 continue # no match, keep looking
948 continue # no match, keep looking
943 if mdst[of] == mb.get(of):
949 if mdst[of] == mb.get(of):
944 return # no merge needed, quit early
950 return # no merge needed, quit early
945 c2 = getdstfctx(of, mdst[of])
951 c2 = getdstfctx(of, mdst[of])
946 # c2 might be a plain new file on added on destination side that is
952 # c2 might be a plain new file on added on destination side that is
947 # unrelated to the droids we are looking for.
953 # unrelated to the droids we are looking for.
948 cr = _related(oc, c2)
954 cr = _related(oc, c2)
949 if cr and (of == f or of == c2.path()): # non-divergent
955 if cr and (of == f or of == c2.path()): # non-divergent
950 if backwards:
956 if backwards:
951 data['copy'][of] = f
957 data['copy'][of] = f
952 elif of in mb:
958 elif of in mb:
953 data['copy'][f] = of
959 data['copy'][f] = of
954 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
960 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
955 data['copy'][of] = f
961 data['copy'][of] = f
956 del data['fullcopy'][f]
962 del data['fullcopy'][f]
957 data['fullcopy'][of] = f
963 data['fullcopy'][of] = f
958 else: # divergence w.r.t. graft CA on one side of topological CA
964 else: # divergence w.r.t. graft CA on one side of topological CA
959 for sf in seen:
965 for sf in seen:
960 if sf in mb:
966 if sf in mb:
961 assert sf not in data['diverge']
967 assert sf not in data['diverge']
962 data['diverge'][sf] = [f, of]
968 data['diverge'][sf] = [f, of]
963 break
969 break
964 return
970 return
965
971
966 if of in mta:
972 if of in mta:
967 if backwards or remotebase:
973 if backwards or remotebase:
968 data['incomplete'][of] = f
974 data['incomplete'][of] = f
969 else:
975 else:
970 for sf in seen:
976 for sf in seen:
971 if sf in mb:
977 if sf in mb:
972 if tca == base:
978 if tca == base:
973 data['diverge'].setdefault(sf, []).append(f)
979 data['diverge'].setdefault(sf, []).append(f)
974 else:
980 else:
975 data['incompletediverge'][sf] = [of, f]
981 data['incompletediverge'][sf] = [of, f]
976 return
982 return
977
983
978 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
984 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
979 """reproduce copies from fromrev to rev in the dirstate
985 """reproduce copies from fromrev to rev in the dirstate
980
986
981 If skiprev is specified, it's a revision that should be used to
987 If skiprev is specified, it's a revision that should be used to
982 filter copy records. Any copies that occur between fromrev and
988 filter copy records. Any copies that occur between fromrev and
983 skiprev will not be duplicated, even if they appear in the set of
989 skiprev will not be duplicated, even if they appear in the set of
984 copies between fromrev and rev.
990 copies between fromrev and rev.
985 """
991 """
986 exclude = {}
992 exclude = {}
987 ctraceconfig = repo.ui.config('experimental', 'copytrace')
993 ctraceconfig = repo.ui.config('experimental', 'copytrace')
988 bctrace = stringutil.parsebool(ctraceconfig)
994 bctrace = stringutil.parsebool(ctraceconfig)
989 if (skiprev is not None and
995 if (skiprev is not None and
990 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
996 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
991 # copytrace='off' skips this line, but not the entire function because
997 # copytrace='off' skips this line, but not the entire function because
992 # the line below is O(size of the repo) during a rebase, while the rest
998 # the line below is O(size of the repo) during a rebase, while the rest
993 # of the function is much faster (and is required for carrying copy
999 # of the function is much faster (and is required for carrying copy
994 # metadata across the rebase anyway).
1000 # metadata across the rebase anyway).
995 exclude = pathcopies(repo[fromrev], repo[skiprev])
1001 exclude = pathcopies(repo[fromrev], repo[skiprev])
996 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
1002 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
997 # copies.pathcopies returns backward renames, so dst might not
1003 # copies.pathcopies returns backward renames, so dst might not
998 # actually be in the dirstate
1004 # actually be in the dirstate
999 if dst in exclude:
1005 if dst in exclude:
1000 continue
1006 continue
1001 wctx[dst].markcopied(src)
1007 wctx[dst].markcopied(src)
@@ -1,633 +1,633 b''
1 #testcases filelog compatibility
1 #testcases filelog compatibility
2
2
3 $ cat >> $HGRCPATH << EOF
3 $ cat >> $HGRCPATH << EOF
4 > [extensions]
4 > [extensions]
5 > rebase=
5 > rebase=
6 > [alias]
6 > [alias]
7 > l = log -G -T '{rev} {desc}\n{files}\n'
7 > l = log -G -T '{rev} {desc}\n{files}\n'
8 > EOF
8 > EOF
9
9
10 #if compatibility
10 #if compatibility
11 $ cat >> $HGRCPATH << EOF
11 $ cat >> $HGRCPATH << EOF
12 > [experimental]
12 > [experimental]
13 > copies.read-from = compatibility
13 > copies.read-from = compatibility
14 > EOF
14 > EOF
15 #endif
15 #endif
16
16
17 $ REPONUM=0
17 $ REPONUM=0
18 $ newrepo() {
18 $ newrepo() {
19 > cd $TESTTMP
19 > cd $TESTTMP
20 > REPONUM=`expr $REPONUM + 1`
20 > REPONUM=`expr $REPONUM + 1`
21 > hg init repo-$REPONUM
21 > hg init repo-$REPONUM
22 > cd repo-$REPONUM
22 > cd repo-$REPONUM
23 > }
23 > }
24
24
25 Simple rename case
25 Simple rename case
26 $ newrepo
26 $ newrepo
27 $ echo x > x
27 $ echo x > x
28 $ hg ci -Aqm 'add x'
28 $ hg ci -Aqm 'add x'
29 $ hg mv x y
29 $ hg mv x y
30 $ hg debugp1copies
30 $ hg debugp1copies
31 x -> y
31 x -> y
32 $ hg debugp2copies
32 $ hg debugp2copies
33 $ hg ci -m 'rename x to y'
33 $ hg ci -m 'rename x to y'
34 $ hg l
34 $ hg l
35 @ 1 rename x to y
35 @ 1 rename x to y
36 | x y
36 | x y
37 o 0 add x
37 o 0 add x
38 x
38 x
39 $ hg debugp1copies -r 1
39 $ hg debugp1copies -r 1
40 x -> y
40 x -> y
41 $ hg debugpathcopies 0 1
41 $ hg debugpathcopies 0 1
42 x -> y
42 x -> y
43 $ hg debugpathcopies 1 0
43 $ hg debugpathcopies 1 0
44 y -> x
44 y -> x
45 Test filtering copies by path. We do filtering by destination.
45 Test filtering copies by path. We do filtering by destination.
46 $ hg debugpathcopies 0 1 x
46 $ hg debugpathcopies 0 1 x
47 $ hg debugpathcopies 1 0 x
47 $ hg debugpathcopies 1 0 x
48 y -> x
48 y -> x
49 $ hg debugpathcopies 0 1 y
49 $ hg debugpathcopies 0 1 y
50 x -> y
50 x -> y
51 $ hg debugpathcopies 1 0 y
51 $ hg debugpathcopies 1 0 y
52
52
53 Copy a file onto another file
53 Copy a file onto another file
54 $ newrepo
54 $ newrepo
55 $ echo x > x
55 $ echo x > x
56 $ echo y > y
56 $ echo y > y
57 $ hg ci -Aqm 'add x and y'
57 $ hg ci -Aqm 'add x and y'
58 $ hg cp -f x y
58 $ hg cp -f x y
59 $ hg debugp1copies
59 $ hg debugp1copies
60 x -> y
60 x -> y
61 $ hg debugp2copies
61 $ hg debugp2copies
62 $ hg ci -m 'copy x onto y'
62 $ hg ci -m 'copy x onto y'
63 $ hg l
63 $ hg l
64 @ 1 copy x onto y
64 @ 1 copy x onto y
65 | y
65 | y
66 o 0 add x and y
66 o 0 add x and y
67 x y
67 x y
68 $ hg debugp1copies -r 1
68 $ hg debugp1copies -r 1
69 x -> y
69 x -> y
70 Incorrectly doesn't show the rename
70 Incorrectly doesn't show the rename
71 $ hg debugpathcopies 0 1
71 $ hg debugpathcopies 0 1
72
72
73 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
73 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
74 produce a new filelog entry. The changeset's "files" entry should still list the file.
74 produce a new filelog entry. The changeset's "files" entry should still list the file.
75 $ newrepo
75 $ newrepo
76 $ echo x > x
76 $ echo x > x
77 $ echo x > x2
77 $ echo x > x2
78 $ hg ci -Aqm 'add x and x2 with same content'
78 $ hg ci -Aqm 'add x and x2 with same content'
79 $ hg cp -f x x2
79 $ hg cp -f x x2
80 $ hg ci -m 'copy x onto x2'
80 $ hg ci -m 'copy x onto x2'
81 $ hg l
81 $ hg l
82 @ 1 copy x onto x2
82 @ 1 copy x onto x2
83 | x2
83 | x2
84 o 0 add x and x2 with same content
84 o 0 add x and x2 with same content
85 x x2
85 x x2
86 $ hg debugp1copies -r 1
86 $ hg debugp1copies -r 1
87 x -> x2
87 x -> x2
88 Incorrectly doesn't show the rename
88 Incorrectly doesn't show the rename
89 $ hg debugpathcopies 0 1
89 $ hg debugpathcopies 0 1
90
90
91 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
91 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
92 $ newrepo
92 $ newrepo
93 $ echo x > x
93 $ echo x > x
94 $ hg ci -Aqm 'add x'
94 $ hg ci -Aqm 'add x'
95 $ hg cp x y
95 $ hg cp x y
96 $ hg ci -m 'copy x to y'
96 $ hg ci -m 'copy x to y'
97 $ hg rm y
97 $ hg rm y
98 $ hg ci -m 'remove y'
98 $ hg ci -m 'remove y'
99 $ hg cp -f x y
99 $ hg cp -f x y
100 $ hg ci -m 'copy x onto y (again)'
100 $ hg ci -m 'copy x onto y (again)'
101 $ hg l
101 $ hg l
102 @ 3 copy x onto y (again)
102 @ 3 copy x onto y (again)
103 | y
103 | y
104 o 2 remove y
104 o 2 remove y
105 | y
105 | y
106 o 1 copy x to y
106 o 1 copy x to y
107 | y
107 | y
108 o 0 add x
108 o 0 add x
109 x
109 x
110 $ hg debugp1copies -r 3
110 $ hg debugp1copies -r 3
111 x -> y
111 x -> y
112 $ hg debugpathcopies 0 3
112 $ hg debugpathcopies 0 3
113 x -> y
113 x -> y
114
114
115 Rename file in a loop: x->y->z->x
115 Rename file in a loop: x->y->z->x
116 $ newrepo
116 $ newrepo
117 $ echo x > x
117 $ echo x > x
118 $ hg ci -Aqm 'add x'
118 $ hg ci -Aqm 'add x'
119 $ hg mv x y
119 $ hg mv x y
120 $ hg debugp1copies
120 $ hg debugp1copies
121 x -> y
121 x -> y
122 $ hg debugp2copies
122 $ hg debugp2copies
123 $ hg ci -m 'rename x to y'
123 $ hg ci -m 'rename x to y'
124 $ hg mv y z
124 $ hg mv y z
125 $ hg ci -m 'rename y to z'
125 $ hg ci -m 'rename y to z'
126 $ hg mv z x
126 $ hg mv z x
127 $ hg ci -m 'rename z to x'
127 $ hg ci -m 'rename z to x'
128 $ hg l
128 $ hg l
129 @ 3 rename z to x
129 @ 3 rename z to x
130 | x z
130 | x z
131 o 2 rename y to z
131 o 2 rename y to z
132 | y z
132 | y z
133 o 1 rename x to y
133 o 1 rename x to y
134 | x y
134 | x y
135 o 0 add x
135 o 0 add x
136 x
136 x
137 $ hg debugpathcopies 0 3
137 $ hg debugpathcopies 0 3
138
138
139 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
139 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
140 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
140 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
141 $ newrepo
141 $ newrepo
142 $ echo x > x
142 $ echo x > x
143 $ hg ci -Aqm 'add x'
143 $ hg ci -Aqm 'add x'
144 $ hg mv x y
144 $ hg mv x y
145 $ hg ci -m 'rename x to y'
145 $ hg ci -m 'rename x to y'
146 $ hg rm y
146 $ hg rm y
147 $ hg ci -qm 'remove y'
147 $ hg ci -qm 'remove y'
148 $ echo x > y
148 $ echo x > y
149 $ hg ci -Aqm 'add back y'
149 $ hg ci -Aqm 'add back y'
150 $ hg l
150 $ hg l
151 @ 3 add back y
151 @ 3 add back y
152 | y
152 | y
153 o 2 remove y
153 o 2 remove y
154 | y
154 | y
155 o 1 rename x to y
155 o 1 rename x to y
156 | x y
156 | x y
157 o 0 add x
157 o 0 add x
158 x
158 x
159 $ hg debugp1copies -r 3
159 $ hg debugp1copies -r 3
160 $ hg debugpathcopies 0 3
160 $ hg debugpathcopies 0 3
161
161
162 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
162 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
163 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
163 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
164 to the first commit that added the file. We should still report the copy as being from x2.
164 to the first commit that added the file. We should still report the copy as being from x2.
165 $ newrepo
165 $ newrepo
166 $ echo x > x
166 $ echo x > x
167 $ echo x > x2
167 $ echo x > x2
168 $ hg ci -Aqm 'add x and x2 with same content'
168 $ hg ci -Aqm 'add x and x2 with same content'
169 $ hg cp x z
169 $ hg cp x z
170 $ hg ci -qm 'copy x to z'
170 $ hg ci -qm 'copy x to z'
171 $ hg rm z
171 $ hg rm z
172 $ hg ci -m 'remove z'
172 $ hg ci -m 'remove z'
173 $ hg cp x2 z
173 $ hg cp x2 z
174 $ hg ci -m 'copy x2 to z'
174 $ hg ci -m 'copy x2 to z'
175 $ hg l
175 $ hg l
176 @ 3 copy x2 to z
176 @ 3 copy x2 to z
177 | z
177 | z
178 o 2 remove z
178 o 2 remove z
179 | z
179 | z
180 o 1 copy x to z
180 o 1 copy x to z
181 | z
181 | z
182 o 0 add x and x2 with same content
182 o 0 add x and x2 with same content
183 x x2
183 x x2
184 $ hg debugp1copies -r 3
184 $ hg debugp1copies -r 3
185 x2 -> z
185 x2 -> z
186 $ hg debugpathcopies 0 3
186 $ hg debugpathcopies 0 3
187 x2 -> z
187 x2 -> z
188
188
189 Create x and y, then rename them both to the same name, but on different sides of a fork
189 Create x and y, then rename them both to the same name, but on different sides of a fork
190 $ newrepo
190 $ newrepo
191 $ echo x > x
191 $ echo x > x
192 $ echo y > y
192 $ echo y > y
193 $ hg ci -Aqm 'add x and y'
193 $ hg ci -Aqm 'add x and y'
194 $ hg mv x z
194 $ hg mv x z
195 $ hg ci -qm 'rename x to z'
195 $ hg ci -qm 'rename x to z'
196 $ hg co -q 0
196 $ hg co -q 0
197 $ hg mv y z
197 $ hg mv y z
198 $ hg ci -qm 'rename y to z'
198 $ hg ci -qm 'rename y to z'
199 $ hg l
199 $ hg l
200 @ 2 rename y to z
200 @ 2 rename y to z
201 | y z
201 | y z
202 | o 1 rename x to z
202 | o 1 rename x to z
203 |/ x z
203 |/ x z
204 o 0 add x and y
204 o 0 add x and y
205 x y
205 x y
206 $ hg debugpathcopies 1 2
206 $ hg debugpathcopies 1 2
207 z -> x
207 z -> x
208 y -> z
208 y -> z
209
209
210 Fork renames x to y on one side and removes x on the other
210 Fork renames x to y on one side and removes x on the other
211 $ newrepo
211 $ newrepo
212 $ echo x > x
212 $ echo x > x
213 $ hg ci -Aqm 'add x'
213 $ hg ci -Aqm 'add x'
214 $ hg mv x y
214 $ hg mv x y
215 $ hg ci -m 'rename x to y'
215 $ hg ci -m 'rename x to y'
216 $ hg co -q 0
216 $ hg co -q 0
217 $ hg rm x
217 $ hg rm x
218 $ hg ci -m 'remove x'
218 $ hg ci -m 'remove x'
219 created new head
219 created new head
220 $ hg l
220 $ hg l
221 @ 2 remove x
221 @ 2 remove x
222 | x
222 | x
223 | o 1 rename x to y
223 | o 1 rename x to y
224 |/ x y
224 |/ x y
225 o 0 add x
225 o 0 add x
226 x
226 x
227 $ hg debugpathcopies 1 2
227 $ hg debugpathcopies 1 2
228
228
229 Copies via null revision (there shouldn't be any)
229 Copies via null revision (there shouldn't be any)
230 $ newrepo
230 $ newrepo
231 $ echo x > x
231 $ echo x > x
232 $ hg ci -Aqm 'add x'
232 $ hg ci -Aqm 'add x'
233 $ hg cp x y
233 $ hg cp x y
234 $ hg ci -m 'copy x to y'
234 $ hg ci -m 'copy x to y'
235 $ hg co -q null
235 $ hg co -q null
236 $ echo x > x
236 $ echo x > x
237 $ hg ci -Aqm 'add x (again)'
237 $ hg ci -Aqm 'add x (again)'
238 $ hg l
238 $ hg l
239 @ 2 add x (again)
239 @ 2 add x (again)
240 x
240 x
241 o 1 copy x to y
241 o 1 copy x to y
242 | y
242 | y
243 o 0 add x
243 o 0 add x
244 x
244 x
245 $ hg debugpathcopies 1 2
245 $ hg debugpathcopies 1 2
246 $ hg debugpathcopies 2 1
246 $ hg debugpathcopies 2 1
247
247
248 Merge rename from other branch
248 Merge rename from other branch
249 $ newrepo
249 $ newrepo
250 $ echo x > x
250 $ echo x > x
251 $ hg ci -Aqm 'add x'
251 $ hg ci -Aqm 'add x'
252 $ hg mv x y
252 $ hg mv x y
253 $ hg ci -m 'rename x to y'
253 $ hg ci -m 'rename x to y'
254 $ hg co -q 0
254 $ hg co -q 0
255 $ echo z > z
255 $ echo z > z
256 $ hg ci -Aqm 'add z'
256 $ hg ci -Aqm 'add z'
257 $ hg merge -q 1
257 $ hg merge -q 1
258 $ hg debugp1copies
258 $ hg debugp1copies
259 $ hg debugp2copies
259 $ hg debugp2copies
260 $ hg ci -m 'merge rename from p2'
260 $ hg ci -m 'merge rename from p2'
261 $ hg l
261 $ hg l
262 @ 3 merge rename from p2
262 @ 3 merge rename from p2
263 |\ x
263 |\ x
264 | o 2 add z
264 | o 2 add z
265 | | z
265 | | z
266 o | 1 rename x to y
266 o | 1 rename x to y
267 |/ x y
267 |/ x y
268 o 0 add x
268 o 0 add x
269 x
269 x
270 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
270 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
271 merges, so...
271 merges, so...
272 $ hg debugp1copies -r 3
272 $ hg debugp1copies -r 3
273 $ hg debugp2copies -r 3
273 $ hg debugp2copies -r 3
274 $ hg debugpathcopies 0 3
274 $ hg debugpathcopies 0 3
275 x -> y
275 x -> y
276 $ hg debugpathcopies 1 2
276 $ hg debugpathcopies 1 2
277 y -> x
277 y -> x
278 $ hg debugpathcopies 1 3
278 $ hg debugpathcopies 1 3
279 $ hg debugpathcopies 2 3
279 $ hg debugpathcopies 2 3
280 x -> y
280 x -> y
281
281
282 Copy file from either side in a merge
282 Copy file from either side in a merge
283 $ newrepo
283 $ newrepo
284 $ echo x > x
284 $ echo x > x
285 $ hg ci -Aqm 'add x'
285 $ hg ci -Aqm 'add x'
286 $ hg co -q null
286 $ hg co -q null
287 $ echo y > y
287 $ echo y > y
288 $ hg ci -Aqm 'add y'
288 $ hg ci -Aqm 'add y'
289 $ hg merge -q 0
289 $ hg merge -q 0
290 $ hg cp y z
290 $ hg cp y z
291 $ hg debugp1copies
291 $ hg debugp1copies
292 y -> z
292 y -> z
293 $ hg debugp2copies
293 $ hg debugp2copies
294 $ hg ci -m 'copy file from p1 in merge'
294 $ hg ci -m 'copy file from p1 in merge'
295 $ hg co -q 1
295 $ hg co -q 1
296 $ hg merge -q 0
296 $ hg merge -q 0
297 $ hg cp x z
297 $ hg cp x z
298 $ hg debugp1copies
298 $ hg debugp1copies
299 $ hg debugp2copies
299 $ hg debugp2copies
300 x -> z
300 x -> z
301 $ hg ci -qm 'copy file from p2 in merge'
301 $ hg ci -qm 'copy file from p2 in merge'
302 $ hg l
302 $ hg l
303 @ 3 copy file from p2 in merge
303 @ 3 copy file from p2 in merge
304 |\ z
304 |\ z
305 +---o 2 copy file from p1 in merge
305 +---o 2 copy file from p1 in merge
306 | |/ z
306 | |/ z
307 | o 1 add y
307 | o 1 add y
308 | y
308 | y
309 o 0 add x
309 o 0 add x
310 x
310 x
311 $ hg debugp1copies -r 2
311 $ hg debugp1copies -r 2
312 y -> z
312 y -> z
313 $ hg debugp2copies -r 2
313 $ hg debugp2copies -r 2
314 $ hg debugpathcopies 1 2
314 $ hg debugpathcopies 1 2
315 y -> z
315 y -> z
316 $ hg debugpathcopies 0 2
316 $ hg debugpathcopies 0 2
317 $ hg debugp1copies -r 3
317 $ hg debugp1copies -r 3
318 $ hg debugp2copies -r 3
318 $ hg debugp2copies -r 3
319 x -> z
319 x -> z
320 $ hg debugpathcopies 1 3
320 $ hg debugpathcopies 1 3
321 $ hg debugpathcopies 0 3
321 $ hg debugpathcopies 0 3
322 x -> z
322 x -> z
323
323
324 Copy file that exists on both sides of the merge, same content on both sides
324 Copy file that exists on both sides of the merge, same content on both sides
325 $ newrepo
325 $ newrepo
326 $ echo x > x
326 $ echo x > x
327 $ hg ci -Aqm 'add x on branch 1'
327 $ hg ci -Aqm 'add x on branch 1'
328 $ hg co -q null
328 $ hg co -q null
329 $ echo x > x
329 $ echo x > x
330 $ hg ci -Aqm 'add x on branch 2'
330 $ hg ci -Aqm 'add x on branch 2'
331 $ hg merge -q 0
331 $ hg merge -q 0
332 $ hg cp x z
332 $ hg cp x z
333 $ hg debugp1copies
333 $ hg debugp1copies
334 x -> z
334 x -> z
335 $ hg debugp2copies
335 $ hg debugp2copies
336 $ hg ci -qm 'merge'
336 $ hg ci -qm 'merge'
337 $ hg l
337 $ hg l
338 @ 2 merge
338 @ 2 merge
339 |\ z
339 |\ z
340 | o 1 add x on branch 2
340 | o 1 add x on branch 2
341 | x
341 | x
342 o 0 add x on branch 1
342 o 0 add x on branch 1
343 x
343 x
344 $ hg debugp1copies -r 2
344 $ hg debugp1copies -r 2
345 x -> z
345 x -> z
346 $ hg debugp2copies -r 2
346 $ hg debugp2copies -r 2
347 It's a little weird that it shows up on both sides
347 It's a little weird that it shows up on both sides
348 $ hg debugpathcopies 1 2
348 $ hg debugpathcopies 1 2
349 x -> z
349 x -> z
350 $ hg debugpathcopies 0 2
350 $ hg debugpathcopies 0 2
351 x -> z (filelog !)
351 x -> z (filelog !)
352
352
353 Copy file that exists on both sides of the merge, different content
353 Copy file that exists on both sides of the merge, different content
354 $ newrepo
354 $ newrepo
355 $ echo branch1 > x
355 $ echo branch1 > x
356 $ hg ci -Aqm 'add x on branch 1'
356 $ hg ci -Aqm 'add x on branch 1'
357 $ hg co -q null
357 $ hg co -q null
358 $ echo branch2 > x
358 $ echo branch2 > x
359 $ hg ci -Aqm 'add x on branch 2'
359 $ hg ci -Aqm 'add x on branch 2'
360 $ hg merge -q 0
360 $ hg merge -q 0
361 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
361 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
362 [1]
362 [1]
363 $ echo resolved > x
363 $ echo resolved > x
364 $ hg resolve -m x
364 $ hg resolve -m x
365 (no more unresolved files)
365 (no more unresolved files)
366 $ hg cp x z
366 $ hg cp x z
367 $ hg debugp1copies
367 $ hg debugp1copies
368 x -> z
368 x -> z
369 $ hg debugp2copies
369 $ hg debugp2copies
370 $ hg ci -qm 'merge'
370 $ hg ci -qm 'merge'
371 $ hg l
371 $ hg l
372 @ 2 merge
372 @ 2 merge
373 |\ x z
373 |\ x z
374 | o 1 add x on branch 2
374 | o 1 add x on branch 2
375 | x
375 | x
376 o 0 add x on branch 1
376 o 0 add x on branch 1
377 x
377 x
378 $ hg debugp1copies -r 2
378 $ hg debugp1copies -r 2
379 $ hg debugp2copies -r 2
379 $ hg debugp2copies -r 2
380 x -> z
380 x -> z
381 $ hg debugpathcopies 1 2
381 $ hg debugpathcopies 1 2
382 $ hg debugpathcopies 0 2
382 $ hg debugpathcopies 0 2
383 x -> z
383 x -> z
384
384
385 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
385 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
386 of the merge to the merge should include the copy from the other side.
386 of the merge to the merge should include the copy from the other side.
387 $ newrepo
387 $ newrepo
388 $ echo x > x
388 $ echo x > x
389 $ hg ci -Aqm 'add x'
389 $ hg ci -Aqm 'add x'
390 $ hg cp x y
390 $ hg cp x y
391 $ hg ci -qm 'copy x to y'
391 $ hg ci -qm 'copy x to y'
392 $ hg co -q 0
392 $ hg co -q 0
393 $ hg cp x z
393 $ hg cp x z
394 $ hg ci -qm 'copy x to z'
394 $ hg ci -qm 'copy x to z'
395 $ hg merge -q 1
395 $ hg merge -q 1
396 $ hg ci -m 'merge copy x->y and copy x->z'
396 $ hg ci -m 'merge copy x->y and copy x->z'
397 $ hg l
397 $ hg l
398 @ 3 merge copy x->y and copy x->z
398 @ 3 merge copy x->y and copy x->z
399 |\
399 |\
400 | o 2 copy x to z
400 | o 2 copy x to z
401 | | z
401 | | z
402 o | 1 copy x to y
402 o | 1 copy x to y
403 |/ y
403 |/ y
404 o 0 add x
404 o 0 add x
405 x
405 x
406 $ hg debugp1copies -r 3
406 $ hg debugp1copies -r 3
407 $ hg debugp2copies -r 3
407 $ hg debugp2copies -r 3
408 $ hg debugpathcopies 2 3
408 $ hg debugpathcopies 2 3
409 x -> y
409 x -> y
410 $ hg debugpathcopies 1 3
410 $ hg debugpathcopies 1 3
411 x -> z
411 x -> z
412
412
413 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
413 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
414 first side should not include the y->z rename since y didn't exist in the merge base.
414 first side should not include the y->z rename since y didn't exist in the merge base.
415 $ newrepo
415 $ newrepo
416 $ echo x > x
416 $ echo x > x
417 $ hg ci -Aqm 'add x'
417 $ hg ci -Aqm 'add x'
418 $ hg cp x y
418 $ hg cp x y
419 $ hg ci -qm 'copy x to y'
419 $ hg ci -qm 'copy x to y'
420 $ hg co -q 0
420 $ hg co -q 0
421 $ echo y > y
421 $ echo y > y
422 $ hg ci -Aqm 'add y'
422 $ hg ci -Aqm 'add y'
423 $ hg mv y z
423 $ hg mv y z
424 $ hg ci -m 'rename y to z'
424 $ hg ci -m 'rename y to z'
425 $ hg merge -q 1
425 $ hg merge -q 1
426 $ hg ci -m 'merge'
426 $ hg ci -m 'merge'
427 $ hg l
427 $ hg l
428 @ 4 merge
428 @ 4 merge
429 |\
429 |\
430 | o 3 rename y to z
430 | o 3 rename y to z
431 | | y z
431 | | y z
432 | o 2 add y
432 | o 2 add y
433 | | y
433 | | y
434 o | 1 copy x to y
434 o | 1 copy x to y
435 |/ y
435 |/ y
436 o 0 add x
436 o 0 add x
437 x
437 x
438 $ hg debugp1copies -r 3
438 $ hg debugp1copies -r 3
439 y -> z
439 y -> z
440 $ hg debugp2copies -r 3
440 $ hg debugp2copies -r 3
441 $ hg debugpathcopies 2 3
441 $ hg debugpathcopies 2 3
442 y -> z
442 y -> z
443 $ hg debugpathcopies 1 3
443 $ hg debugpathcopies 1 3
444
444
445 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
445 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
446 other side.
446 other side.
447 $ newrepo
447 $ newrepo
448 $ echo x > x
448 $ echo x > x
449 $ echo y > y
449 $ echo y > y
450 $ hg ci -Aqm 'add x and y'
450 $ hg ci -Aqm 'add x and y'
451 $ hg mv x z
451 $ hg mv x z
452 $ hg ci -qm 'rename x to z'
452 $ hg ci -qm 'rename x to z'
453 $ hg co -q 0
453 $ hg co -q 0
454 $ hg mv y z
454 $ hg mv y z
455 $ hg ci -qm 'rename y to z'
455 $ hg ci -qm 'rename y to z'
456 $ echo z >> z
456 $ echo z >> z
457 $ hg ci -m 'modify z'
457 $ hg ci -m 'modify z'
458 $ hg merge -q 1
458 $ hg merge -q 1
459 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
459 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
460 [1]
460 [1]
461 $ echo z > z
461 $ echo z > z
462 $ hg resolve -qm z
462 $ hg resolve -qm z
463 $ hg ci -m 'merge 1 into 3'
463 $ hg ci -m 'merge 1 into 3'
464 Try merging the other direction too
464 Try merging the other direction too
465 $ hg co -q 1
465 $ hg co -q 1
466 $ hg merge -q 3
466 $ hg merge -q 3
467 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
467 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
468 [1]
468 [1]
469 $ echo z > z
469 $ echo z > z
470 $ hg resolve -qm z
470 $ hg resolve -qm z
471 $ hg ci -m 'merge 3 into 1'
471 $ hg ci -m 'merge 3 into 1'
472 created new head
472 created new head
473 $ hg l
473 $ hg l
474 @ 5 merge 3 into 1
474 @ 5 merge 3 into 1
475 |\ y z
475 |\ y z
476 +---o 4 merge 1 into 3
476 +---o 4 merge 1 into 3
477 | |/ x z
477 | |/ x z
478 | o 3 modify z
478 | o 3 modify z
479 | | z
479 | | z
480 | o 2 rename y to z
480 | o 2 rename y to z
481 | | y z
481 | | y z
482 o | 1 rename x to z
482 o | 1 rename x to z
483 |/ x z
483 |/ x z
484 o 0 add x and y
484 o 0 add x and y
485 x y
485 x y
486 $ hg debugpathcopies 1 4
486 $ hg debugpathcopies 1 4
487 $ hg debugpathcopies 2 4
487 $ hg debugpathcopies 2 4
488 $ hg debugpathcopies 0 4
488 $ hg debugpathcopies 0 4
489 x -> z (filelog !)
489 x -> z (filelog !)
490 y -> z (compatibility !)
490 y -> z (compatibility !)
491 $ hg debugpathcopies 1 5
491 $ hg debugpathcopies 1 5
492 $ hg debugpathcopies 2 5
492 $ hg debugpathcopies 2 5
493 $ hg debugpathcopies 0 5
493 $ hg debugpathcopies 0 5
494 x -> z
494 x -> z
495
495
496
496
497 Test for a case in fullcopytracing algorithm where both the merging csets are
497 Test for a case in fullcopytracing algorithm where both the merging csets are
498 "dirty"; where a dirty cset means that cset is descendant of merge base. This
498 "dirty"; where a dirty cset means that cset is descendant of merge base. This
499 test reflect that for this particular case this algorithm correctly find the copies:
499 test reflect that for this particular case this algorithm correctly find the copies:
500
500
501 $ cat >> $HGRCPATH << EOF
501 $ cat >> $HGRCPATH << EOF
502 > [experimental]
502 > [experimental]
503 > evolution.createmarkers=True
503 > evolution.createmarkers=True
504 > evolution.allowunstable=True
504 > evolution.allowunstable=True
505 > EOF
505 > EOF
506
506
507 $ newrepo
507 $ newrepo
508 $ echo a > a
508 $ echo a > a
509 $ hg add a
509 $ hg add a
510 $ hg ci -m "added a"
510 $ hg ci -m "added a"
511 $ echo b > b
511 $ echo b > b
512 $ hg add b
512 $ hg add b
513 $ hg ci -m "added b"
513 $ hg ci -m "added b"
514
514
515 $ hg mv b b1
515 $ hg mv b b1
516 $ hg ci -m "rename b to b1"
516 $ hg ci -m "rename b to b1"
517
517
518 $ hg up ".^"
518 $ hg up ".^"
519 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
519 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
520 $ echo d > d
520 $ echo d > d
521 $ hg add d
521 $ hg add d
522 $ hg ci -m "added d"
522 $ hg ci -m "added d"
523 created new head
523 created new head
524
524
525 $ echo baba >> b
525 $ echo baba >> b
526 $ hg ci --amend -m "added d, modified b"
526 $ hg ci --amend -m "added d, modified b"
527
527
528 $ hg l --hidden
528 $ hg l --hidden
529 @ 4 added d, modified b
529 @ 4 added d, modified b
530 | b d
530 | b d
531 | x 3 added d
531 | x 3 added d
532 |/ d
532 |/ d
533 | o 2 rename b to b1
533 | o 2 rename b to b1
534 |/ b b1
534 |/ b b1
535 o 1 added b
535 o 1 added b
536 | b
536 | b
537 o 0 added a
537 o 0 added a
538 a
538 a
539
539
540 Grafting revision 4 on top of revision 2, showing that it respect the rename:
540 Grafting revision 4 on top of revision 2, showing that it respect the rename:
541
541
542 $ hg up 2 -q
542 $ hg up 2 -q
543 $ hg graft -r 4 --base 3 --hidden
543 $ hg graft -r 4 --base 3 --hidden
544 grafting 4:af28412ec03c "added d, modified b" (tip)
544 grafting 4:af28412ec03c "added d, modified b" (tip)
545 merging b1 and b to b1
545 merging b1 and b to b1
546
546
547 $ hg l -l1 -p
547 $ hg l -l1 -p
548 @ 5 added d, modified b
548 @ 5 added d, modified b
549 | b1
549 | b1
550 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1
550 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1
551 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
551 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
552 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
552 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
553 @@ -1,1 +1,2 @@
553 @@ -1,1 +1,2 @@
554 b
554 b
555 +baba
555 +baba
556
556
557 Test which demonstrate that fullcopytracing algorithm can fail to handle a case when both the csets are dirty
557 Test to make sure that fullcopytracing algorithm don't fail when both the merging csets are dirty
558 ----------------------------------------------------------------------------------------------------------
558 (a dirty cset is one who is not the descendant of merge base)
559 -------------------------------------------------------------------------------------------------
559
560
560 $ newrepo
561 $ newrepo
561 $ echo a > a
562 $ echo a > a
562 $ hg add a
563 $ hg add a
563 $ hg ci -m "added a"
564 $ hg ci -m "added a"
564 $ echo b > b
565 $ echo b > b
565 $ hg add b
566 $ hg add b
566 $ hg ci -m "added b"
567 $ hg ci -m "added b"
567
568
568 $ echo foobar > willconflict
569 $ echo foobar > willconflict
569 $ hg add willconflict
570 $ hg add willconflict
570 $ hg ci -m "added willconflict"
571 $ hg ci -m "added willconflict"
571 $ echo c > c
572 $ echo c > c
572 $ hg add c
573 $ hg add c
573 $ hg ci -m "added c"
574 $ hg ci -m "added c"
574
575
575 $ hg l
576 $ hg l
576 @ 3 added c
577 @ 3 added c
577 | c
578 | c
578 o 2 added willconflict
579 o 2 added willconflict
579 | willconflict
580 | willconflict
580 o 1 added b
581 o 1 added b
581 | b
582 | b
582 o 0 added a
583 o 0 added a
583 a
584 a
584
585
585 $ hg up ".^^"
586 $ hg up ".^^"
586 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
587 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
587 $ echo d > d
588 $ echo d > d
588 $ hg add d
589 $ hg add d
589 $ hg ci -m "added d"
590 $ hg ci -m "added d"
590 created new head
591 created new head
591
592
592 $ echo barfoo > willconflict
593 $ echo barfoo > willconflict
593 $ hg add willconflict
594 $ hg add willconflict
594 $ hg ci --amend -m "added willconflict and d"
595 $ hg ci --amend -m "added willconflict and d"
595
596
596 $ hg l
597 $ hg l
597 @ 5 added willconflict and d
598 @ 5 added willconflict and d
598 | d willconflict
599 | d willconflict
599 | o 3 added c
600 | o 3 added c
600 | | c
601 | | c
601 | o 2 added willconflict
602 | o 2 added willconflict
602 |/ willconflict
603 |/ willconflict
603 o 1 added b
604 o 1 added b
604 | b
605 | b
605 o 0 added a
606 o 0 added a
606 a
607 a
607
608
608 $ hg rebase -r . -d 2 -t :other
609 $ hg rebase -r . -d 2 -t :other
609 rebasing 5:5018b1509e94 "added willconflict and d" (tip)
610 rebasing 5:5018b1509e94 "added willconflict and d" (tip)
610
611
611 $ hg up 3 -q
612 $ hg up 3 -q
612 $ hg l --hidden
613 $ hg l --hidden
613 o 6 added willconflict and d
614 o 6 added willconflict and d
614 | d willconflict
615 | d willconflict
615 | x 5 added willconflict and d
616 | x 5 added willconflict and d
616 | | d willconflict
617 | | d willconflict
617 | | x 4 added d
618 | | x 4 added d
618 | |/ d
619 | |/ d
619 +---@ 3 added c
620 +---@ 3 added c
620 | | c
621 | | c
621 o | 2 added willconflict
622 o | 2 added willconflict
622 |/ willconflict
623 |/ willconflict
623 o 1 added b
624 o 1 added b
624 | b
625 | b
625 o 0 added a
626 o 0 added a
626 a
627 a
627
628
628 Now if we trigger a merge between cset revision 3 and 6 using base revision 4, in this case
629 Now if we trigger a merge between cset revision 3 and 6 using base revision 4, in this case
629 both the merging csets will be dirty as no one is descendent of base revision:
630 both the merging csets will be dirty as no one is descendent of base revision:
630
631
631 $ hg graft -r 6 --base 4 --hidden 2>&1 | grep "AssertionError"
632 $ hg graft -r 6 --base 4 --hidden -t :other
632 AssertionError
633 grafting 6:99802e4f1e46 "added willconflict and d" (tip)
633
General Comments 0
You need to be logged in to leave comments. Login now