##// END OF EJS Templates
copies: don't filter out copy targets created on other side of merge commit...
Martin von Zweigbergk -
r43071:35d674a3 default
parent child Browse files
Show More
@@ -1,809 +1,804 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 util,
20 util,
21 )
21 )
22 from .utils import (
22 from .utils import (
23 stringutil,
23 stringutil,
24 )
24 )
25
25
26 def _findlimit(repo, ctxa, ctxb):
26 def _findlimit(repo, ctxa, ctxb):
27 """
27 """
28 Find the last revision that needs to be checked to ensure that a full
28 Find the last revision that needs to be checked to ensure that a full
29 transitive closure for file copies can be properly calculated.
29 transitive closure for file copies can be properly calculated.
30 Generally, this means finding the earliest revision number that's an
30 Generally, this means finding the earliest revision number that's an
31 ancestor of a or b but not both, except when a or b is a direct descendent
31 ancestor of a or b but not both, except when a or b is a direct descendent
32 of the other, in which case we can return the minimum revnum of a and b.
32 of the other, in which case we can return the minimum revnum of a and b.
33 """
33 """
34
34
35 # basic idea:
35 # basic idea:
36 # - mark a and b with different sides
36 # - mark a and b with different sides
37 # - if a parent's children are all on the same side, the parent is
37 # - if a parent's children are all on the same side, the parent is
38 # on that side, otherwise it is on no side
38 # on that side, otherwise it is on no side
39 # - walk the graph in topological order with the help of a heap;
39 # - walk the graph in topological order with the help of a heap;
40 # - add unseen parents to side map
40 # - add unseen parents to side map
41 # - clear side of any parent that has children on different sides
41 # - clear side of any parent that has children on different sides
42 # - track number of interesting revs that might still be on a side
42 # - track number of interesting revs that might still be on a side
43 # - track the lowest interesting rev seen
43 # - track the lowest interesting rev seen
44 # - quit when interesting revs is zero
44 # - quit when interesting revs is zero
45
45
46 cl = repo.changelog
46 cl = repo.changelog
47 wdirparents = None
47 wdirparents = None
48 a = ctxa.rev()
48 a = ctxa.rev()
49 b = ctxb.rev()
49 b = ctxb.rev()
50 if a is None:
50 if a is None:
51 wdirparents = (ctxa.p1(), ctxa.p2())
51 wdirparents = (ctxa.p1(), ctxa.p2())
52 a = node.wdirrev
52 a = node.wdirrev
53 if b is None:
53 if b is None:
54 assert not wdirparents
54 assert not wdirparents
55 wdirparents = (ctxb.p1(), ctxb.p2())
55 wdirparents = (ctxb.p1(), ctxb.p2())
56 b = node.wdirrev
56 b = node.wdirrev
57
57
58 side = {a: -1, b: 1}
58 side = {a: -1, b: 1}
59 visit = [-a, -b]
59 visit = [-a, -b]
60 heapq.heapify(visit)
60 heapq.heapify(visit)
61 interesting = len(visit)
61 interesting = len(visit)
62 limit = node.wdirrev
62 limit = node.wdirrev
63
63
64 while interesting:
64 while interesting:
65 r = -heapq.heappop(visit)
65 r = -heapq.heappop(visit)
66 if r == node.wdirrev:
66 if r == node.wdirrev:
67 parents = [pctx.rev() for pctx in wdirparents]
67 parents = [pctx.rev() for pctx in wdirparents]
68 else:
68 else:
69 parents = cl.parentrevs(r)
69 parents = cl.parentrevs(r)
70 if parents[1] == node.nullrev:
70 if parents[1] == node.nullrev:
71 parents = parents[:1]
71 parents = parents[:1]
72 for p in parents:
72 for p in parents:
73 if p not in side:
73 if p not in side:
74 # first time we see p; add it to visit
74 # first time we see p; add it to visit
75 side[p] = side[r]
75 side[p] = side[r]
76 if side[p]:
76 if side[p]:
77 interesting += 1
77 interesting += 1
78 heapq.heappush(visit, -p)
78 heapq.heappush(visit, -p)
79 elif side[p] and side[p] != side[r]:
79 elif side[p] and side[p] != side[r]:
80 # p was interesting but now we know better
80 # p was interesting but now we know better
81 side[p] = 0
81 side[p] = 0
82 interesting -= 1
82 interesting -= 1
83 if side[r]:
83 if side[r]:
84 limit = r # lowest rev visited
84 limit = r # lowest rev visited
85 interesting -= 1
85 interesting -= 1
86
86
87 # Consider the following flow (see test-commit-amend.t under issue4405):
87 # Consider the following flow (see test-commit-amend.t under issue4405):
88 # 1/ File 'a0' committed
88 # 1/ File 'a0' committed
89 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
89 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
90 # 3/ Move back to first commit
90 # 3/ Move back to first commit
91 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
91 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
92 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
92 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
93 #
93 #
94 # During the amend in step five, we will be in this state:
94 # During the amend in step five, we will be in this state:
95 #
95 #
96 # @ 3 temporary amend commit for a1-amend
96 # @ 3 temporary amend commit for a1-amend
97 # |
97 # |
98 # o 2 a1-amend
98 # o 2 a1-amend
99 # |
99 # |
100 # | o 1 a1
100 # | o 1 a1
101 # |/
101 # |/
102 # o 0 a0
102 # o 0 a0
103 #
103 #
104 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
104 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
105 # yet the filelog has the copy information in rev 1 and we will not look
105 # yet the filelog has the copy information in rev 1 and we will not look
106 # back far enough unless we also look at the a and b as candidates.
106 # back far enough unless we also look at the a and b as candidates.
107 # This only occurs when a is a descendent of b or visa-versa.
107 # This only occurs when a is a descendent of b or visa-versa.
108 return min(limit, a, b)
108 return min(limit, a, b)
109
109
110 def _chainandfilter(src, dst, a, b):
110 def _chainandfilter(src, dst, a, b):
111 """chain two sets of copies 'a' and 'b' and filter result"""
111 """chain two sets of copies 'a' and 'b' and filter result"""
112
112
113 # When chaining copies in 'a' (from 'src' via some other commit 'mid') with
113 # When chaining copies in 'a' (from 'src' via some other commit 'mid') with
114 # copies in 'b' (from 'mid' to 'dst'), we can get the different cases in the
114 # copies in 'b' (from 'mid' to 'dst'), we can get the different cases in the
115 # following table (not including trivial cases). For example, case 2 is
115 # following table (not including trivial cases). For example, case 2 is
116 # where a file existed in 'src' and remained under that name in 'mid' and
116 # where a file existed in 'src' and remained under that name in 'mid' and
117 # then was renamed between 'mid' and 'dst'.
117 # then was renamed between 'mid' and 'dst'.
118 #
118 #
119 # case src mid dst result
119 # case src mid dst result
120 # 1 x y - -
120 # 1 x y - -
121 # 2 x y y x->y
121 # 2 x y y x->y
122 # 3 x y x -
122 # 3 x y x -
123 # 4 x y z x->z
123 # 4 x y z x->z
124 # 5 - x y -
124 # 5 - x y -
125 # 6 x x y x->y
125 # 6 x x y x->y
126 #
126 #
127 # _chain() takes care of chaining the copies in 'a' and 'b', but it
127 # _chain() takes care of chaining the copies in 'a' and 'b', but it
128 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
128 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
129 # between 5 and 6, so it includes all cases in its result.
129 # between 5 and 6, so it includes all cases in its result.
130 # Cases 1, 3, and 5 are then removed by _filter().
130 # Cases 1, 3, and 5 are then removed by _filter().
131
131
132 t = _chain(a, b)
132 t = _chain(a, b)
133 _filter(src, dst, t)
133 _filter(src, dst, t)
134 return t
134 return t
135
135
136 def _filter(src, dst, t):
136 def _filter(src, dst, t):
137 """filters out invalid copies after chaining"""
137 """filters out invalid copies after chaining"""
138 for k, v in list(t.items()):
138 for k, v in list(t.items()):
139 # remove copies from files that didn't exist
139 # remove copies from files that didn't exist
140 if v not in src:
140 if v not in src:
141 del t[k]
141 del t[k]
142 # remove criss-crossed copies
142 # remove criss-crossed copies
143 elif k in src and v in dst:
143 elif k in src and v in dst:
144 del t[k]
144 del t[k]
145 # remove copies to files that were then removed
145 # remove copies to files that were then removed
146 elif k not in dst:
146 elif k not in dst:
147 del t[k]
147 del t[k]
148
148
149 def _chain(a, b):
149 def _chain(a, b):
150 """chain two sets of copies 'a' and 'b'"""
150 """chain two sets of copies 'a' and 'b'"""
151 t = a.copy()
151 t = a.copy()
152 for k, v in b.iteritems():
152 for k, v in b.iteritems():
153 if v in t:
153 if v in t:
154 t[k] = t[v]
154 t[k] = t[v]
155 else:
155 else:
156 t[k] = v
156 t[k] = v
157 return t
157 return t
158
158
159 def _tracefile(fctx, am, limit):
159 def _tracefile(fctx, am, limit):
160 """return file context that is the ancestor of fctx present in ancestor
160 """return file context that is the ancestor of fctx present in ancestor
161 manifest am, stopping after the first ancestor lower than limit"""
161 manifest am, stopping after the first ancestor lower than limit"""
162
162
163 for f in fctx.ancestors():
163 for f in fctx.ancestors():
164 if am.get(f.path(), None) == f.filenode():
164 if am.get(f.path(), None) == f.filenode():
165 return f
165 return f
166 if not f.isintroducedafter(limit):
166 if not f.isintroducedafter(limit):
167 return None
167 return None
168
168
169 def _dirstatecopies(repo, match=None):
169 def _dirstatecopies(repo, match=None):
170 ds = repo.dirstate
170 ds = repo.dirstate
171 c = ds.copies().copy()
171 c = ds.copies().copy()
172 for k in list(c):
172 for k in list(c):
173 if ds[k] not in 'anm' or (match and not match(k)):
173 if ds[k] not in 'anm' or (match and not match(k)):
174 del c[k]
174 del c[k]
175 return c
175 return c
176
176
177 def _computeforwardmissing(a, b, match=None):
177 def _computeforwardmissing(a, b, match=None):
178 """Computes which files are in b but not a.
178 """Computes which files are in b but not a.
179 This is its own function so extensions can easily wrap this call to see what
179 This is its own function so extensions can easily wrap this call to see what
180 files _forwardcopies is about to process.
180 files _forwardcopies is about to process.
181 """
181 """
182 ma = a.manifest()
182 ma = a.manifest()
183 mb = b.manifest()
183 mb = b.manifest()
184 return mb.filesnotin(ma, match=match)
184 return mb.filesnotin(ma, match=match)
185
185
186 def usechangesetcentricalgo(repo):
186 def usechangesetcentricalgo(repo):
187 """Checks if we should use changeset-centric copy algorithms"""
187 """Checks if we should use changeset-centric copy algorithms"""
188 return (repo.ui.config('experimental', 'copies.read-from') in
188 return (repo.ui.config('experimental', 'copies.read-from') in
189 ('changeset-only', 'compatibility'))
189 ('changeset-only', 'compatibility'))
190
190
191 def _committedforwardcopies(a, b, match):
191 def _committedforwardcopies(a, b, match):
192 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
192 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
193 # files might have to be traced back to the fctx parent of the last
193 # files might have to be traced back to the fctx parent of the last
194 # one-side-only changeset, but not further back than that
194 # one-side-only changeset, but not further back than that
195 repo = a._repo
195 repo = a._repo
196
196
197 if usechangesetcentricalgo(repo):
197 if usechangesetcentricalgo(repo):
198 return _changesetforwardcopies(a, b, match)
198 return _changesetforwardcopies(a, b, match)
199
199
200 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
200 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
201 dbg = repo.ui.debug
201 dbg = repo.ui.debug
202 if debug:
202 if debug:
203 dbg('debug.copies: looking into rename from %s to %s\n'
203 dbg('debug.copies: looking into rename from %s to %s\n'
204 % (a, b))
204 % (a, b))
205 limit = _findlimit(repo, a, b)
205 limit = _findlimit(repo, a, b)
206 if debug:
206 if debug:
207 dbg('debug.copies: search limit: %d\n' % limit)
207 dbg('debug.copies: search limit: %d\n' % limit)
208 am = a.manifest()
208 am = a.manifest()
209
209
210 # find where new files came from
210 # find where new files came from
211 # we currently don't try to find where old files went, too expensive
211 # we currently don't try to find where old files went, too expensive
212 # this means we can miss a case like 'hg rm b; hg cp a b'
212 # this means we can miss a case like 'hg rm b; hg cp a b'
213 cm = {}
213 cm = {}
214
214
215 # Computing the forward missing is quite expensive on large manifests, since
215 # Computing the forward missing is quite expensive on large manifests, since
216 # it compares the entire manifests. We can optimize it in the common use
216 # it compares the entire manifests. We can optimize it in the common use
217 # case of computing what copies are in a commit versus its parent (like
217 # case of computing what copies are in a commit versus its parent (like
218 # during a rebase or histedit). Note, we exclude merge commits from this
218 # during a rebase or histedit). Note, we exclude merge commits from this
219 # optimization, since the ctx.files() for a merge commit is not correct for
219 # optimization, since the ctx.files() for a merge commit is not correct for
220 # this comparison.
220 # this comparison.
221 forwardmissingmatch = match
221 forwardmissingmatch = match
222 if b.p1() == a and b.p2().node() == node.nullid:
222 if b.p1() == a and b.p2().node() == node.nullid:
223 filesmatcher = matchmod.exact(b.files())
223 filesmatcher = matchmod.exact(b.files())
224 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
224 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
225 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
225 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
226
226
227 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
227 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
228
228
229 if debug:
229 if debug:
230 dbg('debug.copies: missing files to search: %d\n' % len(missing))
230 dbg('debug.copies: missing files to search: %d\n' % len(missing))
231
231
232 for f in sorted(missing):
232 for f in sorted(missing):
233 if debug:
233 if debug:
234 dbg('debug.copies: tracing file: %s\n' % f)
234 dbg('debug.copies: tracing file: %s\n' % f)
235 fctx = b[f]
235 fctx = b[f]
236 fctx._ancestrycontext = ancestrycontext
236 fctx._ancestrycontext = ancestrycontext
237
237
238 if debug:
238 if debug:
239 start = util.timer()
239 start = util.timer()
240 ofctx = _tracefile(fctx, am, limit)
240 ofctx = _tracefile(fctx, am, limit)
241 if ofctx:
241 if ofctx:
242 if debug:
242 if debug:
243 dbg('debug.copies: rename of: %s\n' % ofctx._path)
243 dbg('debug.copies: rename of: %s\n' % ofctx._path)
244 cm[f] = ofctx.path()
244 cm[f] = ofctx.path()
245 if debug:
245 if debug:
246 dbg('debug.copies: time: %f seconds\n'
246 dbg('debug.copies: time: %f seconds\n'
247 % (util.timer() - start))
247 % (util.timer() - start))
248 return cm
248 return cm
249
249
250 def _changesetforwardcopies(a, b, match):
250 def _changesetforwardcopies(a, b, match):
251 if a.rev() == node.nullrev:
251 if a.rev() == node.nullrev:
252 return {}
252 return {}
253
253
254 repo = a.repo()
254 repo = a.repo()
255 children = {}
255 children = {}
256 cl = repo.changelog
256 cl = repo.changelog
257 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
257 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
258 for r in missingrevs:
258 for r in missingrevs:
259 for p in cl.parentrevs(r):
259 for p in cl.parentrevs(r):
260 if p == node.nullrev:
260 if p == node.nullrev:
261 continue
261 continue
262 if p not in children:
262 if p not in children:
263 children[p] = [r]
263 children[p] = [r]
264 else:
264 else:
265 children[p].append(r)
265 children[p].append(r)
266
266
267 roots = set(children) - set(missingrevs)
267 roots = set(children) - set(missingrevs)
268 # 'work' contains 3-tuples of a (revision number, parent number, copies).
268 # 'work' contains 3-tuples of a (revision number, parent number, copies).
269 # The parent number is only used for knowing which parent the copies dict
269 # The parent number is only used for knowing which parent the copies dict
270 # came from.
270 # came from.
271 work = [(r, 1, {}) for r in roots]
271 work = [(r, 1, {}) for r in roots]
272 heapq.heapify(work)
272 heapq.heapify(work)
273 while work:
273 while work:
274 r, i1, copies1 = heapq.heappop(work)
274 r, i1, copies1 = heapq.heappop(work)
275 if work and work[0][0] == r:
275 if work and work[0][0] == r:
276 # We are tracing copies from both parents
276 # We are tracing copies from both parents
277 r, i2, copies2 = heapq.heappop(work)
277 r, i2, copies2 = heapq.heappop(work)
278 copies = {}
278 copies = {}
279 ctx = repo[r]
280 p1man, p2man = ctx.p1().manifest(), ctx.p2().manifest()
281 allcopies = set(copies1) | set(copies2)
279 allcopies = set(copies1) | set(copies2)
282 # TODO: perhaps this filtering should be done as long as ctx
280 # TODO: perhaps this filtering should be done as long as ctx
283 # is merge, whether or not we're tracing from both parent.
281 # is merge, whether or not we're tracing from both parent.
284 for dst in allcopies:
282 for dst in allcopies:
285 if not match(dst):
283 if not match(dst):
286 continue
284 continue
287 if dst not in copies2:
285 # Unlike when copies are stored in the filelog, we consider
288 # Copied on p1 side: mark as copy from p1 side if it didn't
286 # it a copy even if the destination already existed on the
289 # already exist on p2 side
287 # other branch. It's simply too expensive to check if the
290 if dst not in p2man:
288 # file existed in the manifest.
291 copies[dst] = copies1[dst]
289 if dst in copies1:
292 elif dst not in copies1:
290 # If it was copied on the p1 side, mark it as copied from
293 # Copied on p2 side: mark as copy from p2 side if it didn't
291 # that side, even if it was also copied on the p2 side.
294 # already exist on p1 side
292 copies[dst] = copies1[dst]
295 if dst not in p1man:
296 copies[dst] = copies2[dst]
297 else:
293 else:
298 # Copied on both sides: mark as copy from p1 side
294 copies[dst] = copies2[dst]
299 copies[dst] = copies1[dst]
300 else:
295 else:
301 copies = copies1
296 copies = copies1
302 if r == b.rev():
297 if r == b.rev():
303 _filter(a, b, copies)
298 _filter(a, b, copies)
304 return copies
299 return copies
305 for c in children[r]:
300 for c in children[r]:
306 childctx = repo[c]
301 childctx = repo[c]
307 if r == childctx.p1().rev():
302 if r == childctx.p1().rev():
308 parent = 1
303 parent = 1
309 childcopies = childctx.p1copies()
304 childcopies = childctx.p1copies()
310 else:
305 else:
311 assert r == childctx.p2().rev()
306 assert r == childctx.p2().rev()
312 parent = 2
307 parent = 2
313 childcopies = childctx.p2copies()
308 childcopies = childctx.p2copies()
314 if not match.always():
309 if not match.always():
315 childcopies = {dst: src for dst, src in childcopies.items()
310 childcopies = {dst: src for dst, src in childcopies.items()
316 if match(dst)}
311 if match(dst)}
317 childcopies = _chain(copies, childcopies)
312 childcopies = _chain(copies, childcopies)
318 for f in childctx.filesremoved():
313 for f in childctx.filesremoved():
319 if f in childcopies:
314 if f in childcopies:
320 del childcopies[f]
315 del childcopies[f]
321 heapq.heappush(work, (c, parent, childcopies))
316 heapq.heappush(work, (c, parent, childcopies))
322 assert False
317 assert False
323
318
324 def _forwardcopies(a, b, match=None):
319 def _forwardcopies(a, b, match=None):
325 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
320 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
326
321
327 match = a.repo().narrowmatch(match)
322 match = a.repo().narrowmatch(match)
328 # check for working copy
323 # check for working copy
329 if b.rev() is None:
324 if b.rev() is None:
330 if a == b.p1():
325 if a == b.p1():
331 # short-circuit to avoid issues with merge states
326 # short-circuit to avoid issues with merge states
332 return _dirstatecopies(b._repo, match)
327 return _dirstatecopies(b._repo, match)
333
328
334 cm = _committedforwardcopies(a, b.p1(), match)
329 cm = _committedforwardcopies(a, b.p1(), match)
335 # combine copies from dirstate if necessary
330 # combine copies from dirstate if necessary
336 return _chainandfilter(a, b, cm, _dirstatecopies(b._repo, match))
331 return _chainandfilter(a, b, cm, _dirstatecopies(b._repo, match))
337 return _committedforwardcopies(a, b, match)
332 return _committedforwardcopies(a, b, match)
338
333
339 def _backwardrenames(a, b, match):
334 def _backwardrenames(a, b, match):
340 if a._repo.ui.config('experimental', 'copytrace') == 'off':
335 if a._repo.ui.config('experimental', 'copytrace') == 'off':
341 return {}
336 return {}
342
337
343 # Even though we're not taking copies into account, 1:n rename situations
338 # Even though we're not taking copies into account, 1:n rename situations
344 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
339 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
345 # arbitrarily pick one of the renames.
340 # arbitrarily pick one of the renames.
346 # We don't want to pass in "match" here, since that would filter
341 # We don't want to pass in "match" here, since that would filter
347 # the destination by it. Since we're reversing the copies, we want
342 # the destination by it. Since we're reversing the copies, we want
348 # to filter the source instead.
343 # to filter the source instead.
349 f = _forwardcopies(b, a)
344 f = _forwardcopies(b, a)
350 r = {}
345 r = {}
351 for k, v in sorted(f.iteritems()):
346 for k, v in sorted(f.iteritems()):
352 if match and not match(v):
347 if match and not match(v):
353 continue
348 continue
354 # remove copies
349 # remove copies
355 if v in a:
350 if v in a:
356 continue
351 continue
357 r[v] = k
352 r[v] = k
358 return r
353 return r
359
354
360 def pathcopies(x, y, match=None):
355 def pathcopies(x, y, match=None):
361 """find {dst@y: src@x} copy mapping for directed compare"""
356 """find {dst@y: src@x} copy mapping for directed compare"""
362 repo = x._repo
357 repo = x._repo
363 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
358 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
364 if debug:
359 if debug:
365 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
360 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
366 % (x, y))
361 % (x, y))
367 if x == y or not x or not y:
362 if x == y or not x or not y:
368 return {}
363 return {}
369 a = y.ancestor(x)
364 a = y.ancestor(x)
370 if a == x:
365 if a == x:
371 if debug:
366 if debug:
372 repo.ui.debug('debug.copies: search mode: forward\n')
367 repo.ui.debug('debug.copies: search mode: forward\n')
373 return _forwardcopies(x, y, match=match)
368 return _forwardcopies(x, y, match=match)
374 if a == y:
369 if a == y:
375 if debug:
370 if debug:
376 repo.ui.debug('debug.copies: search mode: backward\n')
371 repo.ui.debug('debug.copies: search mode: backward\n')
377 return _backwardrenames(x, y, match=match)
372 return _backwardrenames(x, y, match=match)
378 if debug:
373 if debug:
379 repo.ui.debug('debug.copies: search mode: combined\n')
374 repo.ui.debug('debug.copies: search mode: combined\n')
380 return _chainandfilter(x, y, _backwardrenames(x, a, match=match),
375 return _chainandfilter(x, y, _backwardrenames(x, a, match=match),
381 _forwardcopies(a, y, match=match))
376 _forwardcopies(a, y, match=match))
382
377
383 def mergecopies(repo, c1, c2, base):
378 def mergecopies(repo, c1, c2, base):
384 """
379 """
385 Finds moves and copies between context c1 and c2 that are relevant for
380 Finds moves and copies between context c1 and c2 that are relevant for
386 merging. 'base' will be used as the merge base.
381 merging. 'base' will be used as the merge base.
387
382
388 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
383 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
389 files that were moved/ copied in one merge parent and modified in another.
384 files that were moved/ copied in one merge parent and modified in another.
390 For example:
385 For example:
391
386
392 o ---> 4 another commit
387 o ---> 4 another commit
393 |
388 |
394 | o ---> 3 commit that modifies a.txt
389 | o ---> 3 commit that modifies a.txt
395 | /
390 | /
396 o / ---> 2 commit that moves a.txt to b.txt
391 o / ---> 2 commit that moves a.txt to b.txt
397 |/
392 |/
398 o ---> 1 merge base
393 o ---> 1 merge base
399
394
400 If we try to rebase revision 3 on revision 4, since there is no a.txt in
395 If we try to rebase revision 3 on revision 4, since there is no a.txt in
401 revision 4, and if user have copytrace disabled, we prints the following
396 revision 4, and if user have copytrace disabled, we prints the following
402 message:
397 message:
403
398
404 ```other changed <file> which local deleted```
399 ```other changed <file> which local deleted```
405
400
406 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
401 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
407 "dirmove".
402 "dirmove".
408
403
409 "copy" is a mapping from destination name -> source name,
404 "copy" is a mapping from destination name -> source name,
410 where source is in c1 and destination is in c2 or vice-versa.
405 where source is in c1 and destination is in c2 or vice-versa.
411
406
412 "movewithdir" is a mapping from source name -> destination name,
407 "movewithdir" is a mapping from source name -> destination name,
413 where the file at source present in one context but not the other
408 where the file at source present in one context but not the other
414 needs to be moved to destination by the merge process, because the
409 needs to be moved to destination by the merge process, because the
415 other context moved the directory it is in.
410 other context moved the directory it is in.
416
411
417 "diverge" is a mapping of source name -> list of destination names
412 "diverge" is a mapping of source name -> list of destination names
418 for divergent renames.
413 for divergent renames.
419
414
420 "renamedelete" is a mapping of source name -> list of destination
415 "renamedelete" is a mapping of source name -> list of destination
421 names for files deleted in c1 that were renamed in c2 or vice-versa.
416 names for files deleted in c1 that were renamed in c2 or vice-versa.
422
417
423 "dirmove" is a mapping of detected source dir -> destination dir renames.
418 "dirmove" is a mapping of detected source dir -> destination dir renames.
424 This is needed for handling changes to new files previously grafted into
419 This is needed for handling changes to new files previously grafted into
425 renamed directories.
420 renamed directories.
426
421
427 This function calls different copytracing algorithms based on config.
422 This function calls different copytracing algorithms based on config.
428 """
423 """
429 # avoid silly behavior for update from empty dir
424 # avoid silly behavior for update from empty dir
430 if not c1 or not c2 or c1 == c2:
425 if not c1 or not c2 or c1 == c2:
431 return {}, {}, {}, {}, {}
426 return {}, {}, {}, {}, {}
432
427
433 narrowmatch = c1.repo().narrowmatch()
428 narrowmatch = c1.repo().narrowmatch()
434
429
435 # avoid silly behavior for parent -> working dir
430 # avoid silly behavior for parent -> working dir
436 if c2.node() is None and c1.node() == repo.dirstate.p1():
431 if c2.node() is None and c1.node() == repo.dirstate.p1():
437 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
432 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
438
433
439 copytracing = repo.ui.config('experimental', 'copytrace')
434 copytracing = repo.ui.config('experimental', 'copytrace')
440 if stringutil.parsebool(copytracing) is False:
435 if stringutil.parsebool(copytracing) is False:
441 # stringutil.parsebool() returns None when it is unable to parse the
436 # stringutil.parsebool() returns None when it is unable to parse the
442 # value, so we should rely on making sure copytracing is on such cases
437 # value, so we should rely on making sure copytracing is on such cases
443 return {}, {}, {}, {}, {}
438 return {}, {}, {}, {}, {}
444
439
445 if usechangesetcentricalgo(repo):
440 if usechangesetcentricalgo(repo):
446 # The heuristics don't make sense when we need changeset-centric algos
441 # The heuristics don't make sense when we need changeset-centric algos
447 return _fullcopytracing(repo, c1, c2, base)
442 return _fullcopytracing(repo, c1, c2, base)
448
443
449 # Copy trace disabling is explicitly below the node == p1 logic above
444 # Copy trace disabling is explicitly below the node == p1 logic above
450 # because the logic above is required for a simple copy to be kept across a
445 # because the logic above is required for a simple copy to be kept across a
451 # rebase.
446 # rebase.
452 if copytracing == 'heuristics':
447 if copytracing == 'heuristics':
453 # Do full copytracing if only non-public revisions are involved as
448 # Do full copytracing if only non-public revisions are involved as
454 # that will be fast enough and will also cover the copies which could
449 # that will be fast enough and will also cover the copies which could
455 # be missed by heuristics
450 # be missed by heuristics
456 if _isfullcopytraceable(repo, c1, base):
451 if _isfullcopytraceable(repo, c1, base):
457 return _fullcopytracing(repo, c1, c2, base)
452 return _fullcopytracing(repo, c1, c2, base)
458 return _heuristicscopytracing(repo, c1, c2, base)
453 return _heuristicscopytracing(repo, c1, c2, base)
459 else:
454 else:
460 return _fullcopytracing(repo, c1, c2, base)
455 return _fullcopytracing(repo, c1, c2, base)
461
456
462 def _isfullcopytraceable(repo, c1, base):
457 def _isfullcopytraceable(repo, c1, base):
463 """ Checks that if base, source and destination are all no-public branches,
458 """ Checks that if base, source and destination are all no-public branches,
464 if yes let's use the full copytrace algorithm for increased capabilities
459 if yes let's use the full copytrace algorithm for increased capabilities
465 since it will be fast enough.
460 since it will be fast enough.
466
461
467 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
462 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
468 number of changesets from c1 to base such that if number of changesets are
463 number of changesets from c1 to base such that if number of changesets are
469 more than the limit, full copytracing algorithm won't be used.
464 more than the limit, full copytracing algorithm won't be used.
470 """
465 """
471 if c1.rev() is None:
466 if c1.rev() is None:
472 c1 = c1.p1()
467 c1 = c1.p1()
473 if c1.mutable() and base.mutable():
468 if c1.mutable() and base.mutable():
474 sourcecommitlimit = repo.ui.configint('experimental',
469 sourcecommitlimit = repo.ui.configint('experimental',
475 'copytrace.sourcecommitlimit')
470 'copytrace.sourcecommitlimit')
476 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
471 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
477 return commits < sourcecommitlimit
472 return commits < sourcecommitlimit
478 return False
473 return False
479
474
480 def _checksinglesidecopies(src, dsts1, m1, m2, mb, c2, base,
475 def _checksinglesidecopies(src, dsts1, m1, m2, mb, c2, base,
481 copy, renamedelete):
476 copy, renamedelete):
482 if src not in m2:
477 if src not in m2:
483 # deleted on side 2
478 # deleted on side 2
484 if src not in m1:
479 if src not in m1:
485 # renamed on side 1, deleted on side 2
480 # renamed on side 1, deleted on side 2
486 renamedelete[src] = dsts1
481 renamedelete[src] = dsts1
487 elif m2[src] != mb[src]:
482 elif m2[src] != mb[src]:
488 if not _related(c2[src], base[src]):
483 if not _related(c2[src], base[src]):
489 return
484 return
490 # modified on side 2
485 # modified on side 2
491 for dst in dsts1:
486 for dst in dsts1:
492 if dst not in m2:
487 if dst not in m2:
493 # dst not added on side 2 (handle as regular
488 # dst not added on side 2 (handle as regular
494 # "both created" case in manifestmerge otherwise)
489 # "both created" case in manifestmerge otherwise)
495 copy[dst] = src
490 copy[dst] = src
496
491
497 def _fullcopytracing(repo, c1, c2, base):
492 def _fullcopytracing(repo, c1, c2, base):
498 """ The full copytracing algorithm which finds all the new files that were
493 """ The full copytracing algorithm which finds all the new files that were
499 added from merge base up to the top commit and for each file it checks if
494 added from merge base up to the top commit and for each file it checks if
500 this file was copied from another file.
495 this file was copied from another file.
501
496
502 This is pretty slow when a lot of changesets are involved but will track all
497 This is pretty slow when a lot of changesets are involved but will track all
503 the copies.
498 the copies.
504 """
499 """
505 m1 = c1.manifest()
500 m1 = c1.manifest()
506 m2 = c2.manifest()
501 m2 = c2.manifest()
507 mb = base.manifest()
502 mb = base.manifest()
508
503
509 copies1 = pathcopies(base, c1)
504 copies1 = pathcopies(base, c1)
510 copies2 = pathcopies(base, c2)
505 copies2 = pathcopies(base, c2)
511
506
512 inversecopies1 = {}
507 inversecopies1 = {}
513 inversecopies2 = {}
508 inversecopies2 = {}
514 for dst, src in copies1.items():
509 for dst, src in copies1.items():
515 inversecopies1.setdefault(src, []).append(dst)
510 inversecopies1.setdefault(src, []).append(dst)
516 for dst, src in copies2.items():
511 for dst, src in copies2.items():
517 inversecopies2.setdefault(src, []).append(dst)
512 inversecopies2.setdefault(src, []).append(dst)
518
513
519 copy = {}
514 copy = {}
520 diverge = {}
515 diverge = {}
521 renamedelete = {}
516 renamedelete = {}
522 allsources = set(inversecopies1) | set(inversecopies2)
517 allsources = set(inversecopies1) | set(inversecopies2)
523 for src in allsources:
518 for src in allsources:
524 dsts1 = inversecopies1.get(src)
519 dsts1 = inversecopies1.get(src)
525 dsts2 = inversecopies2.get(src)
520 dsts2 = inversecopies2.get(src)
526 if dsts1 and dsts2:
521 if dsts1 and dsts2:
527 # copied/renamed on both sides
522 # copied/renamed on both sides
528 if src not in m1 and src not in m2:
523 if src not in m1 and src not in m2:
529 # renamed on both sides
524 # renamed on both sides
530 dsts1 = set(dsts1)
525 dsts1 = set(dsts1)
531 dsts2 = set(dsts2)
526 dsts2 = set(dsts2)
532 # If there's some overlap in the rename destinations, we
527 # If there's some overlap in the rename destinations, we
533 # consider it not divergent. For example, if side 1 copies 'a'
528 # consider it not divergent. For example, if side 1 copies 'a'
534 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
529 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
535 # and 'd' and deletes 'a'.
530 # and 'd' and deletes 'a'.
536 if dsts1 & dsts2:
531 if dsts1 & dsts2:
537 for dst in (dsts1 & dsts2):
532 for dst in (dsts1 & dsts2):
538 copy[dst] = src
533 copy[dst] = src
539 else:
534 else:
540 diverge[src] = sorted(dsts1 | dsts2)
535 diverge[src] = sorted(dsts1 | dsts2)
541 elif src in m1 and src in m2:
536 elif src in m1 and src in m2:
542 # copied on both sides
537 # copied on both sides
543 dsts1 = set(dsts1)
538 dsts1 = set(dsts1)
544 dsts2 = set(dsts2)
539 dsts2 = set(dsts2)
545 for dst in (dsts1 & dsts2):
540 for dst in (dsts1 & dsts2):
546 copy[dst] = src
541 copy[dst] = src
547 # TODO: Handle cases where it was renamed on one side and copied
542 # TODO: Handle cases where it was renamed on one side and copied
548 # on the other side
543 # on the other side
549 elif dsts1:
544 elif dsts1:
550 # copied/renamed only on side 1
545 # copied/renamed only on side 1
551 _checksinglesidecopies(src, dsts1, m1, m2, mb, c2, base,
546 _checksinglesidecopies(src, dsts1, m1, m2, mb, c2, base,
552 copy, renamedelete)
547 copy, renamedelete)
553 elif dsts2:
548 elif dsts2:
554 # copied/renamed only on side 2
549 # copied/renamed only on side 2
555 _checksinglesidecopies(src, dsts2, m2, m1, mb, c1, base,
550 _checksinglesidecopies(src, dsts2, m2, m1, mb, c1, base,
556 copy, renamedelete)
551 copy, renamedelete)
557
552
558 renamedeleteset = set()
553 renamedeleteset = set()
559 divergeset = set()
554 divergeset = set()
560 for dsts in diverge.values():
555 for dsts in diverge.values():
561 divergeset.update(dsts)
556 divergeset.update(dsts)
562 for dsts in renamedelete.values():
557 for dsts in renamedelete.values():
563 renamedeleteset.update(dsts)
558 renamedeleteset.update(dsts)
564
559
565 # find interesting file sets from manifests
560 # find interesting file sets from manifests
566 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
561 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
567 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
562 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
568 u1 = sorted(addedinm1 - addedinm2)
563 u1 = sorted(addedinm1 - addedinm2)
569 u2 = sorted(addedinm2 - addedinm1)
564 u2 = sorted(addedinm2 - addedinm1)
570
565
571 header = " unmatched files in %s"
566 header = " unmatched files in %s"
572 if u1:
567 if u1:
573 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
568 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
574 if u2:
569 if u2:
575 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
570 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
576
571
577 fullcopy = copies1.copy()
572 fullcopy = copies1.copy()
578 fullcopy.update(copies2)
573 fullcopy.update(copies2)
579 if not fullcopy:
574 if not fullcopy:
580 return copy, {}, diverge, renamedelete, {}
575 return copy, {}, diverge, renamedelete, {}
581
576
582 if repo.ui.debugflag:
577 if repo.ui.debugflag:
583 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
578 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
584 "% = renamed and deleted):\n")
579 "% = renamed and deleted):\n")
585 for f in sorted(fullcopy):
580 for f in sorted(fullcopy):
586 note = ""
581 note = ""
587 if f in copy:
582 if f in copy:
588 note += "*"
583 note += "*"
589 if f in divergeset:
584 if f in divergeset:
590 note += "!"
585 note += "!"
591 if f in renamedeleteset:
586 if f in renamedeleteset:
592 note += "%"
587 note += "%"
593 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
588 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
594 note))
589 note))
595 del divergeset
590 del divergeset
596
591
597 repo.ui.debug(" checking for directory renames\n")
592 repo.ui.debug(" checking for directory renames\n")
598
593
599 # generate a directory move map
594 # generate a directory move map
600 d1, d2 = c1.dirs(), c2.dirs()
595 d1, d2 = c1.dirs(), c2.dirs()
601 invalid = set()
596 invalid = set()
602 dirmove = {}
597 dirmove = {}
603
598
604 # examine each file copy for a potential directory move, which is
599 # examine each file copy for a potential directory move, which is
605 # when all the files in a directory are moved to a new directory
600 # when all the files in a directory are moved to a new directory
606 for dst, src in fullcopy.iteritems():
601 for dst, src in fullcopy.iteritems():
607 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
602 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
608 if dsrc in invalid:
603 if dsrc in invalid:
609 # already seen to be uninteresting
604 # already seen to be uninteresting
610 continue
605 continue
611 elif dsrc in d1 and ddst in d1:
606 elif dsrc in d1 and ddst in d1:
612 # directory wasn't entirely moved locally
607 # directory wasn't entirely moved locally
613 invalid.add(dsrc)
608 invalid.add(dsrc)
614 elif dsrc in d2 and ddst in d2:
609 elif dsrc in d2 and ddst in d2:
615 # directory wasn't entirely moved remotely
610 # directory wasn't entirely moved remotely
616 invalid.add(dsrc)
611 invalid.add(dsrc)
617 elif dsrc in dirmove and dirmove[dsrc] != ddst:
612 elif dsrc in dirmove and dirmove[dsrc] != ddst:
618 # files from the same directory moved to two different places
613 # files from the same directory moved to two different places
619 invalid.add(dsrc)
614 invalid.add(dsrc)
620 else:
615 else:
621 # looks good so far
616 # looks good so far
622 dirmove[dsrc] = ddst
617 dirmove[dsrc] = ddst
623
618
624 for i in invalid:
619 for i in invalid:
625 if i in dirmove:
620 if i in dirmove:
626 del dirmove[i]
621 del dirmove[i]
627 del d1, d2, invalid
622 del d1, d2, invalid
628
623
629 if not dirmove:
624 if not dirmove:
630 return copy, {}, diverge, renamedelete, {}
625 return copy, {}, diverge, renamedelete, {}
631
626
632 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
627 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
633
628
634 for d in dirmove:
629 for d in dirmove:
635 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
630 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
636 (d, dirmove[d]))
631 (d, dirmove[d]))
637
632
638 movewithdir = {}
633 movewithdir = {}
639 # check unaccounted nonoverlapping files against directory moves
634 # check unaccounted nonoverlapping files against directory moves
640 for f in u1 + u2:
635 for f in u1 + u2:
641 if f not in fullcopy:
636 if f not in fullcopy:
642 for d in dirmove:
637 for d in dirmove:
643 if f.startswith(d):
638 if f.startswith(d):
644 # new file added in a directory that was moved, move it
639 # new file added in a directory that was moved, move it
645 df = dirmove[d] + f[len(d):]
640 df = dirmove[d] + f[len(d):]
646 if df not in copy:
641 if df not in copy:
647 movewithdir[f] = df
642 movewithdir[f] = df
648 repo.ui.debug((" pending file src: '%s' -> "
643 repo.ui.debug((" pending file src: '%s' -> "
649 "dst: '%s'\n") % (f, df))
644 "dst: '%s'\n") % (f, df))
650 break
645 break
651
646
652 return copy, movewithdir, diverge, renamedelete, dirmove
647 return copy, movewithdir, diverge, renamedelete, dirmove
653
648
654 def _heuristicscopytracing(repo, c1, c2, base):
649 def _heuristicscopytracing(repo, c1, c2, base):
655 """ Fast copytracing using filename heuristics
650 """ Fast copytracing using filename heuristics
656
651
657 Assumes that moves or renames are of following two types:
652 Assumes that moves or renames are of following two types:
658
653
659 1) Inside a directory only (same directory name but different filenames)
654 1) Inside a directory only (same directory name but different filenames)
660 2) Move from one directory to another
655 2) Move from one directory to another
661 (same filenames but different directory names)
656 (same filenames but different directory names)
662
657
663 Works only when there are no merge commits in the "source branch".
658 Works only when there are no merge commits in the "source branch".
664 Source branch is commits from base up to c2 not including base.
659 Source branch is commits from base up to c2 not including base.
665
660
666 If merge is involved it fallbacks to _fullcopytracing().
661 If merge is involved it fallbacks to _fullcopytracing().
667
662
668 Can be used by setting the following config:
663 Can be used by setting the following config:
669
664
670 [experimental]
665 [experimental]
671 copytrace = heuristics
666 copytrace = heuristics
672
667
673 In some cases the copy/move candidates found by heuristics can be very large
668 In some cases the copy/move candidates found by heuristics can be very large
674 in number and that will make the algorithm slow. The number of possible
669 in number and that will make the algorithm slow. The number of possible
675 candidates to check can be limited by using the config
670 candidates to check can be limited by using the config
676 `experimental.copytrace.movecandidateslimit` which defaults to 100.
671 `experimental.copytrace.movecandidateslimit` which defaults to 100.
677 """
672 """
678
673
679 if c1.rev() is None:
674 if c1.rev() is None:
680 c1 = c1.p1()
675 c1 = c1.p1()
681 if c2.rev() is None:
676 if c2.rev() is None:
682 c2 = c2.p1()
677 c2 = c2.p1()
683
678
684 copies = {}
679 copies = {}
685
680
686 changedfiles = set()
681 changedfiles = set()
687 m1 = c1.manifest()
682 m1 = c1.manifest()
688 if not repo.revs('%d::%d', base.rev(), c2.rev()):
683 if not repo.revs('%d::%d', base.rev(), c2.rev()):
689 # If base is not in c2 branch, we switch to fullcopytracing
684 # If base is not in c2 branch, we switch to fullcopytracing
690 repo.ui.debug("switching to full copytracing as base is not "
685 repo.ui.debug("switching to full copytracing as base is not "
691 "an ancestor of c2\n")
686 "an ancestor of c2\n")
692 return _fullcopytracing(repo, c1, c2, base)
687 return _fullcopytracing(repo, c1, c2, base)
693
688
694 ctx = c2
689 ctx = c2
695 while ctx != base:
690 while ctx != base:
696 if len(ctx.parents()) == 2:
691 if len(ctx.parents()) == 2:
697 # To keep things simple let's not handle merges
692 # To keep things simple let's not handle merges
698 repo.ui.debug("switching to full copytracing because of merges\n")
693 repo.ui.debug("switching to full copytracing because of merges\n")
699 return _fullcopytracing(repo, c1, c2, base)
694 return _fullcopytracing(repo, c1, c2, base)
700 changedfiles.update(ctx.files())
695 changedfiles.update(ctx.files())
701 ctx = ctx.p1()
696 ctx = ctx.p1()
702
697
703 cp = _forwardcopies(base, c2)
698 cp = _forwardcopies(base, c2)
704 for dst, src in cp.iteritems():
699 for dst, src in cp.iteritems():
705 if src in m1:
700 if src in m1:
706 copies[dst] = src
701 copies[dst] = src
707
702
708 # file is missing if it isn't present in the destination, but is present in
703 # file is missing if it isn't present in the destination, but is present in
709 # the base and present in the source.
704 # the base and present in the source.
710 # Presence in the base is important to exclude added files, presence in the
705 # Presence in the base is important to exclude added files, presence in the
711 # source is important to exclude removed files.
706 # source is important to exclude removed files.
712 filt = lambda f: f not in m1 and f in base and f in c2
707 filt = lambda f: f not in m1 and f in base and f in c2
713 missingfiles = [f for f in changedfiles if filt(f)]
708 missingfiles = [f for f in changedfiles if filt(f)]
714
709
715 if missingfiles:
710 if missingfiles:
716 basenametofilename = collections.defaultdict(list)
711 basenametofilename = collections.defaultdict(list)
717 dirnametofilename = collections.defaultdict(list)
712 dirnametofilename = collections.defaultdict(list)
718
713
719 for f in m1.filesnotin(base.manifest()):
714 for f in m1.filesnotin(base.manifest()):
720 basename = os.path.basename(f)
715 basename = os.path.basename(f)
721 dirname = os.path.dirname(f)
716 dirname = os.path.dirname(f)
722 basenametofilename[basename].append(f)
717 basenametofilename[basename].append(f)
723 dirnametofilename[dirname].append(f)
718 dirnametofilename[dirname].append(f)
724
719
725 for f in missingfiles:
720 for f in missingfiles:
726 basename = os.path.basename(f)
721 basename = os.path.basename(f)
727 dirname = os.path.dirname(f)
722 dirname = os.path.dirname(f)
728 samebasename = basenametofilename[basename]
723 samebasename = basenametofilename[basename]
729 samedirname = dirnametofilename[dirname]
724 samedirname = dirnametofilename[dirname]
730 movecandidates = samebasename + samedirname
725 movecandidates = samebasename + samedirname
731 # f is guaranteed to be present in c2, that's why
726 # f is guaranteed to be present in c2, that's why
732 # c2.filectx(f) won't fail
727 # c2.filectx(f) won't fail
733 f2 = c2.filectx(f)
728 f2 = c2.filectx(f)
734 # we can have a lot of candidates which can slow down the heuristics
729 # we can have a lot of candidates which can slow down the heuristics
735 # config value to limit the number of candidates moves to check
730 # config value to limit the number of candidates moves to check
736 maxcandidates = repo.ui.configint('experimental',
731 maxcandidates = repo.ui.configint('experimental',
737 'copytrace.movecandidateslimit')
732 'copytrace.movecandidateslimit')
738
733
739 if len(movecandidates) > maxcandidates:
734 if len(movecandidates) > maxcandidates:
740 repo.ui.status(_("skipping copytracing for '%s', more "
735 repo.ui.status(_("skipping copytracing for '%s', more "
741 "candidates than the limit: %d\n")
736 "candidates than the limit: %d\n")
742 % (f, len(movecandidates)))
737 % (f, len(movecandidates)))
743 continue
738 continue
744
739
745 for candidate in movecandidates:
740 for candidate in movecandidates:
746 f1 = c1.filectx(candidate)
741 f1 = c1.filectx(candidate)
747 if _related(f1, f2):
742 if _related(f1, f2):
748 # if there are a few related copies then we'll merge
743 # if there are a few related copies then we'll merge
749 # changes into all of them. This matches the behaviour
744 # changes into all of them. This matches the behaviour
750 # of upstream copytracing
745 # of upstream copytracing
751 copies[candidate] = f
746 copies[candidate] = f
752
747
753 return copies, {}, {}, {}, {}
748 return copies, {}, {}, {}, {}
754
749
755 def _related(f1, f2):
750 def _related(f1, f2):
756 """return True if f1 and f2 filectx have a common ancestor
751 """return True if f1 and f2 filectx have a common ancestor
757
752
758 Walk back to common ancestor to see if the two files originate
753 Walk back to common ancestor to see if the two files originate
759 from the same file. Since workingfilectx's rev() is None it messes
754 from the same file. Since workingfilectx's rev() is None it messes
760 up the integer comparison logic, hence the pre-step check for
755 up the integer comparison logic, hence the pre-step check for
761 None (f1 and f2 can only be workingfilectx's initially).
756 None (f1 and f2 can only be workingfilectx's initially).
762 """
757 """
763
758
764 if f1 == f2:
759 if f1 == f2:
765 return True # a match
760 return True # a match
766
761
767 g1, g2 = f1.ancestors(), f2.ancestors()
762 g1, g2 = f1.ancestors(), f2.ancestors()
768 try:
763 try:
769 f1r, f2r = f1.linkrev(), f2.linkrev()
764 f1r, f2r = f1.linkrev(), f2.linkrev()
770
765
771 if f1r is None:
766 if f1r is None:
772 f1 = next(g1)
767 f1 = next(g1)
773 if f2r is None:
768 if f2r is None:
774 f2 = next(g2)
769 f2 = next(g2)
775
770
776 while True:
771 while True:
777 f1r, f2r = f1.linkrev(), f2.linkrev()
772 f1r, f2r = f1.linkrev(), f2.linkrev()
778 if f1r > f2r:
773 if f1r > f2r:
779 f1 = next(g1)
774 f1 = next(g1)
780 elif f2r > f1r:
775 elif f2r > f1r:
781 f2 = next(g2)
776 f2 = next(g2)
782 else: # f1 and f2 point to files in the same linkrev
777 else: # f1 and f2 point to files in the same linkrev
783 return f1 == f2 # true if they point to the same file
778 return f1 == f2 # true if they point to the same file
784 except StopIteration:
779 except StopIteration:
785 return False
780 return False
786
781
787 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
782 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
788 """reproduce copies from fromrev to rev in the dirstate
783 """reproduce copies from fromrev to rev in the dirstate
789
784
790 If skiprev is specified, it's a revision that should be used to
785 If skiprev is specified, it's a revision that should be used to
791 filter copy records. Any copies that occur between fromrev and
786 filter copy records. Any copies that occur between fromrev and
792 skiprev will not be duplicated, even if they appear in the set of
787 skiprev will not be duplicated, even if they appear in the set of
793 copies between fromrev and rev.
788 copies between fromrev and rev.
794 """
789 """
795 exclude = {}
790 exclude = {}
796 ctraceconfig = repo.ui.config('experimental', 'copytrace')
791 ctraceconfig = repo.ui.config('experimental', 'copytrace')
797 bctrace = stringutil.parsebool(ctraceconfig)
792 bctrace = stringutil.parsebool(ctraceconfig)
798 if (skiprev is not None and
793 if (skiprev is not None and
799 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
794 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
800 # copytrace='off' skips this line, but not the entire function because
795 # copytrace='off' skips this line, but not the entire function because
801 # the line below is O(size of the repo) during a rebase, while the rest
796 # the line below is O(size of the repo) during a rebase, while the rest
802 # of the function is much faster (and is required for carrying copy
797 # of the function is much faster (and is required for carrying copy
803 # metadata across the rebase anyway).
798 # metadata across the rebase anyway).
804 exclude = pathcopies(repo[fromrev], repo[skiprev])
799 exclude = pathcopies(repo[fromrev], repo[skiprev])
805 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
800 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
806 if dst in exclude:
801 if dst in exclude:
807 continue
802 continue
808 if dst in wctx:
803 if dst in wctx:
809 wctx[dst].markcopied(src)
804 wctx[dst].markcopied(src)
@@ -1,647 +1,652 b''
1 #testcases filelog compatibility changeset
1 #testcases filelog compatibility changeset
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 #if changeset
17 #if changeset
18 $ cat >> $HGRCPATH << EOF
18 $ cat >> $HGRCPATH << EOF
19 > [experimental]
19 > [experimental]
20 > copies.read-from = changeset-only
20 > copies.read-from = changeset-only
21 > copies.write-to = changeset-only
21 > copies.write-to = changeset-only
22 > EOF
22 > EOF
23 #endif
23 #endif
24
24
25 $ REPONUM=0
25 $ REPONUM=0
26 $ newrepo() {
26 $ newrepo() {
27 > cd $TESTTMP
27 > cd $TESTTMP
28 > REPONUM=`expr $REPONUM + 1`
28 > REPONUM=`expr $REPONUM + 1`
29 > hg init repo-$REPONUM
29 > hg init repo-$REPONUM
30 > cd repo-$REPONUM
30 > cd repo-$REPONUM
31 > }
31 > }
32
32
33 Simple rename case
33 Simple rename case
34 $ newrepo
34 $ newrepo
35 $ echo x > x
35 $ echo x > x
36 $ hg ci -Aqm 'add x'
36 $ hg ci -Aqm 'add x'
37 $ hg mv x y
37 $ hg mv x y
38 $ hg debugp1copies
38 $ hg debugp1copies
39 x -> y
39 x -> y
40 $ hg debugp2copies
40 $ hg debugp2copies
41 $ hg ci -m 'rename x to y'
41 $ hg ci -m 'rename x to y'
42 $ hg l
42 $ hg l
43 @ 1 rename x to y
43 @ 1 rename x to y
44 | x y
44 | x y
45 o 0 add x
45 o 0 add x
46 x
46 x
47 $ hg debugp1copies -r 1
47 $ hg debugp1copies -r 1
48 x -> y
48 x -> y
49 $ hg debugpathcopies 0 1
49 $ hg debugpathcopies 0 1
50 x -> y
50 x -> y
51 $ hg debugpathcopies 1 0
51 $ hg debugpathcopies 1 0
52 y -> x
52 y -> x
53 Test filtering copies by path. We do filtering by destination.
53 Test filtering copies by path. We do filtering by destination.
54 $ hg debugpathcopies 0 1 x
54 $ hg debugpathcopies 0 1 x
55 $ hg debugpathcopies 1 0 x
55 $ hg debugpathcopies 1 0 x
56 y -> x
56 y -> x
57 $ hg debugpathcopies 0 1 y
57 $ hg debugpathcopies 0 1 y
58 x -> y
58 x -> y
59 $ hg debugpathcopies 1 0 y
59 $ hg debugpathcopies 1 0 y
60
60
61 Copy a file onto another file
61 Copy a file onto another file
62 $ newrepo
62 $ newrepo
63 $ echo x > x
63 $ echo x > x
64 $ echo y > y
64 $ echo y > y
65 $ hg ci -Aqm 'add x and y'
65 $ hg ci -Aqm 'add x and y'
66 $ hg cp -f x y
66 $ hg cp -f x y
67 $ hg debugp1copies
67 $ hg debugp1copies
68 x -> y
68 x -> y
69 $ hg debugp2copies
69 $ hg debugp2copies
70 $ hg ci -m 'copy x onto y'
70 $ hg ci -m 'copy x onto y'
71 $ hg l
71 $ hg l
72 @ 1 copy x onto y
72 @ 1 copy x onto y
73 | y
73 | y
74 o 0 add x and y
74 o 0 add x and y
75 x y
75 x y
76 $ hg debugp1copies -r 1
76 $ hg debugp1copies -r 1
77 x -> y
77 x -> y
78 Incorrectly doesn't show the rename
78 Incorrectly doesn't show the rename
79 $ hg debugpathcopies 0 1
79 $ hg debugpathcopies 0 1
80
80
81 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
81 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
82 produce a new filelog entry. The changeset's "files" entry should still list the file.
82 produce a new filelog entry. The changeset's "files" entry should still list the file.
83 $ newrepo
83 $ newrepo
84 $ echo x > x
84 $ echo x > x
85 $ echo x > x2
85 $ echo x > x2
86 $ hg ci -Aqm 'add x and x2 with same content'
86 $ hg ci -Aqm 'add x and x2 with same content'
87 $ hg cp -f x x2
87 $ hg cp -f x x2
88 $ hg ci -m 'copy x onto x2'
88 $ hg ci -m 'copy x onto x2'
89 $ hg l
89 $ hg l
90 @ 1 copy x onto x2
90 @ 1 copy x onto x2
91 | x2
91 | x2
92 o 0 add x and x2 with same content
92 o 0 add x and x2 with same content
93 x x2
93 x x2
94 $ hg debugp1copies -r 1
94 $ hg debugp1copies -r 1
95 x -> x2
95 x -> x2
96 Incorrectly doesn't show the rename
96 Incorrectly doesn't show the rename
97 $ hg debugpathcopies 0 1
97 $ hg debugpathcopies 0 1
98
98
99 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
99 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
100 $ newrepo
100 $ newrepo
101 $ echo x > x
101 $ echo x > x
102 $ hg ci -Aqm 'add x'
102 $ hg ci -Aqm 'add x'
103 $ hg cp x y
103 $ hg cp x y
104 $ hg ci -m 'copy x to y'
104 $ hg ci -m 'copy x to y'
105 $ hg rm y
105 $ hg rm y
106 $ hg ci -m 'remove y'
106 $ hg ci -m 'remove y'
107 $ hg cp -f x y
107 $ hg cp -f x y
108 $ hg ci -m 'copy x onto y (again)'
108 $ hg ci -m 'copy x onto y (again)'
109 $ hg l
109 $ hg l
110 @ 3 copy x onto y (again)
110 @ 3 copy x onto y (again)
111 | y
111 | y
112 o 2 remove y
112 o 2 remove y
113 | y
113 | y
114 o 1 copy x to y
114 o 1 copy x to y
115 | y
115 | y
116 o 0 add x
116 o 0 add x
117 x
117 x
118 $ hg debugp1copies -r 3
118 $ hg debugp1copies -r 3
119 x -> y
119 x -> y
120 $ hg debugpathcopies 0 3
120 $ hg debugpathcopies 0 3
121 x -> y
121 x -> y
122
122
123 Rename file in a loop: x->y->z->x
123 Rename file in a loop: x->y->z->x
124 $ newrepo
124 $ newrepo
125 $ echo x > x
125 $ echo x > x
126 $ hg ci -Aqm 'add x'
126 $ hg ci -Aqm 'add x'
127 $ hg mv x y
127 $ hg mv x y
128 $ hg debugp1copies
128 $ hg debugp1copies
129 x -> y
129 x -> y
130 $ hg debugp2copies
130 $ hg debugp2copies
131 $ hg ci -m 'rename x to y'
131 $ hg ci -m 'rename x to y'
132 $ hg mv y z
132 $ hg mv y z
133 $ hg ci -m 'rename y to z'
133 $ hg ci -m 'rename y to z'
134 $ hg mv z x
134 $ hg mv z x
135 $ hg ci -m 'rename z to x'
135 $ hg ci -m 'rename z to x'
136 $ hg l
136 $ hg l
137 @ 3 rename z to x
137 @ 3 rename z to x
138 | x z
138 | x z
139 o 2 rename y to z
139 o 2 rename y to z
140 | y z
140 | y z
141 o 1 rename x to y
141 o 1 rename x to y
142 | x y
142 | x y
143 o 0 add x
143 o 0 add x
144 x
144 x
145 $ hg debugpathcopies 0 3
145 $ hg debugpathcopies 0 3
146
146
147 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
147 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
148 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
148 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
149 $ newrepo
149 $ newrepo
150 $ echo x > x
150 $ echo x > x
151 $ hg ci -Aqm 'add x'
151 $ hg ci -Aqm 'add x'
152 $ hg mv x y
152 $ hg mv x y
153 $ hg ci -m 'rename x to y'
153 $ hg ci -m 'rename x to y'
154 $ hg rm y
154 $ hg rm y
155 $ hg ci -qm 'remove y'
155 $ hg ci -qm 'remove y'
156 $ echo x > y
156 $ echo x > y
157 $ hg ci -Aqm 'add back y'
157 $ hg ci -Aqm 'add back y'
158 $ hg l
158 $ hg l
159 @ 3 add back y
159 @ 3 add back y
160 | y
160 | y
161 o 2 remove y
161 o 2 remove y
162 | y
162 | y
163 o 1 rename x to y
163 o 1 rename x to y
164 | x y
164 | x y
165 o 0 add x
165 o 0 add x
166 x
166 x
167 $ hg debugp1copies -r 3
167 $ hg debugp1copies -r 3
168 $ hg debugpathcopies 0 3
168 $ hg debugpathcopies 0 3
169
169
170 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
170 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
171 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
171 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
172 to the first commit that added the file. We should still report the copy as being from x2.
172 to the first commit that added the file. We should still report the copy as being from x2.
173 $ newrepo
173 $ newrepo
174 $ echo x > x
174 $ echo x > x
175 $ echo x > x2
175 $ echo x > x2
176 $ hg ci -Aqm 'add x and x2 with same content'
176 $ hg ci -Aqm 'add x and x2 with same content'
177 $ hg cp x z
177 $ hg cp x z
178 $ hg ci -qm 'copy x to z'
178 $ hg ci -qm 'copy x to z'
179 $ hg rm z
179 $ hg rm z
180 $ hg ci -m 'remove z'
180 $ hg ci -m 'remove z'
181 $ hg cp x2 z
181 $ hg cp x2 z
182 $ hg ci -m 'copy x2 to z'
182 $ hg ci -m 'copy x2 to z'
183 $ hg l
183 $ hg l
184 @ 3 copy x2 to z
184 @ 3 copy x2 to z
185 | z
185 | z
186 o 2 remove z
186 o 2 remove z
187 | z
187 | z
188 o 1 copy x to z
188 o 1 copy x to z
189 | z
189 | z
190 o 0 add x and x2 with same content
190 o 0 add x and x2 with same content
191 x x2
191 x x2
192 $ hg debugp1copies -r 3
192 $ hg debugp1copies -r 3
193 x2 -> z
193 x2 -> z
194 $ hg debugpathcopies 0 3
194 $ hg debugpathcopies 0 3
195 x2 -> z
195 x2 -> z
196
196
197 Create x and y, then rename them both to the same name, but on different sides of a fork
197 Create x and y, then rename them both to the same name, but on different sides of a fork
198 $ newrepo
198 $ newrepo
199 $ echo x > x
199 $ echo x > x
200 $ echo y > y
200 $ echo y > y
201 $ hg ci -Aqm 'add x and y'
201 $ hg ci -Aqm 'add x and y'
202 $ hg mv x z
202 $ hg mv x z
203 $ hg ci -qm 'rename x to z'
203 $ hg ci -qm 'rename x to z'
204 $ hg co -q 0
204 $ hg co -q 0
205 $ hg mv y z
205 $ hg mv y z
206 $ hg ci -qm 'rename y to z'
206 $ hg ci -qm 'rename y to z'
207 $ hg l
207 $ hg l
208 @ 2 rename y to z
208 @ 2 rename y to z
209 | y z
209 | y z
210 | o 1 rename x to z
210 | o 1 rename x to z
211 |/ x z
211 |/ x z
212 o 0 add x and y
212 o 0 add x and y
213 x y
213 x y
214 $ hg debugpathcopies 1 2
214 $ hg debugpathcopies 1 2
215 z -> x
215 z -> x
216 y -> z
216 y -> z
217
217
218 Fork renames x to y on one side and removes x on the other
218 Fork renames x to y on one side and removes x on the other
219 $ newrepo
219 $ newrepo
220 $ echo x > x
220 $ echo x > x
221 $ hg ci -Aqm 'add x'
221 $ hg ci -Aqm 'add x'
222 $ hg mv x y
222 $ hg mv x y
223 $ hg ci -m 'rename x to y'
223 $ hg ci -m 'rename x to y'
224 $ hg co -q 0
224 $ hg co -q 0
225 $ hg rm x
225 $ hg rm x
226 $ hg ci -m 'remove x'
226 $ hg ci -m 'remove x'
227 created new head
227 created new head
228 $ hg l
228 $ hg l
229 @ 2 remove x
229 @ 2 remove x
230 | x
230 | x
231 | o 1 rename x to y
231 | o 1 rename x to y
232 |/ x y
232 |/ x y
233 o 0 add x
233 o 0 add x
234 x
234 x
235 $ hg debugpathcopies 1 2
235 $ hg debugpathcopies 1 2
236
236
237 Copies via null revision (there shouldn't be any)
237 Copies via null revision (there shouldn't be any)
238 $ newrepo
238 $ newrepo
239 $ echo x > x
239 $ echo x > x
240 $ hg ci -Aqm 'add x'
240 $ hg ci -Aqm 'add x'
241 $ hg cp x y
241 $ hg cp x y
242 $ hg ci -m 'copy x to y'
242 $ hg ci -m 'copy x to y'
243 $ hg co -q null
243 $ hg co -q null
244 $ echo x > x
244 $ echo x > x
245 $ hg ci -Aqm 'add x (again)'
245 $ hg ci -Aqm 'add x (again)'
246 $ hg l
246 $ hg l
247 @ 2 add x (again)
247 @ 2 add x (again)
248 x
248 x
249 o 1 copy x to y
249 o 1 copy x to y
250 | y
250 | y
251 o 0 add x
251 o 0 add x
252 x
252 x
253 $ hg debugpathcopies 1 2
253 $ hg debugpathcopies 1 2
254 $ hg debugpathcopies 2 1
254 $ hg debugpathcopies 2 1
255
255
256 Merge rename from other branch
256 Merge rename from other branch
257 $ newrepo
257 $ newrepo
258 $ echo x > x
258 $ echo x > x
259 $ hg ci -Aqm 'add x'
259 $ hg ci -Aqm 'add x'
260 $ hg mv x y
260 $ hg mv x y
261 $ hg ci -m 'rename x to y'
261 $ hg ci -m 'rename x to y'
262 $ hg co -q 0
262 $ hg co -q 0
263 $ echo z > z
263 $ echo z > z
264 $ hg ci -Aqm 'add z'
264 $ hg ci -Aqm 'add z'
265 $ hg merge -q 1
265 $ hg merge -q 1
266 $ hg debugp1copies
266 $ hg debugp1copies
267 $ hg debugp2copies
267 $ hg debugp2copies
268 $ hg ci -m 'merge rename from p2'
268 $ hg ci -m 'merge rename from p2'
269 $ hg l
269 $ hg l
270 @ 3 merge rename from p2
270 @ 3 merge rename from p2
271 |\ x
271 |\ x
272 | o 2 add z
272 | o 2 add z
273 | | z
273 | | z
274 o | 1 rename x to y
274 o | 1 rename x to y
275 |/ x y
275 |/ x y
276 o 0 add x
276 o 0 add x
277 x
277 x
278 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
278 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
279 merges, so...
279 merges, so...
280 $ hg debugp1copies -r 3
280 $ hg debugp1copies -r 3
281 $ hg debugp2copies -r 3
281 $ hg debugp2copies -r 3
282 $ hg debugpathcopies 0 3
282 $ hg debugpathcopies 0 3
283 x -> y
283 x -> y
284 $ hg debugpathcopies 1 2
284 $ hg debugpathcopies 1 2
285 y -> x
285 y -> x
286 $ hg debugpathcopies 1 3
286 $ hg debugpathcopies 1 3
287 $ hg debugpathcopies 2 3
287 $ hg debugpathcopies 2 3
288 x -> y
288 x -> y
289
289
290 Copy file from either side in a merge
290 Copy file from either side in a merge
291 $ newrepo
291 $ newrepo
292 $ echo x > x
292 $ echo x > x
293 $ hg ci -Aqm 'add x'
293 $ hg ci -Aqm 'add x'
294 $ hg co -q null
294 $ hg co -q null
295 $ echo y > y
295 $ echo y > y
296 $ hg ci -Aqm 'add y'
296 $ hg ci -Aqm 'add y'
297 $ hg merge -q 0
297 $ hg merge -q 0
298 $ hg cp y z
298 $ hg cp y z
299 $ hg debugp1copies
299 $ hg debugp1copies
300 y -> z
300 y -> z
301 $ hg debugp2copies
301 $ hg debugp2copies
302 $ hg ci -m 'copy file from p1 in merge'
302 $ hg ci -m 'copy file from p1 in merge'
303 $ hg co -q 1
303 $ hg co -q 1
304 $ hg merge -q 0
304 $ hg merge -q 0
305 $ hg cp x z
305 $ hg cp x z
306 $ hg debugp1copies
306 $ hg debugp1copies
307 $ hg debugp2copies
307 $ hg debugp2copies
308 x -> z
308 x -> z
309 $ hg ci -qm 'copy file from p2 in merge'
309 $ hg ci -qm 'copy file from p2 in merge'
310 $ hg l
310 $ hg l
311 @ 3 copy file from p2 in merge
311 @ 3 copy file from p2 in merge
312 |\ z
312 |\ z
313 +---o 2 copy file from p1 in merge
313 +---o 2 copy file from p1 in merge
314 | |/ z
314 | |/ z
315 | o 1 add y
315 | o 1 add y
316 | y
316 | y
317 o 0 add x
317 o 0 add x
318 x
318 x
319 $ hg debugp1copies -r 2
319 $ hg debugp1copies -r 2
320 y -> z
320 y -> z
321 $ hg debugp2copies -r 2
321 $ hg debugp2copies -r 2
322 $ hg debugpathcopies 1 2
322 $ hg debugpathcopies 1 2
323 y -> z
323 y -> z
324 $ hg debugpathcopies 0 2
324 $ hg debugpathcopies 0 2
325 $ hg debugp1copies -r 3
325 $ hg debugp1copies -r 3
326 $ hg debugp2copies -r 3
326 $ hg debugp2copies -r 3
327 x -> z
327 x -> z
328 $ hg debugpathcopies 1 3
328 $ hg debugpathcopies 1 3
329 $ hg debugpathcopies 0 3
329 $ hg debugpathcopies 0 3
330 x -> z
330 x -> z
331
331
332 Copy file that exists on both sides of the merge, same content on both sides
332 Copy file that exists on both sides of the merge, same content on both sides
333 $ newrepo
333 $ newrepo
334 $ echo x > x
334 $ echo x > x
335 $ hg ci -Aqm 'add x on branch 1'
335 $ hg ci -Aqm 'add x on branch 1'
336 $ hg co -q null
336 $ hg co -q null
337 $ echo x > x
337 $ echo x > x
338 $ hg ci -Aqm 'add x on branch 2'
338 $ hg ci -Aqm 'add x on branch 2'
339 $ hg merge -q 0
339 $ hg merge -q 0
340 $ hg cp x z
340 $ hg cp x z
341 $ hg debugp1copies
341 $ hg debugp1copies
342 x -> z
342 x -> z
343 $ hg debugp2copies
343 $ hg debugp2copies
344 $ hg ci -qm 'merge'
344 $ hg ci -qm 'merge'
345 $ hg l
345 $ hg l
346 @ 2 merge
346 @ 2 merge
347 |\ z
347 |\ z
348 | o 1 add x on branch 2
348 | o 1 add x on branch 2
349 | x
349 | x
350 o 0 add x on branch 1
350 o 0 add x on branch 1
351 x
351 x
352 $ hg debugp1copies -r 2
352 $ hg debugp1copies -r 2
353 x -> z
353 x -> z
354 $ hg debugp2copies -r 2
354 $ hg debugp2copies -r 2
355 It's a little weird that it shows up on both sides
355 It's a little weird that it shows up on both sides
356 $ hg debugpathcopies 1 2
356 $ hg debugpathcopies 1 2
357 x -> z
357 x -> z
358 $ hg debugpathcopies 0 2
358 $ hg debugpathcopies 0 2
359 x -> z (filelog !)
359 x -> z (filelog !)
360
360
361 Copy file that exists on both sides of the merge, different content
361 Copy file that exists on both sides of the merge, different content
362 $ newrepo
362 $ newrepo
363 $ echo branch1 > x
363 $ echo branch1 > x
364 $ hg ci -Aqm 'add x on branch 1'
364 $ hg ci -Aqm 'add x on branch 1'
365 $ hg co -q null
365 $ hg co -q null
366 $ echo branch2 > x
366 $ echo branch2 > x
367 $ hg ci -Aqm 'add x on branch 2'
367 $ hg ci -Aqm 'add x on branch 2'
368 $ hg merge -q 0
368 $ hg merge -q 0
369 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
369 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
370 [1]
370 [1]
371 $ echo resolved > x
371 $ echo resolved > x
372 $ hg resolve -m x
372 $ hg resolve -m x
373 (no more unresolved files)
373 (no more unresolved files)
374 $ hg cp x z
374 $ hg cp x z
375 $ hg debugp1copies
375 $ hg debugp1copies
376 x -> z
376 x -> z
377 $ hg debugp2copies
377 $ hg debugp2copies
378 $ hg ci -qm 'merge'
378 $ hg ci -qm 'merge'
379 $ hg l
379 $ hg l
380 @ 2 merge
380 @ 2 merge
381 |\ x z
381 |\ x z
382 | o 1 add x on branch 2
382 | o 1 add x on branch 2
383 | x
383 | x
384 o 0 add x on branch 1
384 o 0 add x on branch 1
385 x
385 x
386 $ hg debugp1copies -r 2
386 $ hg debugp1copies -r 2
387 x -> z (changeset !)
387 x -> z (changeset !)
388 $ hg debugp2copies -r 2
388 $ hg debugp2copies -r 2
389 x -> z (no-changeset !)
389 x -> z (no-changeset !)
390 $ hg debugpathcopies 1 2
390 $ hg debugpathcopies 1 2
391 x -> z (changeset !)
391 x -> z (changeset !)
392 $ hg debugpathcopies 0 2
392 $ hg debugpathcopies 0 2
393 x -> z (no-changeset !)
393 x -> z (no-changeset !)
394
394
395 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
395 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
396 of the merge to the merge should include the copy from the other side.
396 of the merge to the merge should include the copy from the other side.
397 $ newrepo
397 $ newrepo
398 $ echo x > x
398 $ echo x > x
399 $ hg ci -Aqm 'add x'
399 $ hg ci -Aqm 'add x'
400 $ hg cp x y
400 $ hg cp x y
401 $ hg ci -qm 'copy x to y'
401 $ hg ci -qm 'copy x to y'
402 $ hg co -q 0
402 $ hg co -q 0
403 $ hg cp x z
403 $ hg cp x z
404 $ hg ci -qm 'copy x to z'
404 $ hg ci -qm 'copy x to z'
405 $ hg merge -q 1
405 $ hg merge -q 1
406 $ hg ci -m 'merge copy x->y and copy x->z'
406 $ hg ci -m 'merge copy x->y and copy x->z'
407 $ hg l
407 $ hg l
408 @ 3 merge copy x->y and copy x->z
408 @ 3 merge copy x->y and copy x->z
409 |\
409 |\
410 | o 2 copy x to z
410 | o 2 copy x to z
411 | | z
411 | | z
412 o | 1 copy x to y
412 o | 1 copy x to y
413 |/ y
413 |/ y
414 o 0 add x
414 o 0 add x
415 x
415 x
416 $ hg debugp1copies -r 3
416 $ hg debugp1copies -r 3
417 $ hg debugp2copies -r 3
417 $ hg debugp2copies -r 3
418 $ hg debugpathcopies 2 3
418 $ hg debugpathcopies 2 3
419 x -> y
419 x -> y
420 $ hg debugpathcopies 1 3
420 $ hg debugpathcopies 1 3
421 x -> z
421 x -> z
422
422
423 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
423 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
424 first side should not include the y->z rename since y didn't exist in the merge base.
424 first side should not include the y->z rename since y didn't exist in the merge base.
425 $ newrepo
425 $ newrepo
426 $ echo x > x
426 $ echo x > x
427 $ hg ci -Aqm 'add x'
427 $ hg ci -Aqm 'add x'
428 $ hg cp x y
428 $ hg cp x y
429 $ hg ci -qm 'copy x to y'
429 $ hg ci -qm 'copy x to y'
430 $ hg co -q 0
430 $ hg co -q 0
431 $ echo y > y
431 $ echo y > y
432 $ hg ci -Aqm 'add y'
432 $ hg ci -Aqm 'add y'
433 $ hg mv y z
433 $ hg mv y z
434 $ hg ci -m 'rename y to z'
434 $ hg ci -m 'rename y to z'
435 $ hg merge -q 1
435 $ hg merge -q 1
436 $ hg ci -m 'merge'
436 $ hg ci -m 'merge'
437 $ hg l
437 $ hg l
438 @ 4 merge
438 @ 4 merge
439 |\
439 |\
440 | o 3 rename y to z
440 | o 3 rename y to z
441 | | y z
441 | | y z
442 | o 2 add y
442 | o 2 add y
443 | | y
443 | | y
444 o | 1 copy x to y
444 o | 1 copy x to y
445 |/ y
445 |/ y
446 o 0 add x
446 o 0 add x
447 x
447 x
448 $ hg debugp1copies -r 3
448 $ hg debugp1copies -r 3
449 y -> z
449 y -> z
450 $ hg debugp2copies -r 3
450 $ hg debugp2copies -r 3
451 $ hg debugpathcopies 2 3
451 $ hg debugpathcopies 2 3
452 y -> z
452 y -> z
453 $ hg debugpathcopies 1 3
453 $ hg debugpathcopies 1 3
454
454
455 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
455 Create x and y, then rename x to z on one side of merge, and rename y to z and
456 other side.
456 modify z on the other side. When storing copies in the changeset, we don't
457 filter out copies whose target was created on the other side of the merge.
457 $ newrepo
458 $ newrepo
458 $ echo x > x
459 $ echo x > x
459 $ echo y > y
460 $ echo y > y
460 $ hg ci -Aqm 'add x and y'
461 $ hg ci -Aqm 'add x and y'
461 $ hg mv x z
462 $ hg mv x z
462 $ hg ci -qm 'rename x to z'
463 $ hg ci -qm 'rename x to z'
463 $ hg co -q 0
464 $ hg co -q 0
464 $ hg mv y z
465 $ hg mv y z
465 $ hg ci -qm 'rename y to z'
466 $ hg ci -qm 'rename y to z'
466 $ echo z >> z
467 $ echo z >> z
467 $ hg ci -m 'modify z'
468 $ hg ci -m 'modify z'
468 $ hg merge -q 1
469 $ hg merge -q 1
469 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
470 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
470 [1]
471 [1]
471 $ echo z > z
472 $ echo z > z
472 $ hg resolve -qm z
473 $ hg resolve -qm z
473 $ hg ci -m 'merge 1 into 3'
474 $ hg ci -m 'merge 1 into 3'
474 Try merging the other direction too
475 Try merging the other direction too
475 $ hg co -q 1
476 $ hg co -q 1
476 $ hg merge -q 3
477 $ hg merge -q 3
477 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
478 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
478 [1]
479 [1]
479 $ echo z > z
480 $ echo z > z
480 $ hg resolve -qm z
481 $ hg resolve -qm z
481 $ hg ci -m 'merge 3 into 1'
482 $ hg ci -m 'merge 3 into 1'
482 created new head
483 created new head
483 $ hg l
484 $ hg l
484 @ 5 merge 3 into 1
485 @ 5 merge 3 into 1
485 |\ y z
486 |\ y z
486 +---o 4 merge 1 into 3
487 +---o 4 merge 1 into 3
487 | |/ x z
488 | |/ x z
488 | o 3 modify z
489 | o 3 modify z
489 | | z
490 | | z
490 | o 2 rename y to z
491 | o 2 rename y to z
491 | | y z
492 | | y z
492 o | 1 rename x to z
493 o | 1 rename x to z
493 |/ x z
494 |/ x z
494 o 0 add x and y
495 o 0 add x and y
495 x y
496 x y
496 $ hg debugpathcopies 1 4
497 $ hg debugpathcopies 1 4
498 y -> z (no-filelog !)
497 $ hg debugpathcopies 2 4
499 $ hg debugpathcopies 2 4
500 x -> z (no-filelog !)
498 $ hg debugpathcopies 0 4
501 $ hg debugpathcopies 0 4
499 x -> z (filelog !)
502 x -> z (filelog !)
500 y -> z (compatibility !)
503 y -> z (compatibility !)
501 $ hg debugpathcopies 1 5
504 $ hg debugpathcopies 1 5
505 y -> z (no-filelog !)
502 $ hg debugpathcopies 2 5
506 $ hg debugpathcopies 2 5
507 x -> z (no-filelog !)
503 $ hg debugpathcopies 0 5
508 $ hg debugpathcopies 0 5
504 x -> z
509 x -> z
505
510
506
511
507 Test for a case in fullcopytracing algorithm where neither of the merging csets
512 Test for a case in fullcopytracing algorithm where neither of the merging csets
508 is a descendant of the merge base. This test reflects that the algorithm
513 is a descendant of the merge base. This test reflects that the algorithm
509 correctly finds the copies:
514 correctly finds the copies:
510
515
511 $ cat >> $HGRCPATH << EOF
516 $ cat >> $HGRCPATH << EOF
512 > [experimental]
517 > [experimental]
513 > evolution.createmarkers=True
518 > evolution.createmarkers=True
514 > evolution.allowunstable=True
519 > evolution.allowunstable=True
515 > EOF
520 > EOF
516
521
517 $ newrepo
522 $ newrepo
518 $ echo a > a
523 $ echo a > a
519 $ hg add a
524 $ hg add a
520 $ hg ci -m "added a"
525 $ hg ci -m "added a"
521 $ echo b > b
526 $ echo b > b
522 $ hg add b
527 $ hg add b
523 $ hg ci -m "added b"
528 $ hg ci -m "added b"
524
529
525 $ hg mv b b1
530 $ hg mv b b1
526 $ hg ci -m "rename b to b1"
531 $ hg ci -m "rename b to b1"
527
532
528 $ hg up ".^"
533 $ hg up ".^"
529 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
534 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
530 $ echo d > d
535 $ echo d > d
531 $ hg add d
536 $ hg add d
532 $ hg ci -m "added d"
537 $ hg ci -m "added d"
533 created new head
538 created new head
534
539
535 $ echo baba >> b
540 $ echo baba >> b
536 $ hg ci --amend -m "added d, modified b"
541 $ hg ci --amend -m "added d, modified b"
537
542
538 $ hg l --hidden
543 $ hg l --hidden
539 @ 4 added d, modified b
544 @ 4 added d, modified b
540 | b d
545 | b d
541 | x 3 added d
546 | x 3 added d
542 |/ d
547 |/ d
543 | o 2 rename b to b1
548 | o 2 rename b to b1
544 |/ b b1
549 |/ b b1
545 o 1 added b
550 o 1 added b
546 | b
551 | b
547 o 0 added a
552 o 0 added a
548 a
553 a
549
554
550 Grafting revision 4 on top of revision 2, showing that it respect the rename:
555 Grafting revision 4 on top of revision 2, showing that it respect the rename:
551
556
552 $ hg up 2 -q
557 $ hg up 2 -q
553 $ hg graft -r 4 --base 3 --hidden
558 $ hg graft -r 4 --base 3 --hidden
554 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
559 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
555 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
560 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
556 merging b1 and b to b1
561 merging b1 and b to b1
557
562
558 $ hg l -l1 -p
563 $ hg l -l1 -p
559 @ 5 added d, modified b
564 @ 5 added d, modified b
560 | b1
565 | b1
561 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
566 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
562 ~ diff -r 0a0ed3b3251c -r d544fb655520 b1 (changeset !)
567 ~ diff -r 0a0ed3b3251c -r d544fb655520 b1 (changeset !)
563 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
568 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
564 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
569 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
565 @@ -1,1 +1,2 @@
570 @@ -1,1 +1,2 @@
566 b
571 b
567 +baba
572 +baba
568
573
569 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
574 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
570 merging csets is a descendant of the base.
575 merging csets is a descendant of the base.
571 -------------------------------------------------------------------------------------------------
576 -------------------------------------------------------------------------------------------------
572
577
573 $ newrepo
578 $ newrepo
574 $ echo a > a
579 $ echo a > a
575 $ hg add a
580 $ hg add a
576 $ hg ci -m "added a"
581 $ hg ci -m "added a"
577 $ echo b > b
582 $ echo b > b
578 $ hg add b
583 $ hg add b
579 $ hg ci -m "added b"
584 $ hg ci -m "added b"
580
585
581 $ echo foobar > willconflict
586 $ echo foobar > willconflict
582 $ hg add willconflict
587 $ hg add willconflict
583 $ hg ci -m "added willconflict"
588 $ hg ci -m "added willconflict"
584 $ echo c > c
589 $ echo c > c
585 $ hg add c
590 $ hg add c
586 $ hg ci -m "added c"
591 $ hg ci -m "added c"
587
592
588 $ hg l
593 $ hg l
589 @ 3 added c
594 @ 3 added c
590 | c
595 | c
591 o 2 added willconflict
596 o 2 added willconflict
592 | willconflict
597 | willconflict
593 o 1 added b
598 o 1 added b
594 | b
599 | b
595 o 0 added a
600 o 0 added a
596 a
601 a
597
602
598 $ hg up ".^^"
603 $ hg up ".^^"
599 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
604 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
600 $ echo d > d
605 $ echo d > d
601 $ hg add d
606 $ hg add d
602 $ hg ci -m "added d"
607 $ hg ci -m "added d"
603 created new head
608 created new head
604
609
605 $ echo barfoo > willconflict
610 $ echo barfoo > willconflict
606 $ hg add willconflict
611 $ hg add willconflict
607 $ hg ci --amend -m "added willconflict and d"
612 $ hg ci --amend -m "added willconflict and d"
608
613
609 $ hg l
614 $ hg l
610 @ 5 added willconflict and d
615 @ 5 added willconflict and d
611 | d willconflict
616 | d willconflict
612 | o 3 added c
617 | o 3 added c
613 | | c
618 | | c
614 | o 2 added willconflict
619 | o 2 added willconflict
615 |/ willconflict
620 |/ willconflict
616 o 1 added b
621 o 1 added b
617 | b
622 | b
618 o 0 added a
623 o 0 added a
619 a
624 a
620
625
621 $ hg rebase -r . -d 2 -t :other
626 $ hg rebase -r . -d 2 -t :other
622 rebasing 5:5018b1509e94 "added willconflict and d" (tip) (no-changeset !)
627 rebasing 5:5018b1509e94 "added willconflict and d" (tip) (no-changeset !)
623 rebasing 5:af8d273bf580 "added willconflict and d" (tip) (changeset !)
628 rebasing 5:af8d273bf580 "added willconflict and d" (tip) (changeset !)
624
629
625 $ hg up 3 -q
630 $ hg up 3 -q
626 $ hg l --hidden
631 $ hg l --hidden
627 o 6 added willconflict and d
632 o 6 added willconflict and d
628 | d willconflict
633 | d willconflict
629 | x 5 added willconflict and d
634 | x 5 added willconflict and d
630 | | d willconflict
635 | | d willconflict
631 | | x 4 added d
636 | | x 4 added d
632 | |/ d
637 | |/ d
633 +---@ 3 added c
638 +---@ 3 added c
634 | | c
639 | | c
635 o | 2 added willconflict
640 o | 2 added willconflict
636 |/ willconflict
641 |/ willconflict
637 o 1 added b
642 o 1 added b
638 | b
643 | b
639 o 0 added a
644 o 0 added a
640 a
645 a
641
646
642 Now if we trigger a merge between revision 3 and 6 using base revision 4,
647 Now if we trigger a merge between revision 3 and 6 using base revision 4,
643 neither of the merging csets will be a descendant of the base revision:
648 neither of the merging csets will be a descendant of the base revision:
644
649
645 $ hg graft -r 6 --base 4 --hidden -t :other
650 $ hg graft -r 6 --base 4 --hidden -t :other
646 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
651 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
647 grafting 6:b19f0df72728 "added willconflict and d" (tip) (changeset !)
652 grafting 6:b19f0df72728 "added willconflict and d" (tip) (changeset !)
General Comments 0
You need to be logged in to leave comments. Login now