##// END OF EJS Templates
copies: simplify the conditional for _filter's case 3...
marmoute -
r47128:1d6d1a15 default
parent child Browse files
Show More
@@ -1,1231 +1,1230
1 # coding: utf8
1 # coding: utf8
2 # copies.py - copy detection for Mercurial
2 # copies.py - copy detection for Mercurial
3 #
3 #
4 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2008 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import collections
11 import collections
12 import os
12 import os
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 nullid,
16 nullid,
17 nullrev,
17 nullrev,
18 )
18 )
19
19
20 from . import (
20 from . import (
21 match as matchmod,
21 match as matchmod,
22 pathutil,
22 pathutil,
23 policy,
23 policy,
24 pycompat,
24 pycompat,
25 util,
25 util,
26 )
26 )
27
27
28
28
29 from .utils import stringutil
29 from .utils import stringutil
30
30
31 from .revlogutils import (
31 from .revlogutils import (
32 flagutil,
32 flagutil,
33 sidedata as sidedatamod,
33 sidedata as sidedatamod,
34 )
34 )
35
35
36 rustmod = policy.importrust("copy_tracing")
36 rustmod = policy.importrust("copy_tracing")
37
37
38
38
39 def _filter(src, dst, t):
39 def _filter(src, dst, t):
40 """filters out invalid copies after chaining"""
40 """filters out invalid copies after chaining"""
41
41
42 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
42 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
43 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
43 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
44 # in the following table (not including trivial cases). For example, case 6
44 # in the following table (not including trivial cases). For example, case 6
45 # is where a file existed in 'src' and remained under that name in 'mid' and
45 # is where a file existed in 'src' and remained under that name in 'mid' and
46 # then was renamed between 'mid' and 'dst'.
46 # then was renamed between 'mid' and 'dst'.
47 #
47 #
48 # case src mid dst result
48 # case src mid dst result
49 # 1 x y - -
49 # 1 x y - -
50 # 2 x y y x->y
50 # 2 x y y x->y
51 # 3 x y x -
51 # 3 x y x -
52 # 4 x y z x->z
52 # 4 x y z x->z
53 # 5 - x y -
53 # 5 - x y -
54 # 6 x x y x->y
54 # 6 x x y x->y
55 #
55 #
56 # _chain() takes care of chaining the copies in 'a' and 'b', but it
56 # _chain() takes care of chaining the copies in 'a' and 'b', but it
57 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
57 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
58 # between 5 and 6, so it includes all cases in its result.
58 # between 5 and 6, so it includes all cases in its result.
59 # Cases 1, 3, and 5 are then removed by _filter().
59 # Cases 1, 3, and 5 are then removed by _filter().
60
60
61 for k, v in list(t.items()):
61 for k, v in list(t.items()):
62 # remove copies from files that didn't exist
62 if k == v: # case 3
63 if v not in src: # case 5
64 del t[k]
63 del t[k]
65 # remove criss-crossed copies
64 elif v not in src: # case 5
66 elif k in src and v in dst:
65 # remove copies from files that didn't exist
67 del t[k]
66 del t[k]
67 elif k not in dst: # case 1
68 # remove copies to files that were then removed
68 # remove copies to files that were then removed
69 elif k not in dst: # case 1
70 del t[k]
69 del t[k]
71
70
72
71
73 def _chain(prefix, suffix):
72 def _chain(prefix, suffix):
74 """chain two sets of copies 'prefix' and 'suffix'"""
73 """chain two sets of copies 'prefix' and 'suffix'"""
75 result = prefix.copy()
74 result = prefix.copy()
76 for key, value in pycompat.iteritems(suffix):
75 for key, value in pycompat.iteritems(suffix):
77 result[key] = prefix.get(value, value)
76 result[key] = prefix.get(value, value)
78 return result
77 return result
79
78
80
79
81 def _tracefile(fctx, am, basemf):
80 def _tracefile(fctx, am, basemf):
82 """return file context that is the ancestor of fctx present in ancestor
81 """return file context that is the ancestor of fctx present in ancestor
83 manifest am
82 manifest am
84
83
85 Note: we used to try and stop after a given limit, however checking if that
84 Note: we used to try and stop after a given limit, however checking if that
86 limit is reached turned out to be very expensive. we are better off
85 limit is reached turned out to be very expensive. we are better off
87 disabling that feature."""
86 disabling that feature."""
88
87
89 for f in fctx.ancestors():
88 for f in fctx.ancestors():
90 path = f.path()
89 path = f.path()
91 if am.get(path, None) == f.filenode():
90 if am.get(path, None) == f.filenode():
92 return path
91 return path
93 if basemf and basemf.get(path, None) == f.filenode():
92 if basemf and basemf.get(path, None) == f.filenode():
94 return path
93 return path
95
94
96
95
97 def _dirstatecopies(repo, match=None):
96 def _dirstatecopies(repo, match=None):
98 ds = repo.dirstate
97 ds = repo.dirstate
99 c = ds.copies().copy()
98 c = ds.copies().copy()
100 for k in list(c):
99 for k in list(c):
101 if ds[k] not in b'anm' or (match and not match(k)):
100 if ds[k] not in b'anm' or (match and not match(k)):
102 del c[k]
101 del c[k]
103 return c
102 return c
104
103
105
104
106 def _computeforwardmissing(a, b, match=None):
105 def _computeforwardmissing(a, b, match=None):
107 """Computes which files are in b but not a.
106 """Computes which files are in b but not a.
108 This is its own function so extensions can easily wrap this call to see what
107 This is its own function so extensions can easily wrap this call to see what
109 files _forwardcopies is about to process.
108 files _forwardcopies is about to process.
110 """
109 """
111 ma = a.manifest()
110 ma = a.manifest()
112 mb = b.manifest()
111 mb = b.manifest()
113 return mb.filesnotin(ma, match=match)
112 return mb.filesnotin(ma, match=match)
114
113
115
114
116 def usechangesetcentricalgo(repo):
115 def usechangesetcentricalgo(repo):
117 """Checks if we should use changeset-centric copy algorithms"""
116 """Checks if we should use changeset-centric copy algorithms"""
118 if repo.filecopiesmode == b'changeset-sidedata':
117 if repo.filecopiesmode == b'changeset-sidedata':
119 return True
118 return True
120 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
119 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
121 changesetsource = (b'changeset-only', b'compatibility')
120 changesetsource = (b'changeset-only', b'compatibility')
122 return readfrom in changesetsource
121 return readfrom in changesetsource
123
122
124
123
125 def _committedforwardcopies(a, b, base, match):
124 def _committedforwardcopies(a, b, base, match):
126 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
125 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
127 # files might have to be traced back to the fctx parent of the last
126 # files might have to be traced back to the fctx parent of the last
128 # one-side-only changeset, but not further back than that
127 # one-side-only changeset, but not further back than that
129 repo = a._repo
128 repo = a._repo
130
129
131 if usechangesetcentricalgo(repo):
130 if usechangesetcentricalgo(repo):
132 return _changesetforwardcopies(a, b, match)
131 return _changesetforwardcopies(a, b, match)
133
132
134 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
133 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
135 dbg = repo.ui.debug
134 dbg = repo.ui.debug
136 if debug:
135 if debug:
137 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
136 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
138 am = a.manifest()
137 am = a.manifest()
139 basemf = None if base is None else base.manifest()
138 basemf = None if base is None else base.manifest()
140
139
141 # find where new files came from
140 # find where new files came from
142 # we currently don't try to find where old files went, too expensive
141 # we currently don't try to find where old files went, too expensive
143 # this means we can miss a case like 'hg rm b; hg cp a b'
142 # this means we can miss a case like 'hg rm b; hg cp a b'
144 cm = {}
143 cm = {}
145
144
146 # Computing the forward missing is quite expensive on large manifests, since
145 # Computing the forward missing is quite expensive on large manifests, since
147 # it compares the entire manifests. We can optimize it in the common use
146 # it compares the entire manifests. We can optimize it in the common use
148 # case of computing what copies are in a commit versus its parent (like
147 # case of computing what copies are in a commit versus its parent (like
149 # during a rebase or histedit). Note, we exclude merge commits from this
148 # during a rebase or histedit). Note, we exclude merge commits from this
150 # optimization, since the ctx.files() for a merge commit is not correct for
149 # optimization, since the ctx.files() for a merge commit is not correct for
151 # this comparison.
150 # this comparison.
152 forwardmissingmatch = match
151 forwardmissingmatch = match
153 if b.p1() == a and b.p2().node() == nullid:
152 if b.p1() == a and b.p2().node() == nullid:
154 filesmatcher = matchmod.exact(b.files())
153 filesmatcher = matchmod.exact(b.files())
155 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
154 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
156 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
155 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
157
156
158 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
157 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
159
158
160 if debug:
159 if debug:
161 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
160 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
162
161
163 for f in sorted(missing):
162 for f in sorted(missing):
164 if debug:
163 if debug:
165 dbg(b'debug.copies: tracing file: %s\n' % f)
164 dbg(b'debug.copies: tracing file: %s\n' % f)
166 fctx = b[f]
165 fctx = b[f]
167 fctx._ancestrycontext = ancestrycontext
166 fctx._ancestrycontext = ancestrycontext
168
167
169 if debug:
168 if debug:
170 start = util.timer()
169 start = util.timer()
171 opath = _tracefile(fctx, am, basemf)
170 opath = _tracefile(fctx, am, basemf)
172 if opath:
171 if opath:
173 if debug:
172 if debug:
174 dbg(b'debug.copies: rename of: %s\n' % opath)
173 dbg(b'debug.copies: rename of: %s\n' % opath)
175 cm[f] = opath
174 cm[f] = opath
176 if debug:
175 if debug:
177 dbg(
176 dbg(
178 b'debug.copies: time: %f seconds\n'
177 b'debug.copies: time: %f seconds\n'
179 % (util.timer() - start)
178 % (util.timer() - start)
180 )
179 )
181 return cm
180 return cm
182
181
183
182
184 def _revinfo_getter(repo, match):
183 def _revinfo_getter(repo, match):
185 """returns a function that returns the following data given a <rev>"
184 """returns a function that returns the following data given a <rev>"
186
185
187 * p1: revision number of first parent
186 * p1: revision number of first parent
188 * p2: revision number of first parent
187 * p2: revision number of first parent
189 * changes: a ChangingFiles object
188 * changes: a ChangingFiles object
190 """
189 """
191 cl = repo.changelog
190 cl = repo.changelog
192 parents = cl.parentrevs
191 parents = cl.parentrevs
193 flags = cl.flags
192 flags = cl.flags
194
193
195 HASCOPIESINFO = flagutil.REVIDX_HASCOPIESINFO
194 HASCOPIESINFO = flagutil.REVIDX_HASCOPIESINFO
196
195
197 changelogrevision = cl.changelogrevision
196 changelogrevision = cl.changelogrevision
198
197
199 if rustmod is not None:
198 if rustmod is not None:
200
199
201 def revinfo(rev):
200 def revinfo(rev):
202 p1, p2 = parents(rev)
201 p1, p2 = parents(rev)
203 if flags(rev) & HASCOPIESINFO:
202 if flags(rev) & HASCOPIESINFO:
204 raw = changelogrevision(rev)._sidedata.get(sidedatamod.SD_FILES)
203 raw = changelogrevision(rev)._sidedata.get(sidedatamod.SD_FILES)
205 else:
204 else:
206 raw = None
205 raw = None
207 return (p1, p2, raw)
206 return (p1, p2, raw)
208
207
209 else:
208 else:
210
209
211 def revinfo(rev):
210 def revinfo(rev):
212 p1, p2 = parents(rev)
211 p1, p2 = parents(rev)
213 if flags(rev) & HASCOPIESINFO:
212 if flags(rev) & HASCOPIESINFO:
214 changes = changelogrevision(rev).changes
213 changes = changelogrevision(rev).changes
215 else:
214 else:
216 changes = None
215 changes = None
217 return (p1, p2, changes)
216 return (p1, p2, changes)
218
217
219 return revinfo
218 return revinfo
220
219
221
220
222 def cached_is_ancestor(is_ancestor):
221 def cached_is_ancestor(is_ancestor):
223 """return a cached version of is_ancestor"""
222 """return a cached version of is_ancestor"""
224 cache = {}
223 cache = {}
225
224
226 def _is_ancestor(anc, desc):
225 def _is_ancestor(anc, desc):
227 if anc > desc:
226 if anc > desc:
228 return False
227 return False
229 elif anc == desc:
228 elif anc == desc:
230 return True
229 return True
231 key = (anc, desc)
230 key = (anc, desc)
232 ret = cache.get(key)
231 ret = cache.get(key)
233 if ret is None:
232 if ret is None:
234 ret = cache[key] = is_ancestor(anc, desc)
233 ret = cache[key] = is_ancestor(anc, desc)
235 return ret
234 return ret
236
235
237 return _is_ancestor
236 return _is_ancestor
238
237
239
238
240 def _changesetforwardcopies(a, b, match):
239 def _changesetforwardcopies(a, b, match):
241 if a.rev() in (nullrev, b.rev()):
240 if a.rev() in (nullrev, b.rev()):
242 return {}
241 return {}
243
242
244 repo = a.repo().unfiltered()
243 repo = a.repo().unfiltered()
245 children = {}
244 children = {}
246
245
247 cl = repo.changelog
246 cl = repo.changelog
248 isancestor = cl.isancestorrev
247 isancestor = cl.isancestorrev
249
248
250 # To track rename from "A" to B, we need to gather all parent β†’ children
249 # To track rename from "A" to B, we need to gather all parent β†’ children
251 # edges that are contains in `::B` but not in `::A`.
250 # edges that are contains in `::B` but not in `::A`.
252 #
251 #
253 #
252 #
254 # To do so, we need to gather all revisions exclusiveΒΉ to "B" (ieΒΉ: `::b -
253 # To do so, we need to gather all revisions exclusiveΒΉ to "B" (ieΒΉ: `::b -
255 # ::a`) and also all the "roots point", ie the parents of the exclusive set
254 # ::a`) and also all the "roots point", ie the parents of the exclusive set
256 # that belong to ::a. These are exactly all the revisions needed to express
255 # that belong to ::a. These are exactly all the revisions needed to express
257 # the parent β†’ children we need to combine.
256 # the parent β†’ children we need to combine.
258 #
257 #
259 # [1] actually, we need to gather all the edges within `(::a)::b`, ie:
258 # [1] actually, we need to gather all the edges within `(::a)::b`, ie:
260 # excluding paths that leads to roots that are not ancestors of `a`. We
259 # excluding paths that leads to roots that are not ancestors of `a`. We
261 # keep this out of the explanation because it is hard enough without this special case..
260 # keep this out of the explanation because it is hard enough without this special case..
262
261
263 parents = cl._uncheckedparentrevs
262 parents = cl._uncheckedparentrevs
264 graph_roots = (nullrev, nullrev)
263 graph_roots = (nullrev, nullrev)
265
264
266 ancestors = cl.ancestors([a.rev()], inclusive=True)
265 ancestors = cl.ancestors([a.rev()], inclusive=True)
267 revs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
266 revs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
268 roots = set()
267 roots = set()
269 has_graph_roots = False
268 has_graph_roots = False
270
269
271 # iterate over `only(B, A)`
270 # iterate over `only(B, A)`
272 for r in revs:
271 for r in revs:
273 ps = parents(r)
272 ps = parents(r)
274 if ps == graph_roots:
273 if ps == graph_roots:
275 has_graph_roots = True
274 has_graph_roots = True
276 else:
275 else:
277 p1, p2 = ps
276 p1, p2 = ps
278
277
279 # find all the "root points" (see larger comment above)
278 # find all the "root points" (see larger comment above)
280 if p1 != nullrev and p1 in ancestors:
279 if p1 != nullrev and p1 in ancestors:
281 roots.add(p1)
280 roots.add(p1)
282 if p2 != nullrev and p2 in ancestors:
281 if p2 != nullrev and p2 in ancestors:
283 roots.add(p2)
282 roots.add(p2)
284 if not roots:
283 if not roots:
285 # no common revision to track copies from
284 # no common revision to track copies from
286 return {}
285 return {}
287 if has_graph_roots:
286 if has_graph_roots:
288 # this deal with the special case mentionned in the [1] footnotes. We
287 # this deal with the special case mentionned in the [1] footnotes. We
289 # must filter out revisions that leads to non-common graphroots.
288 # must filter out revisions that leads to non-common graphroots.
290 roots = list(roots)
289 roots = list(roots)
291 m = min(roots)
290 m = min(roots)
292 h = [b.rev()]
291 h = [b.rev()]
293 roots_to_head = cl.reachableroots(m, h, roots, includepath=True)
292 roots_to_head = cl.reachableroots(m, h, roots, includepath=True)
294 roots_to_head = set(roots_to_head)
293 roots_to_head = set(roots_to_head)
295 revs = [r for r in revs if r in roots_to_head]
294 revs = [r for r in revs if r in roots_to_head]
296
295
297 if repo.filecopiesmode == b'changeset-sidedata':
296 if repo.filecopiesmode == b'changeset-sidedata':
298 # When using side-data, we will process the edges "from" the children.
297 # When using side-data, we will process the edges "from" the children.
299 # We iterate over the childre, gathering previous collected data for
298 # We iterate over the childre, gathering previous collected data for
300 # the parents. Do know when the parents data is no longer necessary, we
299 # the parents. Do know when the parents data is no longer necessary, we
301 # keep a counter of how many children each revision has.
300 # keep a counter of how many children each revision has.
302 #
301 #
303 # An interresting property of `children_count` is that it only contains
302 # An interresting property of `children_count` is that it only contains
304 # revision that will be relevant for a edge of the graph. So if a
303 # revision that will be relevant for a edge of the graph. So if a
305 # children has parent not in `children_count`, that edges should not be
304 # children has parent not in `children_count`, that edges should not be
306 # processed.
305 # processed.
307 children_count = dict((r, 0) for r in roots)
306 children_count = dict((r, 0) for r in roots)
308 for r in revs:
307 for r in revs:
309 for p in cl.parentrevs(r):
308 for p in cl.parentrevs(r):
310 if p == nullrev:
309 if p == nullrev:
311 continue
310 continue
312 children_count[r] = 0
311 children_count[r] = 0
313 if p in children_count:
312 if p in children_count:
314 children_count[p] += 1
313 children_count[p] += 1
315 revinfo = _revinfo_getter(repo, match)
314 revinfo = _revinfo_getter(repo, match)
316 return _combine_changeset_copies(
315 return _combine_changeset_copies(
317 revs, children_count, b.rev(), revinfo, match, isancestor
316 revs, children_count, b.rev(), revinfo, match, isancestor
318 )
317 )
319 else:
318 else:
320 # When not using side-data, we will process the edges "from" the parent.
319 # When not using side-data, we will process the edges "from" the parent.
321 # so we need a full mapping of the parent -> children relation.
320 # so we need a full mapping of the parent -> children relation.
322 children = dict((r, []) for r in roots)
321 children = dict((r, []) for r in roots)
323 for r in revs:
322 for r in revs:
324 for p in cl.parentrevs(r):
323 for p in cl.parentrevs(r):
325 if p == nullrev:
324 if p == nullrev:
326 continue
325 continue
327 children[r] = []
326 children[r] = []
328 if p in children:
327 if p in children:
329 children[p].append(r)
328 children[p].append(r)
330 x = revs.pop()
329 x = revs.pop()
331 assert x == b.rev()
330 assert x == b.rev()
332 revs.extend(roots)
331 revs.extend(roots)
333 revs.sort()
332 revs.sort()
334
333
335 revinfo = _revinfo_getter_extra(repo)
334 revinfo = _revinfo_getter_extra(repo)
336 return _combine_changeset_copies_extra(
335 return _combine_changeset_copies_extra(
337 revs, children, b.rev(), revinfo, match, isancestor
336 revs, children, b.rev(), revinfo, match, isancestor
338 )
337 )
339
338
340
339
341 def _combine_changeset_copies(
340 def _combine_changeset_copies(
342 revs, children_count, targetrev, revinfo, match, isancestor
341 revs, children_count, targetrev, revinfo, match, isancestor
343 ):
342 ):
344 """combine the copies information for each item of iterrevs
343 """combine the copies information for each item of iterrevs
345
344
346 revs: sorted iterable of revision to visit
345 revs: sorted iterable of revision to visit
347 children_count: a {parent: <number-of-relevant-children>} mapping.
346 children_count: a {parent: <number-of-relevant-children>} mapping.
348 targetrev: the final copies destination revision (not in iterrevs)
347 targetrev: the final copies destination revision (not in iterrevs)
349 revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
348 revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
350 match: a matcher
349 match: a matcher
351
350
352 It returns the aggregated copies information for `targetrev`.
351 It returns the aggregated copies information for `targetrev`.
353 """
352 """
354
353
355 alwaysmatch = match.always()
354 alwaysmatch = match.always()
356
355
357 if rustmod is not None:
356 if rustmod is not None:
358 final_copies = rustmod.combine_changeset_copies(
357 final_copies = rustmod.combine_changeset_copies(
359 list(revs), children_count, targetrev, revinfo, isancestor
358 list(revs), children_count, targetrev, revinfo, isancestor
360 )
359 )
361 else:
360 else:
362 isancestor = cached_is_ancestor(isancestor)
361 isancestor = cached_is_ancestor(isancestor)
363
362
364 all_copies = {}
363 all_copies = {}
365 # iterate over all the "children" side of copy tracing "edge"
364 # iterate over all the "children" side of copy tracing "edge"
366 for current_rev in revs:
365 for current_rev in revs:
367 p1, p2, changes = revinfo(current_rev)
366 p1, p2, changes = revinfo(current_rev)
368 current_copies = None
367 current_copies = None
369 # iterate over all parents to chain the existing data with the
368 # iterate over all parents to chain the existing data with the
370 # data from the parent β†’ child edge.
369 # data from the parent β†’ child edge.
371 for parent, parent_rev in ((1, p1), (2, p2)):
370 for parent, parent_rev in ((1, p1), (2, p2)):
372 if parent_rev == nullrev:
371 if parent_rev == nullrev:
373 continue
372 continue
374 remaining_children = children_count.get(parent_rev)
373 remaining_children = children_count.get(parent_rev)
375 if remaining_children is None:
374 if remaining_children is None:
376 continue
375 continue
377 remaining_children -= 1
376 remaining_children -= 1
378 children_count[parent_rev] = remaining_children
377 children_count[parent_rev] = remaining_children
379 if remaining_children:
378 if remaining_children:
380 copies = all_copies.get(parent_rev, None)
379 copies = all_copies.get(parent_rev, None)
381 else:
380 else:
382 copies = all_copies.pop(parent_rev, None)
381 copies = all_copies.pop(parent_rev, None)
383
382
384 if copies is None:
383 if copies is None:
385 # this is a root
384 # this is a root
386 newcopies = copies = {}
385 newcopies = copies = {}
387 elif remaining_children:
386 elif remaining_children:
388 newcopies = copies.copy()
387 newcopies = copies.copy()
389 else:
388 else:
390 newcopies = copies
389 newcopies = copies
391 # chain the data in the edge with the existing data
390 # chain the data in the edge with the existing data
392 if changes is not None:
391 if changes is not None:
393 childcopies = {}
392 childcopies = {}
394 if parent == 1:
393 if parent == 1:
395 childcopies = changes.copied_from_p1
394 childcopies = changes.copied_from_p1
396 elif parent == 2:
395 elif parent == 2:
397 childcopies = changes.copied_from_p2
396 childcopies = changes.copied_from_p2
398
397
399 if childcopies:
398 if childcopies:
400 newcopies = copies.copy()
399 newcopies = copies.copy()
401 for dest, source in pycompat.iteritems(childcopies):
400 for dest, source in pycompat.iteritems(childcopies):
402 prev = copies.get(source)
401 prev = copies.get(source)
403 if prev is not None and prev[1] is not None:
402 if prev is not None and prev[1] is not None:
404 source = prev[1]
403 source = prev[1]
405 newcopies[dest] = (current_rev, source)
404 newcopies[dest] = (current_rev, source)
406 assert newcopies is not copies
405 assert newcopies is not copies
407 if changes.removed:
406 if changes.removed:
408 for f in changes.removed:
407 for f in changes.removed:
409 if f in newcopies:
408 if f in newcopies:
410 if newcopies is copies:
409 if newcopies is copies:
411 # copy on write to avoid affecting potential other
410 # copy on write to avoid affecting potential other
412 # branches. when there are no other branches, this
411 # branches. when there are no other branches, this
413 # could be avoided.
412 # could be avoided.
414 newcopies = copies.copy()
413 newcopies = copies.copy()
415 newcopies[f] = (current_rev, None)
414 newcopies[f] = (current_rev, None)
416 # check potential need to combine the data from another parent (for
415 # check potential need to combine the data from another parent (for
417 # that child). See comment below for details.
416 # that child). See comment below for details.
418 if current_copies is None:
417 if current_copies is None:
419 current_copies = newcopies
418 current_copies = newcopies
420 else:
419 else:
421 # we are the second parent to work on c, we need to merge our
420 # we are the second parent to work on c, we need to merge our
422 # work with the other.
421 # work with the other.
423 #
422 #
424 # In case of conflict, parent 1 take precedence over parent 2.
423 # In case of conflict, parent 1 take precedence over parent 2.
425 # This is an arbitrary choice made anew when implementing
424 # This is an arbitrary choice made anew when implementing
426 # changeset based copies. It was made without regards with
425 # changeset based copies. It was made without regards with
427 # potential filelog related behavior.
426 # potential filelog related behavior.
428 assert parent == 2
427 assert parent == 2
429 current_copies = _merge_copies_dict(
428 current_copies = _merge_copies_dict(
430 newcopies, current_copies, isancestor, changes
429 newcopies, current_copies, isancestor, changes
431 )
430 )
432 all_copies[current_rev] = current_copies
431 all_copies[current_rev] = current_copies
433
432
434 # filter out internal details and return a {dest: source mapping}
433 # filter out internal details and return a {dest: source mapping}
435 final_copies = {}
434 final_copies = {}
436 for dest, (tt, source) in all_copies[targetrev].items():
435 for dest, (tt, source) in all_copies[targetrev].items():
437 if source is not None:
436 if source is not None:
438 final_copies[dest] = source
437 final_copies[dest] = source
439 if not alwaysmatch:
438 if not alwaysmatch:
440 for filename in list(final_copies.keys()):
439 for filename in list(final_copies.keys()):
441 if not match(filename):
440 if not match(filename):
442 del final_copies[filename]
441 del final_copies[filename]
443 return final_copies
442 return final_copies
444
443
445
444
446 # constant to decide which side to pick with _merge_copies_dict
445 # constant to decide which side to pick with _merge_copies_dict
447 PICK_MINOR = 0
446 PICK_MINOR = 0
448 PICK_MAJOR = 1
447 PICK_MAJOR = 1
449 PICK_EITHER = 2
448 PICK_EITHER = 2
450
449
451
450
452 def _merge_copies_dict(minor, major, isancestor, changes):
451 def _merge_copies_dict(minor, major, isancestor, changes):
453 """merge two copies-mapping together, minor and major
452 """merge two copies-mapping together, minor and major
454
453
455 In case of conflict, value from "major" will be picked.
454 In case of conflict, value from "major" will be picked.
456
455
457 - `isancestors(low_rev, high_rev)`: callable return True if `low_rev` is an
456 - `isancestors(low_rev, high_rev)`: callable return True if `low_rev` is an
458 ancestors of `high_rev`,
457 ancestors of `high_rev`,
459
458
460 - `ismerged(path)`: callable return True if `path` have been merged in the
459 - `ismerged(path)`: callable return True if `path` have been merged in the
461 current revision,
460 current revision,
462
461
463 return the resulting dict (in practice, the "minor" object, updated)
462 return the resulting dict (in practice, the "minor" object, updated)
464 """
463 """
465 for dest, value in major.items():
464 for dest, value in major.items():
466 other = minor.get(dest)
465 other = minor.get(dest)
467 if other is None:
466 if other is None:
468 minor[dest] = value
467 minor[dest] = value
469 else:
468 else:
470 pick = _compare_values(changes, isancestor, dest, other, value)
469 pick = _compare_values(changes, isancestor, dest, other, value)
471 if pick == PICK_MAJOR:
470 if pick == PICK_MAJOR:
472 minor[dest] = value
471 minor[dest] = value
473 return minor
472 return minor
474
473
475
474
476 def _compare_values(changes, isancestor, dest, minor, major):
475 def _compare_values(changes, isancestor, dest, minor, major):
477 """compare two value within a _merge_copies_dict loop iteration"""
476 """compare two value within a _merge_copies_dict loop iteration"""
478 major_tt, major_value = major
477 major_tt, major_value = major
479 minor_tt, minor_value = minor
478 minor_tt, minor_value = minor
480
479
481 # evacuate some simple case first:
480 # evacuate some simple case first:
482 if major_tt == minor_tt:
481 if major_tt == minor_tt:
483 # if it comes from the same revision it must be the same value
482 # if it comes from the same revision it must be the same value
484 assert major_value == minor_value
483 assert major_value == minor_value
485 return PICK_EITHER
484 return PICK_EITHER
486 elif major[1] == minor[1]:
485 elif major[1] == minor[1]:
487 return PICK_EITHER
486 return PICK_EITHER
488
487
489 # actual merging needed: content from "major" wins, unless it is older than
488 # actual merging needed: content from "major" wins, unless it is older than
490 # the branch point or there is a merge
489 # the branch point or there is a merge
491 elif changes is not None and major[1] is None and dest in changes.salvaged:
490 elif changes is not None and major[1] is None and dest in changes.salvaged:
492 return PICK_MINOR
491 return PICK_MINOR
493 elif changes is not None and minor[1] is None and dest in changes.salvaged:
492 elif changes is not None and minor[1] is None and dest in changes.salvaged:
494 return PICK_MAJOR
493 return PICK_MAJOR
495 elif changes is not None and dest in changes.merged:
494 elif changes is not None and dest in changes.merged:
496 return PICK_MAJOR
495 return PICK_MAJOR
497 elif not isancestor(major_tt, minor_tt):
496 elif not isancestor(major_tt, minor_tt):
498 if major[1] is not None:
497 if major[1] is not None:
499 return PICK_MAJOR
498 return PICK_MAJOR
500 elif isancestor(minor_tt, major_tt):
499 elif isancestor(minor_tt, major_tt):
501 return PICK_MAJOR
500 return PICK_MAJOR
502 return PICK_MINOR
501 return PICK_MINOR
503
502
504
503
505 def _revinfo_getter_extra(repo):
504 def _revinfo_getter_extra(repo):
506 """return a function that return multiple data given a <rev>"i
505 """return a function that return multiple data given a <rev>"i
507
506
508 * p1: revision number of first parent
507 * p1: revision number of first parent
509 * p2: revision number of first parent
508 * p2: revision number of first parent
510 * p1copies: mapping of copies from p1
509 * p1copies: mapping of copies from p1
511 * p2copies: mapping of copies from p2
510 * p2copies: mapping of copies from p2
512 * removed: a list of removed files
511 * removed: a list of removed files
513 * ismerged: a callback to know if file was merged in that revision
512 * ismerged: a callback to know if file was merged in that revision
514 """
513 """
515 cl = repo.changelog
514 cl = repo.changelog
516 parents = cl.parentrevs
515 parents = cl.parentrevs
517
516
518 def get_ismerged(rev):
517 def get_ismerged(rev):
519 ctx = repo[rev]
518 ctx = repo[rev]
520
519
521 def ismerged(path):
520 def ismerged(path):
522 if path not in ctx.files():
521 if path not in ctx.files():
523 return False
522 return False
524 fctx = ctx[path]
523 fctx = ctx[path]
525 parents = fctx._filelog.parents(fctx._filenode)
524 parents = fctx._filelog.parents(fctx._filenode)
526 nb_parents = 0
525 nb_parents = 0
527 for n in parents:
526 for n in parents:
528 if n != nullid:
527 if n != nullid:
529 nb_parents += 1
528 nb_parents += 1
530 return nb_parents >= 2
529 return nb_parents >= 2
531
530
532 return ismerged
531 return ismerged
533
532
534 def revinfo(rev):
533 def revinfo(rev):
535 p1, p2 = parents(rev)
534 p1, p2 = parents(rev)
536 ctx = repo[rev]
535 ctx = repo[rev]
537 p1copies, p2copies = ctx._copies
536 p1copies, p2copies = ctx._copies
538 removed = ctx.filesremoved()
537 removed = ctx.filesremoved()
539 return p1, p2, p1copies, p2copies, removed, get_ismerged(rev)
538 return p1, p2, p1copies, p2copies, removed, get_ismerged(rev)
540
539
541 return revinfo
540 return revinfo
542
541
543
542
544 def _combine_changeset_copies_extra(
543 def _combine_changeset_copies_extra(
545 revs, children, targetrev, revinfo, match, isancestor
544 revs, children, targetrev, revinfo, match, isancestor
546 ):
545 ):
547 """version of `_combine_changeset_copies` that works with the Google
546 """version of `_combine_changeset_copies` that works with the Google
548 specific "extra" based storage for copy information"""
547 specific "extra" based storage for copy information"""
549 all_copies = {}
548 all_copies = {}
550 alwaysmatch = match.always()
549 alwaysmatch = match.always()
551 for r in revs:
550 for r in revs:
552 copies = all_copies.pop(r, None)
551 copies = all_copies.pop(r, None)
553 if copies is None:
552 if copies is None:
554 # this is a root
553 # this is a root
555 copies = {}
554 copies = {}
556 for i, c in enumerate(children[r]):
555 for i, c in enumerate(children[r]):
557 p1, p2, p1copies, p2copies, removed, ismerged = revinfo(c)
556 p1, p2, p1copies, p2copies, removed, ismerged = revinfo(c)
558 if r == p1:
557 if r == p1:
559 parent = 1
558 parent = 1
560 childcopies = p1copies
559 childcopies = p1copies
561 else:
560 else:
562 assert r == p2
561 assert r == p2
563 parent = 2
562 parent = 2
564 childcopies = p2copies
563 childcopies = p2copies
565 if not alwaysmatch:
564 if not alwaysmatch:
566 childcopies = {
565 childcopies = {
567 dst: src for dst, src in childcopies.items() if match(dst)
566 dst: src for dst, src in childcopies.items() if match(dst)
568 }
567 }
569 newcopies = copies
568 newcopies = copies
570 if childcopies:
569 if childcopies:
571 newcopies = copies.copy()
570 newcopies = copies.copy()
572 for dest, source in pycompat.iteritems(childcopies):
571 for dest, source in pycompat.iteritems(childcopies):
573 prev = copies.get(source)
572 prev = copies.get(source)
574 if prev is not None and prev[1] is not None:
573 if prev is not None and prev[1] is not None:
575 source = prev[1]
574 source = prev[1]
576 newcopies[dest] = (c, source)
575 newcopies[dest] = (c, source)
577 assert newcopies is not copies
576 assert newcopies is not copies
578 for f in removed:
577 for f in removed:
579 if f in newcopies:
578 if f in newcopies:
580 if newcopies is copies:
579 if newcopies is copies:
581 # copy on write to avoid affecting potential other
580 # copy on write to avoid affecting potential other
582 # branches. when there are no other branches, this
581 # branches. when there are no other branches, this
583 # could be avoided.
582 # could be avoided.
584 newcopies = copies.copy()
583 newcopies = copies.copy()
585 newcopies[f] = (c, None)
584 newcopies[f] = (c, None)
586 othercopies = all_copies.get(c)
585 othercopies = all_copies.get(c)
587 if othercopies is None:
586 if othercopies is None:
588 all_copies[c] = newcopies
587 all_copies[c] = newcopies
589 else:
588 else:
590 # we are the second parent to work on c, we need to merge our
589 # we are the second parent to work on c, we need to merge our
591 # work with the other.
590 # work with the other.
592 #
591 #
593 # In case of conflict, parent 1 take precedence over parent 2.
592 # In case of conflict, parent 1 take precedence over parent 2.
594 # This is an arbitrary choice made anew when implementing
593 # This is an arbitrary choice made anew when implementing
595 # changeset based copies. It was made without regards with
594 # changeset based copies. It was made without regards with
596 # potential filelog related behavior.
595 # potential filelog related behavior.
597 if parent == 1:
596 if parent == 1:
598 _merge_copies_dict_extra(
597 _merge_copies_dict_extra(
599 othercopies, newcopies, isancestor, ismerged
598 othercopies, newcopies, isancestor, ismerged
600 )
599 )
601 else:
600 else:
602 _merge_copies_dict_extra(
601 _merge_copies_dict_extra(
603 newcopies, othercopies, isancestor, ismerged
602 newcopies, othercopies, isancestor, ismerged
604 )
603 )
605 all_copies[c] = newcopies
604 all_copies[c] = newcopies
606
605
607 final_copies = {}
606 final_copies = {}
608 for dest, (tt, source) in all_copies[targetrev].items():
607 for dest, (tt, source) in all_copies[targetrev].items():
609 if source is not None:
608 if source is not None:
610 final_copies[dest] = source
609 final_copies[dest] = source
611 return final_copies
610 return final_copies
612
611
613
612
614 def _merge_copies_dict_extra(minor, major, isancestor, ismerged):
613 def _merge_copies_dict_extra(minor, major, isancestor, ismerged):
615 """version of `_merge_copies_dict` that works with the Google
614 """version of `_merge_copies_dict` that works with the Google
616 specific "extra" based storage for copy information"""
615 specific "extra" based storage for copy information"""
617 for dest, value in major.items():
616 for dest, value in major.items():
618 other = minor.get(dest)
617 other = minor.get(dest)
619 if other is None:
618 if other is None:
620 minor[dest] = value
619 minor[dest] = value
621 else:
620 else:
622 new_tt = value[0]
621 new_tt = value[0]
623 other_tt = other[0]
622 other_tt = other[0]
624 if value[1] == other[1]:
623 if value[1] == other[1]:
625 continue
624 continue
626 # content from "major" wins, unless it is older
625 # content from "major" wins, unless it is older
627 # than the branch point or there is a merge
626 # than the branch point or there is a merge
628 if (
627 if (
629 new_tt == other_tt
628 new_tt == other_tt
630 or not isancestor(new_tt, other_tt)
629 or not isancestor(new_tt, other_tt)
631 or ismerged(dest)
630 or ismerged(dest)
632 ):
631 ):
633 minor[dest] = value
632 minor[dest] = value
634
633
635
634
636 def _forwardcopies(a, b, base=None, match=None):
635 def _forwardcopies(a, b, base=None, match=None):
637 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
636 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
638
637
639 if base is None:
638 if base is None:
640 base = a
639 base = a
641 match = a.repo().narrowmatch(match)
640 match = a.repo().narrowmatch(match)
642 # check for working copy
641 # check for working copy
643 if b.rev() is None:
642 if b.rev() is None:
644 cm = _committedforwardcopies(a, b.p1(), base, match)
643 cm = _committedforwardcopies(a, b.p1(), base, match)
645 # combine copies from dirstate if necessary
644 # combine copies from dirstate if necessary
646 copies = _chain(cm, _dirstatecopies(b._repo, match))
645 copies = _chain(cm, _dirstatecopies(b._repo, match))
647 else:
646 else:
648 copies = _committedforwardcopies(a, b, base, match)
647 copies = _committedforwardcopies(a, b, base, match)
649 return copies
648 return copies
650
649
651
650
652 def _backwardrenames(a, b, match):
651 def _backwardrenames(a, b, match):
653 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
652 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
654 return {}
653 return {}
655
654
656 # Even though we're not taking copies into account, 1:n rename situations
655 # Even though we're not taking copies into account, 1:n rename situations
657 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
656 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
658 # arbitrarily pick one of the renames.
657 # arbitrarily pick one of the renames.
659 # We don't want to pass in "match" here, since that would filter
658 # We don't want to pass in "match" here, since that would filter
660 # the destination by it. Since we're reversing the copies, we want
659 # the destination by it. Since we're reversing the copies, we want
661 # to filter the source instead.
660 # to filter the source instead.
662 f = _forwardcopies(b, a)
661 f = _forwardcopies(b, a)
663 r = {}
662 r = {}
664 for k, v in sorted(pycompat.iteritems(f)):
663 for k, v in sorted(pycompat.iteritems(f)):
665 if match and not match(v):
664 if match and not match(v):
666 continue
665 continue
667 # remove copies
666 # remove copies
668 if v in a:
667 if v in a:
669 continue
668 continue
670 r[v] = k
669 r[v] = k
671 return r
670 return r
672
671
673
672
674 def pathcopies(x, y, match=None):
673 def pathcopies(x, y, match=None):
675 """find {dst@y: src@x} copy mapping for directed compare"""
674 """find {dst@y: src@x} copy mapping for directed compare"""
676 repo = x._repo
675 repo = x._repo
677 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
676 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
678 if debug:
677 if debug:
679 repo.ui.debug(
678 repo.ui.debug(
680 b'debug.copies: searching copies from %s to %s\n' % (x, y)
679 b'debug.copies: searching copies from %s to %s\n' % (x, y)
681 )
680 )
682 if x == y or not x or not y:
681 if x == y or not x or not y:
683 return {}
682 return {}
684 if y.rev() is None and x == y.p1():
683 if y.rev() is None and x == y.p1():
685 if debug:
684 if debug:
686 repo.ui.debug(b'debug.copies: search mode: dirstate\n')
685 repo.ui.debug(b'debug.copies: search mode: dirstate\n')
687 # short-circuit to avoid issues with merge states
686 # short-circuit to avoid issues with merge states
688 return _dirstatecopies(repo, match)
687 return _dirstatecopies(repo, match)
689 a = y.ancestor(x)
688 a = y.ancestor(x)
690 if a == x:
689 if a == x:
691 if debug:
690 if debug:
692 repo.ui.debug(b'debug.copies: search mode: forward\n')
691 repo.ui.debug(b'debug.copies: search mode: forward\n')
693 copies = _forwardcopies(x, y, match=match)
692 copies = _forwardcopies(x, y, match=match)
694 elif a == y:
693 elif a == y:
695 if debug:
694 if debug:
696 repo.ui.debug(b'debug.copies: search mode: backward\n')
695 repo.ui.debug(b'debug.copies: search mode: backward\n')
697 copies = _backwardrenames(x, y, match=match)
696 copies = _backwardrenames(x, y, match=match)
698 else:
697 else:
699 if debug:
698 if debug:
700 repo.ui.debug(b'debug.copies: search mode: combined\n')
699 repo.ui.debug(b'debug.copies: search mode: combined\n')
701 base = None
700 base = None
702 if a.rev() != nullrev:
701 if a.rev() != nullrev:
703 base = x
702 base = x
704 copies = _chain(
703 copies = _chain(
705 _backwardrenames(x, a, match=match),
704 _backwardrenames(x, a, match=match),
706 _forwardcopies(a, y, base, match=match),
705 _forwardcopies(a, y, base, match=match),
707 )
706 )
708 _filter(x, y, copies)
707 _filter(x, y, copies)
709 return copies
708 return copies
710
709
711
710
712 def mergecopies(repo, c1, c2, base):
711 def mergecopies(repo, c1, c2, base):
713 """
712 """
714 Finds moves and copies between context c1 and c2 that are relevant for
713 Finds moves and copies between context c1 and c2 that are relevant for
715 merging. 'base' will be used as the merge base.
714 merging. 'base' will be used as the merge base.
716
715
717 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
716 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
718 files that were moved/ copied in one merge parent and modified in another.
717 files that were moved/ copied in one merge parent and modified in another.
719 For example:
718 For example:
720
719
721 o ---> 4 another commit
720 o ---> 4 another commit
722 |
721 |
723 | o ---> 3 commit that modifies a.txt
722 | o ---> 3 commit that modifies a.txt
724 | /
723 | /
725 o / ---> 2 commit that moves a.txt to b.txt
724 o / ---> 2 commit that moves a.txt to b.txt
726 |/
725 |/
727 o ---> 1 merge base
726 o ---> 1 merge base
728
727
729 If we try to rebase revision 3 on revision 4, since there is no a.txt in
728 If we try to rebase revision 3 on revision 4, since there is no a.txt in
730 revision 4, and if user have copytrace disabled, we prints the following
729 revision 4, and if user have copytrace disabled, we prints the following
731 message:
730 message:
732
731
733 ```other changed <file> which local deleted```
732 ```other changed <file> which local deleted```
734
733
735 Returns a tuple where:
734 Returns a tuple where:
736
735
737 "branch_copies" an instance of branch_copies.
736 "branch_copies" an instance of branch_copies.
738
737
739 "diverge" is a mapping of source name -> list of destination names
738 "diverge" is a mapping of source name -> list of destination names
740 for divergent renames.
739 for divergent renames.
741
740
742 This function calls different copytracing algorithms based on config.
741 This function calls different copytracing algorithms based on config.
743 """
742 """
744 # avoid silly behavior for update from empty dir
743 # avoid silly behavior for update from empty dir
745 if not c1 or not c2 or c1 == c2:
744 if not c1 or not c2 or c1 == c2:
746 return branch_copies(), branch_copies(), {}
745 return branch_copies(), branch_copies(), {}
747
746
748 narrowmatch = c1.repo().narrowmatch()
747 narrowmatch = c1.repo().narrowmatch()
749
748
750 # avoid silly behavior for parent -> working dir
749 # avoid silly behavior for parent -> working dir
751 if c2.node() is None and c1.node() == repo.dirstate.p1():
750 if c2.node() is None and c1.node() == repo.dirstate.p1():
752 return (
751 return (
753 branch_copies(_dirstatecopies(repo, narrowmatch)),
752 branch_copies(_dirstatecopies(repo, narrowmatch)),
754 branch_copies(),
753 branch_copies(),
755 {},
754 {},
756 )
755 )
757
756
758 copytracing = repo.ui.config(b'experimental', b'copytrace')
757 copytracing = repo.ui.config(b'experimental', b'copytrace')
759 if stringutil.parsebool(copytracing) is False:
758 if stringutil.parsebool(copytracing) is False:
760 # stringutil.parsebool() returns None when it is unable to parse the
759 # stringutil.parsebool() returns None when it is unable to parse the
761 # value, so we should rely on making sure copytracing is on such cases
760 # value, so we should rely on making sure copytracing is on such cases
762 return branch_copies(), branch_copies(), {}
761 return branch_copies(), branch_copies(), {}
763
762
764 if usechangesetcentricalgo(repo):
763 if usechangesetcentricalgo(repo):
765 # The heuristics don't make sense when we need changeset-centric algos
764 # The heuristics don't make sense when we need changeset-centric algos
766 return _fullcopytracing(repo, c1, c2, base)
765 return _fullcopytracing(repo, c1, c2, base)
767
766
768 # Copy trace disabling is explicitly below the node == p1 logic above
767 # Copy trace disabling is explicitly below the node == p1 logic above
769 # because the logic above is required for a simple copy to be kept across a
768 # because the logic above is required for a simple copy to be kept across a
770 # rebase.
769 # rebase.
771 if copytracing == b'heuristics':
770 if copytracing == b'heuristics':
772 # Do full copytracing if only non-public revisions are involved as
771 # Do full copytracing if only non-public revisions are involved as
773 # that will be fast enough and will also cover the copies which could
772 # that will be fast enough and will also cover the copies which could
774 # be missed by heuristics
773 # be missed by heuristics
775 if _isfullcopytraceable(repo, c1, base):
774 if _isfullcopytraceable(repo, c1, base):
776 return _fullcopytracing(repo, c1, c2, base)
775 return _fullcopytracing(repo, c1, c2, base)
777 return _heuristicscopytracing(repo, c1, c2, base)
776 return _heuristicscopytracing(repo, c1, c2, base)
778 else:
777 else:
779 return _fullcopytracing(repo, c1, c2, base)
778 return _fullcopytracing(repo, c1, c2, base)
780
779
781
780
782 def _isfullcopytraceable(repo, c1, base):
781 def _isfullcopytraceable(repo, c1, base):
783 """Checks that if base, source and destination are all no-public branches,
782 """Checks that if base, source and destination are all no-public branches,
784 if yes let's use the full copytrace algorithm for increased capabilities
783 if yes let's use the full copytrace algorithm for increased capabilities
785 since it will be fast enough.
784 since it will be fast enough.
786
785
787 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
786 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
788 number of changesets from c1 to base such that if number of changesets are
787 number of changesets from c1 to base such that if number of changesets are
789 more than the limit, full copytracing algorithm won't be used.
788 more than the limit, full copytracing algorithm won't be used.
790 """
789 """
791 if c1.rev() is None:
790 if c1.rev() is None:
792 c1 = c1.p1()
791 c1 = c1.p1()
793 if c1.mutable() and base.mutable():
792 if c1.mutable() and base.mutable():
794 sourcecommitlimit = repo.ui.configint(
793 sourcecommitlimit = repo.ui.configint(
795 b'experimental', b'copytrace.sourcecommitlimit'
794 b'experimental', b'copytrace.sourcecommitlimit'
796 )
795 )
797 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
796 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
798 return commits < sourcecommitlimit
797 return commits < sourcecommitlimit
799 return False
798 return False
800
799
801
800
802 def _checksinglesidecopies(
801 def _checksinglesidecopies(
803 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
802 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
804 ):
803 ):
805 if src not in m2:
804 if src not in m2:
806 # deleted on side 2
805 # deleted on side 2
807 if src not in m1:
806 if src not in m1:
808 # renamed on side 1, deleted on side 2
807 # renamed on side 1, deleted on side 2
809 renamedelete[src] = dsts1
808 renamedelete[src] = dsts1
810 elif src not in mb:
809 elif src not in mb:
811 # Work around the "short-circuit to avoid issues with merge states"
810 # Work around the "short-circuit to avoid issues with merge states"
812 # thing in pathcopies(): pathcopies(x, y) can return a copy where the
811 # thing in pathcopies(): pathcopies(x, y) can return a copy where the
813 # destination doesn't exist in y.
812 # destination doesn't exist in y.
814 pass
813 pass
815 elif mb[src] != m2[src] and not _related(c2[src], base[src]):
814 elif mb[src] != m2[src] and not _related(c2[src], base[src]):
816 return
815 return
817 elif mb[src] != m2[src] or mb.flags(src) != m2.flags(src):
816 elif mb[src] != m2[src] or mb.flags(src) != m2.flags(src):
818 # modified on side 2
817 # modified on side 2
819 for dst in dsts1:
818 for dst in dsts1:
820 copy[dst] = src
819 copy[dst] = src
821
820
822
821
823 class branch_copies(object):
822 class branch_copies(object):
824 """Information about copies made on one side of a merge/graft.
823 """Information about copies made on one side of a merge/graft.
825
824
826 "copy" is a mapping from destination name -> source name,
825 "copy" is a mapping from destination name -> source name,
827 where source is in c1 and destination is in c2 or vice-versa.
826 where source is in c1 and destination is in c2 or vice-versa.
828
827
829 "movewithdir" is a mapping from source name -> destination name,
828 "movewithdir" is a mapping from source name -> destination name,
830 where the file at source present in one context but not the other
829 where the file at source present in one context but not the other
831 needs to be moved to destination by the merge process, because the
830 needs to be moved to destination by the merge process, because the
832 other context moved the directory it is in.
831 other context moved the directory it is in.
833
832
834 "renamedelete" is a mapping of source name -> list of destination
833 "renamedelete" is a mapping of source name -> list of destination
835 names for files deleted in c1 that were renamed in c2 or vice-versa.
834 names for files deleted in c1 that were renamed in c2 or vice-versa.
836
835
837 "dirmove" is a mapping of detected source dir -> destination dir renames.
836 "dirmove" is a mapping of detected source dir -> destination dir renames.
838 This is needed for handling changes to new files previously grafted into
837 This is needed for handling changes to new files previously grafted into
839 renamed directories.
838 renamed directories.
840 """
839 """
841
840
842 def __init__(
841 def __init__(
843 self, copy=None, renamedelete=None, dirmove=None, movewithdir=None
842 self, copy=None, renamedelete=None, dirmove=None, movewithdir=None
844 ):
843 ):
845 self.copy = {} if copy is None else copy
844 self.copy = {} if copy is None else copy
846 self.renamedelete = {} if renamedelete is None else renamedelete
845 self.renamedelete = {} if renamedelete is None else renamedelete
847 self.dirmove = {} if dirmove is None else dirmove
846 self.dirmove = {} if dirmove is None else dirmove
848 self.movewithdir = {} if movewithdir is None else movewithdir
847 self.movewithdir = {} if movewithdir is None else movewithdir
849
848
850 def __repr__(self):
849 def __repr__(self):
851 return '<branch_copies\n copy=%r\n renamedelete=%r\n dirmove=%r\n movewithdir=%r\n>' % (
850 return '<branch_copies\n copy=%r\n renamedelete=%r\n dirmove=%r\n movewithdir=%r\n>' % (
852 self.copy,
851 self.copy,
853 self.renamedelete,
852 self.renamedelete,
854 self.dirmove,
853 self.dirmove,
855 self.movewithdir,
854 self.movewithdir,
856 )
855 )
857
856
858
857
859 def _fullcopytracing(repo, c1, c2, base):
858 def _fullcopytracing(repo, c1, c2, base):
860 """The full copytracing algorithm which finds all the new files that were
859 """The full copytracing algorithm which finds all the new files that were
861 added from merge base up to the top commit and for each file it checks if
860 added from merge base up to the top commit and for each file it checks if
862 this file was copied from another file.
861 this file was copied from another file.
863
862
864 This is pretty slow when a lot of changesets are involved but will track all
863 This is pretty slow when a lot of changesets are involved but will track all
865 the copies.
864 the copies.
866 """
865 """
867 m1 = c1.manifest()
866 m1 = c1.manifest()
868 m2 = c2.manifest()
867 m2 = c2.manifest()
869 mb = base.manifest()
868 mb = base.manifest()
870
869
871 copies1 = pathcopies(base, c1)
870 copies1 = pathcopies(base, c1)
872 copies2 = pathcopies(base, c2)
871 copies2 = pathcopies(base, c2)
873
872
874 if not (copies1 or copies2):
873 if not (copies1 or copies2):
875 return branch_copies(), branch_copies(), {}
874 return branch_copies(), branch_copies(), {}
876
875
877 inversecopies1 = {}
876 inversecopies1 = {}
878 inversecopies2 = {}
877 inversecopies2 = {}
879 for dst, src in copies1.items():
878 for dst, src in copies1.items():
880 inversecopies1.setdefault(src, []).append(dst)
879 inversecopies1.setdefault(src, []).append(dst)
881 for dst, src in copies2.items():
880 for dst, src in copies2.items():
882 inversecopies2.setdefault(src, []).append(dst)
881 inversecopies2.setdefault(src, []).append(dst)
883
882
884 copy1 = {}
883 copy1 = {}
885 copy2 = {}
884 copy2 = {}
886 diverge = {}
885 diverge = {}
887 renamedelete1 = {}
886 renamedelete1 = {}
888 renamedelete2 = {}
887 renamedelete2 = {}
889 allsources = set(inversecopies1) | set(inversecopies2)
888 allsources = set(inversecopies1) | set(inversecopies2)
890 for src in allsources:
889 for src in allsources:
891 dsts1 = inversecopies1.get(src)
890 dsts1 = inversecopies1.get(src)
892 dsts2 = inversecopies2.get(src)
891 dsts2 = inversecopies2.get(src)
893 if dsts1 and dsts2:
892 if dsts1 and dsts2:
894 # copied/renamed on both sides
893 # copied/renamed on both sides
895 if src not in m1 and src not in m2:
894 if src not in m1 and src not in m2:
896 # renamed on both sides
895 # renamed on both sides
897 dsts1 = set(dsts1)
896 dsts1 = set(dsts1)
898 dsts2 = set(dsts2)
897 dsts2 = set(dsts2)
899 # If there's some overlap in the rename destinations, we
898 # If there's some overlap in the rename destinations, we
900 # consider it not divergent. For example, if side 1 copies 'a'
899 # consider it not divergent. For example, if side 1 copies 'a'
901 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
900 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
902 # and 'd' and deletes 'a'.
901 # and 'd' and deletes 'a'.
903 if dsts1 & dsts2:
902 if dsts1 & dsts2:
904 for dst in dsts1 & dsts2:
903 for dst in dsts1 & dsts2:
905 copy1[dst] = src
904 copy1[dst] = src
906 copy2[dst] = src
905 copy2[dst] = src
907 else:
906 else:
908 diverge[src] = sorted(dsts1 | dsts2)
907 diverge[src] = sorted(dsts1 | dsts2)
909 elif src in m1 and src in m2:
908 elif src in m1 and src in m2:
910 # copied on both sides
909 # copied on both sides
911 dsts1 = set(dsts1)
910 dsts1 = set(dsts1)
912 dsts2 = set(dsts2)
911 dsts2 = set(dsts2)
913 for dst in dsts1 & dsts2:
912 for dst in dsts1 & dsts2:
914 copy1[dst] = src
913 copy1[dst] = src
915 copy2[dst] = src
914 copy2[dst] = src
916 # TODO: Handle cases where it was renamed on one side and copied
915 # TODO: Handle cases where it was renamed on one side and copied
917 # on the other side
916 # on the other side
918 elif dsts1:
917 elif dsts1:
919 # copied/renamed only on side 1
918 # copied/renamed only on side 1
920 _checksinglesidecopies(
919 _checksinglesidecopies(
921 src, dsts1, m1, m2, mb, c2, base, copy1, renamedelete1
920 src, dsts1, m1, m2, mb, c2, base, copy1, renamedelete1
922 )
921 )
923 elif dsts2:
922 elif dsts2:
924 # copied/renamed only on side 2
923 # copied/renamed only on side 2
925 _checksinglesidecopies(
924 _checksinglesidecopies(
926 src, dsts2, m2, m1, mb, c1, base, copy2, renamedelete2
925 src, dsts2, m2, m1, mb, c1, base, copy2, renamedelete2
927 )
926 )
928
927
929 # find interesting file sets from manifests
928 # find interesting file sets from manifests
930 cache = []
929 cache = []
931
930
932 def _get_addedfiles(idx):
931 def _get_addedfiles(idx):
933 if not cache:
932 if not cache:
934 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
933 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
935 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
934 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
936 u1 = sorted(addedinm1 - addedinm2)
935 u1 = sorted(addedinm1 - addedinm2)
937 u2 = sorted(addedinm2 - addedinm1)
936 u2 = sorted(addedinm2 - addedinm1)
938 cache.extend((u1, u2))
937 cache.extend((u1, u2))
939 return cache[idx]
938 return cache[idx]
940
939
941 u1fn = lambda: _get_addedfiles(0)
940 u1fn = lambda: _get_addedfiles(0)
942 u2fn = lambda: _get_addedfiles(1)
941 u2fn = lambda: _get_addedfiles(1)
943 if repo.ui.debugflag:
942 if repo.ui.debugflag:
944 u1 = u1fn()
943 u1 = u1fn()
945 u2 = u2fn()
944 u2 = u2fn()
946
945
947 header = b" unmatched files in %s"
946 header = b" unmatched files in %s"
948 if u1:
947 if u1:
949 repo.ui.debug(
948 repo.ui.debug(
950 b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1))
949 b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1))
951 )
950 )
952 if u2:
951 if u2:
953 repo.ui.debug(
952 repo.ui.debug(
954 b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2))
953 b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2))
955 )
954 )
956
955
957 renamedeleteset = set()
956 renamedeleteset = set()
958 divergeset = set()
957 divergeset = set()
959 for dsts in diverge.values():
958 for dsts in diverge.values():
960 divergeset.update(dsts)
959 divergeset.update(dsts)
961 for dsts in renamedelete1.values():
960 for dsts in renamedelete1.values():
962 renamedeleteset.update(dsts)
961 renamedeleteset.update(dsts)
963 for dsts in renamedelete2.values():
962 for dsts in renamedelete2.values():
964 renamedeleteset.update(dsts)
963 renamedeleteset.update(dsts)
965
964
966 repo.ui.debug(
965 repo.ui.debug(
967 b" all copies found (* = to merge, ! = divergent, "
966 b" all copies found (* = to merge, ! = divergent, "
968 b"% = renamed and deleted):\n"
967 b"% = renamed and deleted):\n"
969 )
968 )
970 for side, copies in ((b"local", copies1), (b"remote", copies2)):
969 for side, copies in ((b"local", copies1), (b"remote", copies2)):
971 if not copies:
970 if not copies:
972 continue
971 continue
973 repo.ui.debug(b" on %s side:\n" % side)
972 repo.ui.debug(b" on %s side:\n" % side)
974 for f in sorted(copies):
973 for f in sorted(copies):
975 note = b""
974 note = b""
976 if f in copy1 or f in copy2:
975 if f in copy1 or f in copy2:
977 note += b"*"
976 note += b"*"
978 if f in divergeset:
977 if f in divergeset:
979 note += b"!"
978 note += b"!"
980 if f in renamedeleteset:
979 if f in renamedeleteset:
981 note += b"%"
980 note += b"%"
982 repo.ui.debug(
981 repo.ui.debug(
983 b" src: '%s' -> dst: '%s' %s\n" % (copies[f], f, note)
982 b" src: '%s' -> dst: '%s' %s\n" % (copies[f], f, note)
984 )
983 )
985 del renamedeleteset
984 del renamedeleteset
986 del divergeset
985 del divergeset
987
986
988 repo.ui.debug(b" checking for directory renames\n")
987 repo.ui.debug(b" checking for directory renames\n")
989
988
990 dirmove1, movewithdir2 = _dir_renames(repo, c1, copy1, copies1, u2fn)
989 dirmove1, movewithdir2 = _dir_renames(repo, c1, copy1, copies1, u2fn)
991 dirmove2, movewithdir1 = _dir_renames(repo, c2, copy2, copies2, u1fn)
990 dirmove2, movewithdir1 = _dir_renames(repo, c2, copy2, copies2, u1fn)
992
991
993 branch_copies1 = branch_copies(copy1, renamedelete1, dirmove1, movewithdir1)
992 branch_copies1 = branch_copies(copy1, renamedelete1, dirmove1, movewithdir1)
994 branch_copies2 = branch_copies(copy2, renamedelete2, dirmove2, movewithdir2)
993 branch_copies2 = branch_copies(copy2, renamedelete2, dirmove2, movewithdir2)
995
994
996 return branch_copies1, branch_copies2, diverge
995 return branch_copies1, branch_copies2, diverge
997
996
998
997
999 def _dir_renames(repo, ctx, copy, fullcopy, addedfilesfn):
998 def _dir_renames(repo, ctx, copy, fullcopy, addedfilesfn):
1000 """Finds moved directories and files that should move with them.
999 """Finds moved directories and files that should move with them.
1001
1000
1002 ctx: the context for one of the sides
1001 ctx: the context for one of the sides
1003 copy: files copied on the same side (as ctx)
1002 copy: files copied on the same side (as ctx)
1004 fullcopy: files copied on the same side (as ctx), including those that
1003 fullcopy: files copied on the same side (as ctx), including those that
1005 merge.manifestmerge() won't care about
1004 merge.manifestmerge() won't care about
1006 addedfilesfn: function returning added files on the other side (compared to
1005 addedfilesfn: function returning added files on the other side (compared to
1007 ctx)
1006 ctx)
1008 """
1007 """
1009 # generate a directory move map
1008 # generate a directory move map
1010 invalid = set()
1009 invalid = set()
1011 dirmove = {}
1010 dirmove = {}
1012
1011
1013 # examine each file copy for a potential directory move, which is
1012 # examine each file copy for a potential directory move, which is
1014 # when all the files in a directory are moved to a new directory
1013 # when all the files in a directory are moved to a new directory
1015 for dst, src in pycompat.iteritems(fullcopy):
1014 for dst, src in pycompat.iteritems(fullcopy):
1016 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
1015 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
1017 if dsrc in invalid:
1016 if dsrc in invalid:
1018 # already seen to be uninteresting
1017 # already seen to be uninteresting
1019 continue
1018 continue
1020 elif ctx.hasdir(dsrc) and ctx.hasdir(ddst):
1019 elif ctx.hasdir(dsrc) and ctx.hasdir(ddst):
1021 # directory wasn't entirely moved locally
1020 # directory wasn't entirely moved locally
1022 invalid.add(dsrc)
1021 invalid.add(dsrc)
1023 elif dsrc in dirmove and dirmove[dsrc] != ddst:
1022 elif dsrc in dirmove and dirmove[dsrc] != ddst:
1024 # files from the same directory moved to two different places
1023 # files from the same directory moved to two different places
1025 invalid.add(dsrc)
1024 invalid.add(dsrc)
1026 else:
1025 else:
1027 # looks good so far
1026 # looks good so far
1028 dirmove[dsrc] = ddst
1027 dirmove[dsrc] = ddst
1029
1028
1030 for i in invalid:
1029 for i in invalid:
1031 if i in dirmove:
1030 if i in dirmove:
1032 del dirmove[i]
1031 del dirmove[i]
1033 del invalid
1032 del invalid
1034
1033
1035 if not dirmove:
1034 if not dirmove:
1036 return {}, {}
1035 return {}, {}
1037
1036
1038 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
1037 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
1039
1038
1040 for d in dirmove:
1039 for d in dirmove:
1041 repo.ui.debug(
1040 repo.ui.debug(
1042 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
1041 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
1043 )
1042 )
1044
1043
1045 movewithdir = {}
1044 movewithdir = {}
1046 # check unaccounted nonoverlapping files against directory moves
1045 # check unaccounted nonoverlapping files against directory moves
1047 for f in addedfilesfn():
1046 for f in addedfilesfn():
1048 if f not in fullcopy:
1047 if f not in fullcopy:
1049 for d in dirmove:
1048 for d in dirmove:
1050 if f.startswith(d):
1049 if f.startswith(d):
1051 # new file added in a directory that was moved, move it
1050 # new file added in a directory that was moved, move it
1052 df = dirmove[d] + f[len(d) :]
1051 df = dirmove[d] + f[len(d) :]
1053 if df not in copy:
1052 if df not in copy:
1054 movewithdir[f] = df
1053 movewithdir[f] = df
1055 repo.ui.debug(
1054 repo.ui.debug(
1056 b" pending file src: '%s' -> dst: '%s'\n"
1055 b" pending file src: '%s' -> dst: '%s'\n"
1057 % (f, df)
1056 % (f, df)
1058 )
1057 )
1059 break
1058 break
1060
1059
1061 return dirmove, movewithdir
1060 return dirmove, movewithdir
1062
1061
1063
1062
1064 def _heuristicscopytracing(repo, c1, c2, base):
1063 def _heuristicscopytracing(repo, c1, c2, base):
1065 """Fast copytracing using filename heuristics
1064 """Fast copytracing using filename heuristics
1066
1065
1067 Assumes that moves or renames are of following two types:
1066 Assumes that moves or renames are of following two types:
1068
1067
1069 1) Inside a directory only (same directory name but different filenames)
1068 1) Inside a directory only (same directory name but different filenames)
1070 2) Move from one directory to another
1069 2) Move from one directory to another
1071 (same filenames but different directory names)
1070 (same filenames but different directory names)
1072
1071
1073 Works only when there are no merge commits in the "source branch".
1072 Works only when there are no merge commits in the "source branch".
1074 Source branch is commits from base up to c2 not including base.
1073 Source branch is commits from base up to c2 not including base.
1075
1074
1076 If merge is involved it fallbacks to _fullcopytracing().
1075 If merge is involved it fallbacks to _fullcopytracing().
1077
1076
1078 Can be used by setting the following config:
1077 Can be used by setting the following config:
1079
1078
1080 [experimental]
1079 [experimental]
1081 copytrace = heuristics
1080 copytrace = heuristics
1082
1081
1083 In some cases the copy/move candidates found by heuristics can be very large
1082 In some cases the copy/move candidates found by heuristics can be very large
1084 in number and that will make the algorithm slow. The number of possible
1083 in number and that will make the algorithm slow. The number of possible
1085 candidates to check can be limited by using the config
1084 candidates to check can be limited by using the config
1086 `experimental.copytrace.movecandidateslimit` which defaults to 100.
1085 `experimental.copytrace.movecandidateslimit` which defaults to 100.
1087 """
1086 """
1088
1087
1089 if c1.rev() is None:
1088 if c1.rev() is None:
1090 c1 = c1.p1()
1089 c1 = c1.p1()
1091 if c2.rev() is None:
1090 if c2.rev() is None:
1092 c2 = c2.p1()
1091 c2 = c2.p1()
1093
1092
1094 changedfiles = set()
1093 changedfiles = set()
1095 m1 = c1.manifest()
1094 m1 = c1.manifest()
1096 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
1095 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
1097 # If base is not in c2 branch, we switch to fullcopytracing
1096 # If base is not in c2 branch, we switch to fullcopytracing
1098 repo.ui.debug(
1097 repo.ui.debug(
1099 b"switching to full copytracing as base is not "
1098 b"switching to full copytracing as base is not "
1100 b"an ancestor of c2\n"
1099 b"an ancestor of c2\n"
1101 )
1100 )
1102 return _fullcopytracing(repo, c1, c2, base)
1101 return _fullcopytracing(repo, c1, c2, base)
1103
1102
1104 ctx = c2
1103 ctx = c2
1105 while ctx != base:
1104 while ctx != base:
1106 if len(ctx.parents()) == 2:
1105 if len(ctx.parents()) == 2:
1107 # To keep things simple let's not handle merges
1106 # To keep things simple let's not handle merges
1108 repo.ui.debug(b"switching to full copytracing because of merges\n")
1107 repo.ui.debug(b"switching to full copytracing because of merges\n")
1109 return _fullcopytracing(repo, c1, c2, base)
1108 return _fullcopytracing(repo, c1, c2, base)
1110 changedfiles.update(ctx.files())
1109 changedfiles.update(ctx.files())
1111 ctx = ctx.p1()
1110 ctx = ctx.p1()
1112
1111
1113 copies2 = {}
1112 copies2 = {}
1114 cp = _forwardcopies(base, c2)
1113 cp = _forwardcopies(base, c2)
1115 for dst, src in pycompat.iteritems(cp):
1114 for dst, src in pycompat.iteritems(cp):
1116 if src in m1:
1115 if src in m1:
1117 copies2[dst] = src
1116 copies2[dst] = src
1118
1117
1119 # file is missing if it isn't present in the destination, but is present in
1118 # file is missing if it isn't present in the destination, but is present in
1120 # the base and present in the source.
1119 # the base and present in the source.
1121 # Presence in the base is important to exclude added files, presence in the
1120 # Presence in the base is important to exclude added files, presence in the
1122 # source is important to exclude removed files.
1121 # source is important to exclude removed files.
1123 filt = lambda f: f not in m1 and f in base and f in c2
1122 filt = lambda f: f not in m1 and f in base and f in c2
1124 missingfiles = [f for f in changedfiles if filt(f)]
1123 missingfiles = [f for f in changedfiles if filt(f)]
1125
1124
1126 copies1 = {}
1125 copies1 = {}
1127 if missingfiles:
1126 if missingfiles:
1128 basenametofilename = collections.defaultdict(list)
1127 basenametofilename = collections.defaultdict(list)
1129 dirnametofilename = collections.defaultdict(list)
1128 dirnametofilename = collections.defaultdict(list)
1130
1129
1131 for f in m1.filesnotin(base.manifest()):
1130 for f in m1.filesnotin(base.manifest()):
1132 basename = os.path.basename(f)
1131 basename = os.path.basename(f)
1133 dirname = os.path.dirname(f)
1132 dirname = os.path.dirname(f)
1134 basenametofilename[basename].append(f)
1133 basenametofilename[basename].append(f)
1135 dirnametofilename[dirname].append(f)
1134 dirnametofilename[dirname].append(f)
1136
1135
1137 for f in missingfiles:
1136 for f in missingfiles:
1138 basename = os.path.basename(f)
1137 basename = os.path.basename(f)
1139 dirname = os.path.dirname(f)
1138 dirname = os.path.dirname(f)
1140 samebasename = basenametofilename[basename]
1139 samebasename = basenametofilename[basename]
1141 samedirname = dirnametofilename[dirname]
1140 samedirname = dirnametofilename[dirname]
1142 movecandidates = samebasename + samedirname
1141 movecandidates = samebasename + samedirname
1143 # f is guaranteed to be present in c2, that's why
1142 # f is guaranteed to be present in c2, that's why
1144 # c2.filectx(f) won't fail
1143 # c2.filectx(f) won't fail
1145 f2 = c2.filectx(f)
1144 f2 = c2.filectx(f)
1146 # we can have a lot of candidates which can slow down the heuristics
1145 # we can have a lot of candidates which can slow down the heuristics
1147 # config value to limit the number of candidates moves to check
1146 # config value to limit the number of candidates moves to check
1148 maxcandidates = repo.ui.configint(
1147 maxcandidates = repo.ui.configint(
1149 b'experimental', b'copytrace.movecandidateslimit'
1148 b'experimental', b'copytrace.movecandidateslimit'
1150 )
1149 )
1151
1150
1152 if len(movecandidates) > maxcandidates:
1151 if len(movecandidates) > maxcandidates:
1153 repo.ui.status(
1152 repo.ui.status(
1154 _(
1153 _(
1155 b"skipping copytracing for '%s', more "
1154 b"skipping copytracing for '%s', more "
1156 b"candidates than the limit: %d\n"
1155 b"candidates than the limit: %d\n"
1157 )
1156 )
1158 % (f, len(movecandidates))
1157 % (f, len(movecandidates))
1159 )
1158 )
1160 continue
1159 continue
1161
1160
1162 for candidate in movecandidates:
1161 for candidate in movecandidates:
1163 f1 = c1.filectx(candidate)
1162 f1 = c1.filectx(candidate)
1164 if _related(f1, f2):
1163 if _related(f1, f2):
1165 # if there are a few related copies then we'll merge
1164 # if there are a few related copies then we'll merge
1166 # changes into all of them. This matches the behaviour
1165 # changes into all of them. This matches the behaviour
1167 # of upstream copytracing
1166 # of upstream copytracing
1168 copies1[candidate] = f
1167 copies1[candidate] = f
1169
1168
1170 return branch_copies(copies1), branch_copies(copies2), {}
1169 return branch_copies(copies1), branch_copies(copies2), {}
1171
1170
1172
1171
1173 def _related(f1, f2):
1172 def _related(f1, f2):
1174 """return True if f1 and f2 filectx have a common ancestor
1173 """return True if f1 and f2 filectx have a common ancestor
1175
1174
1176 Walk back to common ancestor to see if the two files originate
1175 Walk back to common ancestor to see if the two files originate
1177 from the same file. Since workingfilectx's rev() is None it messes
1176 from the same file. Since workingfilectx's rev() is None it messes
1178 up the integer comparison logic, hence the pre-step check for
1177 up the integer comparison logic, hence the pre-step check for
1179 None (f1 and f2 can only be workingfilectx's initially).
1178 None (f1 and f2 can only be workingfilectx's initially).
1180 """
1179 """
1181
1180
1182 if f1 == f2:
1181 if f1 == f2:
1183 return True # a match
1182 return True # a match
1184
1183
1185 g1, g2 = f1.ancestors(), f2.ancestors()
1184 g1, g2 = f1.ancestors(), f2.ancestors()
1186 try:
1185 try:
1187 f1r, f2r = f1.linkrev(), f2.linkrev()
1186 f1r, f2r = f1.linkrev(), f2.linkrev()
1188
1187
1189 if f1r is None:
1188 if f1r is None:
1190 f1 = next(g1)
1189 f1 = next(g1)
1191 if f2r is None:
1190 if f2r is None:
1192 f2 = next(g2)
1191 f2 = next(g2)
1193
1192
1194 while True:
1193 while True:
1195 f1r, f2r = f1.linkrev(), f2.linkrev()
1194 f1r, f2r = f1.linkrev(), f2.linkrev()
1196 if f1r > f2r:
1195 if f1r > f2r:
1197 f1 = next(g1)
1196 f1 = next(g1)
1198 elif f2r > f1r:
1197 elif f2r > f1r:
1199 f2 = next(g2)
1198 f2 = next(g2)
1200 else: # f1 and f2 point to files in the same linkrev
1199 else: # f1 and f2 point to files in the same linkrev
1201 return f1 == f2 # true if they point to the same file
1200 return f1 == f2 # true if they point to the same file
1202 except StopIteration:
1201 except StopIteration:
1203 return False
1202 return False
1204
1203
1205
1204
1206 def graftcopies(wctx, ctx, base):
1205 def graftcopies(wctx, ctx, base):
1207 """reproduce copies between base and ctx in the wctx
1206 """reproduce copies between base and ctx in the wctx
1208
1207
1209 Unlike mergecopies(), this function will only consider copies between base
1208 Unlike mergecopies(), this function will only consider copies between base
1210 and ctx; it will ignore copies between base and wctx. Also unlike
1209 and ctx; it will ignore copies between base and wctx. Also unlike
1211 mergecopies(), this function will apply copies to the working copy (instead
1210 mergecopies(), this function will apply copies to the working copy (instead
1212 of just returning information about the copies). That makes it cheaper
1211 of just returning information about the copies). That makes it cheaper
1213 (especially in the common case of base==ctx.p1()) and useful also when
1212 (especially in the common case of base==ctx.p1()) and useful also when
1214 experimental.copytrace=off.
1213 experimental.copytrace=off.
1215
1214
1216 merge.update() will have already marked most copies, but it will only
1215 merge.update() will have already marked most copies, but it will only
1217 mark copies if it thinks the source files are related (see
1216 mark copies if it thinks the source files are related (see
1218 merge._related()). It will also not mark copies if the file wasn't modified
1217 merge._related()). It will also not mark copies if the file wasn't modified
1219 on the local side. This function adds the copies that were "missed"
1218 on the local side. This function adds the copies that were "missed"
1220 by merge.update().
1219 by merge.update().
1221 """
1220 """
1222 new_copies = pathcopies(base, ctx)
1221 new_copies = pathcopies(base, ctx)
1223 parent = wctx.p1()
1222 parent = wctx.p1()
1224 _filter(parent, wctx, new_copies)
1223 _filter(parent, wctx, new_copies)
1225 # extra filtering to drop copy information for files that existed before
1224 # extra filtering to drop copy information for files that existed before
1226 # the graft (otherwise we would create merge filelog for non-merge commit
1225 # the graft (otherwise we would create merge filelog for non-merge commit
1227 for dest, __ in list(new_copies.items()):
1226 for dest, __ in list(new_copies.items()):
1228 if dest in parent:
1227 if dest in parent:
1229 del new_copies[dest]
1228 del new_copies[dest]
1230 for dst, src in pycompat.iteritems(new_copies):
1229 for dst, src in pycompat.iteritems(new_copies):
1231 wctx[dst].markcopied(src)
1230 wctx[dst].markcopied(src)
@@ -1,721 +1,721
1 #testcases filelog compatibility changeset sidedata
1 #testcases filelog compatibility changeset sidedata
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 #if sidedata
25 #if sidedata
26 $ cat >> $HGRCPATH << EOF
26 $ cat >> $HGRCPATH << EOF
27 > [format]
27 > [format]
28 > exp-use-copies-side-data-changeset = yes
28 > exp-use-copies-side-data-changeset = yes
29 > EOF
29 > EOF
30 #endif
30 #endif
31
31
32 $ REPONUM=0
32 $ REPONUM=0
33 $ newrepo() {
33 $ newrepo() {
34 > cd $TESTTMP
34 > cd $TESTTMP
35 > REPONUM=`expr $REPONUM + 1`
35 > REPONUM=`expr $REPONUM + 1`
36 > hg init repo-$REPONUM
36 > hg init repo-$REPONUM
37 > cd repo-$REPONUM
37 > cd repo-$REPONUM
38 > }
38 > }
39
39
40 Simple rename case
40 Simple rename case
41 $ newrepo
41 $ newrepo
42 $ echo x > x
42 $ echo x > x
43 $ hg ci -Aqm 'add x'
43 $ hg ci -Aqm 'add x'
44 $ hg mv x y
44 $ hg mv x y
45 $ hg debugp1copies
45 $ hg debugp1copies
46 x -> y
46 x -> y
47 $ hg debugp2copies
47 $ hg debugp2copies
48 $ hg ci -m 'rename x to y'
48 $ hg ci -m 'rename x to y'
49 $ hg l
49 $ hg l
50 @ 1 rename x to y
50 @ 1 rename x to y
51 | x y
51 | x y
52 o 0 add x
52 o 0 add x
53 x
53 x
54 $ hg debugp1copies -r 1
54 $ hg debugp1copies -r 1
55 x -> y
55 x -> y
56 $ hg debugpathcopies 0 1
56 $ hg debugpathcopies 0 1
57 x -> y
57 x -> y
58 $ hg debugpathcopies 1 0
58 $ hg debugpathcopies 1 0
59 y -> x
59 y -> x
60 Test filtering copies by path. We do filtering by destination.
60 Test filtering copies by path. We do filtering by destination.
61 $ hg debugpathcopies 0 1 x
61 $ hg debugpathcopies 0 1 x
62 $ hg debugpathcopies 1 0 x
62 $ hg debugpathcopies 1 0 x
63 y -> x
63 y -> x
64 $ hg debugpathcopies 0 1 y
64 $ hg debugpathcopies 0 1 y
65 x -> y
65 x -> y
66 $ hg debugpathcopies 1 0 y
66 $ hg debugpathcopies 1 0 y
67
67
68 Copies not including commit changes
68 Copies not including commit changes
69 $ newrepo
69 $ newrepo
70 $ echo x > x
70 $ echo x > x
71 $ hg ci -Aqm 'add x'
71 $ hg ci -Aqm 'add x'
72 $ hg mv x y
72 $ hg mv x y
73 $ hg debugpathcopies . .
73 $ hg debugpathcopies . .
74 $ hg debugpathcopies . 'wdir()'
74 $ hg debugpathcopies . 'wdir()'
75 x -> y
75 x -> y
76 $ hg debugpathcopies 'wdir()' .
76 $ hg debugpathcopies 'wdir()' .
77 y -> x
77 y -> x
78
78
79 Copy a file onto another file
79 Copy a file onto another file
80 $ newrepo
80 $ newrepo
81 $ echo x > x
81 $ echo x > x
82 $ echo y > y
82 $ echo y > y
83 $ hg ci -Aqm 'add x and y'
83 $ hg ci -Aqm 'add x and y'
84 $ hg cp -f x y
84 $ hg cp -f x y
85 $ hg debugp1copies
85 $ hg debugp1copies
86 x -> y
86 x -> y
87 $ hg debugp2copies
87 $ hg debugp2copies
88 $ hg ci -m 'copy x onto y'
88 $ hg ci -m 'copy x onto y'
89 $ hg l
89 $ hg l
90 @ 1 copy x onto y
90 @ 1 copy x onto y
91 | y
91 | y
92 o 0 add x and y
92 o 0 add x and y
93 x y
93 x y
94 $ hg debugp1copies -r 1
94 $ hg debugp1copies -r 1
95 x -> y
95 x -> y
96 Incorrectly doesn't show the rename
97 $ hg debugpathcopies 0 1
96 $ hg debugpathcopies 0 1
97 x -> y (no-filelog !)
98
98
99 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
99 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
100 produce a new filelog entry. The changeset's "files" entry should still list the file.
100 produce a new filelog entry. The changeset's "files" entry should still list the file.
101 $ newrepo
101 $ newrepo
102 $ echo x > x
102 $ echo x > x
103 $ echo x > x2
103 $ echo x > x2
104 $ hg ci -Aqm 'add x and x2 with same content'
104 $ hg ci -Aqm 'add x and x2 with same content'
105 $ hg cp -f x x2
105 $ hg cp -f x x2
106 $ hg ci -m 'copy x onto x2'
106 $ hg ci -m 'copy x onto x2'
107 $ hg l
107 $ hg l
108 @ 1 copy x onto x2
108 @ 1 copy x onto x2
109 | x2
109 | x2
110 o 0 add x and x2 with same content
110 o 0 add x and x2 with same content
111 x x2
111 x x2
112 $ hg debugp1copies -r 1
112 $ hg debugp1copies -r 1
113 x -> x2
113 x -> x2
114 Incorrectly doesn't show the rename
115 $ hg debugpathcopies 0 1
114 $ hg debugpathcopies 0 1
115 x -> x2 (no-filelog !)
116
116
117 Rename file in a loop: x->y->z->x
117 Rename file in a loop: x->y->z->x
118 $ newrepo
118 $ newrepo
119 $ echo x > x
119 $ echo x > x
120 $ hg ci -Aqm 'add x'
120 $ hg ci -Aqm 'add x'
121 $ hg mv x y
121 $ hg mv x y
122 $ hg debugp1copies
122 $ hg debugp1copies
123 x -> y
123 x -> y
124 $ hg debugp2copies
124 $ hg debugp2copies
125 $ hg ci -m 'rename x to y'
125 $ hg ci -m 'rename x to y'
126 $ hg mv y z
126 $ hg mv y z
127 $ hg ci -m 'rename y to z'
127 $ hg ci -m 'rename y to z'
128 $ hg mv z x
128 $ hg mv z x
129 $ hg ci -m 'rename z to x'
129 $ hg ci -m 'rename z to x'
130 $ hg l
130 $ hg l
131 @ 3 rename z to x
131 @ 3 rename z to x
132 | x z
132 | x z
133 o 2 rename y to z
133 o 2 rename y to z
134 | y z
134 | y z
135 o 1 rename x to y
135 o 1 rename x to y
136 | x y
136 | x y
137 o 0 add x
137 o 0 add x
138 x
138 x
139 $ hg debugpathcopies 0 3
139 $ hg debugpathcopies 0 3
140
140
141 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
141 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
142 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
142 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
143 to the first commit that added the file. We should still report the copy as being from x2.
143 to the first commit that added the file. We should still report the copy as being from x2.
144 $ newrepo
144 $ newrepo
145 $ echo x > x
145 $ echo x > x
146 $ echo x > x2
146 $ echo x > x2
147 $ hg ci -Aqm 'add x and x2 with same content'
147 $ hg ci -Aqm 'add x and x2 with same content'
148 $ hg cp x z
148 $ hg cp x z
149 $ hg ci -qm 'copy x to z'
149 $ hg ci -qm 'copy x to z'
150 $ hg rm z
150 $ hg rm z
151 $ hg ci -m 'remove z'
151 $ hg ci -m 'remove z'
152 $ hg cp x2 z
152 $ hg cp x2 z
153 $ hg ci -m 'copy x2 to z'
153 $ hg ci -m 'copy x2 to z'
154 $ hg l
154 $ hg l
155 @ 3 copy x2 to z
155 @ 3 copy x2 to z
156 | z
156 | z
157 o 2 remove z
157 o 2 remove z
158 | z
158 | z
159 o 1 copy x to z
159 o 1 copy x to z
160 | z
160 | z
161 o 0 add x and x2 with same content
161 o 0 add x and x2 with same content
162 x x2
162 x x2
163 $ hg debugp1copies -r 3
163 $ hg debugp1copies -r 3
164 x2 -> z
164 x2 -> z
165 $ hg debugpathcopies 0 3
165 $ hg debugpathcopies 0 3
166 x2 -> z
166 x2 -> z
167
167
168 Create x and y, then rename them both to the same name, but on different sides of a fork
168 Create x and y, then rename them both to the same name, but on different sides of a fork
169 $ newrepo
169 $ newrepo
170 $ echo x > x
170 $ echo x > x
171 $ echo y > y
171 $ echo y > y
172 $ hg ci -Aqm 'add x and y'
172 $ hg ci -Aqm 'add x and y'
173 $ hg mv x z
173 $ hg mv x z
174 $ hg ci -qm 'rename x to z'
174 $ hg ci -qm 'rename x to z'
175 $ hg co -q 0
175 $ hg co -q 0
176 $ hg mv y z
176 $ hg mv y z
177 $ hg ci -qm 'rename y to z'
177 $ hg ci -qm 'rename y to z'
178 $ hg l
178 $ hg l
179 @ 2 rename y to z
179 @ 2 rename y to z
180 | y z
180 | y z
181 | o 1 rename x to z
181 | o 1 rename x to z
182 |/ x z
182 |/ x z
183 o 0 add x and y
183 o 0 add x and y
184 x y
184 x y
185 $ hg debugpathcopies 1 2
185 $ hg debugpathcopies 1 2
186 z -> x
186 z -> x
187 y -> z
187 y -> z
188
188
189 Fork renames x to y on one side and removes x on the other
189 Fork renames x to y on one side and removes x on the other
190 $ newrepo
190 $ newrepo
191 $ echo x > x
191 $ echo x > x
192 $ hg ci -Aqm 'add x'
192 $ hg ci -Aqm 'add x'
193 $ hg mv x y
193 $ hg mv x y
194 $ hg ci -m 'rename x to y'
194 $ hg ci -m 'rename x to y'
195 $ hg co -q 0
195 $ hg co -q 0
196 $ hg rm x
196 $ hg rm x
197 $ hg ci -m 'remove x'
197 $ hg ci -m 'remove x'
198 created new head
198 created new head
199 $ hg l
199 $ hg l
200 @ 2 remove x
200 @ 2 remove x
201 | x
201 | x
202 | o 1 rename x to y
202 | o 1 rename x to y
203 |/ x y
203 |/ x y
204 o 0 add x
204 o 0 add x
205 x
205 x
206 $ hg debugpathcopies 1 2
206 $ hg debugpathcopies 1 2
207
207
208 Merge rename from other branch
208 Merge rename from other branch
209 $ newrepo
209 $ newrepo
210 $ echo x > x
210 $ echo x > x
211 $ hg ci -Aqm 'add x'
211 $ hg ci -Aqm 'add x'
212 $ hg mv x y
212 $ hg mv x y
213 $ hg ci -m 'rename x to y'
213 $ hg ci -m 'rename x to y'
214 $ hg co -q 0
214 $ hg co -q 0
215 $ echo z > z
215 $ echo z > z
216 $ hg ci -Aqm 'add z'
216 $ hg ci -Aqm 'add z'
217 $ hg merge -q 1
217 $ hg merge -q 1
218 $ hg debugp1copies
218 $ hg debugp1copies
219 $ hg debugp2copies
219 $ hg debugp2copies
220 $ hg ci -m 'merge rename from p2'
220 $ hg ci -m 'merge rename from p2'
221 $ hg l
221 $ hg l
222 @ 3 merge rename from p2
222 @ 3 merge rename from p2
223 |\
223 |\
224 | o 2 add z
224 | o 2 add z
225 | | z
225 | | z
226 o | 1 rename x to y
226 o | 1 rename x to y
227 |/ x y
227 |/ x y
228 o 0 add x
228 o 0 add x
229 x
229 x
230 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
230 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
231 merges, so...
231 merges, so...
232 $ hg debugp1copies -r 3
232 $ hg debugp1copies -r 3
233 $ hg debugp2copies -r 3
233 $ hg debugp2copies -r 3
234 $ hg debugpathcopies 0 3
234 $ hg debugpathcopies 0 3
235 x -> y
235 x -> y
236 $ hg debugpathcopies 1 2
236 $ hg debugpathcopies 1 2
237 y -> x
237 y -> x
238 $ hg debugpathcopies 1 3
238 $ hg debugpathcopies 1 3
239 $ hg debugpathcopies 2 3
239 $ hg debugpathcopies 2 3
240 x -> y
240 x -> y
241
241
242 Copy file from either side in a merge
242 Copy file from either side in a merge
243 $ newrepo
243 $ newrepo
244 $ echo x > x
244 $ echo x > x
245 $ hg ci -Aqm 'add x'
245 $ hg ci -Aqm 'add x'
246 $ hg co -q null
246 $ hg co -q null
247 $ echo y > y
247 $ echo y > y
248 $ hg ci -Aqm 'add y'
248 $ hg ci -Aqm 'add y'
249 $ hg merge -q 0
249 $ hg merge -q 0
250 $ hg cp y z
250 $ hg cp y z
251 $ hg debugp1copies
251 $ hg debugp1copies
252 y -> z
252 y -> z
253 $ hg debugp2copies
253 $ hg debugp2copies
254 $ hg ci -m 'copy file from p1 in merge'
254 $ hg ci -m 'copy file from p1 in merge'
255 $ hg co -q 1
255 $ hg co -q 1
256 $ hg merge -q 0
256 $ hg merge -q 0
257 $ hg cp x z
257 $ hg cp x z
258 $ hg debugp1copies
258 $ hg debugp1copies
259 $ hg debugp2copies
259 $ hg debugp2copies
260 x -> z
260 x -> z
261 $ hg ci -qm 'copy file from p2 in merge'
261 $ hg ci -qm 'copy file from p2 in merge'
262 $ hg l
262 $ hg l
263 @ 3 copy file from p2 in merge
263 @ 3 copy file from p2 in merge
264 |\ z
264 |\ z
265 +---o 2 copy file from p1 in merge
265 +---o 2 copy file from p1 in merge
266 | |/ z
266 | |/ z
267 | o 1 add y
267 | o 1 add y
268 | y
268 | y
269 o 0 add x
269 o 0 add x
270 x
270 x
271 $ hg debugp1copies -r 2
271 $ hg debugp1copies -r 2
272 y -> z
272 y -> z
273 $ hg debugp2copies -r 2
273 $ hg debugp2copies -r 2
274 $ hg debugpathcopies 1 2
274 $ hg debugpathcopies 1 2
275 y -> z
275 y -> z
276 $ hg debugpathcopies 0 2
276 $ hg debugpathcopies 0 2
277 $ hg debugp1copies -r 3
277 $ hg debugp1copies -r 3
278 $ hg debugp2copies -r 3
278 $ hg debugp2copies -r 3
279 x -> z
279 x -> z
280 $ hg debugpathcopies 1 3
280 $ hg debugpathcopies 1 3
281 $ hg debugpathcopies 0 3
281 $ hg debugpathcopies 0 3
282 x -> z
282 x -> z
283
283
284 Copy file that exists on both sides of the merge, same content on both sides
284 Copy file that exists on both sides of the merge, same content on both sides
285 $ newrepo
285 $ newrepo
286 $ echo x > x
286 $ echo x > x
287 $ hg ci -Aqm 'add x on branch 1'
287 $ hg ci -Aqm 'add x on branch 1'
288 $ hg co -q null
288 $ hg co -q null
289 $ echo x > x
289 $ echo x > x
290 $ hg ci -Aqm 'add x on branch 2'
290 $ hg ci -Aqm 'add x on branch 2'
291 $ hg merge -q 0
291 $ hg merge -q 0
292 $ hg cp x z
292 $ hg cp x z
293 $ hg debugp1copies
293 $ hg debugp1copies
294 x -> z
294 x -> z
295 $ hg debugp2copies
295 $ hg debugp2copies
296 $ hg ci -qm 'merge'
296 $ hg ci -qm 'merge'
297 $ hg l
297 $ hg l
298 @ 2 merge
298 @ 2 merge
299 |\ z
299 |\ z
300 | o 1 add x on branch 2
300 | o 1 add x on branch 2
301 | x
301 | x
302 o 0 add x on branch 1
302 o 0 add x on branch 1
303 x
303 x
304 $ hg debugp1copies -r 2
304 $ hg debugp1copies -r 2
305 x -> z
305 x -> z
306 $ hg debugp2copies -r 2
306 $ hg debugp2copies -r 2
307 It's a little weird that it shows up on both sides
307 It's a little weird that it shows up on both sides
308 $ hg debugpathcopies 1 2
308 $ hg debugpathcopies 1 2
309 x -> z
309 x -> z
310 $ hg debugpathcopies 0 2
310 $ hg debugpathcopies 0 2
311 x -> z (filelog !)
311 x -> z (filelog !)
312
312
313 Copy file that exists on both sides of the merge, different content
313 Copy file that exists on both sides of the merge, different content
314 $ newrepo
314 $ newrepo
315 $ echo branch1 > x
315 $ echo branch1 > x
316 $ hg ci -Aqm 'add x on branch 1'
316 $ hg ci -Aqm 'add x on branch 1'
317 $ hg co -q null
317 $ hg co -q null
318 $ echo branch2 > x
318 $ echo branch2 > x
319 $ hg ci -Aqm 'add x on branch 2'
319 $ hg ci -Aqm 'add x on branch 2'
320 $ hg merge -q 0
320 $ hg merge -q 0
321 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
321 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
322 [1]
322 [1]
323 $ echo resolved > x
323 $ echo resolved > x
324 $ hg resolve -m x
324 $ hg resolve -m x
325 (no more unresolved files)
325 (no more unresolved files)
326 $ hg cp x z
326 $ hg cp x z
327 $ hg debugp1copies
327 $ hg debugp1copies
328 x -> z
328 x -> z
329 $ hg debugp2copies
329 $ hg debugp2copies
330 $ hg ci -qm 'merge'
330 $ hg ci -qm 'merge'
331 $ hg l
331 $ hg l
332 @ 2 merge
332 @ 2 merge
333 |\ x z
333 |\ x z
334 | o 1 add x on branch 2
334 | o 1 add x on branch 2
335 | x
335 | x
336 o 0 add x on branch 1
336 o 0 add x on branch 1
337 x
337 x
338 $ hg debugp1copies -r 2
338 $ hg debugp1copies -r 2
339 x -> z (changeset !)
339 x -> z (changeset !)
340 x -> z (sidedata !)
340 x -> z (sidedata !)
341 $ hg debugp2copies -r 2
341 $ hg debugp2copies -r 2
342 x -> z (no-changeset no-sidedata !)
342 x -> z (no-changeset no-sidedata !)
343 $ hg debugpathcopies 1 2
343 $ hg debugpathcopies 1 2
344 x -> z (changeset !)
344 x -> z (changeset !)
345 x -> z (sidedata !)
345 x -> z (sidedata !)
346 $ hg debugpathcopies 0 2
346 $ hg debugpathcopies 0 2
347 x -> z (no-changeset no-sidedata !)
347 x -> z (no-changeset no-sidedata !)
348
348
349 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
349 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
350 of the merge to the merge should include the copy from the other side.
350 of the merge to the merge should include the copy from the other side.
351 $ newrepo
351 $ newrepo
352 $ echo x > x
352 $ echo x > x
353 $ hg ci -Aqm 'add x'
353 $ hg ci -Aqm 'add x'
354 $ hg cp x y
354 $ hg cp x y
355 $ hg ci -qm 'copy x to y'
355 $ hg ci -qm 'copy x to y'
356 $ hg co -q 0
356 $ hg co -q 0
357 $ hg cp x z
357 $ hg cp x z
358 $ hg ci -qm 'copy x to z'
358 $ hg ci -qm 'copy x to z'
359 $ hg merge -q 1
359 $ hg merge -q 1
360 $ hg ci -m 'merge copy x->y and copy x->z'
360 $ hg ci -m 'merge copy x->y and copy x->z'
361 $ hg l
361 $ hg l
362 @ 3 merge copy x->y and copy x->z
362 @ 3 merge copy x->y and copy x->z
363 |\
363 |\
364 | o 2 copy x to z
364 | o 2 copy x to z
365 | | z
365 | | z
366 o | 1 copy x to y
366 o | 1 copy x to y
367 |/ y
367 |/ y
368 o 0 add x
368 o 0 add x
369 x
369 x
370 $ hg debugp1copies -r 3
370 $ hg debugp1copies -r 3
371 $ hg debugp2copies -r 3
371 $ hg debugp2copies -r 3
372 $ hg debugpathcopies 2 3
372 $ hg debugpathcopies 2 3
373 x -> y
373 x -> y
374 $ hg debugpathcopies 1 3
374 $ hg debugpathcopies 1 3
375 x -> z
375 x -> z
376
376
377 Copy x to y on one side of merge, create y and rename to z on the other side.
377 Copy x to y on one side of merge, create y and rename to z on the other side.
378 $ newrepo
378 $ newrepo
379 $ echo x > x
379 $ echo x > x
380 $ hg ci -Aqm 'add x'
380 $ hg ci -Aqm 'add x'
381 $ hg cp x y
381 $ hg cp x y
382 $ hg ci -qm 'copy x to y'
382 $ hg ci -qm 'copy x to y'
383 $ hg co -q 0
383 $ hg co -q 0
384 $ echo y > y
384 $ echo y > y
385 $ hg ci -Aqm 'add y'
385 $ hg ci -Aqm 'add y'
386 $ hg mv y z
386 $ hg mv y z
387 $ hg ci -m 'rename y to z'
387 $ hg ci -m 'rename y to z'
388 $ hg merge -q 1
388 $ hg merge -q 1
389 $ hg ci -m 'merge'
389 $ hg ci -m 'merge'
390 $ hg l
390 $ hg l
391 @ 4 merge
391 @ 4 merge
392 |\
392 |\
393 | o 3 rename y to z
393 | o 3 rename y to z
394 | | y z
394 | | y z
395 | o 2 add y
395 | o 2 add y
396 | | y
396 | | y
397 o | 1 copy x to y
397 o | 1 copy x to y
398 |/ y
398 |/ y
399 o 0 add x
399 o 0 add x
400 x
400 x
401 $ hg debugp1copies -r 3
401 $ hg debugp1copies -r 3
402 y -> z
402 y -> z
403 $ hg debugp2copies -r 3
403 $ hg debugp2copies -r 3
404 $ hg debugpathcopies 2 3
404 $ hg debugpathcopies 2 3
405 y -> z
405 y -> z
406 $ hg debugpathcopies 1 3
406 $ hg debugpathcopies 1 3
407 y -> z (no-filelog !)
407 y -> z (no-filelog !)
408
408
409 Create x and y, then rename x to z on one side of merge, and rename y to z and
409 Create x and y, then rename x to z on one side of merge, and rename y to z and
410 modify z on the other side. When storing copies in the changeset, we don't
410 modify z on the other side. When storing copies in the changeset, we don't
411 filter out copies whose target was created on the other side of the merge.
411 filter out copies whose target was created on the other side of the merge.
412 $ newrepo
412 $ newrepo
413 $ echo x > x
413 $ echo x > x
414 $ echo y > y
414 $ echo y > y
415 $ hg ci -Aqm 'add x and y'
415 $ hg ci -Aqm 'add x and y'
416 $ hg mv x z
416 $ hg mv x z
417 $ hg ci -qm 'rename x to z'
417 $ hg ci -qm 'rename x to z'
418 $ hg co -q 0
418 $ hg co -q 0
419 $ hg mv y z
419 $ hg mv y z
420 $ hg ci -qm 'rename y to z'
420 $ hg ci -qm 'rename y to z'
421 $ echo z >> z
421 $ echo z >> z
422 $ hg ci -m 'modify z'
422 $ hg ci -m 'modify z'
423 $ hg merge -q 1
423 $ hg merge -q 1
424 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
424 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
425 [1]
425 [1]
426 $ echo z > z
426 $ echo z > z
427 $ hg resolve -qm z
427 $ hg resolve -qm z
428 $ hg ci -m 'merge 1 into 3'
428 $ hg ci -m 'merge 1 into 3'
429 Try merging the other direction too
429 Try merging the other direction too
430 $ hg co -q 1
430 $ hg co -q 1
431 $ hg merge -q 3
431 $ hg merge -q 3
432 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
432 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
433 [1]
433 [1]
434 $ echo z > z
434 $ echo z > z
435 $ hg resolve -qm z
435 $ hg resolve -qm z
436 $ hg ci -m 'merge 3 into 1'
436 $ hg ci -m 'merge 3 into 1'
437 created new head
437 created new head
438 $ hg l
438 $ hg l
439 @ 5 merge 3 into 1
439 @ 5 merge 3 into 1
440 |\ z
440 |\ z
441 +---o 4 merge 1 into 3
441 +---o 4 merge 1 into 3
442 | |/ z
442 | |/ z
443 | o 3 modify z
443 | o 3 modify z
444 | | z
444 | | z
445 | o 2 rename y to z
445 | o 2 rename y to z
446 | | y z
446 | | y z
447 o | 1 rename x to z
447 o | 1 rename x to z
448 |/ x z
448 |/ x z
449 o 0 add x and y
449 o 0 add x and y
450 x y
450 x y
451 $ hg debugpathcopies 1 4
451 $ hg debugpathcopies 1 4
452 y -> z (no-filelog !)
452 y -> z (no-filelog !)
453 $ hg debugpathcopies 2 4
453 $ hg debugpathcopies 2 4
454 x -> z (no-filelog !)
454 x -> z (no-filelog !)
455 $ hg debugpathcopies 0 4
455 $ hg debugpathcopies 0 4
456 x -> z (filelog !)
456 x -> z (filelog !)
457 y -> z (no-filelog !)
457 y -> z (no-filelog !)
458 $ hg debugpathcopies 1 5
458 $ hg debugpathcopies 1 5
459 y -> z (no-filelog !)
459 y -> z (no-filelog !)
460 $ hg debugpathcopies 2 5
460 $ hg debugpathcopies 2 5
461 x -> z (no-filelog !)
461 x -> z (no-filelog !)
462 $ hg debugpathcopies 0 5
462 $ hg debugpathcopies 0 5
463 x -> z
463 x -> z
464
464
465 Create x and y, then remove y and rename x to y on one side of merge, and
465 Create x and y, then remove y and rename x to y on one side of merge, and
466 modify x on the other side. The modification to x from the second side
466 modify x on the other side. The modification to x from the second side
467 should be propagated to y.
467 should be propagated to y.
468 $ newrepo
468 $ newrepo
469 $ echo original > x
469 $ echo original > x
470 $ hg add x
470 $ hg add x
471 $ echo unrelated > y
471 $ echo unrelated > y
472 $ hg add y
472 $ hg add y
473 $ hg commit -m 'add x and y'
473 $ hg commit -m 'add x and y'
474 $ hg remove y
474 $ hg remove y
475 $ hg commit -m 'remove y'
475 $ hg commit -m 'remove y'
476 $ hg rename x y
476 $ hg rename x y
477 $ hg commit -m 'rename x to y'
477 $ hg commit -m 'rename x to y'
478 $ hg checkout -q 0
478 $ hg checkout -q 0
479 $ echo modified > x
479 $ echo modified > x
480 $ hg commit -m 'modify x'
480 $ hg commit -m 'modify x'
481 created new head
481 created new head
482 $ hg l
482 $ hg l
483 @ 3 modify x
483 @ 3 modify x
484 | x
484 | x
485 | o 2 rename x to y
485 | o 2 rename x to y
486 | | x y
486 | | x y
487 | o 1 remove y
487 | o 1 remove y
488 |/ y
488 |/ y
489 o 0 add x and y
489 o 0 add x and y
490 x y
490 x y
491 #if filelog
491 #if filelog
492 $ hg merge 2
492 $ hg merge 2
493 file 'x' was deleted in other [merge rev] but was modified in local [working copy].
493 file 'x' was deleted in other [merge rev] but was modified in local [working copy].
494 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
494 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
495 What do you want to do? u
495 What do you want to do? u
496 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
496 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
497 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
497 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
498 [1]
498 [1]
499 This should ideally be "modified", but we will probably not be able to fix
499 This should ideally be "modified", but we will probably not be able to fix
500 that in the filelog case.
500 that in the filelog case.
501 $ cat y
501 $ cat y
502 original
502 original
503 #else
503 #else
504 $ hg merge 2
504 $ hg merge 2
505 merging x and y to y
505 merging x and y to y
506 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
506 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
507 (branch merge, don't forget to commit)
507 (branch merge, don't forget to commit)
508 $ cat y
508 $ cat y
509 modified
509 modified
510 #endif
510 #endif
511 Same as above, but in the opposite direction
511 Same as above, but in the opposite direction
512 #if filelog
512 #if filelog
513 $ hg co -qC 2
513 $ hg co -qC 2
514 $ hg merge 3
514 $ hg merge 3
515 file 'x' was deleted in local [working copy] but was modified in other [merge rev].
515 file 'x' was deleted in local [working copy] but was modified in other [merge rev].
516 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
516 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
517 What do you want to do? u
517 What do you want to do? u
518 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
518 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
519 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
519 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
520 [1]
520 [1]
521 BROKEN: should be "modified"
521 BROKEN: should be "modified"
522 $ cat y
522 $ cat y
523 original
523 original
524 #else
524 #else
525 $ hg co -qC 2
525 $ hg co -qC 2
526 $ hg merge 3
526 $ hg merge 3
527 merging y and x to y
527 merging y and x to y
528 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
528 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
529 (branch merge, don't forget to commit)
529 (branch merge, don't forget to commit)
530 $ cat y
530 $ cat y
531 modified
531 modified
532 #endif
532 #endif
533
533
534 Create x and y, then rename x to z on one side of merge, and rename y to z and
534 Create x and y, then rename x to z on one side of merge, and rename y to z and
535 then delete z on the other side.
535 then delete z on the other side.
536 $ newrepo
536 $ newrepo
537 $ echo x > x
537 $ echo x > x
538 $ echo y > y
538 $ echo y > y
539 $ hg ci -Aqm 'add x and y'
539 $ hg ci -Aqm 'add x and y'
540 $ hg mv x z
540 $ hg mv x z
541 $ hg ci -qm 'rename x to z'
541 $ hg ci -qm 'rename x to z'
542 $ hg co -q 0
542 $ hg co -q 0
543 $ hg mv y z
543 $ hg mv y z
544 $ hg ci -qm 'rename y to z'
544 $ hg ci -qm 'rename y to z'
545 $ hg rm z
545 $ hg rm z
546 $ hg ci -m 'delete z'
546 $ hg ci -m 'delete z'
547 $ hg merge -q 1
547 $ hg merge -q 1
548 $ echo z > z
548 $ echo z > z
549 $ hg ci -m 'merge 1 into 3'
549 $ hg ci -m 'merge 1 into 3'
550 Try merging the other direction too
550 Try merging the other direction too
551 $ hg co -q 1
551 $ hg co -q 1
552 $ hg merge -q 3
552 $ hg merge -q 3
553 $ echo z > z
553 $ echo z > z
554 $ hg ci -m 'merge 3 into 1'
554 $ hg ci -m 'merge 3 into 1'
555 created new head
555 created new head
556 $ hg l
556 $ hg l
557 @ 5 merge 3 into 1
557 @ 5 merge 3 into 1
558 |\ z
558 |\ z
559 +---o 4 merge 1 into 3
559 +---o 4 merge 1 into 3
560 | |/ z
560 | |/ z
561 | o 3 delete z
561 | o 3 delete z
562 | | z
562 | | z
563 | o 2 rename y to z
563 | o 2 rename y to z
564 | | y z
564 | | y z
565 o | 1 rename x to z
565 o | 1 rename x to z
566 |/ x z
566 |/ x z
567 o 0 add x and y
567 o 0 add x and y
568 x y
568 x y
569 $ hg debugpathcopies 1 4
569 $ hg debugpathcopies 1 4
570 $ hg debugpathcopies 2 4
570 $ hg debugpathcopies 2 4
571 x -> z (no-filelog !)
571 x -> z (no-filelog !)
572 $ hg debugpathcopies 0 4
572 $ hg debugpathcopies 0 4
573 x -> z (no-changeset no-compatibility !)
573 x -> z (no-changeset no-compatibility !)
574 $ hg debugpathcopies 1 5
574 $ hg debugpathcopies 1 5
575 $ hg debugpathcopies 2 5
575 $ hg debugpathcopies 2 5
576 x -> z (no-filelog !)
576 x -> z (no-filelog !)
577 $ hg debugpathcopies 0 5
577 $ hg debugpathcopies 0 5
578 x -> z
578 x -> z
579
579
580
580
581 Test for a case in fullcopytracing algorithm where neither of the merging csets
581 Test for a case in fullcopytracing algorithm where neither of the merging csets
582 is a descendant of the merge base. This test reflects that the algorithm
582 is a descendant of the merge base. This test reflects that the algorithm
583 correctly finds the copies:
583 correctly finds the copies:
584
584
585 $ cat >> $HGRCPATH << EOF
585 $ cat >> $HGRCPATH << EOF
586 > [experimental]
586 > [experimental]
587 > evolution.createmarkers=True
587 > evolution.createmarkers=True
588 > evolution.allowunstable=True
588 > evolution.allowunstable=True
589 > EOF
589 > EOF
590
590
591 $ newrepo
591 $ newrepo
592 $ echo a > a
592 $ echo a > a
593 $ hg add a
593 $ hg add a
594 $ hg ci -m "added a"
594 $ hg ci -m "added a"
595 $ echo b > b
595 $ echo b > b
596 $ hg add b
596 $ hg add b
597 $ hg ci -m "added b"
597 $ hg ci -m "added b"
598
598
599 $ hg mv b b1
599 $ hg mv b b1
600 $ hg ci -m "rename b to b1"
600 $ hg ci -m "rename b to b1"
601
601
602 $ hg up ".^"
602 $ hg up ".^"
603 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
603 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
604 $ echo d > d
604 $ echo d > d
605 $ hg add d
605 $ hg add d
606 $ hg ci -m "added d"
606 $ hg ci -m "added d"
607 created new head
607 created new head
608
608
609 $ echo baba >> b
609 $ echo baba >> b
610 $ hg ci --amend -m "added d, modified b"
610 $ hg ci --amend -m "added d, modified b"
611
611
612 $ hg l --hidden
612 $ hg l --hidden
613 @ 4 added d, modified b
613 @ 4 added d, modified b
614 | b d
614 | b d
615 | x 3 added d
615 | x 3 added d
616 |/ d
616 |/ d
617 | o 2 rename b to b1
617 | o 2 rename b to b1
618 |/ b b1
618 |/ b b1
619 o 1 added b
619 o 1 added b
620 | b
620 | b
621 o 0 added a
621 o 0 added a
622 a
622 a
623
623
624 Grafting revision 4 on top of revision 2, showing that it respect the rename:
624 Grafting revision 4 on top of revision 2, showing that it respect the rename:
625
625
626 $ hg up 2 -q
626 $ hg up 2 -q
627 $ hg graft -r 4 --base 3 --hidden
627 $ hg graft -r 4 --base 3 --hidden
628 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
628 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
629 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
629 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
630 merging b1 and b to b1
630 merging b1 and b to b1
631
631
632 $ hg l -l1 -p
632 $ hg l -l1 -p
633 @ 5 added d, modified b
633 @ 5 added d, modified b
634 | b1
634 | b1
635 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
635 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
636 ~ diff -r 0a0ed3b3251c -r d544fb655520 b1 (changeset !)
636 ~ diff -r 0a0ed3b3251c -r d544fb655520 b1 (changeset !)
637 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
637 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
638 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
638 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
639 @@ -1,1 +1,2 @@
639 @@ -1,1 +1,2 @@
640 b
640 b
641 +baba
641 +baba
642
642
643 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
643 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
644 merging csets is a descendant of the base.
644 merging csets is a descendant of the base.
645 -------------------------------------------------------------------------------------------------
645 -------------------------------------------------------------------------------------------------
646
646
647 $ newrepo
647 $ newrepo
648 $ echo a > a
648 $ echo a > a
649 $ hg add a
649 $ hg add a
650 $ hg ci -m "added a"
650 $ hg ci -m "added a"
651 $ echo b > b
651 $ echo b > b
652 $ hg add b
652 $ hg add b
653 $ hg ci -m "added b"
653 $ hg ci -m "added b"
654
654
655 $ echo foobar > willconflict
655 $ echo foobar > willconflict
656 $ hg add willconflict
656 $ hg add willconflict
657 $ hg ci -m "added willconflict"
657 $ hg ci -m "added willconflict"
658 $ echo c > c
658 $ echo c > c
659 $ hg add c
659 $ hg add c
660 $ hg ci -m "added c"
660 $ hg ci -m "added c"
661
661
662 $ hg l
662 $ hg l
663 @ 3 added c
663 @ 3 added c
664 | c
664 | c
665 o 2 added willconflict
665 o 2 added willconflict
666 | willconflict
666 | willconflict
667 o 1 added b
667 o 1 added b
668 | b
668 | b
669 o 0 added a
669 o 0 added a
670 a
670 a
671
671
672 $ hg up ".^^"
672 $ hg up ".^^"
673 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
673 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
674 $ echo d > d
674 $ echo d > d
675 $ hg add d
675 $ hg add d
676 $ hg ci -m "added d"
676 $ hg ci -m "added d"
677 created new head
677 created new head
678
678
679 $ echo barfoo > willconflict
679 $ echo barfoo > willconflict
680 $ hg add willconflict
680 $ hg add willconflict
681 $ hg ci --amend -m "added willconflict and d"
681 $ hg ci --amend -m "added willconflict and d"
682
682
683 $ hg l
683 $ hg l
684 @ 5 added willconflict and d
684 @ 5 added willconflict and d
685 | d willconflict
685 | d willconflict
686 | o 3 added c
686 | o 3 added c
687 | | c
687 | | c
688 | o 2 added willconflict
688 | o 2 added willconflict
689 |/ willconflict
689 |/ willconflict
690 o 1 added b
690 o 1 added b
691 | b
691 | b
692 o 0 added a
692 o 0 added a
693 a
693 a
694
694
695 $ hg rebase -r . -d 2 -t :other
695 $ hg rebase -r . -d 2 -t :other
696 rebasing 5:5018b1509e94 tip "added willconflict and d" (no-changeset !)
696 rebasing 5:5018b1509e94 tip "added willconflict and d" (no-changeset !)
697 rebasing 5:af8d273bf580 tip "added willconflict and d" (changeset !)
697 rebasing 5:af8d273bf580 tip "added willconflict and d" (changeset !)
698
698
699 $ hg up 3 -q
699 $ hg up 3 -q
700 $ hg l --hidden
700 $ hg l --hidden
701 o 6 added willconflict and d
701 o 6 added willconflict and d
702 | d willconflict
702 | d willconflict
703 | x 5 added willconflict and d
703 | x 5 added willconflict and d
704 | | d willconflict
704 | | d willconflict
705 | | x 4 added d
705 | | x 4 added d
706 | |/ d
706 | |/ d
707 +---@ 3 added c
707 +---@ 3 added c
708 | | c
708 | | c
709 o | 2 added willconflict
709 o | 2 added willconflict
710 |/ willconflict
710 |/ willconflict
711 o 1 added b
711 o 1 added b
712 | b
712 | b
713 o 0 added a
713 o 0 added a
714 a
714 a
715
715
716 Now if we trigger a merge between revision 3 and 6 using base revision 4,
716 Now if we trigger a merge between revision 3 and 6 using base revision 4,
717 neither of the merging csets will be a descendant of the base revision:
717 neither of the merging csets will be a descendant of the base revision:
718
718
719 $ hg graft -r 6 --base 4 --hidden -t :other
719 $ hg graft -r 6 --base 4 --hidden -t :other
720 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
720 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
721 grafting 6:b19f0df72728 "added willconflict and d" (tip) (changeset !)
721 grafting 6:b19f0df72728 "added willconflict and d" (tip) (changeset !)
General Comments 0
You need to be logged in to leave comments. Login now