##// END OF EJS Templates
copies: fix the changeset based algorithm regarding merge...
marmoute -
r45252:45f3f35c default
parent child Browse files
Show More
@@ -1,1171 +1,1241 b''
1 # copies.py - copy detection for Mercurial
1 # copies.py - copy detection for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import multiprocessing
11 import multiprocessing
12 import os
12 import os
13
13
14 from .i18n import _
14 from .i18n import _
15
15
16
16
17 from .revlogutils.flagutil import REVIDX_SIDEDATA
17 from .revlogutils.flagutil import REVIDX_SIDEDATA
18
18
19 from . import (
19 from . import (
20 error,
20 error,
21 match as matchmod,
21 match as matchmod,
22 node,
22 node,
23 pathutil,
23 pathutil,
24 pycompat,
24 pycompat,
25 util,
25 util,
26 )
26 )
27
27
28 from .revlogutils import sidedata as sidedatamod
28 from .revlogutils import sidedata as sidedatamod
29
29
30 from .utils import stringutil
30 from .utils import stringutil
31
31
32
32
33 def _filter(src, dst, t):
33 def _filter(src, dst, t):
34 """filters out invalid copies after chaining"""
34 """filters out invalid copies after chaining"""
35
35
36 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
36 # When _chain()'ing copies in 'a' (from 'src' via some other commit 'mid')
37 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
37 # with copies in 'b' (from 'mid' to 'dst'), we can get the different cases
38 # in the following table (not including trivial cases). For example, case 2
38 # in the following table (not including trivial cases). For example, case 2
39 # is where a file existed in 'src' and remained under that name in 'mid' and
39 # is where a file existed in 'src' and remained under that name in 'mid' and
40 # then was renamed between 'mid' and 'dst'.
40 # then was renamed between 'mid' and 'dst'.
41 #
41 #
42 # case src mid dst result
42 # case src mid dst result
43 # 1 x y - -
43 # 1 x y - -
44 # 2 x y y x->y
44 # 2 x y y x->y
45 # 3 x y x -
45 # 3 x y x -
46 # 4 x y z x->z
46 # 4 x y z x->z
47 # 5 - x y -
47 # 5 - x y -
48 # 6 x x y x->y
48 # 6 x x y x->y
49 #
49 #
50 # _chain() takes care of chaining the copies in 'a' and 'b', but it
50 # _chain() takes care of chaining the copies in 'a' and 'b', but it
51 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
51 # cannot tell the difference between cases 1 and 2, between 3 and 4, or
52 # between 5 and 6, so it includes all cases in its result.
52 # between 5 and 6, so it includes all cases in its result.
53 # Cases 1, 3, and 5 are then removed by _filter().
53 # Cases 1, 3, and 5 are then removed by _filter().
54
54
55 for k, v in list(t.items()):
55 for k, v in list(t.items()):
56 # remove copies from files that didn't exist
56 # remove copies from files that didn't exist
57 if v not in src:
57 if v not in src:
58 del t[k]
58 del t[k]
59 # remove criss-crossed copies
59 # remove criss-crossed copies
60 elif k in src and v in dst:
60 elif k in src and v in dst:
61 del t[k]
61 del t[k]
62 # remove copies to files that were then removed
62 # remove copies to files that were then removed
63 elif k not in dst:
63 elif k not in dst:
64 del t[k]
64 del t[k]
65
65
66
66
67 def _chain(prefix, suffix):
67 def _chain(prefix, suffix):
68 """chain two sets of copies 'prefix' and 'suffix'"""
68 """chain two sets of copies 'prefix' and 'suffix'"""
69 result = prefix.copy()
69 result = prefix.copy()
70 for key, value in pycompat.iteritems(suffix):
70 for key, value in pycompat.iteritems(suffix):
71 result[key] = prefix.get(value, value)
71 result[key] = prefix.get(value, value)
72 return result
72 return result
73
73
74
74
75 def _tracefile(fctx, am, basemf):
75 def _tracefile(fctx, am, basemf):
76 """return file context that is the ancestor of fctx present in ancestor
76 """return file context that is the ancestor of fctx present in ancestor
77 manifest am
77 manifest am
78
78
79 Note: we used to try and stop after a given limit, however checking if that
79 Note: we used to try and stop after a given limit, however checking if that
80 limit is reached turned out to be very expensive. we are better off
80 limit is reached turned out to be very expensive. we are better off
81 disabling that feature."""
81 disabling that feature."""
82
82
83 for f in fctx.ancestors():
83 for f in fctx.ancestors():
84 path = f.path()
84 path = f.path()
85 if am.get(path, None) == f.filenode():
85 if am.get(path, None) == f.filenode():
86 return path
86 return path
87 if basemf and basemf.get(path, None) == f.filenode():
87 if basemf and basemf.get(path, None) == f.filenode():
88 return path
88 return path
89
89
90
90
91 def _dirstatecopies(repo, match=None):
91 def _dirstatecopies(repo, match=None):
92 ds = repo.dirstate
92 ds = repo.dirstate
93 c = ds.copies().copy()
93 c = ds.copies().copy()
94 for k in list(c):
94 for k in list(c):
95 if ds[k] not in b'anm' or (match and not match(k)):
95 if ds[k] not in b'anm' or (match and not match(k)):
96 del c[k]
96 del c[k]
97 return c
97 return c
98
98
99
99
100 def _computeforwardmissing(a, b, match=None):
100 def _computeforwardmissing(a, b, match=None):
101 """Computes which files are in b but not a.
101 """Computes which files are in b but not a.
102 This is its own function so extensions can easily wrap this call to see what
102 This is its own function so extensions can easily wrap this call to see what
103 files _forwardcopies is about to process.
103 files _forwardcopies is about to process.
104 """
104 """
105 ma = a.manifest()
105 ma = a.manifest()
106 mb = b.manifest()
106 mb = b.manifest()
107 return mb.filesnotin(ma, match=match)
107 return mb.filesnotin(ma, match=match)
108
108
109
109
110 def usechangesetcentricalgo(repo):
110 def usechangesetcentricalgo(repo):
111 """Checks if we should use changeset-centric copy algorithms"""
111 """Checks if we should use changeset-centric copy algorithms"""
112 if repo.filecopiesmode == b'changeset-sidedata':
112 if repo.filecopiesmode == b'changeset-sidedata':
113 return True
113 return True
114 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
114 readfrom = repo.ui.config(b'experimental', b'copies.read-from')
115 changesetsource = (b'changeset-only', b'compatibility')
115 changesetsource = (b'changeset-only', b'compatibility')
116 return readfrom in changesetsource
116 return readfrom in changesetsource
117
117
118
118
119 def _committedforwardcopies(a, b, base, match):
119 def _committedforwardcopies(a, b, base, match):
120 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
120 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
121 # files might have to be traced back to the fctx parent of the last
121 # files might have to be traced back to the fctx parent of the last
122 # one-side-only changeset, but not further back than that
122 # one-side-only changeset, but not further back than that
123 repo = a._repo
123 repo = a._repo
124
124
125 if usechangesetcentricalgo(repo):
125 if usechangesetcentricalgo(repo):
126 return _changesetforwardcopies(a, b, match)
126 return _changesetforwardcopies(a, b, match)
127
127
128 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
128 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
129 dbg = repo.ui.debug
129 dbg = repo.ui.debug
130 if debug:
130 if debug:
131 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
131 dbg(b'debug.copies: looking into rename from %s to %s\n' % (a, b))
132 am = a.manifest()
132 am = a.manifest()
133 basemf = None if base is None else base.manifest()
133 basemf = None if base is None else base.manifest()
134
134
135 # find where new files came from
135 # find where new files came from
136 # we currently don't try to find where old files went, too expensive
136 # we currently don't try to find where old files went, too expensive
137 # this means we can miss a case like 'hg rm b; hg cp a b'
137 # this means we can miss a case like 'hg rm b; hg cp a b'
138 cm = {}
138 cm = {}
139
139
140 # Computing the forward missing is quite expensive on large manifests, since
140 # Computing the forward missing is quite expensive on large manifests, since
141 # it compares the entire manifests. We can optimize it in the common use
141 # it compares the entire manifests. We can optimize it in the common use
142 # case of computing what copies are in a commit versus its parent (like
142 # case of computing what copies are in a commit versus its parent (like
143 # during a rebase or histedit). Note, we exclude merge commits from this
143 # during a rebase or histedit). Note, we exclude merge commits from this
144 # optimization, since the ctx.files() for a merge commit is not correct for
144 # optimization, since the ctx.files() for a merge commit is not correct for
145 # this comparison.
145 # this comparison.
146 forwardmissingmatch = match
146 forwardmissingmatch = match
147 if b.p1() == a and b.p2().node() == node.nullid:
147 if b.p1() == a and b.p2().node() == node.nullid:
148 filesmatcher = matchmod.exact(b.files())
148 filesmatcher = matchmod.exact(b.files())
149 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
149 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
150 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
150 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
151
151
152 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
152 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
153
153
154 if debug:
154 if debug:
155 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
155 dbg(b'debug.copies: missing files to search: %d\n' % len(missing))
156
156
157 for f in sorted(missing):
157 for f in sorted(missing):
158 if debug:
158 if debug:
159 dbg(b'debug.copies: tracing file: %s\n' % f)
159 dbg(b'debug.copies: tracing file: %s\n' % f)
160 fctx = b[f]
160 fctx = b[f]
161 fctx._ancestrycontext = ancestrycontext
161 fctx._ancestrycontext = ancestrycontext
162
162
163 if debug:
163 if debug:
164 start = util.timer()
164 start = util.timer()
165 opath = _tracefile(fctx, am, basemf)
165 opath = _tracefile(fctx, am, basemf)
166 if opath:
166 if opath:
167 if debug:
167 if debug:
168 dbg(b'debug.copies: rename of: %s\n' % opath)
168 dbg(b'debug.copies: rename of: %s\n' % opath)
169 cm[f] = opath
169 cm[f] = opath
170 if debug:
170 if debug:
171 dbg(
171 dbg(
172 b'debug.copies: time: %f seconds\n'
172 b'debug.copies: time: %f seconds\n'
173 % (util.timer() - start)
173 % (util.timer() - start)
174 )
174 )
175 return cm
175 return cm
176
176
177
177
178 def _revinfogetter(repo):
178 def _revinfogetter(repo):
179 """return a function that return multiple data given a <rev>"i
179 """return a function that return multiple data given a <rev>"i
180
180
181 * p1: revision number of first parent
181 * p1: revision number of first parent
182 * p2: revision number of first parent
182 * p2: revision number of first parent
183 * p1copies: mapping of copies from p1
183 * p1copies: mapping of copies from p1
184 * p2copies: mapping of copies from p2
184 * p2copies: mapping of copies from p2
185 * removed: a list of removed files
185 * removed: a list of removed files
186 * ismerged: a callback to know if file was merged in that revision
186 """
187 """
187 cl = repo.changelog
188 cl = repo.changelog
188 parents = cl.parentrevs
189 parents = cl.parentrevs
189
190
191 def get_ismerged(rev):
192 ctx = repo[rev]
193
194 def ismerged(path):
195 if path not in ctx.files():
196 return False
197 fctx = ctx[path]
198 parents = fctx._filelog.parents(fctx._filenode)
199 nb_parents = 0
200 for n in parents:
201 if n != node.nullid:
202 nb_parents += 1
203 return nb_parents >= 2
204
205 return ismerged
206
190 if repo.filecopiesmode == b'changeset-sidedata':
207 if repo.filecopiesmode == b'changeset-sidedata':
191 changelogrevision = cl.changelogrevision
208 changelogrevision = cl.changelogrevision
192 flags = cl.flags
209 flags = cl.flags
193
210
194 # A small cache to avoid doing the work twice for merges
211 # A small cache to avoid doing the work twice for merges
195 #
212 #
196 # In the vast majority of cases, if we ask information for a revision
213 # In the vast majority of cases, if we ask information for a revision
197 # about 1 parent, we'll later ask it for the other. So it make sense to
214 # about 1 parent, we'll later ask it for the other. So it make sense to
198 # keep the information around when reaching the first parent of a merge
215 # keep the information around when reaching the first parent of a merge
199 # and dropping it after it was provided for the second parents.
216 # and dropping it after it was provided for the second parents.
200 #
217 #
201 # It exists cases were only one parent of the merge will be walked. It
218 # It exists cases were only one parent of the merge will be walked. It
202 # happens when the "destination" the copy tracing is descendant from a
219 # happens when the "destination" the copy tracing is descendant from a
203 # new root, not common with the "source". In that case, we will only walk
220 # new root, not common with the "source". In that case, we will only walk
204 # through merge parents that are descendant of changesets common
221 # through merge parents that are descendant of changesets common
205 # between "source" and "destination".
222 # between "source" and "destination".
206 #
223 #
207 # With the current case implementation if such changesets have a copy
224 # With the current case implementation if such changesets have a copy
208 # information, we'll keep them in memory until the end of
225 # information, we'll keep them in memory until the end of
209 # _changesetforwardcopies. We don't expect the case to be frequent
226 # _changesetforwardcopies. We don't expect the case to be frequent
210 # enough to matters.
227 # enough to matters.
211 #
228 #
212 # In addition, it would be possible to reach pathological case, were
229 # In addition, it would be possible to reach pathological case, were
213 # many first parent are met before any second parent is reached. In
230 # many first parent are met before any second parent is reached. In
214 # that case the cache could grow. If this even become an issue one can
231 # that case the cache could grow. If this even become an issue one can
215 # safely introduce a maximum cache size. This would trade extra CPU/IO
232 # safely introduce a maximum cache size. This would trade extra CPU/IO
216 # time to save memory.
233 # time to save memory.
217 merge_caches = {}
234 merge_caches = {}
218
235
219 def revinfo(rev):
236 def revinfo(rev):
220 p1, p2 = parents(rev)
237 p1, p2 = parents(rev)
238 value = None
221 if flags(rev) & REVIDX_SIDEDATA:
239 if flags(rev) & REVIDX_SIDEDATA:
222 e = merge_caches.pop(rev, None)
240 e = merge_caches.pop(rev, None)
223 if e is not None:
241 if e is not None:
224 return e
242 return e
225 c = changelogrevision(rev)
243 c = changelogrevision(rev)
226 p1copies = c.p1copies
244 p1copies = c.p1copies
227 p2copies = c.p2copies
245 p2copies = c.p2copies
228 removed = c.filesremoved
246 removed = c.filesremoved
229 if p1 != node.nullrev and p2 != node.nullrev:
247 if p1 != node.nullrev and p2 != node.nullrev:
230 # XXX some case we over cache, IGNORE
248 # XXX some case we over cache, IGNORE
231 merge_caches[rev] = (p1, p2, p1copies, p2copies, removed)
249 value = merge_caches[rev] = (
250 p1,
251 p2,
252 p1copies,
253 p2copies,
254 removed,
255 get_ismerged(rev),
256 )
232 else:
257 else:
233 p1copies = {}
258 p1copies = {}
234 p2copies = {}
259 p2copies = {}
235 removed = []
260 removed = []
236 return p1, p2, p1copies, p2copies, removed
261
262 if value is None:
263 value = (p1, p2, p1copies, p2copies, removed, get_ismerged(rev))
264 return value
237
265
238 else:
266 else:
239
267
240 def revinfo(rev):
268 def revinfo(rev):
241 p1, p2 = parents(rev)
269 p1, p2 = parents(rev)
242 ctx = repo[rev]
270 ctx = repo[rev]
243 p1copies, p2copies = ctx._copies
271 p1copies, p2copies = ctx._copies
244 removed = ctx.filesremoved()
272 removed = ctx.filesremoved()
245 return p1, p2, p1copies, p2copies, removed
273 return p1, p2, p1copies, p2copies, removed, get_ismerged(rev)
246
274
247 return revinfo
275 return revinfo
248
276
249
277
250 def _changesetforwardcopies(a, b, match):
278 def _changesetforwardcopies(a, b, match):
251 if a.rev() in (node.nullrev, b.rev()):
279 if a.rev() in (node.nullrev, b.rev()):
252 return {}
280 return {}
253
281
254 repo = a.repo().unfiltered()
282 repo = a.repo().unfiltered()
255 children = {}
283 children = {}
256 revinfo = _revinfogetter(repo)
284 revinfo = _revinfogetter(repo)
257
285
258 cl = repo.changelog
286 cl = repo.changelog
287 isancestor = cl.isancestorrev # XXX we should had chaching to this.
259 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
288 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
260 mrset = set(missingrevs)
289 mrset = set(missingrevs)
261 roots = set()
290 roots = set()
262 for r in missingrevs:
291 for r in missingrevs:
263 for p in cl.parentrevs(r):
292 for p in cl.parentrevs(r):
264 if p == node.nullrev:
293 if p == node.nullrev:
265 continue
294 continue
266 if p not in children:
295 if p not in children:
267 children[p] = [r]
296 children[p] = [r]
268 else:
297 else:
269 children[p].append(r)
298 children[p].append(r)
270 if p not in mrset:
299 if p not in mrset:
271 roots.add(p)
300 roots.add(p)
272 if not roots:
301 if not roots:
273 # no common revision to track copies from
302 # no common revision to track copies from
274 return {}
303 return {}
275 min_root = min(roots)
304 min_root = min(roots)
276
305
277 from_head = set(
306 from_head = set(
278 cl.reachableroots(min_root, [b.rev()], list(roots), includepath=True)
307 cl.reachableroots(min_root, [b.rev()], list(roots), includepath=True)
279 )
308 )
280
309
281 iterrevs = set(from_head)
310 iterrevs = set(from_head)
282 iterrevs &= mrset
311 iterrevs &= mrset
283 iterrevs.update(roots)
312 iterrevs.update(roots)
284 iterrevs.remove(b.rev())
313 iterrevs.remove(b.rev())
285 revs = sorted(iterrevs)
314 revs = sorted(iterrevs)
286 return _combinechangesetcopies(revs, children, b.rev(), revinfo, match)
315 return _combinechangesetcopies(
316 revs, children, b.rev(), revinfo, match, isancestor
317 )
287
318
288
319
289 def _combinechangesetcopies(revs, children, targetrev, revinfo, match):
320 def _combinechangesetcopies(
321 revs, children, targetrev, revinfo, match, isancestor
322 ):
290 """combine the copies information for each item of iterrevs
323 """combine the copies information for each item of iterrevs
291
324
292 revs: sorted iterable of revision to visit
325 revs: sorted iterable of revision to visit
293 children: a {parent: [children]} mapping.
326 children: a {parent: [children]} mapping.
294 targetrev: the final copies destination revision (not in iterrevs)
327 targetrev: the final copies destination revision (not in iterrevs)
295 revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
328 revinfo(rev): a function that return (p1, p2, p1copies, p2copies, removed)
296 match: a matcher
329 match: a matcher
297
330
298 It returns the aggregated copies information for `targetrev`.
331 It returns the aggregated copies information for `targetrev`.
299 """
332 """
300 all_copies = {}
333 all_copies = {}
301 alwaysmatch = match.always()
334 alwaysmatch = match.always()
302 for r in revs:
335 for r in revs:
303 copies = all_copies.pop(r, None)
336 copies = all_copies.pop(r, None)
304 if copies is None:
337 if copies is None:
305 # this is a root
338 # this is a root
306 copies = {}
339 copies = {}
307 for i, c in enumerate(children[r]):
340 for i, c in enumerate(children[r]):
308 p1, p2, p1copies, p2copies, removed = revinfo(c)
341 p1, p2, p1copies, p2copies, removed, ismerged = revinfo(c)
309 if r == p1:
342 if r == p1:
310 parent = 1
343 parent = 1
311 childcopies = p1copies
344 childcopies = p1copies
312 else:
345 else:
313 assert r == p2
346 assert r == p2
314 parent = 2
347 parent = 2
315 childcopies = p2copies
348 childcopies = p2copies
316 if not alwaysmatch:
349 if not alwaysmatch:
317 childcopies = {
350 childcopies = {
318 dst: src for dst, src in childcopies.items() if match(dst)
351 dst: src for dst, src in childcopies.items() if match(dst)
319 }
352 }
320 newcopies = copies
353 newcopies = copies
321 if childcopies:
354 if childcopies:
322 newcopies = _chain(newcopies, childcopies)
355 newcopies = copies.copy()
323 # _chain makes a copies, we can avoid doing so in some
356 for dest, source in pycompat.iteritems(childcopies):
324 # simple/linear cases.
357 prev = copies.get(source)
358 if prev is not None and prev[1] is not None:
359 source = prev[1]
360 newcopies[dest] = (c, source)
325 assert newcopies is not copies
361 assert newcopies is not copies
326 for f in removed:
362 for f in removed:
327 if f in newcopies:
363 if f in newcopies:
328 if newcopies is copies:
364 if newcopies is copies:
329 # copy on write to avoid affecting potential other
365 # copy on write to avoid affecting potential other
330 # branches. when there are no other branches, this
366 # branches. when there are no other branches, this
331 # could be avoided.
367 # could be avoided.
332 newcopies = copies.copy()
368 newcopies = copies.copy()
333 del newcopies[f]
369 newcopies[f] = (c, None)
334 othercopies = all_copies.get(c)
370 othercopies = all_copies.get(c)
335 if othercopies is None:
371 if othercopies is None:
336 all_copies[c] = newcopies
372 all_copies[c] = newcopies
337 else:
373 else:
338 # we are the second parent to work on c, we need to merge our
374 # we are the second parent to work on c, we need to merge our
339 # work with the other.
375 # work with the other.
340 #
376 #
341 # Unlike when copies are stored in the filelog, we consider
342 # it a copy even if the destination already existed on the
343 # other branch. It's simply too expensive to check if the
344 # file existed in the manifest.
345 #
346 # In case of conflict, parent 1 take precedence over parent 2.
377 # In case of conflict, parent 1 take precedence over parent 2.
347 # This is an arbitrary choice made anew when implementing
378 # This is an arbitrary choice made anew when implementing
348 # changeset based copies. It was made without regards with
379 # changeset based copies. It was made without regards with
349 # potential filelog related behavior.
380 # potential filelog related behavior.
350 if parent == 1:
381 if parent == 1:
351 othercopies.update(newcopies)
382 _merge_copies_dict(
383 othercopies, newcopies, isancestor, ismerged
384 )
352 else:
385 else:
353 newcopies.update(othercopies)
386 _merge_copies_dict(
387 newcopies, othercopies, isancestor, ismerged
388 )
354 all_copies[c] = newcopies
389 all_copies[c] = newcopies
355 return all_copies[targetrev]
390
391 final_copies = {}
392 for dest, (tt, source) in all_copies[targetrev].items():
393 if source is not None:
394 final_copies[dest] = source
395 return final_copies
396
397
398 def _merge_copies_dict(minor, major, isancestor, ismerged):
399 """merge two copies-mapping together, minor and major
400
401 In case of conflict, value from "major" will be picked.
402
403 - `isancestors(low_rev, high_rev)`: callable return True if `low_rev` is an
404 ancestors of `high_rev`,
405
406 - `ismerged(path)`: callable return True if `path` have been merged in the
407 current revision,
408 """
409 for dest, value in major.items():
410 other = minor.get(dest)
411 if other is None:
412 minor[dest] = value
413 else:
414 new_tt = value[0]
415 other_tt = other[0]
416 if value[1] == other[1]:
417 continue
418 # content from "major" wins, unless it is older
419 # than the branch point or there is a merge
420 if (
421 new_tt == other_tt
422 or not isancestor(new_tt, other_tt)
423 or ismerged(dest)
424 ):
425 minor[dest] = value
356
426
357
427
358 def _forwardcopies(a, b, base=None, match=None):
428 def _forwardcopies(a, b, base=None, match=None):
359 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
429 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
360
430
361 if base is None:
431 if base is None:
362 base = a
432 base = a
363 match = a.repo().narrowmatch(match)
433 match = a.repo().narrowmatch(match)
364 # check for working copy
434 # check for working copy
365 if b.rev() is None:
435 if b.rev() is None:
366 cm = _committedforwardcopies(a, b.p1(), base, match)
436 cm = _committedforwardcopies(a, b.p1(), base, match)
367 # combine copies from dirstate if necessary
437 # combine copies from dirstate if necessary
368 copies = _chain(cm, _dirstatecopies(b._repo, match))
438 copies = _chain(cm, _dirstatecopies(b._repo, match))
369 else:
439 else:
370 copies = _committedforwardcopies(a, b, base, match)
440 copies = _committedforwardcopies(a, b, base, match)
371 return copies
441 return copies
372
442
373
443
374 def _backwardrenames(a, b, match):
444 def _backwardrenames(a, b, match):
375 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
445 if a._repo.ui.config(b'experimental', b'copytrace') == b'off':
376 return {}
446 return {}
377
447
378 # Even though we're not taking copies into account, 1:n rename situations
448 # Even though we're not taking copies into account, 1:n rename situations
379 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
449 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
380 # arbitrarily pick one of the renames.
450 # arbitrarily pick one of the renames.
381 # We don't want to pass in "match" here, since that would filter
451 # We don't want to pass in "match" here, since that would filter
382 # the destination by it. Since we're reversing the copies, we want
452 # the destination by it. Since we're reversing the copies, we want
383 # to filter the source instead.
453 # to filter the source instead.
384 f = _forwardcopies(b, a)
454 f = _forwardcopies(b, a)
385 r = {}
455 r = {}
386 for k, v in sorted(pycompat.iteritems(f)):
456 for k, v in sorted(pycompat.iteritems(f)):
387 if match and not match(v):
457 if match and not match(v):
388 continue
458 continue
389 # remove copies
459 # remove copies
390 if v in a:
460 if v in a:
391 continue
461 continue
392 r[v] = k
462 r[v] = k
393 return r
463 return r
394
464
395
465
396 def pathcopies(x, y, match=None):
466 def pathcopies(x, y, match=None):
397 """find {dst@y: src@x} copy mapping for directed compare"""
467 """find {dst@y: src@x} copy mapping for directed compare"""
398 repo = x._repo
468 repo = x._repo
399 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
469 debug = repo.ui.debugflag and repo.ui.configbool(b'devel', b'debug.copies')
400 if debug:
470 if debug:
401 repo.ui.debug(
471 repo.ui.debug(
402 b'debug.copies: searching copies from %s to %s\n' % (x, y)
472 b'debug.copies: searching copies from %s to %s\n' % (x, y)
403 )
473 )
404 if x == y or not x or not y:
474 if x == y or not x or not y:
405 return {}
475 return {}
406 if y.rev() is None and x == y.p1():
476 if y.rev() is None and x == y.p1():
407 if debug:
477 if debug:
408 repo.ui.debug(b'debug.copies: search mode: dirstate\n')
478 repo.ui.debug(b'debug.copies: search mode: dirstate\n')
409 # short-circuit to avoid issues with merge states
479 # short-circuit to avoid issues with merge states
410 return _dirstatecopies(repo, match)
480 return _dirstatecopies(repo, match)
411 a = y.ancestor(x)
481 a = y.ancestor(x)
412 if a == x:
482 if a == x:
413 if debug:
483 if debug:
414 repo.ui.debug(b'debug.copies: search mode: forward\n')
484 repo.ui.debug(b'debug.copies: search mode: forward\n')
415 copies = _forwardcopies(x, y, match=match)
485 copies = _forwardcopies(x, y, match=match)
416 elif a == y:
486 elif a == y:
417 if debug:
487 if debug:
418 repo.ui.debug(b'debug.copies: search mode: backward\n')
488 repo.ui.debug(b'debug.copies: search mode: backward\n')
419 copies = _backwardrenames(x, y, match=match)
489 copies = _backwardrenames(x, y, match=match)
420 else:
490 else:
421 if debug:
491 if debug:
422 repo.ui.debug(b'debug.copies: search mode: combined\n')
492 repo.ui.debug(b'debug.copies: search mode: combined\n')
423 base = None
493 base = None
424 if a.rev() != node.nullrev:
494 if a.rev() != node.nullrev:
425 base = x
495 base = x
426 copies = _chain(
496 copies = _chain(
427 _backwardrenames(x, a, match=match),
497 _backwardrenames(x, a, match=match),
428 _forwardcopies(a, y, base, match=match),
498 _forwardcopies(a, y, base, match=match),
429 )
499 )
430 _filter(x, y, copies)
500 _filter(x, y, copies)
431 return copies
501 return copies
432
502
433
503
434 def mergecopies(repo, c1, c2, base):
504 def mergecopies(repo, c1, c2, base):
435 """
505 """
436 Finds moves and copies between context c1 and c2 that are relevant for
506 Finds moves and copies between context c1 and c2 that are relevant for
437 merging. 'base' will be used as the merge base.
507 merging. 'base' will be used as the merge base.
438
508
439 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
509 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
440 files that were moved/ copied in one merge parent and modified in another.
510 files that were moved/ copied in one merge parent and modified in another.
441 For example:
511 For example:
442
512
443 o ---> 4 another commit
513 o ---> 4 another commit
444 |
514 |
445 | o ---> 3 commit that modifies a.txt
515 | o ---> 3 commit that modifies a.txt
446 | /
516 | /
447 o / ---> 2 commit that moves a.txt to b.txt
517 o / ---> 2 commit that moves a.txt to b.txt
448 |/
518 |/
449 o ---> 1 merge base
519 o ---> 1 merge base
450
520
451 If we try to rebase revision 3 on revision 4, since there is no a.txt in
521 If we try to rebase revision 3 on revision 4, since there is no a.txt in
452 revision 4, and if user have copytrace disabled, we prints the following
522 revision 4, and if user have copytrace disabled, we prints the following
453 message:
523 message:
454
524
455 ```other changed <file> which local deleted```
525 ```other changed <file> which local deleted```
456
526
457 Returns a tuple where:
527 Returns a tuple where:
458
528
459 "branch_copies" an instance of branch_copies.
529 "branch_copies" an instance of branch_copies.
460
530
461 "diverge" is a mapping of source name -> list of destination names
531 "diverge" is a mapping of source name -> list of destination names
462 for divergent renames.
532 for divergent renames.
463
533
464 This function calls different copytracing algorithms based on config.
534 This function calls different copytracing algorithms based on config.
465 """
535 """
466 # avoid silly behavior for update from empty dir
536 # avoid silly behavior for update from empty dir
467 if not c1 or not c2 or c1 == c2:
537 if not c1 or not c2 or c1 == c2:
468 return branch_copies(), branch_copies(), {}
538 return branch_copies(), branch_copies(), {}
469
539
470 narrowmatch = c1.repo().narrowmatch()
540 narrowmatch = c1.repo().narrowmatch()
471
541
472 # avoid silly behavior for parent -> working dir
542 # avoid silly behavior for parent -> working dir
473 if c2.node() is None and c1.node() == repo.dirstate.p1():
543 if c2.node() is None and c1.node() == repo.dirstate.p1():
474 return (
544 return (
475 branch_copies(_dirstatecopies(repo, narrowmatch)),
545 branch_copies(_dirstatecopies(repo, narrowmatch)),
476 branch_copies(),
546 branch_copies(),
477 {},
547 {},
478 )
548 )
479
549
480 copytracing = repo.ui.config(b'experimental', b'copytrace')
550 copytracing = repo.ui.config(b'experimental', b'copytrace')
481 if stringutil.parsebool(copytracing) is False:
551 if stringutil.parsebool(copytracing) is False:
482 # stringutil.parsebool() returns None when it is unable to parse the
552 # stringutil.parsebool() returns None when it is unable to parse the
483 # value, so we should rely on making sure copytracing is on such cases
553 # value, so we should rely on making sure copytracing is on such cases
484 return branch_copies(), branch_copies(), {}
554 return branch_copies(), branch_copies(), {}
485
555
486 if usechangesetcentricalgo(repo):
556 if usechangesetcentricalgo(repo):
487 # The heuristics don't make sense when we need changeset-centric algos
557 # The heuristics don't make sense when we need changeset-centric algos
488 return _fullcopytracing(repo, c1, c2, base)
558 return _fullcopytracing(repo, c1, c2, base)
489
559
490 # Copy trace disabling is explicitly below the node == p1 logic above
560 # Copy trace disabling is explicitly below the node == p1 logic above
491 # because the logic above is required for a simple copy to be kept across a
561 # because the logic above is required for a simple copy to be kept across a
492 # rebase.
562 # rebase.
493 if copytracing == b'heuristics':
563 if copytracing == b'heuristics':
494 # Do full copytracing if only non-public revisions are involved as
564 # Do full copytracing if only non-public revisions are involved as
495 # that will be fast enough and will also cover the copies which could
565 # that will be fast enough and will also cover the copies which could
496 # be missed by heuristics
566 # be missed by heuristics
497 if _isfullcopytraceable(repo, c1, base):
567 if _isfullcopytraceable(repo, c1, base):
498 return _fullcopytracing(repo, c1, c2, base)
568 return _fullcopytracing(repo, c1, c2, base)
499 return _heuristicscopytracing(repo, c1, c2, base)
569 return _heuristicscopytracing(repo, c1, c2, base)
500 else:
570 else:
501 return _fullcopytracing(repo, c1, c2, base)
571 return _fullcopytracing(repo, c1, c2, base)
502
572
503
573
504 def _isfullcopytraceable(repo, c1, base):
574 def _isfullcopytraceable(repo, c1, base):
505 """ Checks that if base, source and destination are all no-public branches,
575 """ Checks that if base, source and destination are all no-public branches,
506 if yes let's use the full copytrace algorithm for increased capabilities
576 if yes let's use the full copytrace algorithm for increased capabilities
507 since it will be fast enough.
577 since it will be fast enough.
508
578
509 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
579 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
510 number of changesets from c1 to base such that if number of changesets are
580 number of changesets from c1 to base such that if number of changesets are
511 more than the limit, full copytracing algorithm won't be used.
581 more than the limit, full copytracing algorithm won't be used.
512 """
582 """
513 if c1.rev() is None:
583 if c1.rev() is None:
514 c1 = c1.p1()
584 c1 = c1.p1()
515 if c1.mutable() and base.mutable():
585 if c1.mutable() and base.mutable():
516 sourcecommitlimit = repo.ui.configint(
586 sourcecommitlimit = repo.ui.configint(
517 b'experimental', b'copytrace.sourcecommitlimit'
587 b'experimental', b'copytrace.sourcecommitlimit'
518 )
588 )
519 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
589 commits = len(repo.revs(b'%d::%d', base.rev(), c1.rev()))
520 return commits < sourcecommitlimit
590 return commits < sourcecommitlimit
521 return False
591 return False
522
592
523
593
524 def _checksinglesidecopies(
594 def _checksinglesidecopies(
525 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
595 src, dsts1, m1, m2, mb, c2, base, copy, renamedelete
526 ):
596 ):
527 if src not in m2:
597 if src not in m2:
528 # deleted on side 2
598 # deleted on side 2
529 if src not in m1:
599 if src not in m1:
530 # renamed on side 1, deleted on side 2
600 # renamed on side 1, deleted on side 2
531 renamedelete[src] = dsts1
601 renamedelete[src] = dsts1
532 elif src not in mb:
602 elif src not in mb:
533 # Work around the "short-circuit to avoid issues with merge states"
603 # Work around the "short-circuit to avoid issues with merge states"
534 # thing in pathcopies(): pathcopies(x, y) can return a copy where the
604 # thing in pathcopies(): pathcopies(x, y) can return a copy where the
535 # destination doesn't exist in y.
605 # destination doesn't exist in y.
536 pass
606 pass
537 elif m2[src] != mb[src]:
607 elif m2[src] != mb[src]:
538 if not _related(c2[src], base[src]):
608 if not _related(c2[src], base[src]):
539 return
609 return
540 # modified on side 2
610 # modified on side 2
541 for dst in dsts1:
611 for dst in dsts1:
542 copy[dst] = src
612 copy[dst] = src
543
613
544
614
545 class branch_copies(object):
615 class branch_copies(object):
546 """Information about copies made on one side of a merge/graft.
616 """Information about copies made on one side of a merge/graft.
547
617
548 "copy" is a mapping from destination name -> source name,
618 "copy" is a mapping from destination name -> source name,
549 where source is in c1 and destination is in c2 or vice-versa.
619 where source is in c1 and destination is in c2 or vice-versa.
550
620
551 "movewithdir" is a mapping from source name -> destination name,
621 "movewithdir" is a mapping from source name -> destination name,
552 where the file at source present in one context but not the other
622 where the file at source present in one context but not the other
553 needs to be moved to destination by the merge process, because the
623 needs to be moved to destination by the merge process, because the
554 other context moved the directory it is in.
624 other context moved the directory it is in.
555
625
556 "renamedelete" is a mapping of source name -> list of destination
626 "renamedelete" is a mapping of source name -> list of destination
557 names for files deleted in c1 that were renamed in c2 or vice-versa.
627 names for files deleted in c1 that were renamed in c2 or vice-versa.
558
628
559 "dirmove" is a mapping of detected source dir -> destination dir renames.
629 "dirmove" is a mapping of detected source dir -> destination dir renames.
560 This is needed for handling changes to new files previously grafted into
630 This is needed for handling changes to new files previously grafted into
561 renamed directories.
631 renamed directories.
562 """
632 """
563
633
564 def __init__(
634 def __init__(
565 self, copy=None, renamedelete=None, dirmove=None, movewithdir=None
635 self, copy=None, renamedelete=None, dirmove=None, movewithdir=None
566 ):
636 ):
567 self.copy = {} if copy is None else copy
637 self.copy = {} if copy is None else copy
568 self.renamedelete = {} if renamedelete is None else renamedelete
638 self.renamedelete = {} if renamedelete is None else renamedelete
569 self.dirmove = {} if dirmove is None else dirmove
639 self.dirmove = {} if dirmove is None else dirmove
570 self.movewithdir = {} if movewithdir is None else movewithdir
640 self.movewithdir = {} if movewithdir is None else movewithdir
571
641
572
642
573 def _fullcopytracing(repo, c1, c2, base):
643 def _fullcopytracing(repo, c1, c2, base):
574 """ The full copytracing algorithm which finds all the new files that were
644 """ The full copytracing algorithm which finds all the new files that were
575 added from merge base up to the top commit and for each file it checks if
645 added from merge base up to the top commit and for each file it checks if
576 this file was copied from another file.
646 this file was copied from another file.
577
647
578 This is pretty slow when a lot of changesets are involved but will track all
648 This is pretty slow when a lot of changesets are involved but will track all
579 the copies.
649 the copies.
580 """
650 """
581 m1 = c1.manifest()
651 m1 = c1.manifest()
582 m2 = c2.manifest()
652 m2 = c2.manifest()
583 mb = base.manifest()
653 mb = base.manifest()
584
654
585 copies1 = pathcopies(base, c1)
655 copies1 = pathcopies(base, c1)
586 copies2 = pathcopies(base, c2)
656 copies2 = pathcopies(base, c2)
587
657
588 if not (copies1 or copies2):
658 if not (copies1 or copies2):
589 return branch_copies(), branch_copies(), {}
659 return branch_copies(), branch_copies(), {}
590
660
591 inversecopies1 = {}
661 inversecopies1 = {}
592 inversecopies2 = {}
662 inversecopies2 = {}
593 for dst, src in copies1.items():
663 for dst, src in copies1.items():
594 inversecopies1.setdefault(src, []).append(dst)
664 inversecopies1.setdefault(src, []).append(dst)
595 for dst, src in copies2.items():
665 for dst, src in copies2.items():
596 inversecopies2.setdefault(src, []).append(dst)
666 inversecopies2.setdefault(src, []).append(dst)
597
667
598 copy1 = {}
668 copy1 = {}
599 copy2 = {}
669 copy2 = {}
600 diverge = {}
670 diverge = {}
601 renamedelete1 = {}
671 renamedelete1 = {}
602 renamedelete2 = {}
672 renamedelete2 = {}
603 allsources = set(inversecopies1) | set(inversecopies2)
673 allsources = set(inversecopies1) | set(inversecopies2)
604 for src in allsources:
674 for src in allsources:
605 dsts1 = inversecopies1.get(src)
675 dsts1 = inversecopies1.get(src)
606 dsts2 = inversecopies2.get(src)
676 dsts2 = inversecopies2.get(src)
607 if dsts1 and dsts2:
677 if dsts1 and dsts2:
608 # copied/renamed on both sides
678 # copied/renamed on both sides
609 if src not in m1 and src not in m2:
679 if src not in m1 and src not in m2:
610 # renamed on both sides
680 # renamed on both sides
611 dsts1 = set(dsts1)
681 dsts1 = set(dsts1)
612 dsts2 = set(dsts2)
682 dsts2 = set(dsts2)
613 # If there's some overlap in the rename destinations, we
683 # If there's some overlap in the rename destinations, we
614 # consider it not divergent. For example, if side 1 copies 'a'
684 # consider it not divergent. For example, if side 1 copies 'a'
615 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
685 # to 'b' and 'c' and deletes 'a', and side 2 copies 'a' to 'c'
616 # and 'd' and deletes 'a'.
686 # and 'd' and deletes 'a'.
617 if dsts1 & dsts2:
687 if dsts1 & dsts2:
618 for dst in dsts1 & dsts2:
688 for dst in dsts1 & dsts2:
619 copy1[dst] = src
689 copy1[dst] = src
620 copy2[dst] = src
690 copy2[dst] = src
621 else:
691 else:
622 diverge[src] = sorted(dsts1 | dsts2)
692 diverge[src] = sorted(dsts1 | dsts2)
623 elif src in m1 and src in m2:
693 elif src in m1 and src in m2:
624 # copied on both sides
694 # copied on both sides
625 dsts1 = set(dsts1)
695 dsts1 = set(dsts1)
626 dsts2 = set(dsts2)
696 dsts2 = set(dsts2)
627 for dst in dsts1 & dsts2:
697 for dst in dsts1 & dsts2:
628 copy1[dst] = src
698 copy1[dst] = src
629 copy2[dst] = src
699 copy2[dst] = src
630 # TODO: Handle cases where it was renamed on one side and copied
700 # TODO: Handle cases where it was renamed on one side and copied
631 # on the other side
701 # on the other side
632 elif dsts1:
702 elif dsts1:
633 # copied/renamed only on side 1
703 # copied/renamed only on side 1
634 _checksinglesidecopies(
704 _checksinglesidecopies(
635 src, dsts1, m1, m2, mb, c2, base, copy1, renamedelete1
705 src, dsts1, m1, m2, mb, c2, base, copy1, renamedelete1
636 )
706 )
637 elif dsts2:
707 elif dsts2:
638 # copied/renamed only on side 2
708 # copied/renamed only on side 2
639 _checksinglesidecopies(
709 _checksinglesidecopies(
640 src, dsts2, m2, m1, mb, c1, base, copy2, renamedelete2
710 src, dsts2, m2, m1, mb, c1, base, copy2, renamedelete2
641 )
711 )
642
712
643 # find interesting file sets from manifests
713 # find interesting file sets from manifests
644 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
714 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
645 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
715 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
646 u1 = sorted(addedinm1 - addedinm2)
716 u1 = sorted(addedinm1 - addedinm2)
647 u2 = sorted(addedinm2 - addedinm1)
717 u2 = sorted(addedinm2 - addedinm1)
648
718
649 header = b" unmatched files in %s"
719 header = b" unmatched files in %s"
650 if u1:
720 if u1:
651 repo.ui.debug(b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1)))
721 repo.ui.debug(b"%s:\n %s\n" % (header % b'local', b"\n ".join(u1)))
652 if u2:
722 if u2:
653 repo.ui.debug(b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2)))
723 repo.ui.debug(b"%s:\n %s\n" % (header % b'other', b"\n ".join(u2)))
654
724
655 if repo.ui.debugflag:
725 if repo.ui.debugflag:
656 renamedeleteset = set()
726 renamedeleteset = set()
657 divergeset = set()
727 divergeset = set()
658 for dsts in diverge.values():
728 for dsts in diverge.values():
659 divergeset.update(dsts)
729 divergeset.update(dsts)
660 for dsts in renamedelete1.values():
730 for dsts in renamedelete1.values():
661 renamedeleteset.update(dsts)
731 renamedeleteset.update(dsts)
662 for dsts in renamedelete2.values():
732 for dsts in renamedelete2.values():
663 renamedeleteset.update(dsts)
733 renamedeleteset.update(dsts)
664
734
665 repo.ui.debug(
735 repo.ui.debug(
666 b" all copies found (* = to merge, ! = divergent, "
736 b" all copies found (* = to merge, ! = divergent, "
667 b"% = renamed and deleted):\n"
737 b"% = renamed and deleted):\n"
668 )
738 )
669 for side, copies in ((b"local", copies1), (b"remote", copies2)):
739 for side, copies in ((b"local", copies1), (b"remote", copies2)):
670 if not copies:
740 if not copies:
671 continue
741 continue
672 repo.ui.debug(b" on %s side:\n" % side)
742 repo.ui.debug(b" on %s side:\n" % side)
673 for f in sorted(copies):
743 for f in sorted(copies):
674 note = b""
744 note = b""
675 if f in copy1 or f in copy2:
745 if f in copy1 or f in copy2:
676 note += b"*"
746 note += b"*"
677 if f in divergeset:
747 if f in divergeset:
678 note += b"!"
748 note += b"!"
679 if f in renamedeleteset:
749 if f in renamedeleteset:
680 note += b"%"
750 note += b"%"
681 repo.ui.debug(
751 repo.ui.debug(
682 b" src: '%s' -> dst: '%s' %s\n" % (copies[f], f, note)
752 b" src: '%s' -> dst: '%s' %s\n" % (copies[f], f, note)
683 )
753 )
684 del renamedeleteset
754 del renamedeleteset
685 del divergeset
755 del divergeset
686
756
687 repo.ui.debug(b" checking for directory renames\n")
757 repo.ui.debug(b" checking for directory renames\n")
688
758
689 dirmove1, movewithdir2 = _dir_renames(repo, c1, copy1, copies1, u2)
759 dirmove1, movewithdir2 = _dir_renames(repo, c1, copy1, copies1, u2)
690 dirmove2, movewithdir1 = _dir_renames(repo, c2, copy2, copies2, u1)
760 dirmove2, movewithdir1 = _dir_renames(repo, c2, copy2, copies2, u1)
691
761
692 branch_copies1 = branch_copies(copy1, renamedelete1, dirmove1, movewithdir1)
762 branch_copies1 = branch_copies(copy1, renamedelete1, dirmove1, movewithdir1)
693 branch_copies2 = branch_copies(copy2, renamedelete2, dirmove2, movewithdir2)
763 branch_copies2 = branch_copies(copy2, renamedelete2, dirmove2, movewithdir2)
694
764
695 return branch_copies1, branch_copies2, diverge
765 return branch_copies1, branch_copies2, diverge
696
766
697
767
698 def _dir_renames(repo, ctx, copy, fullcopy, addedfiles):
768 def _dir_renames(repo, ctx, copy, fullcopy, addedfiles):
699 """Finds moved directories and files that should move with them.
769 """Finds moved directories and files that should move with them.
700
770
701 ctx: the context for one of the sides
771 ctx: the context for one of the sides
702 copy: files copied on the same side (as ctx)
772 copy: files copied on the same side (as ctx)
703 fullcopy: files copied on the same side (as ctx), including those that
773 fullcopy: files copied on the same side (as ctx), including those that
704 merge.manifestmerge() won't care about
774 merge.manifestmerge() won't care about
705 addedfiles: added files on the other side (compared to ctx)
775 addedfiles: added files on the other side (compared to ctx)
706 """
776 """
707 # generate a directory move map
777 # generate a directory move map
708 d = ctx.dirs()
778 d = ctx.dirs()
709 invalid = set()
779 invalid = set()
710 dirmove = {}
780 dirmove = {}
711
781
712 # examine each file copy for a potential directory move, which is
782 # examine each file copy for a potential directory move, which is
713 # when all the files in a directory are moved to a new directory
783 # when all the files in a directory are moved to a new directory
714 for dst, src in pycompat.iteritems(fullcopy):
784 for dst, src in pycompat.iteritems(fullcopy):
715 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
785 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
716 if dsrc in invalid:
786 if dsrc in invalid:
717 # already seen to be uninteresting
787 # already seen to be uninteresting
718 continue
788 continue
719 elif dsrc in d and ddst in d:
789 elif dsrc in d and ddst in d:
720 # directory wasn't entirely moved locally
790 # directory wasn't entirely moved locally
721 invalid.add(dsrc)
791 invalid.add(dsrc)
722 elif dsrc in dirmove and dirmove[dsrc] != ddst:
792 elif dsrc in dirmove and dirmove[dsrc] != ddst:
723 # files from the same directory moved to two different places
793 # files from the same directory moved to two different places
724 invalid.add(dsrc)
794 invalid.add(dsrc)
725 else:
795 else:
726 # looks good so far
796 # looks good so far
727 dirmove[dsrc] = ddst
797 dirmove[dsrc] = ddst
728
798
729 for i in invalid:
799 for i in invalid:
730 if i in dirmove:
800 if i in dirmove:
731 del dirmove[i]
801 del dirmove[i]
732 del d, invalid
802 del d, invalid
733
803
734 if not dirmove:
804 if not dirmove:
735 return {}, {}
805 return {}, {}
736
806
737 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
807 dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
738
808
739 for d in dirmove:
809 for d in dirmove:
740 repo.ui.debug(
810 repo.ui.debug(
741 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
811 b" discovered dir src: '%s' -> dst: '%s'\n" % (d, dirmove[d])
742 )
812 )
743
813
744 movewithdir = {}
814 movewithdir = {}
745 # check unaccounted nonoverlapping files against directory moves
815 # check unaccounted nonoverlapping files against directory moves
746 for f in addedfiles:
816 for f in addedfiles:
747 if f not in fullcopy:
817 if f not in fullcopy:
748 for d in dirmove:
818 for d in dirmove:
749 if f.startswith(d):
819 if f.startswith(d):
750 # new file added in a directory that was moved, move it
820 # new file added in a directory that was moved, move it
751 df = dirmove[d] + f[len(d) :]
821 df = dirmove[d] + f[len(d) :]
752 if df not in copy:
822 if df not in copy:
753 movewithdir[f] = df
823 movewithdir[f] = df
754 repo.ui.debug(
824 repo.ui.debug(
755 b" pending file src: '%s' -> dst: '%s'\n"
825 b" pending file src: '%s' -> dst: '%s'\n"
756 % (f, df)
826 % (f, df)
757 )
827 )
758 break
828 break
759
829
760 return dirmove, movewithdir
830 return dirmove, movewithdir
761
831
762
832
763 def _heuristicscopytracing(repo, c1, c2, base):
833 def _heuristicscopytracing(repo, c1, c2, base):
764 """ Fast copytracing using filename heuristics
834 """ Fast copytracing using filename heuristics
765
835
766 Assumes that moves or renames are of following two types:
836 Assumes that moves or renames are of following two types:
767
837
768 1) Inside a directory only (same directory name but different filenames)
838 1) Inside a directory only (same directory name but different filenames)
769 2) Move from one directory to another
839 2) Move from one directory to another
770 (same filenames but different directory names)
840 (same filenames but different directory names)
771
841
772 Works only when there are no merge commits in the "source branch".
842 Works only when there are no merge commits in the "source branch".
773 Source branch is commits from base up to c2 not including base.
843 Source branch is commits from base up to c2 not including base.
774
844
775 If merge is involved it fallbacks to _fullcopytracing().
845 If merge is involved it fallbacks to _fullcopytracing().
776
846
777 Can be used by setting the following config:
847 Can be used by setting the following config:
778
848
779 [experimental]
849 [experimental]
780 copytrace = heuristics
850 copytrace = heuristics
781
851
782 In some cases the copy/move candidates found by heuristics can be very large
852 In some cases the copy/move candidates found by heuristics can be very large
783 in number and that will make the algorithm slow. The number of possible
853 in number and that will make the algorithm slow. The number of possible
784 candidates to check can be limited by using the config
854 candidates to check can be limited by using the config
785 `experimental.copytrace.movecandidateslimit` which defaults to 100.
855 `experimental.copytrace.movecandidateslimit` which defaults to 100.
786 """
856 """
787
857
788 if c1.rev() is None:
858 if c1.rev() is None:
789 c1 = c1.p1()
859 c1 = c1.p1()
790 if c2.rev() is None:
860 if c2.rev() is None:
791 c2 = c2.p1()
861 c2 = c2.p1()
792
862
793 changedfiles = set()
863 changedfiles = set()
794 m1 = c1.manifest()
864 m1 = c1.manifest()
795 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
865 if not repo.revs(b'%d::%d', base.rev(), c2.rev()):
796 # If base is not in c2 branch, we switch to fullcopytracing
866 # If base is not in c2 branch, we switch to fullcopytracing
797 repo.ui.debug(
867 repo.ui.debug(
798 b"switching to full copytracing as base is not "
868 b"switching to full copytracing as base is not "
799 b"an ancestor of c2\n"
869 b"an ancestor of c2\n"
800 )
870 )
801 return _fullcopytracing(repo, c1, c2, base)
871 return _fullcopytracing(repo, c1, c2, base)
802
872
803 ctx = c2
873 ctx = c2
804 while ctx != base:
874 while ctx != base:
805 if len(ctx.parents()) == 2:
875 if len(ctx.parents()) == 2:
806 # To keep things simple let's not handle merges
876 # To keep things simple let's not handle merges
807 repo.ui.debug(b"switching to full copytracing because of merges\n")
877 repo.ui.debug(b"switching to full copytracing because of merges\n")
808 return _fullcopytracing(repo, c1, c2, base)
878 return _fullcopytracing(repo, c1, c2, base)
809 changedfiles.update(ctx.files())
879 changedfiles.update(ctx.files())
810 ctx = ctx.p1()
880 ctx = ctx.p1()
811
881
812 copies2 = {}
882 copies2 = {}
813 cp = _forwardcopies(base, c2)
883 cp = _forwardcopies(base, c2)
814 for dst, src in pycompat.iteritems(cp):
884 for dst, src in pycompat.iteritems(cp):
815 if src in m1:
885 if src in m1:
816 copies2[dst] = src
886 copies2[dst] = src
817
887
818 # file is missing if it isn't present in the destination, but is present in
888 # file is missing if it isn't present in the destination, but is present in
819 # the base and present in the source.
889 # the base and present in the source.
820 # Presence in the base is important to exclude added files, presence in the
890 # Presence in the base is important to exclude added files, presence in the
821 # source is important to exclude removed files.
891 # source is important to exclude removed files.
822 filt = lambda f: f not in m1 and f in base and f in c2
892 filt = lambda f: f not in m1 and f in base and f in c2
823 missingfiles = [f for f in changedfiles if filt(f)]
893 missingfiles = [f for f in changedfiles if filt(f)]
824
894
825 copies1 = {}
895 copies1 = {}
826 if missingfiles:
896 if missingfiles:
827 basenametofilename = collections.defaultdict(list)
897 basenametofilename = collections.defaultdict(list)
828 dirnametofilename = collections.defaultdict(list)
898 dirnametofilename = collections.defaultdict(list)
829
899
830 for f in m1.filesnotin(base.manifest()):
900 for f in m1.filesnotin(base.manifest()):
831 basename = os.path.basename(f)
901 basename = os.path.basename(f)
832 dirname = os.path.dirname(f)
902 dirname = os.path.dirname(f)
833 basenametofilename[basename].append(f)
903 basenametofilename[basename].append(f)
834 dirnametofilename[dirname].append(f)
904 dirnametofilename[dirname].append(f)
835
905
836 for f in missingfiles:
906 for f in missingfiles:
837 basename = os.path.basename(f)
907 basename = os.path.basename(f)
838 dirname = os.path.dirname(f)
908 dirname = os.path.dirname(f)
839 samebasename = basenametofilename[basename]
909 samebasename = basenametofilename[basename]
840 samedirname = dirnametofilename[dirname]
910 samedirname = dirnametofilename[dirname]
841 movecandidates = samebasename + samedirname
911 movecandidates = samebasename + samedirname
842 # f is guaranteed to be present in c2, that's why
912 # f is guaranteed to be present in c2, that's why
843 # c2.filectx(f) won't fail
913 # c2.filectx(f) won't fail
844 f2 = c2.filectx(f)
914 f2 = c2.filectx(f)
845 # we can have a lot of candidates which can slow down the heuristics
915 # we can have a lot of candidates which can slow down the heuristics
846 # config value to limit the number of candidates moves to check
916 # config value to limit the number of candidates moves to check
847 maxcandidates = repo.ui.configint(
917 maxcandidates = repo.ui.configint(
848 b'experimental', b'copytrace.movecandidateslimit'
918 b'experimental', b'copytrace.movecandidateslimit'
849 )
919 )
850
920
851 if len(movecandidates) > maxcandidates:
921 if len(movecandidates) > maxcandidates:
852 repo.ui.status(
922 repo.ui.status(
853 _(
923 _(
854 b"skipping copytracing for '%s', more "
924 b"skipping copytracing for '%s', more "
855 b"candidates than the limit: %d\n"
925 b"candidates than the limit: %d\n"
856 )
926 )
857 % (f, len(movecandidates))
927 % (f, len(movecandidates))
858 )
928 )
859 continue
929 continue
860
930
861 for candidate in movecandidates:
931 for candidate in movecandidates:
862 f1 = c1.filectx(candidate)
932 f1 = c1.filectx(candidate)
863 if _related(f1, f2):
933 if _related(f1, f2):
864 # if there are a few related copies then we'll merge
934 # if there are a few related copies then we'll merge
865 # changes into all of them. This matches the behaviour
935 # changes into all of them. This matches the behaviour
866 # of upstream copytracing
936 # of upstream copytracing
867 copies1[candidate] = f
937 copies1[candidate] = f
868
938
869 return branch_copies(copies1), branch_copies(copies2), {}
939 return branch_copies(copies1), branch_copies(copies2), {}
870
940
871
941
872 def _related(f1, f2):
942 def _related(f1, f2):
873 """return True if f1 and f2 filectx have a common ancestor
943 """return True if f1 and f2 filectx have a common ancestor
874
944
875 Walk back to common ancestor to see if the two files originate
945 Walk back to common ancestor to see if the two files originate
876 from the same file. Since workingfilectx's rev() is None it messes
946 from the same file. Since workingfilectx's rev() is None it messes
877 up the integer comparison logic, hence the pre-step check for
947 up the integer comparison logic, hence the pre-step check for
878 None (f1 and f2 can only be workingfilectx's initially).
948 None (f1 and f2 can only be workingfilectx's initially).
879 """
949 """
880
950
881 if f1 == f2:
951 if f1 == f2:
882 return True # a match
952 return True # a match
883
953
884 g1, g2 = f1.ancestors(), f2.ancestors()
954 g1, g2 = f1.ancestors(), f2.ancestors()
885 try:
955 try:
886 f1r, f2r = f1.linkrev(), f2.linkrev()
956 f1r, f2r = f1.linkrev(), f2.linkrev()
887
957
888 if f1r is None:
958 if f1r is None:
889 f1 = next(g1)
959 f1 = next(g1)
890 if f2r is None:
960 if f2r is None:
891 f2 = next(g2)
961 f2 = next(g2)
892
962
893 while True:
963 while True:
894 f1r, f2r = f1.linkrev(), f2.linkrev()
964 f1r, f2r = f1.linkrev(), f2.linkrev()
895 if f1r > f2r:
965 if f1r > f2r:
896 f1 = next(g1)
966 f1 = next(g1)
897 elif f2r > f1r:
967 elif f2r > f1r:
898 f2 = next(g2)
968 f2 = next(g2)
899 else: # f1 and f2 point to files in the same linkrev
969 else: # f1 and f2 point to files in the same linkrev
900 return f1 == f2 # true if they point to the same file
970 return f1 == f2 # true if they point to the same file
901 except StopIteration:
971 except StopIteration:
902 return False
972 return False
903
973
904
974
905 def graftcopies(wctx, ctx, base):
975 def graftcopies(wctx, ctx, base):
906 """reproduce copies between base and ctx in the wctx
976 """reproduce copies between base and ctx in the wctx
907
977
908 Unlike mergecopies(), this function will only consider copies between base
978 Unlike mergecopies(), this function will only consider copies between base
909 and ctx; it will ignore copies between base and wctx. Also unlike
979 and ctx; it will ignore copies between base and wctx. Also unlike
910 mergecopies(), this function will apply copies to the working copy (instead
980 mergecopies(), this function will apply copies to the working copy (instead
911 of just returning information about the copies). That makes it cheaper
981 of just returning information about the copies). That makes it cheaper
912 (especially in the common case of base==ctx.p1()) and useful also when
982 (especially in the common case of base==ctx.p1()) and useful also when
913 experimental.copytrace=off.
983 experimental.copytrace=off.
914
984
915 merge.update() will have already marked most copies, but it will only
985 merge.update() will have already marked most copies, but it will only
916 mark copies if it thinks the source files are related (see
986 mark copies if it thinks the source files are related (see
917 merge._related()). It will also not mark copies if the file wasn't modified
987 merge._related()). It will also not mark copies if the file wasn't modified
918 on the local side. This function adds the copies that were "missed"
988 on the local side. This function adds the copies that were "missed"
919 by merge.update().
989 by merge.update().
920 """
990 """
921 new_copies = pathcopies(base, ctx)
991 new_copies = pathcopies(base, ctx)
922 _filter(wctx.p1(), wctx, new_copies)
992 _filter(wctx.p1(), wctx, new_copies)
923 for dst, src in pycompat.iteritems(new_copies):
993 for dst, src in pycompat.iteritems(new_copies):
924 wctx[dst].markcopied(src)
994 wctx[dst].markcopied(src)
925
995
926
996
927 def computechangesetfilesadded(ctx):
997 def computechangesetfilesadded(ctx):
928 """return the list of files added in a changeset
998 """return the list of files added in a changeset
929 """
999 """
930 added = []
1000 added = []
931 for f in ctx.files():
1001 for f in ctx.files():
932 if not any(f in p for p in ctx.parents()):
1002 if not any(f in p for p in ctx.parents()):
933 added.append(f)
1003 added.append(f)
934 return added
1004 return added
935
1005
936
1006
937 def computechangesetfilesremoved(ctx):
1007 def computechangesetfilesremoved(ctx):
938 """return the list of files removed in a changeset
1008 """return the list of files removed in a changeset
939 """
1009 """
940 removed = []
1010 removed = []
941 for f in ctx.files():
1011 for f in ctx.files():
942 if f not in ctx:
1012 if f not in ctx:
943 removed.append(f)
1013 removed.append(f)
944 return removed
1014 return removed
945
1015
946
1016
947 def computechangesetcopies(ctx):
1017 def computechangesetcopies(ctx):
948 """return the copies data for a changeset
1018 """return the copies data for a changeset
949
1019
950 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
1020 The copies data are returned as a pair of dictionnary (p1copies, p2copies).
951
1021
952 Each dictionnary are in the form: `{newname: oldname}`
1022 Each dictionnary are in the form: `{newname: oldname}`
953 """
1023 """
954 p1copies = {}
1024 p1copies = {}
955 p2copies = {}
1025 p2copies = {}
956 p1 = ctx.p1()
1026 p1 = ctx.p1()
957 p2 = ctx.p2()
1027 p2 = ctx.p2()
958 narrowmatch = ctx._repo.narrowmatch()
1028 narrowmatch = ctx._repo.narrowmatch()
959 for dst in ctx.files():
1029 for dst in ctx.files():
960 if not narrowmatch(dst) or dst not in ctx:
1030 if not narrowmatch(dst) or dst not in ctx:
961 continue
1031 continue
962 copied = ctx[dst].renamed()
1032 copied = ctx[dst].renamed()
963 if not copied:
1033 if not copied:
964 continue
1034 continue
965 src, srcnode = copied
1035 src, srcnode = copied
966 if src in p1 and p1[src].filenode() == srcnode:
1036 if src in p1 and p1[src].filenode() == srcnode:
967 p1copies[dst] = src
1037 p1copies[dst] = src
968 elif src in p2 and p2[src].filenode() == srcnode:
1038 elif src in p2 and p2[src].filenode() == srcnode:
969 p2copies[dst] = src
1039 p2copies[dst] = src
970 return p1copies, p2copies
1040 return p1copies, p2copies
971
1041
972
1042
973 def encodecopies(files, copies):
1043 def encodecopies(files, copies):
974 items = []
1044 items = []
975 for i, dst in enumerate(files):
1045 for i, dst in enumerate(files):
976 if dst in copies:
1046 if dst in copies:
977 items.append(b'%d\0%s' % (i, copies[dst]))
1047 items.append(b'%d\0%s' % (i, copies[dst]))
978 if len(items) != len(copies):
1048 if len(items) != len(copies):
979 raise error.ProgrammingError(
1049 raise error.ProgrammingError(
980 b'some copy targets missing from file list'
1050 b'some copy targets missing from file list'
981 )
1051 )
982 return b"\n".join(items)
1052 return b"\n".join(items)
983
1053
984
1054
985 def decodecopies(files, data):
1055 def decodecopies(files, data):
986 try:
1056 try:
987 copies = {}
1057 copies = {}
988 if not data:
1058 if not data:
989 return copies
1059 return copies
990 for l in data.split(b'\n'):
1060 for l in data.split(b'\n'):
991 strindex, src = l.split(b'\0')
1061 strindex, src = l.split(b'\0')
992 i = int(strindex)
1062 i = int(strindex)
993 dst = files[i]
1063 dst = files[i]
994 copies[dst] = src
1064 copies[dst] = src
995 return copies
1065 return copies
996 except (ValueError, IndexError):
1066 except (ValueError, IndexError):
997 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
1067 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
998 # used different syntax for the value.
1068 # used different syntax for the value.
999 return None
1069 return None
1000
1070
1001
1071
1002 def encodefileindices(files, subset):
1072 def encodefileindices(files, subset):
1003 subset = set(subset)
1073 subset = set(subset)
1004 indices = []
1074 indices = []
1005 for i, f in enumerate(files):
1075 for i, f in enumerate(files):
1006 if f in subset:
1076 if f in subset:
1007 indices.append(b'%d' % i)
1077 indices.append(b'%d' % i)
1008 return b'\n'.join(indices)
1078 return b'\n'.join(indices)
1009
1079
1010
1080
1011 def decodefileindices(files, data):
1081 def decodefileindices(files, data):
1012 try:
1082 try:
1013 subset = []
1083 subset = []
1014 if not data:
1084 if not data:
1015 return subset
1085 return subset
1016 for strindex in data.split(b'\n'):
1086 for strindex in data.split(b'\n'):
1017 i = int(strindex)
1087 i = int(strindex)
1018 if i < 0 or i >= len(files):
1088 if i < 0 or i >= len(files):
1019 return None
1089 return None
1020 subset.append(files[i])
1090 subset.append(files[i])
1021 return subset
1091 return subset
1022 except (ValueError, IndexError):
1092 except (ValueError, IndexError):
1023 # Perhaps someone had chosen the same key name (e.g. "added") and
1093 # Perhaps someone had chosen the same key name (e.g. "added") and
1024 # used different syntax for the value.
1094 # used different syntax for the value.
1025 return None
1095 return None
1026
1096
1027
1097
1028 def _getsidedata(srcrepo, rev):
1098 def _getsidedata(srcrepo, rev):
1029 ctx = srcrepo[rev]
1099 ctx = srcrepo[rev]
1030 filescopies = computechangesetcopies(ctx)
1100 filescopies = computechangesetcopies(ctx)
1031 filesadded = computechangesetfilesadded(ctx)
1101 filesadded = computechangesetfilesadded(ctx)
1032 filesremoved = computechangesetfilesremoved(ctx)
1102 filesremoved = computechangesetfilesremoved(ctx)
1033 sidedata = {}
1103 sidedata = {}
1034 if any([filescopies, filesadded, filesremoved]):
1104 if any([filescopies, filesadded, filesremoved]):
1035 sortedfiles = sorted(ctx.files())
1105 sortedfiles = sorted(ctx.files())
1036 p1copies, p2copies = filescopies
1106 p1copies, p2copies = filescopies
1037 p1copies = encodecopies(sortedfiles, p1copies)
1107 p1copies = encodecopies(sortedfiles, p1copies)
1038 p2copies = encodecopies(sortedfiles, p2copies)
1108 p2copies = encodecopies(sortedfiles, p2copies)
1039 filesadded = encodefileindices(sortedfiles, filesadded)
1109 filesadded = encodefileindices(sortedfiles, filesadded)
1040 filesremoved = encodefileindices(sortedfiles, filesremoved)
1110 filesremoved = encodefileindices(sortedfiles, filesremoved)
1041 if p1copies:
1111 if p1copies:
1042 sidedata[sidedatamod.SD_P1COPIES] = p1copies
1112 sidedata[sidedatamod.SD_P1COPIES] = p1copies
1043 if p2copies:
1113 if p2copies:
1044 sidedata[sidedatamod.SD_P2COPIES] = p2copies
1114 sidedata[sidedatamod.SD_P2COPIES] = p2copies
1045 if filesadded:
1115 if filesadded:
1046 sidedata[sidedatamod.SD_FILESADDED] = filesadded
1116 sidedata[sidedatamod.SD_FILESADDED] = filesadded
1047 if filesremoved:
1117 if filesremoved:
1048 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
1118 sidedata[sidedatamod.SD_FILESREMOVED] = filesremoved
1049 return sidedata
1119 return sidedata
1050
1120
1051
1121
1052 def getsidedataadder(srcrepo, destrepo):
1122 def getsidedataadder(srcrepo, destrepo):
1053 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
1123 use_w = srcrepo.ui.configbool(b'experimental', b'worker.repository-upgrade')
1054 if pycompat.iswindows or not use_w:
1124 if pycompat.iswindows or not use_w:
1055 return _get_simple_sidedata_adder(srcrepo, destrepo)
1125 return _get_simple_sidedata_adder(srcrepo, destrepo)
1056 else:
1126 else:
1057 return _get_worker_sidedata_adder(srcrepo, destrepo)
1127 return _get_worker_sidedata_adder(srcrepo, destrepo)
1058
1128
1059
1129
1060 def _sidedata_worker(srcrepo, revs_queue, sidedata_queue, tokens):
1130 def _sidedata_worker(srcrepo, revs_queue, sidedata_queue, tokens):
1061 """The function used by worker precomputing sidedata
1131 """The function used by worker precomputing sidedata
1062
1132
1063 It read an input queue containing revision numbers
1133 It read an input queue containing revision numbers
1064 It write in an output queue containing (rev, <sidedata-map>)
1134 It write in an output queue containing (rev, <sidedata-map>)
1065
1135
1066 The `None` input value is used as a stop signal.
1136 The `None` input value is used as a stop signal.
1067
1137
1068 The `tokens` semaphore is user to avoid having too many unprocessed
1138 The `tokens` semaphore is user to avoid having too many unprocessed
1069 entries. The workers needs to acquire one token before fetching a task.
1139 entries. The workers needs to acquire one token before fetching a task.
1070 They will be released by the consumer of the produced data.
1140 They will be released by the consumer of the produced data.
1071 """
1141 """
1072 tokens.acquire()
1142 tokens.acquire()
1073 rev = revs_queue.get()
1143 rev = revs_queue.get()
1074 while rev is not None:
1144 while rev is not None:
1075 data = _getsidedata(srcrepo, rev)
1145 data = _getsidedata(srcrepo, rev)
1076 sidedata_queue.put((rev, data))
1146 sidedata_queue.put((rev, data))
1077 tokens.acquire()
1147 tokens.acquire()
1078 rev = revs_queue.get()
1148 rev = revs_queue.get()
1079 # processing of `None` is completed, release the token.
1149 # processing of `None` is completed, release the token.
1080 tokens.release()
1150 tokens.release()
1081
1151
1082
1152
1083 BUFF_PER_WORKER = 50
1153 BUFF_PER_WORKER = 50
1084
1154
1085
1155
1086 def _get_worker_sidedata_adder(srcrepo, destrepo):
1156 def _get_worker_sidedata_adder(srcrepo, destrepo):
1087 """The parallel version of the sidedata computation
1157 """The parallel version of the sidedata computation
1088
1158
1089 This code spawn a pool of worker that precompute a buffer of sidedata
1159 This code spawn a pool of worker that precompute a buffer of sidedata
1090 before we actually need them"""
1160 before we actually need them"""
1091 # avoid circular import copies -> scmutil -> worker -> copies
1161 # avoid circular import copies -> scmutil -> worker -> copies
1092 from . import worker
1162 from . import worker
1093
1163
1094 nbworkers = worker._numworkers(srcrepo.ui)
1164 nbworkers = worker._numworkers(srcrepo.ui)
1095
1165
1096 tokens = multiprocessing.BoundedSemaphore(nbworkers * BUFF_PER_WORKER)
1166 tokens = multiprocessing.BoundedSemaphore(nbworkers * BUFF_PER_WORKER)
1097 revsq = multiprocessing.Queue()
1167 revsq = multiprocessing.Queue()
1098 sidedataq = multiprocessing.Queue()
1168 sidedataq = multiprocessing.Queue()
1099
1169
1100 assert srcrepo.filtername is None
1170 assert srcrepo.filtername is None
1101 # queue all tasks beforehand, revision numbers are small and it make
1171 # queue all tasks beforehand, revision numbers are small and it make
1102 # synchronisation simpler
1172 # synchronisation simpler
1103 #
1173 #
1104 # Since the computation for each node can be quite expensive, the overhead
1174 # Since the computation for each node can be quite expensive, the overhead
1105 # of using a single queue is not revelant. In practice, most computation
1175 # of using a single queue is not revelant. In practice, most computation
1106 # are fast but some are very expensive and dominate all the other smaller
1176 # are fast but some are very expensive and dominate all the other smaller
1107 # cost.
1177 # cost.
1108 for r in srcrepo.changelog.revs():
1178 for r in srcrepo.changelog.revs():
1109 revsq.put(r)
1179 revsq.put(r)
1110 # queue the "no more tasks" markers
1180 # queue the "no more tasks" markers
1111 for i in range(nbworkers):
1181 for i in range(nbworkers):
1112 revsq.put(None)
1182 revsq.put(None)
1113
1183
1114 allworkers = []
1184 allworkers = []
1115 for i in range(nbworkers):
1185 for i in range(nbworkers):
1116 args = (srcrepo, revsq, sidedataq, tokens)
1186 args = (srcrepo, revsq, sidedataq, tokens)
1117 w = multiprocessing.Process(target=_sidedata_worker, args=args)
1187 w = multiprocessing.Process(target=_sidedata_worker, args=args)
1118 allworkers.append(w)
1188 allworkers.append(w)
1119 w.start()
1189 w.start()
1120
1190
1121 # dictionnary to store results for revision higher than we one we are
1191 # dictionnary to store results for revision higher than we one we are
1122 # looking for. For example, if we need the sidedatamap for 42, and 43 is
1192 # looking for. For example, if we need the sidedatamap for 42, and 43 is
1123 # received, when shelve 43 for later use.
1193 # received, when shelve 43 for later use.
1124 staging = {}
1194 staging = {}
1125
1195
1126 def sidedata_companion(revlog, rev):
1196 def sidedata_companion(revlog, rev):
1127 sidedata = {}
1197 sidedata = {}
1128 if util.safehasattr(revlog, b'filteredrevs'): # this is a changelog
1198 if util.safehasattr(revlog, b'filteredrevs'): # this is a changelog
1129 # Is the data previously shelved ?
1199 # Is the data previously shelved ?
1130 sidedata = staging.pop(rev, None)
1200 sidedata = staging.pop(rev, None)
1131 if sidedata is None:
1201 if sidedata is None:
1132 # look at the queued result until we find the one we are lookig
1202 # look at the queued result until we find the one we are lookig
1133 # for (shelve the other ones)
1203 # for (shelve the other ones)
1134 r, sidedata = sidedataq.get()
1204 r, sidedata = sidedataq.get()
1135 while r != rev:
1205 while r != rev:
1136 staging[r] = sidedata
1206 staging[r] = sidedata
1137 r, sidedata = sidedataq.get()
1207 r, sidedata = sidedataq.get()
1138 tokens.release()
1208 tokens.release()
1139 return False, (), sidedata
1209 return False, (), sidedata
1140
1210
1141 return sidedata_companion
1211 return sidedata_companion
1142
1212
1143
1213
1144 def _get_simple_sidedata_adder(srcrepo, destrepo):
1214 def _get_simple_sidedata_adder(srcrepo, destrepo):
1145 """The simple version of the sidedata computation
1215 """The simple version of the sidedata computation
1146
1216
1147 It just compute it in the same thread on request"""
1217 It just compute it in the same thread on request"""
1148
1218
1149 def sidedatacompanion(revlog, rev):
1219 def sidedatacompanion(revlog, rev):
1150 sidedata = {}
1220 sidedata = {}
1151 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1221 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1152 sidedata = _getsidedata(srcrepo, rev)
1222 sidedata = _getsidedata(srcrepo, rev)
1153 return False, (), sidedata
1223 return False, (), sidedata
1154
1224
1155 return sidedatacompanion
1225 return sidedatacompanion
1156
1226
1157
1227
1158 def getsidedataremover(srcrepo, destrepo):
1228 def getsidedataremover(srcrepo, destrepo):
1159 def sidedatacompanion(revlog, rev):
1229 def sidedatacompanion(revlog, rev):
1160 f = ()
1230 f = ()
1161 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1231 if util.safehasattr(revlog, 'filteredrevs'): # this is a changelog
1162 if revlog.flags(rev) & REVIDX_SIDEDATA:
1232 if revlog.flags(rev) & REVIDX_SIDEDATA:
1163 f = (
1233 f = (
1164 sidedatamod.SD_P1COPIES,
1234 sidedatamod.SD_P1COPIES,
1165 sidedatamod.SD_P2COPIES,
1235 sidedatamod.SD_P2COPIES,
1166 sidedatamod.SD_FILESADDED,
1236 sidedatamod.SD_FILESADDED,
1167 sidedatamod.SD_FILESREMOVED,
1237 sidedatamod.SD_FILESREMOVED,
1168 )
1238 )
1169 return False, f, {}
1239 return False, f, {}
1170
1240
1171 return sidedatacompanion
1241 return sidedatacompanion
@@ -1,798 +1,855 b''
1 #testcases filelog compatibility sidedata
2
1 =====================================================
3 =====================================================
2 Test Copy tracing for chain of copies involving merge
4 Test Copy tracing for chain of copies involving merge
3 =====================================================
5 =====================================================
4
6
5 This test files covers copies/rename case for a chains of commit where merges
7 This test files covers copies/rename case for a chains of commit where merges
6 are involved. It cheks we do not have unwanted update of behavior and that the
8 are involved. It cheks we do not have unwanted update of behavior and that the
7 different options to retrieve copies behave correctly.
9 different options to retrieve copies behave correctly.
8
10
11
9 Setup
12 Setup
10 =====
13 =====
11
14
12 use git diff to see rename
15 use git diff to see rename
13
16
14 $ cat << EOF >> $HGRCPATH
17 $ cat << EOF >> $HGRCPATH
15 > [diff]
18 > [diff]
16 > git=yes
19 > git=yes
17 > [ui]
20 > [ui]
18 > logtemplate={rev} {desc}\n
21 > logtemplate={rev} {desc}\n
19 > EOF
22 > EOF
20
23
24 #if compatibility
25 $ cat >> $HGRCPATH << EOF
26 > [experimental]
27 > copies.read-from = compatibility
28 > EOF
29 #endif
30
31 #if sidedata
32 $ cat >> $HGRCPATH << EOF
33 > [format]
34 > exp-use-side-data = yes
35 > exp-use-copies-side-data-changeset = yes
36 > EOF
37 #endif
38
39
21 $ hg init repo-chain
40 $ hg init repo-chain
22 $ cd repo-chain
41 $ cd repo-chain
23
42
24 Add some linear rename initialy
43 Add some linear rename initialy
25
44
26 $ touch a b h
45 $ touch a b h
27 $ hg ci -Am 'i-0 initial commit: a b h'
46 $ hg ci -Am 'i-0 initial commit: a b h'
28 adding a
47 adding a
29 adding b
48 adding b
30 adding h
49 adding h
31 $ hg mv a c
50 $ hg mv a c
32 $ hg ci -Am 'i-1: a -move-> c'
51 $ hg ci -Am 'i-1: a -move-> c'
33 $ hg mv c d
52 $ hg mv c d
34 $ hg ci -Am 'i-2: c -move-> d'
53 $ hg ci -Am 'i-2: c -move-> d'
35 $ hg log -G
54 $ hg log -G
36 @ 2 i-2: c -move-> d
55 @ 2 i-2: c -move-> d
37 |
56 |
38 o 1 i-1: a -move-> c
57 o 1 i-1: a -move-> c
39 |
58 |
40 o 0 i-0 initial commit: a b h
59 o 0 i-0 initial commit: a b h
41
60
42
61
43 And having another branch with renames on the other side
62 And having another branch with renames on the other side
44
63
45 $ hg mv d e
64 $ hg mv d e
46 $ hg ci -Am 'a-1: d -move-> e'
65 $ hg ci -Am 'a-1: d -move-> e'
47 $ hg mv e f
66 $ hg mv e f
48 $ hg ci -Am 'a-2: e -move-> f'
67 $ hg ci -Am 'a-2: e -move-> f'
49 $ hg log -G --rev '::.'
68 $ hg log -G --rev '::.'
50 @ 4 a-2: e -move-> f
69 @ 4 a-2: e -move-> f
51 |
70 |
52 o 3 a-1: d -move-> e
71 o 3 a-1: d -move-> e
53 |
72 |
54 o 2 i-2: c -move-> d
73 o 2 i-2: c -move-> d
55 |
74 |
56 o 1 i-1: a -move-> c
75 o 1 i-1: a -move-> c
57 |
76 |
58 o 0 i-0 initial commit: a b h
77 o 0 i-0 initial commit: a b h
59
78
60
79
61 Have a branching with nothing on one side
80 Have a branching with nothing on one side
62
81
63 $ hg up 'desc("i-2")'
82 $ hg up 'desc("i-2")'
64 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
83 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
65 $ echo foo > b
84 $ echo foo > b
66 $ hg ci -m 'b-1: b update'
85 $ hg ci -m 'b-1: b update'
67 created new head
86 created new head
68 $ hg log -G --rev '::.'
87 $ hg log -G --rev '::.'
69 @ 5 b-1: b update
88 @ 5 b-1: b update
70 |
89 |
71 o 2 i-2: c -move-> d
90 o 2 i-2: c -move-> d
72 |
91 |
73 o 1 i-1: a -move-> c
92 o 1 i-1: a -move-> c
74 |
93 |
75 o 0 i-0 initial commit: a b h
94 o 0 i-0 initial commit: a b h
76
95
77
96
78 Create a branch that delete a file previous renamed
97 Create a branch that delete a file previous renamed
79
98
80 $ hg up 'desc("i-2")'
99 $ hg up 'desc("i-2")'
81 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
100 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 $ hg rm d
101 $ hg rm d
83 $ hg ci -m 'c-1 delete d'
102 $ hg ci -m 'c-1 delete d'
84 created new head
103 created new head
85 $ hg log -G --rev '::.'
104 $ hg log -G --rev '::.'
86 @ 6 c-1 delete d
105 @ 6 c-1 delete d
87 |
106 |
88 o 2 i-2: c -move-> d
107 o 2 i-2: c -move-> d
89 |
108 |
90 o 1 i-1: a -move-> c
109 o 1 i-1: a -move-> c
91 |
110 |
92 o 0 i-0 initial commit: a b h
111 o 0 i-0 initial commit: a b h
93
112
94
113
95 Create a branch that delete a file previous renamed and recreate it
114 Create a branch that delete a file previous renamed and recreate it
96
115
97 $ hg up 'desc("i-2")'
116 $ hg up 'desc("i-2")'
98 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 $ hg rm d
118 $ hg rm d
100 $ hg ci -m 'd-1 delete d'
119 $ hg ci -m 'd-1 delete d'
101 created new head
120 created new head
102 $ echo bar > d
121 $ echo bar > d
103 $ hg add d
122 $ hg add d
104 $ hg ci -m 'd-2 re-add d'
123 $ hg ci -m 'd-2 re-add d'
105 $ hg log -G --rev '::.'
124 $ hg log -G --rev '::.'
106 @ 8 d-2 re-add d
125 @ 8 d-2 re-add d
107 |
126 |
108 o 7 d-1 delete d
127 o 7 d-1 delete d
109 |
128 |
110 o 2 i-2: c -move-> d
129 o 2 i-2: c -move-> d
111 |
130 |
112 o 1 i-1: a -move-> c
131 o 1 i-1: a -move-> c
113 |
132 |
114 o 0 i-0 initial commit: a b h
133 o 0 i-0 initial commit: a b h
115
134
116
135
117 Having another branch renaming a different file to the same filename as another
136 Having another branch renaming a different file to the same filename as another
118
137
119 $ hg up 'desc("i-2")'
138 $ hg up 'desc("i-2")'
120 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 $ hg mv b g
140 $ hg mv b g
122 $ hg ci -m 'e-1 b -move-> g'
141 $ hg ci -m 'e-1 b -move-> g'
123 created new head
142 created new head
124 $ hg mv g f
143 $ hg mv g f
125 $ hg ci -m 'e-2 g -move-> f'
144 $ hg ci -m 'e-2 g -move-> f'
126 $ hg log -G --rev '::.'
145 $ hg log -G --rev '::.'
127 @ 10 e-2 g -move-> f
146 @ 10 e-2 g -move-> f
128 |
147 |
129 o 9 e-1 b -move-> g
148 o 9 e-1 b -move-> g
130 |
149 |
131 o 2 i-2: c -move-> d
150 o 2 i-2: c -move-> d
132 |
151 |
133 o 1 i-1: a -move-> c
152 o 1 i-1: a -move-> c
134 |
153 |
135 o 0 i-0 initial commit: a b h
154 o 0 i-0 initial commit: a b h
136
155
137
156
138 merging with unrelated change does not interfere with the renames
157 merging with unrelated change does not interfere with the renames
139 ---------------------------------------------------------------
158 ---------------------------------------------------------------
140
159
141 - rename on one side
160 - rename on one side
142 - unrelated change on the other side
161 - unrelated change on the other side
143
162
144 $ hg up 'desc("b-1")'
163 $ hg up 'desc("b-1")'
145 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
164 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
146 $ hg merge 'desc("a-2")'
165 $ hg merge 'desc("a-2")'
147 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
166 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
148 (branch merge, don't forget to commit)
167 (branch merge, don't forget to commit)
149 $ hg ci -m 'mBAm-0 simple merge - one way'
168 $ hg ci -m 'mBAm-0 simple merge - one way'
150 $ hg up 'desc("a-2")'
169 $ hg up 'desc("a-2")'
151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 $ hg merge 'desc("b-1")'
171 $ hg merge 'desc("b-1")'
153 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
172 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 (branch merge, don't forget to commit)
173 (branch merge, don't forget to commit)
155 $ hg ci -m 'mABm-0 simple merge - the other way'
174 $ hg ci -m 'mABm-0 simple merge - the other way'
156 created new head
175 created new head
157 $ hg log -G --rev '::(desc("mABm")+desc("mBAm"))'
176 $ hg log -G --rev '::(desc("mABm")+desc("mBAm"))'
158 @ 12 mABm-0 simple merge - the other way
177 @ 12 mABm-0 simple merge - the other way
159 |\
178 |\
160 +---o 11 mBAm-0 simple merge - one way
179 +---o 11 mBAm-0 simple merge - one way
161 | |/
180 | |/
162 | o 5 b-1: b update
181 | o 5 b-1: b update
163 | |
182 | |
164 o | 4 a-2: e -move-> f
183 o | 4 a-2: e -move-> f
165 | |
184 | |
166 o | 3 a-1: d -move-> e
185 o | 3 a-1: d -move-> e
167 |/
186 |/
168 o 2 i-2: c -move-> d
187 o 2 i-2: c -move-> d
169 |
188 |
170 o 1 i-1: a -move-> c
189 o 1 i-1: a -move-> c
171 |
190 |
172 o 0 i-0 initial commit: a b h
191 o 0 i-0 initial commit: a b h
173
192
174
193
175 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mABm")'
194 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mABm")'
176 A f
195 A f
177 d
196 d
178 R d
197 R d
179 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBAm")'
198 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBAm")'
180 A f
199 A f
181 d
200 d
182 R d
201 R d
183 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mABm")'
202 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mABm")'
184 M b
203 M b
185 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mBAm")'
204 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mBAm")'
186 M b
205 M b
187 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mABm")'
206 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mABm")'
188 M b
207 M b
189 A f
208 A f
190 d
209 d
191 R d
210 R d
192 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBAm")'
211 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBAm")'
193 M b
212 M b
194 A f
213 A f
195 d
214 d
196 R d
215 R d
197 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mABm")'
216 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mABm")'
198 M b
217 M b
199 A f
218 A f
200 a
219 a
201 R a
220 R a
202 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBAm")'
221 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBAm")'
203 M b
222 M b
204 A f
223 A f
205 a
224 a
206 R a
225 R a
207
226
208 merging with the side having a delete
227 merging with the side having a delete
209 -------------------------------------
228 -------------------------------------
210
229
211 case summary:
230 case summary:
212 - one with change to an unrelated file
231 - one with change to an unrelated file
213 - one deleting the change
232 - one deleting the change
214 and recreate an unrelated file after the merge
233 and recreate an unrelated file after the merge
215
234
216 $ hg up 'desc("b-1")'
235 $ hg up 'desc("b-1")'
217 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
236 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
218 $ hg merge 'desc("c-1")'
237 $ hg merge 'desc("c-1")'
219 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
238 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
220 (branch merge, don't forget to commit)
239 (branch merge, don't forget to commit)
221 $ hg ci -m 'mBCm-0 simple merge - one way'
240 $ hg ci -m 'mBCm-0 simple merge - one way'
222 $ echo bar > d
241 $ echo bar > d
223 $ hg add d
242 $ hg add d
224 $ hg ci -m 'mBCm-1 re-add d'
243 $ hg ci -m 'mBCm-1 re-add d'
225 $ hg up 'desc("c-1")'
244 $ hg up 'desc("c-1")'
226 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
245 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
227 $ hg merge 'desc("b-1")'
246 $ hg merge 'desc("b-1")'
228 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
247 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
229 (branch merge, don't forget to commit)
248 (branch merge, don't forget to commit)
230 $ hg ci -m 'mCBm-0 simple merge - the other way'
249 $ hg ci -m 'mCBm-0 simple merge - the other way'
231 created new head
250 created new head
232 $ echo bar > d
251 $ echo bar > d
233 $ hg add d
252 $ hg add d
234 $ hg ci -m 'mCBm-1 re-add d'
253 $ hg ci -m 'mCBm-1 re-add d'
235 $ hg log -G --rev '::(desc("mCBm")+desc("mBCm"))'
254 $ hg log -G --rev '::(desc("mCBm")+desc("mBCm"))'
236 @ 16 mCBm-1 re-add d
255 @ 16 mCBm-1 re-add d
237 |
256 |
238 o 15 mCBm-0 simple merge - the other way
257 o 15 mCBm-0 simple merge - the other way
239 |\
258 |\
240 | | o 14 mBCm-1 re-add d
259 | | o 14 mBCm-1 re-add d
241 | | |
260 | | |
242 +---o 13 mBCm-0 simple merge - one way
261 +---o 13 mBCm-0 simple merge - one way
243 | |/
262 | |/
244 | o 6 c-1 delete d
263 | o 6 c-1 delete d
245 | |
264 | |
246 o | 5 b-1: b update
265 o | 5 b-1: b update
247 |/
266 |/
248 o 2 i-2: c -move-> d
267 o 2 i-2: c -move-> d
249 |
268 |
250 o 1 i-1: a -move-> c
269 o 1 i-1: a -move-> c
251 |
270 |
252 o 0 i-0 initial commit: a b h
271 o 0 i-0 initial commit: a b h
253
272
254 - comparing from the merge
273 - comparing from the merge
255
274
256 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBCm-0")'
275 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBCm-0")'
257 R d
276 R d
258 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCBm-0")'
277 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCBm-0")'
259 R d
278 R d
260 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBCm-0")'
279 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBCm-0")'
261 M b
280 M b
262 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCBm-0")'
281 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCBm-0")'
263 M b
282 M b
264 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBCm-0")'
283 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBCm-0")'
265 M b
284 M b
266 R d
285 R d
267 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mCBm-0")'
286 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mCBm-0")'
268 M b
287 M b
269 R d
288 R d
270 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-0")'
289 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-0")'
271 M b
290 M b
272 R a
291 R a
273 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-0")'
292 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-0")'
274 M b
293 M b
275 R a
294 R a
276
295
277 - comparing with the merge children re-adding the file
296 - comparing with the merge children re-adding the file
278
297
279 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBCm-1")'
298 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBCm-1")'
280 M d
299 M d
281 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCBm-1")'
300 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mCBm-1")'
282 M d
301 M d
283 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBCm-1")'
302 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mBCm-1")'
284 M b
303 M b
285 A d
304 A d
286 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCBm-1")'
305 $ hg status --copies --rev 'desc("c-1")' --rev 'desc("mCBm-1")'
287 M b
306 M b
288 A d
307 A d
289 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBCm-1")'
308 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBCm-1")'
290 M b
309 M b
291 M d
310 M d
292 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mCBm-1")'
311 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mCBm-1")'
293 M b
312 M b
294 M d
313 M d
295 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-1")'
314 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBCm-1")'
296 M b
315 M b
297 A d
316 A d
298 R a
317 R a
299 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-1")'
318 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mCBm-1")'
300 M b
319 M b
301 A d
320 A d
302 R a
321 R a
303
322
304 Comparing with a merge re-adding the file afterward
323 Comparing with a merge re-adding the file afterward
305 ---------------------------------------------------
324 ---------------------------------------------------
306
325
307 Merge:
326 Merge:
308 - one with change to an unrelated file
327 - one with change to an unrelated file
309 - one deleting and recreating the change
328 - one deleting and recreating the change
310
329
311 Note:
330 Note:
312 | In this case, one of the merge wrongly record a merge while there is none.
331 | In this case, one of the merge wrongly record a merge while there is none.
313 | This lead to bad copy tracing information to be dug up.
332 | This lead to bad copy tracing information to be dug up.
314
333
315 $ hg up 'desc("b-1")'
334 $ hg up 'desc("b-1")'
316 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
335 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
317 $ hg merge 'desc("d-2")'
336 $ hg merge 'desc("d-2")'
318 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
319 (branch merge, don't forget to commit)
338 (branch merge, don't forget to commit)
320 $ hg ci -m 'mBDm-0 simple merge - one way'
339 $ hg ci -m 'mBDm-0 simple merge - one way'
321 $ hg up 'desc("d-2")'
340 $ hg up 'desc("d-2")'
322 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
323 $ hg merge 'desc("b-1")'
342 $ hg merge 'desc("b-1")'
324 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
343 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
325 (branch merge, don't forget to commit)
344 (branch merge, don't forget to commit)
326 $ hg ci -m 'mDBm-0 simple merge - the other way'
345 $ hg ci -m 'mDBm-0 simple merge - the other way'
327 created new head
346 created new head
328 $ hg log -G --rev '::(desc("mDBm")+desc("mBDm"))'
347 $ hg log -G --rev '::(desc("mDBm")+desc("mBDm"))'
329 @ 18 mDBm-0 simple merge - the other way
348 @ 18 mDBm-0 simple merge - the other way
330 |\
349 |\
331 +---o 17 mBDm-0 simple merge - one way
350 +---o 17 mBDm-0 simple merge - one way
332 | |/
351 | |/
333 | o 8 d-2 re-add d
352 | o 8 d-2 re-add d
334 | |
353 | |
335 | o 7 d-1 delete d
354 | o 7 d-1 delete d
336 | |
355 | |
337 o | 5 b-1: b update
356 o | 5 b-1: b update
338 |/
357 |/
339 o 2 i-2: c -move-> d
358 o 2 i-2: c -move-> d
340 |
359 |
341 o 1 i-1: a -move-> c
360 o 1 i-1: a -move-> c
342 |
361 |
343 o 0 i-0 initial commit: a b h
362 o 0 i-0 initial commit: a b h
344
363
345 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBDm-0")'
364 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBDm-0")'
346 M d
365 M d
347 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mDBm-0")'
366 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mDBm-0")'
348 M d
367 M d
349 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mBDm-0")'
368 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mBDm-0")'
350 M b
369 M b
351 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mDBm-0")'
370 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mDBm-0")'
352 M b
371 M b
353 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBDm-0")'
372 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mBDm-0")'
354 M b
373 M b
355 M d
374 M d
356 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mDBm-0")'
375 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mDBm-0")'
357 M b
376 M b
358 M d
377 M d
359
378
360 The bugs makes recorded copy is different depending of where we started the merge from since
379 The bugs makes recorded copy is different depending of where we started the merge from since
361
380
362 $ hg manifest --debug --rev 'desc("mBDm-0")' | grep '644 d'
381 $ hg manifest --debug --rev 'desc("mBDm-0")' | grep '644 d'
363 b004912a8510032a0350a74daa2803dadfb00e12 644 d
382 b004912a8510032a0350a74daa2803dadfb00e12 644 d
364 $ hg manifest --debug --rev 'desc("mDBm-0")' | grep '644 d'
383 $ hg manifest --debug --rev 'desc("mDBm-0")' | grep '644 d'
365 b004912a8510032a0350a74daa2803dadfb00e12 644 d
384 b004912a8510032a0350a74daa2803dadfb00e12 644 d
366
385
367 The 0bb5445dc4d02f4e0d86cf16f9f3a411d0f17744 entry is wrong, since the file was
386 The 0bb5445dc4d02f4e0d86cf16f9f3a411d0f17744 entry is wrong, since the file was
368 deleted on one side (then recreate) and untouched on the other side, no "merge"
387 deleted on one side (then recreate) and untouched on the other side, no "merge"
369 has happened. The resulting `d` file is the untouched version from branch `D`,
388 has happened. The resulting `d` file is the untouched version from branch `D`,
370 not a merge.
389 not a merge.
371
390
372 $ hg manifest --debug --rev 'desc("d-2")' | grep '644 d'
391 $ hg manifest --debug --rev 'desc("d-2")' | grep '644 d'
373 b004912a8510032a0350a74daa2803dadfb00e12 644 d
392 b004912a8510032a0350a74daa2803dadfb00e12 644 d
374 $ hg manifest --debug --rev 'desc("b-1")' | grep '644 d'
393 $ hg manifest --debug --rev 'desc("b-1")' | grep '644 d'
375 01c2f5eabdc4ce2bdee42b5f86311955e6c8f573 644 d
394 01c2f5eabdc4ce2bdee42b5f86311955e6c8f573 644 d
376 $ hg debugindex d
395 $ hg debugindex d
377 rev linkrev nodeid p1 p2
396 rev linkrev nodeid p1 p2
378 0 2 01c2f5eabdc4 000000000000 000000000000
397 0 2 01c2f5eabdc4 000000000000 000000000000
379 1 8 b004912a8510 000000000000 000000000000
398 1 8 b004912a8510 000000000000 000000000000
380
399
381 (This `hg log` output if wrong, since no merge actually happened).
400 (This `hg log` output if wrong, since no merge actually happened).
382
401
383 $ hg log -Gfr 'desc("mBDm-0")' d
402 $ hg log -Gfr 'desc("mBDm-0")' d
384 o 8 d-2 re-add d
403 o 8 d-2 re-add d
385 |
404 |
386 ~
405 ~
387
406
388 This `hg log` output is correct
407 This `hg log` output is correct
389
408
390 $ hg log -Gfr 'desc("mDBm-0")' d
409 $ hg log -Gfr 'desc("mDBm-0")' d
391 o 8 d-2 re-add d
410 o 8 d-2 re-add d
392 |
411 |
393 ~
412 ~
394
413
395 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBDm-0")'
414 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBDm-0")'
396 M b
415 M b
397 A d
416 A d
398 R a
417 R a
399 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDBm-0")'
418 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDBm-0")'
400 M b
419 M b
401 A d
420 A d
402 R a
421 R a
403
422
404
423
405 Comparing with a merge with colliding rename
424 Comparing with a merge with colliding rename
406 --------------------------------------------
425 --------------------------------------------
407
426
408 - the "e-" branch renaming b to f (through 'g')
427 - the "e-" branch renaming b to f (through 'g')
409 - the "a-" branch renaming d to f (through e)
428 - the "a-" branch renaming d to f (through e)
410
429
411 $ hg up 'desc("a-2")'
430 $ hg up 'desc("a-2")'
412 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
431 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
413 $ hg merge 'desc("e-2")'
432 $ hg merge 'desc("e-2")'
414 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
433 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
415 (branch merge, don't forget to commit)
434 (branch merge, don't forget to commit)
416 $ hg ci -m 'mAEm-0 simple merge - one way'
435 $ hg ci -m 'mAEm-0 simple merge - one way'
417 $ hg up 'desc("e-2")'
436 $ hg up 'desc("e-2")'
418 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
419 $ hg merge 'desc("a-2")'
438 $ hg merge 'desc("a-2")'
420 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
439 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
421 (branch merge, don't forget to commit)
440 (branch merge, don't forget to commit)
422 $ hg ci -m 'mEAm-0 simple merge - the other way'
441 $ hg ci -m 'mEAm-0 simple merge - the other way'
423 created new head
442 created new head
424 $ hg log -G --rev '::(desc("mAEm")+desc("mEAm"))'
443 $ hg log -G --rev '::(desc("mAEm")+desc("mEAm"))'
425 @ 20 mEAm-0 simple merge - the other way
444 @ 20 mEAm-0 simple merge - the other way
426 |\
445 |\
427 +---o 19 mAEm-0 simple merge - one way
446 +---o 19 mAEm-0 simple merge - one way
428 | |/
447 | |/
429 | o 10 e-2 g -move-> f
448 | o 10 e-2 g -move-> f
430 | |
449 | |
431 | o 9 e-1 b -move-> g
450 | o 9 e-1 b -move-> g
432 | |
451 | |
433 o | 4 a-2: e -move-> f
452 o | 4 a-2: e -move-> f
434 | |
453 | |
435 o | 3 a-1: d -move-> e
454 o | 3 a-1: d -move-> e
436 |/
455 |/
437 o 2 i-2: c -move-> d
456 o 2 i-2: c -move-> d
438 |
457 |
439 o 1 i-1: a -move-> c
458 o 1 i-1: a -move-> c
440 |
459 |
441 o 0 i-0 initial commit: a b h
460 o 0 i-0 initial commit: a b h
442
461
443 $ hg manifest --debug --rev 'desc("mAEm-0")' | grep '644 f'
462 $ hg manifest --debug --rev 'desc("mAEm-0")' | grep '644 f'
444 eb806e34ef6be4c264effd5933d31004ad15a793 644 f
463 eb806e34ef6be4c264effd5933d31004ad15a793 644 f
445 $ hg manifest --debug --rev 'desc("mEAm-0")' | grep '644 f'
464 $ hg manifest --debug --rev 'desc("mEAm-0")' | grep '644 f'
446 eb806e34ef6be4c264effd5933d31004ad15a793 644 f
465 eb806e34ef6be4c264effd5933d31004ad15a793 644 f
447 $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f'
466 $ hg manifest --debug --rev 'desc("a-2")' | grep '644 f'
448 0dd616bc7ab1a111921d95d76f69cda5c2ac539c 644 f
467 0dd616bc7ab1a111921d95d76f69cda5c2ac539c 644 f
449 $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f'
468 $ hg manifest --debug --rev 'desc("e-2")' | grep '644 f'
450 6da5a2eecb9c833f830b67a4972366d49a9a142c 644 f
469 6da5a2eecb9c833f830b67a4972366d49a9a142c 644 f
451 $ hg debugindex f
470 $ hg debugindex f
452 rev linkrev nodeid p1 p2
471 rev linkrev nodeid p1 p2
453 0 4 0dd616bc7ab1 000000000000 000000000000
472 0 4 0dd616bc7ab1 000000000000 000000000000
454 1 10 6da5a2eecb9c 000000000000 000000000000
473 1 10 6da5a2eecb9c 000000000000 000000000000
455 2 19 eb806e34ef6b 0dd616bc7ab1 6da5a2eecb9c
474 2 19 eb806e34ef6b 0dd616bc7ab1 6da5a2eecb9c
475
476 # Here the filelog based implementation is not looking at the rename
477 # information (because the file exist on both side). However the changelog
478 # based on works fine. We have different output.
479
456 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")'
480 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")'
457 M f
481 M f
482 b (no-filelog !)
458 R b
483 R b
459 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")'
484 $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")'
460 M f
485 M f
486 b (no-filelog !)
461 R b
487 R b
462 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")'
488 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")'
463 M f
489 M f
490 d (no-filelog !)
464 R d
491 R d
465 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")'
492 $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")'
466 M f
493 M f
494 d (no-filelog !)
467 R d
495 R d
468 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")'
496 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")'
469 A f
497 A f
470 d
498 d
471 R d
499 R d
472 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("e-2")'
500 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("e-2")'
473 A f
501 A f
474 b
502 b
475 R b
503 R b
504
505 # From here, we run status against revision where both source file exists.
506 #
507 # The filelog based implementation picks an arbitrary side based on revision
508 # numbers. So the same side "wins" whatever the parents order is. This is
509 # sub-optimal because depending on revision numbers means the result can be
510 # different from one repository to the next.
511 #
512 # The changeset based algorithm use the parent order to break tie on conflicting
513 # information and will have a different order depending on who is p1 and p2.
514 # That order is stable accross repositories. (data from p1 prevails)
515
476 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAEm-0")'
516 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAEm-0")'
477 A f
517 A f
478 d
518 d
479 R b
519 R b
480 R d
520 R d
481 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEAm-0")'
521 $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEAm-0")'
482 A f
522 A f
483 d
523 d (filelog !)
524 b (no-filelog !)
484 R b
525 R b
485 R d
526 R d
486 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")'
527 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")'
487 A f
528 A f
488 a
529 a
489 R a
530 R a
490 R b
531 R b
491 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")'
532 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")'
492 A f
533 A f
493 a
534 a (filelog !)
535 b (no-filelog !)
494 R a
536 R a
495 R b
537 R b
496
538
497
539
498 Note:
540 Note:
499 | In this case, one of the merge wrongly record a merge while there is none.
541 | In this case, one of the merge wrongly record a merge while there is none.
500 | This lead to bad copy tracing information to be dug up.
542 | This lead to bad copy tracing information to be dug up.
501
543
502
544
503 Merge:
545 Merge:
504 - one with change to an unrelated file (b)
546 - one with change to an unrelated file (b)
505 - one overwriting a file (d) with a rename (from h to i to d)
547 - one overwriting a file (d) with a rename (from h to i to d)
506
548
507 $ hg up 'desc("i-2")'
549 $ hg up 'desc("i-2")'
508 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
550 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
509 $ hg mv h i
551 $ hg mv h i
510 $ hg commit -m "f-1: rename h -> i"
552 $ hg commit -m "f-1: rename h -> i"
511 created new head
553 created new head
512 $ hg mv --force i d
554 $ hg mv --force i d
513 $ hg commit -m "f-2: rename i -> d"
555 $ hg commit -m "f-2: rename i -> d"
514 $ hg debugindex d
556 $ hg debugindex d
515 rev linkrev nodeid p1 p2
557 rev linkrev nodeid p1 p2
516 0 2 01c2f5eabdc4 000000000000 000000000000
558 0 2 01c2f5eabdc4 000000000000 000000000000
517 1 8 b004912a8510 000000000000 000000000000
559 1 8 b004912a8510 000000000000 000000000000
518 2 22 c72365ee036f 000000000000 000000000000
560 2 22 c72365ee036f 000000000000 000000000000
519 $ hg up 'desc("b-1")'
561 $ hg up 'desc("b-1")'
520 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
562 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
521 $ hg merge 'desc("f-2")'
563 $ hg merge 'desc("f-2")'
522 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
564 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
523 (branch merge, don't forget to commit)
565 (branch merge, don't forget to commit)
524 $ hg ci -m 'mBFm-0 simple merge - one way'
566 $ hg ci -m 'mBFm-0 simple merge - one way'
525 $ hg up 'desc("f-2")'
567 $ hg up 'desc("f-2")'
526 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
568 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
527 $ hg merge 'desc("b-1")'
569 $ hg merge 'desc("b-1")'
528 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
570 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
529 (branch merge, don't forget to commit)
571 (branch merge, don't forget to commit)
530 $ hg ci -m 'mFBm-0 simple merge - the other way'
572 $ hg ci -m 'mFBm-0 simple merge - the other way'
531 created new head
573 created new head
532 $ hg log -G --rev '::(desc("mBFm")+desc("mFBm"))'
574 $ hg log -G --rev '::(desc("mBFm")+desc("mFBm"))'
533 @ 24 mFBm-0 simple merge - the other way
575 @ 24 mFBm-0 simple merge - the other way
534 |\
576 |\
535 +---o 23 mBFm-0 simple merge - one way
577 +---o 23 mBFm-0 simple merge - one way
536 | |/
578 | |/
537 | o 22 f-2: rename i -> d
579 | o 22 f-2: rename i -> d
538 | |
580 | |
539 | o 21 f-1: rename h -> i
581 | o 21 f-1: rename h -> i
540 | |
582 | |
541 o | 5 b-1: b update
583 o | 5 b-1: b update
542 |/
584 |/
543 o 2 i-2: c -move-> d
585 o 2 i-2: c -move-> d
544 |
586 |
545 o 1 i-1: a -move-> c
587 o 1 i-1: a -move-> c
546 |
588 |
547 o 0 i-0 initial commit: a b h
589 o 0 i-0 initial commit: a b h
548
590
549 The overwriting should take over. However, the behavior is currently buggy
591 The overwriting should take over. However, the behavior is currently buggy
550
592
551 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBFm-0")'
593 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mBFm-0")'
552 M b
594 M b
553 A d
595 A d
554 h
596 h
555 h (false !)
597 h (false !)
556 R a
598 R a
557 R h
599 R h
558 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFBm-0")'
600 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFBm-0")'
559 M b
601 M b
560 A d
602 A d
561 h
603 h
562 R a
604 R a
563 R h
605 R h
564 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")'
606 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")'
565 M d
607 M d
608 h (no-filelog !)
566 R h
609 R h
567 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")'
610 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")'
568 M b
611 M b
569 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")'
612 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")'
570 M b
613 M b
571 M d
614 M d
615 i (no-filelog !)
572 R i
616 R i
573 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")'
617 $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")'
574 M d
618 M d
619 h (no-filelog !)
575 R h
620 R h
576 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")'
621 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")'
577 M b
622 M b
578 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")'
623 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")'
579 M b
624 M b
580 M d
625 M d
626 i (no-filelog !)
581 R i
627 R i
582
628
583 The following graphlog is wrong, the "a -> c -> d" chain was overwritten and should not appear.
629 The following graphlog is wrong, the "a -> c -> d" chain was overwritten and should not appear.
584
630
585 $ hg log -Gfr 'desc("mBFm-0")' d
631 $ hg log -Gfr 'desc("mBFm-0")' d
586 o 22 f-2: rename i -> d
632 o 22 f-2: rename i -> d
587 |
633 |
588 o 21 f-1: rename h -> i
634 o 21 f-1: rename h -> i
589 :
635 :
590 o 0 i-0 initial commit: a b h
636 o 0 i-0 initial commit: a b h
591
637
592
638
593 The following output is correct.
639 The following output is correct.
594
640
595 $ hg log -Gfr 'desc("mFBm-0")' d
641 $ hg log -Gfr 'desc("mFBm-0")' d
596 o 22 f-2: rename i -> d
642 o 22 f-2: rename i -> d
597 |
643 |
598 o 21 f-1: rename h -> i
644 o 21 f-1: rename h -> i
599 :
645 :
600 o 0 i-0 initial commit: a b h
646 o 0 i-0 initial commit: a b h
601
647
602
648
603
649
604 Merge:
650 Merge:
605 - one with change to a file
651 - one with change to a file
606 - one deleting and recreating the file
652 - one deleting and recreating the file
607
653
608 Unlike in the 'BD/DB' cases, an actual merge happened here. So we should
654 Unlike in the 'BD/DB' cases, an actual merge happened here. So we should
609 consider history and rename on both branch of the merge.
655 consider history and rename on both branch of the merge.
610
656
611 $ hg up 'desc("i-2")'
657 $ hg up 'desc("i-2")'
612 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
658 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
613 $ echo "some update" >> d
659 $ echo "some update" >> d
614 $ hg commit -m "g-1: update d"
660 $ hg commit -m "g-1: update d"
615 created new head
661 created new head
616 $ hg up 'desc("d-2")'
662 $ hg up 'desc("d-2")'
617 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
663 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
618 $ hg merge 'desc("g-1")' --tool :union
664 $ hg merge 'desc("g-1")' --tool :union
619 merging d
665 merging d
620 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
666 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
621 (branch merge, don't forget to commit)
667 (branch merge, don't forget to commit)
622 $ hg ci -m 'mDGm-0 simple merge - one way'
668 $ hg ci -m 'mDGm-0 simple merge - one way'
623 $ hg up 'desc("g-1")'
669 $ hg up 'desc("g-1")'
624 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
670 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
625 $ hg merge 'desc("d-2")' --tool :union
671 $ hg merge 'desc("d-2")' --tool :union
626 merging d
672 merging d
627 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
673 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
628 (branch merge, don't forget to commit)
674 (branch merge, don't forget to commit)
629 $ hg ci -m 'mGDm-0 simple merge - the other way'
675 $ hg ci -m 'mGDm-0 simple merge - the other way'
630 created new head
676 created new head
631 $ hg log -G --rev '::(desc("mDGm")+desc("mGDm"))'
677 $ hg log -G --rev '::(desc("mDGm")+desc("mGDm"))'
632 @ 27 mGDm-0 simple merge - the other way
678 @ 27 mGDm-0 simple merge - the other way
633 |\
679 |\
634 +---o 26 mDGm-0 simple merge - one way
680 +---o 26 mDGm-0 simple merge - one way
635 | |/
681 | |/
636 | o 25 g-1: update d
682 | o 25 g-1: update d
637 | |
683 | |
638 o | 8 d-2 re-add d
684 o | 8 d-2 re-add d
639 | |
685 | |
640 o | 7 d-1 delete d
686 o | 7 d-1 delete d
641 |/
687 |/
642 o 2 i-2: c -move-> d
688 o 2 i-2: c -move-> d
643 |
689 |
644 o 1 i-1: a -move-> c
690 o 1 i-1: a -move-> c
645 |
691 |
646 o 0 i-0 initial commit: a b h
692 o 0 i-0 initial commit: a b h
647
693
694 One side of the merge have a long history with rename. The other side of the
695 merge point to a new file with a smaller history. Each side is "valid".
696
697 (and again the filelog based algorithm only explore one, with a pick based on
698 revision numbers)
699
648 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")'
700 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")'
649 A d
701 A d
650 a
702 a (filelog !)
651 R a
703 R a
652 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")'
704 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")'
653 A d
705 A d
654 a
706 a
655 R a
707 R a
656 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mDGm-0")'
708 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mDGm-0")'
657 M d
709 M d
658 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mGDm-0")'
710 $ hg status --copies --rev 'desc("d-2")' --rev 'desc("mGDm-0")'
659 M d
711 M d
660 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mDGm-0")'
712 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mDGm-0")'
661 M d
713 M d
662 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGDm-0")'
714 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGDm-0")'
663 M d
715 M d
664
716
665 $ hg log -Gfr 'desc("mDGm-0")' d
717 $ hg log -Gfr 'desc("mDGm-0")' d
666 o 26 mDGm-0 simple merge - one way
718 o 26 mDGm-0 simple merge - one way
667 |\
719 |\
668 | o 25 g-1: update d
720 | o 25 g-1: update d
669 | |
721 | |
670 o | 8 d-2 re-add d
722 o | 8 d-2 re-add d
671 |/
723 |/
672 o 2 i-2: c -move-> d
724 o 2 i-2: c -move-> d
673 |
725 |
674 o 1 i-1: a -move-> c
726 o 1 i-1: a -move-> c
675 |
727 |
676 o 0 i-0 initial commit: a b h
728 o 0 i-0 initial commit: a b h
677
729
678
730
679
731
680 $ hg log -Gfr 'desc("mDGm-0")' d
732 $ hg log -Gfr 'desc("mDGm-0")' d
681 o 26 mDGm-0 simple merge - one way
733 o 26 mDGm-0 simple merge - one way
682 |\
734 |\
683 | o 25 g-1: update d
735 | o 25 g-1: update d
684 | |
736 | |
685 o | 8 d-2 re-add d
737 o | 8 d-2 re-add d
686 |/
738 |/
687 o 2 i-2: c -move-> d
739 o 2 i-2: c -move-> d
688 |
740 |
689 o 1 i-1: a -move-> c
741 o 1 i-1: a -move-> c
690 |
742 |
691 o 0 i-0 initial commit: a b h
743 o 0 i-0 initial commit: a b h
692
744
693
745
694
746
695 Merge:
747 Merge:
696 - one with change to a file (d)
748 - one with change to a file (d)
697 - one overwriting that file with a rename (from h to i, to d)
749 - one overwriting that file with a rename (from h to i, to d)
698
750
699 This case is similar to BF/FB, but an actual merge happens, so both side of the
751 This case is similar to BF/FB, but an actual merge happens, so both side of the
700 history are relevant.
752 history are relevant.
701
753
702 Note:
754 Note:
703 | In this case, the merge get conflicting information since on one side we have
755 | In this case, the merge get conflicting information since on one side we have
704 | "a -> c -> d". and one the other one we have "h -> i -> d".
756 | "a -> c -> d". and one the other one we have "h -> i -> d".
705 |
757 |
706 | The current code arbitrarily pick one side
758 | The current code arbitrarily pick one side
707
759
708 $ hg up 'desc("f-2")'
760 $ hg up 'desc("f-2")'
709 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
761 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
710 $ hg merge 'desc("g-1")' --tool :union
762 $ hg merge 'desc("g-1")' --tool :union
711 merging d
763 merging d
712 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
764 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
713 (branch merge, don't forget to commit)
765 (branch merge, don't forget to commit)
714 $ hg ci -m 'mFGm-0 simple merge - one way'
766 $ hg ci -m 'mFGm-0 simple merge - one way'
715 created new head
767 created new head
716 $ hg up 'desc("g-1")'
768 $ hg up 'desc("g-1")'
717 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
769 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
718 $ hg merge 'desc("f-2")' --tool :union
770 $ hg merge 'desc("f-2")' --tool :union
719 merging d
771 merging d
720 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
772 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
721 (branch merge, don't forget to commit)
773 (branch merge, don't forget to commit)
722 $ hg ci -m 'mGFm-0 simple merge - the other way'
774 $ hg ci -m 'mGFm-0 simple merge - the other way'
723 created new head
775 created new head
724 $ hg log -G --rev '::(desc("mGFm")+desc("mFGm"))'
776 $ hg log -G --rev '::(desc("mGFm")+desc("mFGm"))'
725 @ 29 mGFm-0 simple merge - the other way
777 @ 29 mGFm-0 simple merge - the other way
726 |\
778 |\
727 +---o 28 mFGm-0 simple merge - one way
779 +---o 28 mFGm-0 simple merge - one way
728 | |/
780 | |/
729 | o 25 g-1: update d
781 | o 25 g-1: update d
730 | |
782 | |
731 o | 22 f-2: rename i -> d
783 o | 22 f-2: rename i -> d
732 | |
784 | |
733 o | 21 f-1: rename h -> i
785 o | 21 f-1: rename h -> i
734 |/
786 |/
735 o 2 i-2: c -move-> d
787 o 2 i-2: c -move-> d
736 |
788 |
737 o 1 i-1: a -move-> c
789 o 1 i-1: a -move-> c
738 |
790 |
739 o 0 i-0 initial commit: a b h
791 o 0 i-0 initial commit: a b h
740
792
741 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")'
793 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")'
742 A d
794 A d
743 a
795 h (no-filelog !)
796 a (filelog !)
744 R a
797 R a
745 R h
798 R h
746 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")'
799 $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")'
747 A d
800 A d
748 a
801 a
749 R a
802 R a
750 R h
803 R h
751 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFGm-0")'
804 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFGm-0")'
752 M d
805 M d
753 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mGFm-0")'
806 $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mGFm-0")'
754 M d
807 M d
755 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFGm-0")'
808 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFGm-0")'
756 M d
809 M d
810 i (no-filelog !)
757 R i
811 R i
758 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mGFm-0")'
812 $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mGFm-0")'
759 M d
813 M d
814 i (no-filelog !)
760 R i
815 R i
761 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")'
816 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")'
762 M d
817 M d
818 h (no-filelog !)
763 R h
819 R h
764 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")'
820 $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")'
765 M d
821 M d
822 h (no-filelog !)
766 R h
823 R h
767
824
768 $ hg log -Gfr 'desc("mFGm-0")' d
825 $ hg log -Gfr 'desc("mFGm-0")' d
769 o 28 mFGm-0 simple merge - one way
826 o 28 mFGm-0 simple merge - one way
770 |\
827 |\
771 | o 25 g-1: update d
828 | o 25 g-1: update d
772 | |
829 | |
773 o | 22 f-2: rename i -> d
830 o | 22 f-2: rename i -> d
774 | |
831 | |
775 o | 21 f-1: rename h -> i
832 o | 21 f-1: rename h -> i
776 |/
833 |/
777 o 2 i-2: c -move-> d
834 o 2 i-2: c -move-> d
778 |
835 |
779 o 1 i-1: a -move-> c
836 o 1 i-1: a -move-> c
780 |
837 |
781 o 0 i-0 initial commit: a b h
838 o 0 i-0 initial commit: a b h
782
839
783
840
784 $ hg log -Gfr 'desc("mGFm-0")' d
841 $ hg log -Gfr 'desc("mGFm-0")' d
785 @ 29 mGFm-0 simple merge - the other way
842 @ 29 mGFm-0 simple merge - the other way
786 |\
843 |\
787 | o 25 g-1: update d
844 | o 25 g-1: update d
788 | |
845 | |
789 o | 22 f-2: rename i -> d
846 o | 22 f-2: rename i -> d
790 | |
847 | |
791 o | 21 f-1: rename h -> i
848 o | 21 f-1: rename h -> i
792 |/
849 |/
793 o 2 i-2: c -move-> d
850 o 2 i-2: c -move-> d
794 |
851 |
795 o 1 i-1: a -move-> c
852 o 1 i-1: a -move-> c
796 |
853 |
797 o 0 i-0 initial commit: a b h
854 o 0 i-0 initial commit: a b h
798
855
General Comments 0
You need to be logged in to leave comments. Login now