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