##// END OF EJS Templates
copytrace: use the full copytracing method if only drafts are involved...
Pulkit Goyal -
r34289:fc3b8483 default
parent child Browse files
Show More
@@ -1,844 +1,862 b''
1 # copies.py - copy detection for Mercurial
1 # copies.py - copy detection for Mercurial
2 #
2 #
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import heapq
11 import heapq
12 import os
12 import os
13
13
14 from . import (
14 from . import (
15 match as matchmod,
15 match as matchmod,
16 node,
16 node,
17 pathutil,
17 pathutil,
18 phases,
18 scmutil,
19 scmutil,
19 util,
20 util,
20 )
21 )
21
22
22 def _findlimit(repo, a, b):
23 def _findlimit(repo, a, b):
23 """
24 """
24 Find the last revision that needs to be checked to ensure that a full
25 Find the last revision that needs to be checked to ensure that a full
25 transitive closure for file copies can be properly calculated.
26 transitive closure for file copies can be properly calculated.
26 Generally, this means finding the earliest revision number that's an
27 Generally, this means finding the earliest revision number that's an
27 ancestor of a or b but not both, except when a or b is a direct descendent
28 ancestor of a or b but not both, except when a or b is a direct descendent
28 of the other, in which case we can return the minimum revnum of a and b.
29 of the other, in which case we can return the minimum revnum of a and b.
29 None if no such revision exists.
30 None if no such revision exists.
30 """
31 """
31
32
32 # basic idea:
33 # basic idea:
33 # - mark a and b with different sides
34 # - mark a and b with different sides
34 # - if a parent's children are all on the same side, the parent is
35 # - if a parent's children are all on the same side, the parent is
35 # on that side, otherwise it is on no side
36 # on that side, otherwise it is on no side
36 # - walk the graph in topological order with the help of a heap;
37 # - walk the graph in topological order with the help of a heap;
37 # - add unseen parents to side map
38 # - add unseen parents to side map
38 # - clear side of any parent that has children on different sides
39 # - clear side of any parent that has children on different sides
39 # - track number of interesting revs that might still be on a side
40 # - track number of interesting revs that might still be on a side
40 # - track the lowest interesting rev seen
41 # - track the lowest interesting rev seen
41 # - quit when interesting revs is zero
42 # - quit when interesting revs is zero
42
43
43 cl = repo.changelog
44 cl = repo.changelog
44 working = len(cl) # pseudo rev for the working directory
45 working = len(cl) # pseudo rev for the working directory
45 if a is None:
46 if a is None:
46 a = working
47 a = working
47 if b is None:
48 if b is None:
48 b = working
49 b = working
49
50
50 side = {a: -1, b: 1}
51 side = {a: -1, b: 1}
51 visit = [-a, -b]
52 visit = [-a, -b]
52 heapq.heapify(visit)
53 heapq.heapify(visit)
53 interesting = len(visit)
54 interesting = len(visit)
54 hascommonancestor = False
55 hascommonancestor = False
55 limit = working
56 limit = working
56
57
57 while interesting:
58 while interesting:
58 r = -heapq.heappop(visit)
59 r = -heapq.heappop(visit)
59 if r == working:
60 if r == working:
60 parents = [cl.rev(p) for p in repo.dirstate.parents()]
61 parents = [cl.rev(p) for p in repo.dirstate.parents()]
61 else:
62 else:
62 parents = cl.parentrevs(r)
63 parents = cl.parentrevs(r)
63 for p in parents:
64 for p in parents:
64 if p < 0:
65 if p < 0:
65 continue
66 continue
66 if p not in side:
67 if p not in side:
67 # first time we see p; add it to visit
68 # first time we see p; add it to visit
68 side[p] = side[r]
69 side[p] = side[r]
69 if side[p]:
70 if side[p]:
70 interesting += 1
71 interesting += 1
71 heapq.heappush(visit, -p)
72 heapq.heappush(visit, -p)
72 elif side[p] and side[p] != side[r]:
73 elif side[p] and side[p] != side[r]:
73 # p was interesting but now we know better
74 # p was interesting but now we know better
74 side[p] = 0
75 side[p] = 0
75 interesting -= 1
76 interesting -= 1
76 hascommonancestor = True
77 hascommonancestor = True
77 if side[r]:
78 if side[r]:
78 limit = r # lowest rev visited
79 limit = r # lowest rev visited
79 interesting -= 1
80 interesting -= 1
80
81
81 if not hascommonancestor:
82 if not hascommonancestor:
82 return None
83 return None
83
84
84 # Consider the following flow (see test-commit-amend.t under issue4405):
85 # Consider the following flow (see test-commit-amend.t under issue4405):
85 # 1/ File 'a0' committed
86 # 1/ File 'a0' committed
86 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
87 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
87 # 3/ Move back to first commit
88 # 3/ Move back to first commit
88 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
89 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
89 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
90 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
90 #
91 #
91 # During the amend in step five, we will be in this state:
92 # During the amend in step five, we will be in this state:
92 #
93 #
93 # @ 3 temporary amend commit for a1-amend
94 # @ 3 temporary amend commit for a1-amend
94 # |
95 # |
95 # o 2 a1-amend
96 # o 2 a1-amend
96 # |
97 # |
97 # | o 1 a1
98 # | o 1 a1
98 # |/
99 # |/
99 # o 0 a0
100 # o 0 a0
100 #
101 #
101 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
102 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
102 # yet the filelog has the copy information in rev 1 and we will not look
103 # yet the filelog has the copy information in rev 1 and we will not look
103 # back far enough unless we also look at the a and b as candidates.
104 # back far enough unless we also look at the a and b as candidates.
104 # This only occurs when a is a descendent of b or visa-versa.
105 # This only occurs when a is a descendent of b or visa-versa.
105 return min(limit, a, b)
106 return min(limit, a, b)
106
107
107 def _chain(src, dst, a, b):
108 def _chain(src, dst, a, b):
108 '''chain two sets of copies a->b'''
109 '''chain two sets of copies a->b'''
109 t = a.copy()
110 t = a.copy()
110 for k, v in b.iteritems():
111 for k, v in b.iteritems():
111 if v in t:
112 if v in t:
112 # found a chain
113 # found a chain
113 if t[v] != k:
114 if t[v] != k:
114 # file wasn't renamed back to itself
115 # file wasn't renamed back to itself
115 t[k] = t[v]
116 t[k] = t[v]
116 if v not in dst:
117 if v not in dst:
117 # chain was a rename, not a copy
118 # chain was a rename, not a copy
118 del t[v]
119 del t[v]
119 if v in src:
120 if v in src:
120 # file is a copy of an existing file
121 # file is a copy of an existing file
121 t[k] = v
122 t[k] = v
122
123
123 # remove criss-crossed copies
124 # remove criss-crossed copies
124 for k, v in t.items():
125 for k, v in t.items():
125 if k in src and v in dst:
126 if k in src and v in dst:
126 del t[k]
127 del t[k]
127
128
128 return t
129 return t
129
130
130 def _tracefile(fctx, am, limit=-1):
131 def _tracefile(fctx, am, limit=-1):
131 '''return file context that is the ancestor of fctx present in ancestor
132 '''return file context that is the ancestor of fctx present in ancestor
132 manifest am, stopping after the first ancestor lower than limit'''
133 manifest am, stopping after the first ancestor lower than limit'''
133
134
134 for f in fctx.ancestors():
135 for f in fctx.ancestors():
135 if am.get(f.path(), None) == f.filenode():
136 if am.get(f.path(), None) == f.filenode():
136 return f
137 return f
137 if limit >= 0 and f.linkrev() < limit and f.rev() < limit:
138 if limit >= 0 and f.linkrev() < limit and f.rev() < limit:
138 return None
139 return None
139
140
140 def _dirstatecopies(d):
141 def _dirstatecopies(d):
141 ds = d._repo.dirstate
142 ds = d._repo.dirstate
142 c = ds.copies().copy()
143 c = ds.copies().copy()
143 for k in c.keys():
144 for k in c.keys():
144 if ds[k] not in 'anm':
145 if ds[k] not in 'anm':
145 del c[k]
146 del c[k]
146 return c
147 return c
147
148
148 def _computeforwardmissing(a, b, match=None):
149 def _computeforwardmissing(a, b, match=None):
149 """Computes which files are in b but not a.
150 """Computes which files are in b but not a.
150 This is its own function so extensions can easily wrap this call to see what
151 This is its own function so extensions can easily wrap this call to see what
151 files _forwardcopies is about to process.
152 files _forwardcopies is about to process.
152 """
153 """
153 ma = a.manifest()
154 ma = a.manifest()
154 mb = b.manifest()
155 mb = b.manifest()
155 return mb.filesnotin(ma, match=match)
156 return mb.filesnotin(ma, match=match)
156
157
157 def _forwardcopies(a, b, match=None):
158 def _forwardcopies(a, b, match=None):
158 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
159 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
159
160
160 # check for working copy
161 # check for working copy
161 w = None
162 w = None
162 if b.rev() is None:
163 if b.rev() is None:
163 w = b
164 w = b
164 b = w.p1()
165 b = w.p1()
165 if a == b:
166 if a == b:
166 # short-circuit to avoid issues with merge states
167 # short-circuit to avoid issues with merge states
167 return _dirstatecopies(w)
168 return _dirstatecopies(w)
168
169
169 # files might have to be traced back to the fctx parent of the last
170 # files might have to be traced back to the fctx parent of the last
170 # one-side-only changeset, but not further back than that
171 # one-side-only changeset, but not further back than that
171 limit = _findlimit(a._repo, a.rev(), b.rev())
172 limit = _findlimit(a._repo, a.rev(), b.rev())
172 if limit is None:
173 if limit is None:
173 limit = -1
174 limit = -1
174 am = a.manifest()
175 am = a.manifest()
175
176
176 # find where new files came from
177 # find where new files came from
177 # we currently don't try to find where old files went, too expensive
178 # we currently don't try to find where old files went, too expensive
178 # this means we can miss a case like 'hg rm b; hg cp a b'
179 # this means we can miss a case like 'hg rm b; hg cp a b'
179 cm = {}
180 cm = {}
180
181
181 # Computing the forward missing is quite expensive on large manifests, since
182 # Computing the forward missing is quite expensive on large manifests, since
182 # it compares the entire manifests. We can optimize it in the common use
183 # it compares the entire manifests. We can optimize it in the common use
183 # case of computing what copies are in a commit versus its parent (like
184 # case of computing what copies are in a commit versus its parent (like
184 # during a rebase or histedit). Note, we exclude merge commits from this
185 # during a rebase or histedit). Note, we exclude merge commits from this
185 # optimization, since the ctx.files() for a merge commit is not correct for
186 # optimization, since the ctx.files() for a merge commit is not correct for
186 # this comparison.
187 # this comparison.
187 forwardmissingmatch = match
188 forwardmissingmatch = match
188 if b.p1() == a and b.p2().node() == node.nullid:
189 if b.p1() == a and b.p2().node() == node.nullid:
189 filesmatcher = scmutil.matchfiles(a._repo, b.files())
190 filesmatcher = scmutil.matchfiles(a._repo, b.files())
190 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
191 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
191 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
192 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
192
193
193 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
194 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
194 for f in missing:
195 for f in missing:
195 fctx = b[f]
196 fctx = b[f]
196 fctx._ancestrycontext = ancestrycontext
197 fctx._ancestrycontext = ancestrycontext
197 ofctx = _tracefile(fctx, am, limit)
198 ofctx = _tracefile(fctx, am, limit)
198 if ofctx:
199 if ofctx:
199 cm[f] = ofctx.path()
200 cm[f] = ofctx.path()
200
201
201 # combine copies from dirstate if necessary
202 # combine copies from dirstate if necessary
202 if w is not None:
203 if w is not None:
203 cm = _chain(a, w, cm, _dirstatecopies(w))
204 cm = _chain(a, w, cm, _dirstatecopies(w))
204
205
205 return cm
206 return cm
206
207
207 def _backwardrenames(a, b):
208 def _backwardrenames(a, b):
208 if a._repo.ui.config('experimental', 'copytrace') == 'off':
209 if a._repo.ui.config('experimental', 'copytrace') == 'off':
209 return {}
210 return {}
210
211
211 # Even though we're not taking copies into account, 1:n rename situations
212 # Even though we're not taking copies into account, 1:n rename situations
212 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
213 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
213 # arbitrarily pick one of the renames.
214 # arbitrarily pick one of the renames.
214 f = _forwardcopies(b, a)
215 f = _forwardcopies(b, a)
215 r = {}
216 r = {}
216 for k, v in sorted(f.iteritems()):
217 for k, v in sorted(f.iteritems()):
217 # remove copies
218 # remove copies
218 if v in a:
219 if v in a:
219 continue
220 continue
220 r[v] = k
221 r[v] = k
221 return r
222 return r
222
223
223 def pathcopies(x, y, match=None):
224 def pathcopies(x, y, match=None):
224 '''find {dst@y: src@x} copy mapping for directed compare'''
225 '''find {dst@y: src@x} copy mapping for directed compare'''
225 if x == y or not x or not y:
226 if x == y or not x or not y:
226 return {}
227 return {}
227 a = y.ancestor(x)
228 a = y.ancestor(x)
228 if a == x:
229 if a == x:
229 return _forwardcopies(x, y, match=match)
230 return _forwardcopies(x, y, match=match)
230 if a == y:
231 if a == y:
231 return _backwardrenames(x, y)
232 return _backwardrenames(x, y)
232 return _chain(x, y, _backwardrenames(x, a),
233 return _chain(x, y, _backwardrenames(x, a),
233 _forwardcopies(a, y, match=match))
234 _forwardcopies(a, y, match=match))
234
235
235 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
236 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
236 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
237 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
237 and c2. This is its own function so extensions can easily wrap this call
238 and c2. This is its own function so extensions can easily wrap this call
238 to see what files mergecopies is about to process.
239 to see what files mergecopies is about to process.
239
240
240 Even though c1 and c2 are not used in this function, they are useful in
241 Even though c1 and c2 are not used in this function, they are useful in
241 other extensions for being able to read the file nodes of the changed files.
242 other extensions for being able to read the file nodes of the changed files.
242
243
243 "baselabel" can be passed to help distinguish the multiple computations
244 "baselabel" can be passed to help distinguish the multiple computations
244 done in the graft case.
245 done in the graft case.
245 """
246 """
246 u1 = sorted(addedinm1 - addedinm2)
247 u1 = sorted(addedinm1 - addedinm2)
247 u2 = sorted(addedinm2 - addedinm1)
248 u2 = sorted(addedinm2 - addedinm1)
248
249
249 header = " unmatched files in %s"
250 header = " unmatched files in %s"
250 if baselabel:
251 if baselabel:
251 header += ' (from %s)' % baselabel
252 header += ' (from %s)' % baselabel
252 if u1:
253 if u1:
253 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
254 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
254 if u2:
255 if u2:
255 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
256 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
256 return u1, u2
257 return u1, u2
257
258
258 def _makegetfctx(ctx):
259 def _makegetfctx(ctx):
259 """return a 'getfctx' function suitable for _checkcopies usage
260 """return a 'getfctx' function suitable for _checkcopies usage
260
261
261 We have to re-setup the function building 'filectx' for each
262 We have to re-setup the function building 'filectx' for each
262 '_checkcopies' to ensure the linkrev adjustment is properly setup for
263 '_checkcopies' to ensure the linkrev adjustment is properly setup for
263 each. Linkrev adjustment is important to avoid bug in rename
264 each. Linkrev adjustment is important to avoid bug in rename
264 detection. Moreover, having a proper '_ancestrycontext' setup ensures
265 detection. Moreover, having a proper '_ancestrycontext' setup ensures
265 the performance impact of this adjustment is kept limited. Without it,
266 the performance impact of this adjustment is kept limited. Without it,
266 each file could do a full dag traversal making the time complexity of
267 each file could do a full dag traversal making the time complexity of
267 the operation explode (see issue4537).
268 the operation explode (see issue4537).
268
269
269 This function exists here mostly to limit the impact on stable. Feel
270 This function exists here mostly to limit the impact on stable. Feel
270 free to refactor on default.
271 free to refactor on default.
271 """
272 """
272 rev = ctx.rev()
273 rev = ctx.rev()
273 repo = ctx._repo
274 repo = ctx._repo
274 ac = getattr(ctx, '_ancestrycontext', None)
275 ac = getattr(ctx, '_ancestrycontext', None)
275 if ac is None:
276 if ac is None:
276 revs = [rev]
277 revs = [rev]
277 if rev is None:
278 if rev is None:
278 revs = [p.rev() for p in ctx.parents()]
279 revs = [p.rev() for p in ctx.parents()]
279 ac = repo.changelog.ancestors(revs, inclusive=True)
280 ac = repo.changelog.ancestors(revs, inclusive=True)
280 ctx._ancestrycontext = ac
281 ctx._ancestrycontext = ac
281 def makectx(f, n):
282 def makectx(f, n):
282 if n in node.wdirnodes: # in a working context?
283 if n in node.wdirnodes: # in a working context?
283 if ctx.rev() is None:
284 if ctx.rev() is None:
284 return ctx.filectx(f)
285 return ctx.filectx(f)
285 return repo[None][f]
286 return repo[None][f]
286 fctx = repo.filectx(f, fileid=n)
287 fctx = repo.filectx(f, fileid=n)
287 # setup only needed for filectx not create from a changectx
288 # setup only needed for filectx not create from a changectx
288 fctx._ancestrycontext = ac
289 fctx._ancestrycontext = ac
289 fctx._descendantrev = rev
290 fctx._descendantrev = rev
290 return fctx
291 return fctx
291 return util.lrucachefunc(makectx)
292 return util.lrucachefunc(makectx)
292
293
293 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
294 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
294 """combine partial copy paths"""
295 """combine partial copy paths"""
295 remainder = {}
296 remainder = {}
296 for f in copyfrom:
297 for f in copyfrom:
297 if f in copyto:
298 if f in copyto:
298 finalcopy[copyto[f]] = copyfrom[f]
299 finalcopy[copyto[f]] = copyfrom[f]
299 del copyto[f]
300 del copyto[f]
300 for f in incompletediverge:
301 for f in incompletediverge:
301 assert f not in diverge
302 assert f not in diverge
302 ic = incompletediverge[f]
303 ic = incompletediverge[f]
303 if ic[0] in copyto:
304 if ic[0] in copyto:
304 diverge[f] = [copyto[ic[0]], ic[1]]
305 diverge[f] = [copyto[ic[0]], ic[1]]
305 else:
306 else:
306 remainder[f] = ic
307 remainder[f] = ic
307 return remainder
308 return remainder
308
309
309 def mergecopies(repo, c1, c2, base):
310 def mergecopies(repo, c1, c2, base):
310 """
311 """
311 The function calling different copytracing algorithms on the basis of config
312 The function calling different copytracing algorithms on the basis of config
312 which find moves and copies between context c1 and c2 that are relevant for
313 which find moves and copies between context c1 and c2 that are relevant for
313 merging. 'base' will be used as the merge base.
314 merging. 'base' will be used as the merge base.
314
315
315 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
316 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
316 files that were moved/ copied in one merge parent and modified in another.
317 files that were moved/ copied in one merge parent and modified in another.
317 For example:
318 For example:
318
319
319 o ---> 4 another commit
320 o ---> 4 another commit
320 |
321 |
321 | o ---> 3 commit that modifies a.txt
322 | o ---> 3 commit that modifies a.txt
322 | /
323 | /
323 o / ---> 2 commit that moves a.txt to b.txt
324 o / ---> 2 commit that moves a.txt to b.txt
324 |/
325 |/
325 o ---> 1 merge base
326 o ---> 1 merge base
326
327
327 If we try to rebase revision 3 on revision 4, since there is no a.txt in
328 If we try to rebase revision 3 on revision 4, since there is no a.txt in
328 revision 4, and if user have copytrace disabled, we prints the following
329 revision 4, and if user have copytrace disabled, we prints the following
329 message:
330 message:
330
331
331 ```other changed <file> which local deleted```
332 ```other changed <file> which local deleted```
332
333
333 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
334 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
334 "dirmove".
335 "dirmove".
335
336
336 "copy" is a mapping from destination name -> source name,
337 "copy" is a mapping from destination name -> source name,
337 where source is in c1 and destination is in c2 or vice-versa.
338 where source is in c1 and destination is in c2 or vice-versa.
338
339
339 "movewithdir" is a mapping from source name -> destination name,
340 "movewithdir" is a mapping from source name -> destination name,
340 where the file at source present in one context but not the other
341 where the file at source present in one context but not the other
341 needs to be moved to destination by the merge process, because the
342 needs to be moved to destination by the merge process, because the
342 other context moved the directory it is in.
343 other context moved the directory it is in.
343
344
344 "diverge" is a mapping of source name -> list of destination names
345 "diverge" is a mapping of source name -> list of destination names
345 for divergent renames.
346 for divergent renames.
346
347
347 "renamedelete" is a mapping of source name -> list of destination
348 "renamedelete" is a mapping of source name -> list of destination
348 names for files deleted in c1 that were renamed in c2 or vice-versa.
349 names for files deleted in c1 that were renamed in c2 or vice-versa.
349
350
350 "dirmove" is a mapping of detected source dir -> destination dir renames.
351 "dirmove" is a mapping of detected source dir -> destination dir renames.
351 This is needed for handling changes to new files previously grafted into
352 This is needed for handling changes to new files previously grafted into
352 renamed directories.
353 renamed directories.
353 """
354 """
354 # avoid silly behavior for update from empty dir
355 # avoid silly behavior for update from empty dir
355 if not c1 or not c2 or c1 == c2:
356 if not c1 or not c2 or c1 == c2:
356 return {}, {}, {}, {}, {}
357 return {}, {}, {}, {}, {}
357
358
358 # avoid silly behavior for parent -> working dir
359 # avoid silly behavior for parent -> working dir
359 if c2.node() is None and c1.node() == repo.dirstate.p1():
360 if c2.node() is None and c1.node() == repo.dirstate.p1():
360 return repo.dirstate.copies(), {}, {}, {}, {}
361 return repo.dirstate.copies(), {}, {}, {}, {}
361
362
362 copytracing = repo.ui.config('experimental', 'copytrace')
363 copytracing = repo.ui.config('experimental', 'copytrace')
363
364
364 # Copy trace disabling is explicitly below the node == p1 logic above
365 # Copy trace disabling is explicitly below the node == p1 logic above
365 # because the logic above is required for a simple copy to be kept across a
366 # because the logic above is required for a simple copy to be kept across a
366 # rebase.
367 # rebase.
367 if copytracing == 'off':
368 if copytracing == 'off':
368 return {}, {}, {}, {}, {}
369 return {}, {}, {}, {}, {}
369 elif copytracing == 'heuristics':
370 elif copytracing == 'heuristics':
371 # Do full copytracing if only drafts are involved as that will be fast
372 # enough and will also cover the copies which can be missed by
373 # heuristics
374 if _isfullcopytraceable(c1, base):
375 return _fullcopytracing(repo, c1, c2, base)
370 return _heuristicscopytracing(repo, c1, c2, base)
376 return _heuristicscopytracing(repo, c1, c2, base)
371 else:
377 else:
372 return _fullcopytracing(repo, c1, c2, base)
378 return _fullcopytracing(repo, c1, c2, base)
373
379
380 def _isfullcopytraceable(c1, base):
381 """ Checks that if base, source and destination are all draft branches, if
382 yes let's use the full copytrace algorithm for increased capabilities since
383 it will be fast enough.
384 """
385
386 nonpublicphases = set([phases.draft, phases.secret])
387
388 if (c1.phase() in nonpublicphases) and (base.phase() in nonpublicphases):
389 return True
390 return False
391
374 def _fullcopytracing(repo, c1, c2, base):
392 def _fullcopytracing(repo, c1, c2, base):
375 """ The full copytracing algorithm which finds all the new files that were
393 """ The full copytracing algorithm which finds all the new files that were
376 added from merge base up to the top commit and for each file it checks if
394 added from merge base up to the top commit and for each file it checks if
377 this file was copied from another file.
395 this file was copied from another file.
378
396
379 This is pretty slow when a lot of changesets are involved but will track all
397 This is pretty slow when a lot of changesets are involved but will track all
380 the copies.
398 the copies.
381 """
399 """
382 # In certain scenarios (e.g. graft, update or rebase), base can be
400 # In certain scenarios (e.g. graft, update or rebase), base can be
383 # overridden We still need to know a real common ancestor in this case We
401 # overridden We still need to know a real common ancestor in this case We
384 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
402 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
385 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
403 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
386 # caller may not know if the revision passed in lieu of the CA is a genuine
404 # caller may not know if the revision passed in lieu of the CA is a genuine
387 # common ancestor or not without explicitly checking it, it's better to
405 # common ancestor or not without explicitly checking it, it's better to
388 # determine that here.
406 # determine that here.
389 #
407 #
390 # base.descendant(wc) and base.descendant(base) are False, work around that
408 # base.descendant(wc) and base.descendant(base) are False, work around that
391 _c1 = c1.p1() if c1.rev() is None else c1
409 _c1 = c1.p1() if c1.rev() is None else c1
392 _c2 = c2.p1() if c2.rev() is None else c2
410 _c2 = c2.p1() if c2.rev() is None else c2
393 # an endpoint is "dirty" if it isn't a descendant of the merge base
411 # an endpoint is "dirty" if it isn't a descendant of the merge base
394 # if we have a dirty endpoint, we need to trigger graft logic, and also
412 # if we have a dirty endpoint, we need to trigger graft logic, and also
395 # keep track of which endpoint is dirty
413 # keep track of which endpoint is dirty
396 dirtyc1 = not (base == _c1 or base.descendant(_c1))
414 dirtyc1 = not (base == _c1 or base.descendant(_c1))
397 dirtyc2 = not (base == _c2 or base.descendant(_c2))
415 dirtyc2 = not (base == _c2 or base.descendant(_c2))
398 graft = dirtyc1 or dirtyc2
416 graft = dirtyc1 or dirtyc2
399 tca = base
417 tca = base
400 if graft:
418 if graft:
401 tca = _c1.ancestor(_c2)
419 tca = _c1.ancestor(_c2)
402
420
403 limit = _findlimit(repo, c1.rev(), c2.rev())
421 limit = _findlimit(repo, c1.rev(), c2.rev())
404 if limit is None:
422 if limit is None:
405 # no common ancestor, no copies
423 # no common ancestor, no copies
406 return {}, {}, {}, {}, {}
424 return {}, {}, {}, {}, {}
407 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
425 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
408
426
409 m1 = c1.manifest()
427 m1 = c1.manifest()
410 m2 = c2.manifest()
428 m2 = c2.manifest()
411 mb = base.manifest()
429 mb = base.manifest()
412
430
413 # gather data from _checkcopies:
431 # gather data from _checkcopies:
414 # - diverge = record all diverges in this dict
432 # - diverge = record all diverges in this dict
415 # - copy = record all non-divergent copies in this dict
433 # - copy = record all non-divergent copies in this dict
416 # - fullcopy = record all copies in this dict
434 # - fullcopy = record all copies in this dict
417 # - incomplete = record non-divergent partial copies here
435 # - incomplete = record non-divergent partial copies here
418 # - incompletediverge = record divergent partial copies here
436 # - incompletediverge = record divergent partial copies here
419 diverge = {} # divergence data is shared
437 diverge = {} # divergence data is shared
420 incompletediverge = {}
438 incompletediverge = {}
421 data1 = {'copy': {},
439 data1 = {'copy': {},
422 'fullcopy': {},
440 'fullcopy': {},
423 'incomplete': {},
441 'incomplete': {},
424 'diverge': diverge,
442 'diverge': diverge,
425 'incompletediverge': incompletediverge,
443 'incompletediverge': incompletediverge,
426 }
444 }
427 data2 = {'copy': {},
445 data2 = {'copy': {},
428 'fullcopy': {},
446 'fullcopy': {},
429 'incomplete': {},
447 'incomplete': {},
430 'diverge': diverge,
448 'diverge': diverge,
431 'incompletediverge': incompletediverge,
449 'incompletediverge': incompletediverge,
432 }
450 }
433
451
434 # find interesting file sets from manifests
452 # find interesting file sets from manifests
435 addedinm1 = m1.filesnotin(mb)
453 addedinm1 = m1.filesnotin(mb)
436 addedinm2 = m2.filesnotin(mb)
454 addedinm2 = m2.filesnotin(mb)
437 bothnew = sorted(addedinm1 & addedinm2)
455 bothnew = sorted(addedinm1 & addedinm2)
438 if tca == base:
456 if tca == base:
439 # unmatched file from base
457 # unmatched file from base
440 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
458 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
441 u1u, u2u = u1r, u2r
459 u1u, u2u = u1r, u2r
442 else:
460 else:
443 # unmatched file from base (DAG rotation in the graft case)
461 # unmatched file from base (DAG rotation in the graft case)
444 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
462 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
445 baselabel='base')
463 baselabel='base')
446 # unmatched file from topological common ancestors (no DAG rotation)
464 # unmatched file from topological common ancestors (no DAG rotation)
447 # need to recompute this for directory move handling when grafting
465 # need to recompute this for directory move handling when grafting
448 mta = tca.manifest()
466 mta = tca.manifest()
449 u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
467 u1u, u2u = _computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
450 m2.filesnotin(mta),
468 m2.filesnotin(mta),
451 baselabel='topological common ancestor')
469 baselabel='topological common ancestor')
452
470
453 for f in u1u:
471 for f in u1u:
454 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
472 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
455
473
456 for f in u2u:
474 for f in u2u:
457 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
475 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
458
476
459 copy = dict(data1['copy'])
477 copy = dict(data1['copy'])
460 copy.update(data2['copy'])
478 copy.update(data2['copy'])
461 fullcopy = dict(data1['fullcopy'])
479 fullcopy = dict(data1['fullcopy'])
462 fullcopy.update(data2['fullcopy'])
480 fullcopy.update(data2['fullcopy'])
463
481
464 if dirtyc1:
482 if dirtyc1:
465 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
483 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
466 incompletediverge)
484 incompletediverge)
467 else:
485 else:
468 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
486 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
469 incompletediverge)
487 incompletediverge)
470
488
471 renamedelete = {}
489 renamedelete = {}
472 renamedeleteset = set()
490 renamedeleteset = set()
473 divergeset = set()
491 divergeset = set()
474 for of, fl in diverge.items():
492 for of, fl in diverge.items():
475 if len(fl) == 1 or of in c1 or of in c2:
493 if len(fl) == 1 or of in c1 or of in c2:
476 del diverge[of] # not actually divergent, or not a rename
494 del diverge[of] # not actually divergent, or not a rename
477 if of not in c1 and of not in c2:
495 if of not in c1 and of not in c2:
478 # renamed on one side, deleted on the other side, but filter
496 # renamed on one side, deleted on the other side, but filter
479 # out files that have been renamed and then deleted
497 # out files that have been renamed and then deleted
480 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
498 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
481 renamedeleteset.update(fl) # reverse map for below
499 renamedeleteset.update(fl) # reverse map for below
482 else:
500 else:
483 divergeset.update(fl) # reverse map for below
501 divergeset.update(fl) # reverse map for below
484
502
485 if bothnew:
503 if bothnew:
486 repo.ui.debug(" unmatched files new in both:\n %s\n"
504 repo.ui.debug(" unmatched files new in both:\n %s\n"
487 % "\n ".join(bothnew))
505 % "\n ".join(bothnew))
488 bothdiverge = {}
506 bothdiverge = {}
489 bothincompletediverge = {}
507 bothincompletediverge = {}
490 remainder = {}
508 remainder = {}
491 both1 = {'copy': {},
509 both1 = {'copy': {},
492 'fullcopy': {},
510 'fullcopy': {},
493 'incomplete': {},
511 'incomplete': {},
494 'diverge': bothdiverge,
512 'diverge': bothdiverge,
495 'incompletediverge': bothincompletediverge
513 'incompletediverge': bothincompletediverge
496 }
514 }
497 both2 = {'copy': {},
515 both2 = {'copy': {},
498 'fullcopy': {},
516 'fullcopy': {},
499 'incomplete': {},
517 'incomplete': {},
500 'diverge': bothdiverge,
518 'diverge': bothdiverge,
501 'incompletediverge': bothincompletediverge
519 'incompletediverge': bothincompletediverge
502 }
520 }
503 for f in bothnew:
521 for f in bothnew:
504 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
522 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
505 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
523 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
506 if dirtyc1:
524 if dirtyc1:
507 # incomplete copies may only be found on the "dirty" side for bothnew
525 # incomplete copies may only be found on the "dirty" side for bothnew
508 assert not both2['incomplete']
526 assert not both2['incomplete']
509 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
527 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
510 bothincompletediverge)
528 bothincompletediverge)
511 elif dirtyc2:
529 elif dirtyc2:
512 assert not both1['incomplete']
530 assert not both1['incomplete']
513 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
531 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
514 bothincompletediverge)
532 bothincompletediverge)
515 else:
533 else:
516 # incomplete copies and divergences can't happen outside grafts
534 # incomplete copies and divergences can't happen outside grafts
517 assert not both1['incomplete']
535 assert not both1['incomplete']
518 assert not both2['incomplete']
536 assert not both2['incomplete']
519 assert not bothincompletediverge
537 assert not bothincompletediverge
520 for f in remainder:
538 for f in remainder:
521 assert f not in bothdiverge
539 assert f not in bothdiverge
522 ic = remainder[f]
540 ic = remainder[f]
523 if ic[0] in (m1 if dirtyc1 else m2):
541 if ic[0] in (m1 if dirtyc1 else m2):
524 # backed-out rename on one side, but watch out for deleted files
542 # backed-out rename on one side, but watch out for deleted files
525 bothdiverge[f] = ic
543 bothdiverge[f] = ic
526 for of, fl in bothdiverge.items():
544 for of, fl in bothdiverge.items():
527 if len(fl) == 2 and fl[0] == fl[1]:
545 if len(fl) == 2 and fl[0] == fl[1]:
528 copy[fl[0]] = of # not actually divergent, just matching renames
546 copy[fl[0]] = of # not actually divergent, just matching renames
529
547
530 if fullcopy and repo.ui.debugflag:
548 if fullcopy and repo.ui.debugflag:
531 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
549 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
532 "% = renamed and deleted):\n")
550 "% = renamed and deleted):\n")
533 for f in sorted(fullcopy):
551 for f in sorted(fullcopy):
534 note = ""
552 note = ""
535 if f in copy:
553 if f in copy:
536 note += "*"
554 note += "*"
537 if f in divergeset:
555 if f in divergeset:
538 note += "!"
556 note += "!"
539 if f in renamedeleteset:
557 if f in renamedeleteset:
540 note += "%"
558 note += "%"
541 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
559 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
542 note))
560 note))
543 del divergeset
561 del divergeset
544
562
545 if not fullcopy:
563 if not fullcopy:
546 return copy, {}, diverge, renamedelete, {}
564 return copy, {}, diverge, renamedelete, {}
547
565
548 repo.ui.debug(" checking for directory renames\n")
566 repo.ui.debug(" checking for directory renames\n")
549
567
550 # generate a directory move map
568 # generate a directory move map
551 d1, d2 = c1.dirs(), c2.dirs()
569 d1, d2 = c1.dirs(), c2.dirs()
552 # Hack for adding '', which is not otherwise added, to d1 and d2
570 # Hack for adding '', which is not otherwise added, to d1 and d2
553 d1.addpath('/')
571 d1.addpath('/')
554 d2.addpath('/')
572 d2.addpath('/')
555 invalid = set()
573 invalid = set()
556 dirmove = {}
574 dirmove = {}
557
575
558 # examine each file copy for a potential directory move, which is
576 # examine each file copy for a potential directory move, which is
559 # when all the files in a directory are moved to a new directory
577 # when all the files in a directory are moved to a new directory
560 for dst, src in fullcopy.iteritems():
578 for dst, src in fullcopy.iteritems():
561 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
579 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
562 if dsrc in invalid:
580 if dsrc in invalid:
563 # already seen to be uninteresting
581 # already seen to be uninteresting
564 continue
582 continue
565 elif dsrc in d1 and ddst in d1:
583 elif dsrc in d1 and ddst in d1:
566 # directory wasn't entirely moved locally
584 # directory wasn't entirely moved locally
567 invalid.add(dsrc + "/")
585 invalid.add(dsrc + "/")
568 elif dsrc in d2 and ddst in d2:
586 elif dsrc in d2 and ddst in d2:
569 # directory wasn't entirely moved remotely
587 # directory wasn't entirely moved remotely
570 invalid.add(dsrc + "/")
588 invalid.add(dsrc + "/")
571 elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
589 elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
572 # files from the same directory moved to two different places
590 # files from the same directory moved to two different places
573 invalid.add(dsrc + "/")
591 invalid.add(dsrc + "/")
574 else:
592 else:
575 # looks good so far
593 # looks good so far
576 dirmove[dsrc + "/"] = ddst + "/"
594 dirmove[dsrc + "/"] = ddst + "/"
577
595
578 for i in invalid:
596 for i in invalid:
579 if i in dirmove:
597 if i in dirmove:
580 del dirmove[i]
598 del dirmove[i]
581 del d1, d2, invalid
599 del d1, d2, invalid
582
600
583 if not dirmove:
601 if not dirmove:
584 return copy, {}, diverge, renamedelete, {}
602 return copy, {}, diverge, renamedelete, {}
585
603
586 for d in dirmove:
604 for d in dirmove:
587 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
605 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
588 (d, dirmove[d]))
606 (d, dirmove[d]))
589
607
590 movewithdir = {}
608 movewithdir = {}
591 # check unaccounted nonoverlapping files against directory moves
609 # check unaccounted nonoverlapping files against directory moves
592 for f in u1r + u2r:
610 for f in u1r + u2r:
593 if f not in fullcopy:
611 if f not in fullcopy:
594 for d in dirmove:
612 for d in dirmove:
595 if f.startswith(d):
613 if f.startswith(d):
596 # new file added in a directory that was moved, move it
614 # new file added in a directory that was moved, move it
597 df = dirmove[d] + f[len(d):]
615 df = dirmove[d] + f[len(d):]
598 if df not in copy:
616 if df not in copy:
599 movewithdir[f] = df
617 movewithdir[f] = df
600 repo.ui.debug((" pending file src: '%s' -> "
618 repo.ui.debug((" pending file src: '%s' -> "
601 "dst: '%s'\n") % (f, df))
619 "dst: '%s'\n") % (f, df))
602 break
620 break
603
621
604 return copy, movewithdir, diverge, renamedelete, dirmove
622 return copy, movewithdir, diverge, renamedelete, dirmove
605
623
606 def _heuristicscopytracing(repo, c1, c2, base):
624 def _heuristicscopytracing(repo, c1, c2, base):
607 """ Fast copytracing using filename heuristics
625 """ Fast copytracing using filename heuristics
608
626
609 Assumes that moves or renames are of following two types:
627 Assumes that moves or renames are of following two types:
610
628
611 1) Inside a directory only (same directory name but different filenames)
629 1) Inside a directory only (same directory name but different filenames)
612 2) Move from one directory to another
630 2) Move from one directory to another
613 (same filenames but different directory names)
631 (same filenames but different directory names)
614
632
615 Works only when there are no merge commits in the "source branch".
633 Works only when there are no merge commits in the "source branch".
616 Source branch is commits from base up to c2 not including base.
634 Source branch is commits from base up to c2 not including base.
617
635
618 If merge is involved it fallbacks to _fullcopytracing().
636 If merge is involved it fallbacks to _fullcopytracing().
619
637
620 Can be used by setting the following config:
638 Can be used by setting the following config:
621
639
622 [experimental]
640 [experimental]
623 copytrace = heuristics
641 copytrace = heuristics
624 """
642 """
625
643
626 if c1.rev() is None:
644 if c1.rev() is None:
627 c1 = c1.p1()
645 c1 = c1.p1()
628 if c2.rev() is None:
646 if c2.rev() is None:
629 c2 = c2.p1()
647 c2 = c2.p1()
630
648
631 copies = {}
649 copies = {}
632
650
633 changedfiles = set()
651 changedfiles = set()
634 m1 = c1.manifest()
652 m1 = c1.manifest()
635 if not repo.revs('%d::%d', base.rev(), c2.rev()):
653 if not repo.revs('%d::%d', base.rev(), c2.rev()):
636 # If base is not in c2 branch, we switch to fullcopytracing
654 # If base is not in c2 branch, we switch to fullcopytracing
637 repo.ui.debug("switching to full copytracing as base is not "
655 repo.ui.debug("switching to full copytracing as base is not "
638 "an ancestor of c2\n")
656 "an ancestor of c2\n")
639 return _fullcopytracing(repo, c1, c2, base)
657 return _fullcopytracing(repo, c1, c2, base)
640
658
641 ctx = c2
659 ctx = c2
642 while ctx != base:
660 while ctx != base:
643 if len(ctx.parents()) == 2:
661 if len(ctx.parents()) == 2:
644 # To keep things simple let's not handle merges
662 # To keep things simple let's not handle merges
645 repo.ui.debug("switching to full copytracing because of merges\n")
663 repo.ui.debug("switching to full copytracing because of merges\n")
646 return _fullcopytracing(repo, c1, c2, base)
664 return _fullcopytracing(repo, c1, c2, base)
647 changedfiles.update(ctx.files())
665 changedfiles.update(ctx.files())
648 ctx = ctx.p1()
666 ctx = ctx.p1()
649
667
650 cp = _forwardcopies(base, c2)
668 cp = _forwardcopies(base, c2)
651 for dst, src in cp.iteritems():
669 for dst, src in cp.iteritems():
652 if src in m1:
670 if src in m1:
653 copies[dst] = src
671 copies[dst] = src
654
672
655 # file is missing if it isn't present in the destination, but is present in
673 # file is missing if it isn't present in the destination, but is present in
656 # the base and present in the source.
674 # the base and present in the source.
657 # Presence in the base is important to exclude added files, presence in the
675 # Presence in the base is important to exclude added files, presence in the
658 # source is important to exclude removed files.
676 # source is important to exclude removed files.
659 missingfiles = filter(lambda f: f not in m1 and f in base and f in c2,
677 missingfiles = filter(lambda f: f not in m1 and f in base and f in c2,
660 changedfiles)
678 changedfiles)
661
679
662 if missingfiles:
680 if missingfiles:
663 basenametofilename = collections.defaultdict(list)
681 basenametofilename = collections.defaultdict(list)
664 dirnametofilename = collections.defaultdict(list)
682 dirnametofilename = collections.defaultdict(list)
665
683
666 for f in m1.filesnotin(base.manifest()):
684 for f in m1.filesnotin(base.manifest()):
667 basename = os.path.basename(f)
685 basename = os.path.basename(f)
668 dirname = os.path.dirname(f)
686 dirname = os.path.dirname(f)
669 basenametofilename[basename].append(f)
687 basenametofilename[basename].append(f)
670 dirnametofilename[dirname].append(f)
688 dirnametofilename[dirname].append(f)
671
689
672 # in case of a rebase/graft, base may not be a common ancestor
690 # in case of a rebase/graft, base may not be a common ancestor
673 anc = c1.ancestor(c2)
691 anc = c1.ancestor(c2)
674
692
675 for f in missingfiles:
693 for f in missingfiles:
676 basename = os.path.basename(f)
694 basename = os.path.basename(f)
677 dirname = os.path.dirname(f)
695 dirname = os.path.dirname(f)
678 samebasename = basenametofilename[basename]
696 samebasename = basenametofilename[basename]
679 samedirname = dirnametofilename[dirname]
697 samedirname = dirnametofilename[dirname]
680 movecandidates = samebasename + samedirname
698 movecandidates = samebasename + samedirname
681 # f is guaranteed to be present in c2, that's why
699 # f is guaranteed to be present in c2, that's why
682 # c2.filectx(f) won't fail
700 # c2.filectx(f) won't fail
683 f2 = c2.filectx(f)
701 f2 = c2.filectx(f)
684 for candidate in movecandidates:
702 for candidate in movecandidates:
685 f1 = c1.filectx(candidate)
703 f1 = c1.filectx(candidate)
686 if _related(f1, f2, anc.rev()):
704 if _related(f1, f2, anc.rev()):
687 # if there are a few related copies then we'll merge
705 # if there are a few related copies then we'll merge
688 # changes into all of them. This matches the behaviour
706 # changes into all of them. This matches the behaviour
689 # of upstream copytracing
707 # of upstream copytracing
690 copies[candidate] = f
708 copies[candidate] = f
691
709
692 return copies, {}, {}, {}, {}
710 return copies, {}, {}, {}, {}
693
711
694 def _related(f1, f2, limit):
712 def _related(f1, f2, limit):
695 """return True if f1 and f2 filectx have a common ancestor
713 """return True if f1 and f2 filectx have a common ancestor
696
714
697 Walk back to common ancestor to see if the two files originate
715 Walk back to common ancestor to see if the two files originate
698 from the same file. Since workingfilectx's rev() is None it messes
716 from the same file. Since workingfilectx's rev() is None it messes
699 up the integer comparison logic, hence the pre-step check for
717 up the integer comparison logic, hence the pre-step check for
700 None (f1 and f2 can only be workingfilectx's initially).
718 None (f1 and f2 can only be workingfilectx's initially).
701 """
719 """
702
720
703 if f1 == f2:
721 if f1 == f2:
704 return f1 # a match
722 return f1 # a match
705
723
706 g1, g2 = f1.ancestors(), f2.ancestors()
724 g1, g2 = f1.ancestors(), f2.ancestors()
707 try:
725 try:
708 f1r, f2r = f1.linkrev(), f2.linkrev()
726 f1r, f2r = f1.linkrev(), f2.linkrev()
709
727
710 if f1r is None:
728 if f1r is None:
711 f1 = next(g1)
729 f1 = next(g1)
712 if f2r is None:
730 if f2r is None:
713 f2 = next(g2)
731 f2 = next(g2)
714
732
715 while True:
733 while True:
716 f1r, f2r = f1.linkrev(), f2.linkrev()
734 f1r, f2r = f1.linkrev(), f2.linkrev()
717 if f1r > f2r:
735 if f1r > f2r:
718 f1 = next(g1)
736 f1 = next(g1)
719 elif f2r > f1r:
737 elif f2r > f1r:
720 f2 = next(g2)
738 f2 = next(g2)
721 elif f1 == f2:
739 elif f1 == f2:
722 return f1 # a match
740 return f1 # a match
723 elif f1r == f2r or f1r < limit or f2r < limit:
741 elif f1r == f2r or f1r < limit or f2r < limit:
724 return False # copy no longer relevant
742 return False # copy no longer relevant
725 except StopIteration:
743 except StopIteration:
726 return False
744 return False
727
745
728 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
746 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
729 """
747 """
730 check possible copies of f from msrc to mdst
748 check possible copies of f from msrc to mdst
731
749
732 srcctx = starting context for f in msrc
750 srcctx = starting context for f in msrc
733 dstctx = destination context for f in mdst
751 dstctx = destination context for f in mdst
734 f = the filename to check (as in msrc)
752 f = the filename to check (as in msrc)
735 base = the changectx used as a merge base
753 base = the changectx used as a merge base
736 tca = topological common ancestor for graft-like scenarios
754 tca = topological common ancestor for graft-like scenarios
737 remotebase = True if base is outside tca::srcctx, False otherwise
755 remotebase = True if base is outside tca::srcctx, False otherwise
738 limit = the rev number to not search beyond
756 limit = the rev number to not search beyond
739 data = dictionary of dictionary to store copy data. (see mergecopies)
757 data = dictionary of dictionary to store copy data. (see mergecopies)
740
758
741 note: limit is only an optimization, and provides no guarantee that
759 note: limit is only an optimization, and provides no guarantee that
742 irrelevant revisions will not be visited
760 irrelevant revisions will not be visited
743 there is no easy way to make this algorithm stop in a guaranteed way
761 there is no easy way to make this algorithm stop in a guaranteed way
744 once it "goes behind a certain revision".
762 once it "goes behind a certain revision".
745 """
763 """
746
764
747 msrc = srcctx.manifest()
765 msrc = srcctx.manifest()
748 mdst = dstctx.manifest()
766 mdst = dstctx.manifest()
749 mb = base.manifest()
767 mb = base.manifest()
750 mta = tca.manifest()
768 mta = tca.manifest()
751 # Might be true if this call is about finding backward renames,
769 # Might be true if this call is about finding backward renames,
752 # This happens in the case of grafts because the DAG is then rotated.
770 # This happens in the case of grafts because the DAG is then rotated.
753 # If the file exists in both the base and the source, we are not looking
771 # If the file exists in both the base and the source, we are not looking
754 # for a rename on the source side, but on the part of the DAG that is
772 # for a rename on the source side, but on the part of the DAG that is
755 # traversed backwards.
773 # traversed backwards.
756 #
774 #
757 # In the case there is both backward and forward renames (before and after
775 # In the case there is both backward and forward renames (before and after
758 # the base) this is more complicated as we must detect a divergence.
776 # the base) this is more complicated as we must detect a divergence.
759 # We use 'backwards = False' in that case.
777 # We use 'backwards = False' in that case.
760 backwards = not remotebase and base != tca and f in mb
778 backwards = not remotebase and base != tca and f in mb
761 getsrcfctx = _makegetfctx(srcctx)
779 getsrcfctx = _makegetfctx(srcctx)
762 getdstfctx = _makegetfctx(dstctx)
780 getdstfctx = _makegetfctx(dstctx)
763
781
764 if msrc[f] == mb.get(f) and not remotebase:
782 if msrc[f] == mb.get(f) and not remotebase:
765 # Nothing to merge
783 # Nothing to merge
766 return
784 return
767
785
768 of = None
786 of = None
769 seen = {f}
787 seen = {f}
770 for oc in getsrcfctx(f, msrc[f]).ancestors():
788 for oc in getsrcfctx(f, msrc[f]).ancestors():
771 ocr = oc.linkrev()
789 ocr = oc.linkrev()
772 of = oc.path()
790 of = oc.path()
773 if of in seen:
791 if of in seen:
774 # check limit late - grab last rename before
792 # check limit late - grab last rename before
775 if ocr < limit:
793 if ocr < limit:
776 break
794 break
777 continue
795 continue
778 seen.add(of)
796 seen.add(of)
779
797
780 # remember for dir rename detection
798 # remember for dir rename detection
781 if backwards:
799 if backwards:
782 data['fullcopy'][of] = f # grafting backwards through renames
800 data['fullcopy'][of] = f # grafting backwards through renames
783 else:
801 else:
784 data['fullcopy'][f] = of
802 data['fullcopy'][f] = of
785 if of not in mdst:
803 if of not in mdst:
786 continue # no match, keep looking
804 continue # no match, keep looking
787 if mdst[of] == mb.get(of):
805 if mdst[of] == mb.get(of):
788 return # no merge needed, quit early
806 return # no merge needed, quit early
789 c2 = getdstfctx(of, mdst[of])
807 c2 = getdstfctx(of, mdst[of])
790 # c2 might be a plain new file on added on destination side that is
808 # c2 might be a plain new file on added on destination side that is
791 # unrelated to the droids we are looking for.
809 # unrelated to the droids we are looking for.
792 cr = _related(oc, c2, tca.rev())
810 cr = _related(oc, c2, tca.rev())
793 if cr and (of == f or of == c2.path()): # non-divergent
811 if cr and (of == f or of == c2.path()): # non-divergent
794 if backwards:
812 if backwards:
795 data['copy'][of] = f
813 data['copy'][of] = f
796 elif of in mb:
814 elif of in mb:
797 data['copy'][f] = of
815 data['copy'][f] = of
798 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
816 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
799 data['copy'][of] = f
817 data['copy'][of] = f
800 del data['fullcopy'][f]
818 del data['fullcopy'][f]
801 data['fullcopy'][of] = f
819 data['fullcopy'][of] = f
802 else: # divergence w.r.t. graft CA on one side of topological CA
820 else: # divergence w.r.t. graft CA on one side of topological CA
803 for sf in seen:
821 for sf in seen:
804 if sf in mb:
822 if sf in mb:
805 assert sf not in data['diverge']
823 assert sf not in data['diverge']
806 data['diverge'][sf] = [f, of]
824 data['diverge'][sf] = [f, of]
807 break
825 break
808 return
826 return
809
827
810 if of in mta:
828 if of in mta:
811 if backwards or remotebase:
829 if backwards or remotebase:
812 data['incomplete'][of] = f
830 data['incomplete'][of] = f
813 else:
831 else:
814 for sf in seen:
832 for sf in seen:
815 if sf in mb:
833 if sf in mb:
816 if tca == base:
834 if tca == base:
817 data['diverge'].setdefault(sf, []).append(f)
835 data['diverge'].setdefault(sf, []).append(f)
818 else:
836 else:
819 data['incompletediverge'][sf] = [of, f]
837 data['incompletediverge'][sf] = [of, f]
820 return
838 return
821
839
822 def duplicatecopies(repo, rev, fromrev, skiprev=None):
840 def duplicatecopies(repo, rev, fromrev, skiprev=None):
823 '''reproduce copies from fromrev to rev in the dirstate
841 '''reproduce copies from fromrev to rev in the dirstate
824
842
825 If skiprev is specified, it's a revision that should be used to
843 If skiprev is specified, it's a revision that should be used to
826 filter copy records. Any copies that occur between fromrev and
844 filter copy records. Any copies that occur between fromrev and
827 skiprev will not be duplicated, even if they appear in the set of
845 skiprev will not be duplicated, even if they appear in the set of
828 copies between fromrev and rev.
846 copies between fromrev and rev.
829 '''
847 '''
830 exclude = {}
848 exclude = {}
831 if (skiprev is not None and
849 if (skiprev is not None and
832 repo.ui.config('experimental', 'copytrace') != 'off'):
850 repo.ui.config('experimental', 'copytrace') != 'off'):
833 # copytrace='off' skips this line, but not the entire function because
851 # copytrace='off' skips this line, but not the entire function because
834 # the line below is O(size of the repo) during a rebase, while the rest
852 # the line below is O(size of the repo) during a rebase, while the rest
835 # of the function is much faster (and is required for carrying copy
853 # of the function is much faster (and is required for carrying copy
836 # metadata across the rebase anyway).
854 # metadata across the rebase anyway).
837 exclude = pathcopies(repo[fromrev], repo[skiprev])
855 exclude = pathcopies(repo[fromrev], repo[skiprev])
838 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
856 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
839 # copies.pathcopies returns backward renames, so dst might not
857 # copies.pathcopies returns backward renames, so dst might not
840 # actually be in the dirstate
858 # actually be in the dirstate
841 if dst in exclude:
859 if dst in exclude:
842 continue
860 continue
843 if repo.dirstate[dst] in "nma":
861 if repo.dirstate[dst] in "nma":
844 repo.dirstate.copy(src, dst)
862 repo.dirstate.copy(src, dst)
@@ -1,591 +1,667 b''
1 Test for the heuristic copytracing algorithm
1 Test for the heuristic copytracing algorithm
2 ============================================
2 ============================================
3
3
4 $ cat >> $TESTTMP/copytrace.sh << '__EOF__'
4 $ cat >> $TESTTMP/copytrace.sh << '__EOF__'
5 > initclient() {
5 > initclient() {
6 > cat >> $1/.hg/hgrc <<EOF
6 > cat >> $1/.hg/hgrc <<EOF
7 > [experimental]
7 > [experimental]
8 > copytrace = heuristics
8 > copytrace = heuristics
9 > EOF
9 > EOF
10 > }
10 > }
11 > __EOF__
11 > __EOF__
12 $ . "$TESTTMP/copytrace.sh"
12 $ . "$TESTTMP/copytrace.sh"
13
13
14 $ cat >> $HGRCPATH << EOF
14 $ cat >> $HGRCPATH << EOF
15 > [extensions]
15 > [extensions]
16 > rebase=
16 > rebase=
17 > shelve=
17 > shelve=
18 > EOF
18 > EOF
19
19
20 Check filename heuristics (same dirname and same basename)
20 Check filename heuristics (same dirname and same basename)
21 $ hg init server
21 $ hg init server
22 $ cd server
22 $ cd server
23 $ echo a > a
23 $ echo a > a
24 $ mkdir dir
24 $ mkdir dir
25 $ echo a > dir/file.txt
25 $ echo a > dir/file.txt
26 $ hg addremove
26 $ hg addremove
27 adding a
27 adding a
28 adding dir/file.txt
28 adding dir/file.txt
29 $ hg ci -m initial
29 $ hg ci -m initial
30 $ hg mv a b
30 $ hg mv a b
31 $ hg mv -q dir dir2
31 $ hg mv -q dir dir2
32 $ hg ci -m 'mv a b, mv dir/ dir2/'
32 $ hg ci -m 'mv a b, mv dir/ dir2/'
33 $ cd ..
33 $ cd ..
34 $ hg clone -q server repo
34 $ hg clone -q server repo
35 $ initclient repo
35 $ initclient repo
36 $ cd repo
36 $ cd repo
37 $ hg up -q 0
37 $ hg up -q 0
38 $ echo b > a
38 $ echo b > a
39 $ echo b > dir/file.txt
39 $ echo b > dir/file.txt
40 $ hg ci -qm 'mod a, mod dir/file.txt'
40 $ hg ci -qm 'mod a, mod dir/file.txt'
41
41
42 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
42 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
43 @ changeset: 557f403c0afd2a3cf15d7e2fb1f1001a8b85e081
43 @ changeset: 557f403c0afd2a3cf15d7e2fb1f1001a8b85e081
44 | desc: mod a, mod dir/file.txt, phase: draft
44 | desc: mod a, mod dir/file.txt, phase: draft
45 | o changeset: 928d74bc9110681920854d845c06959f6dfc9547
45 | o changeset: 928d74bc9110681920854d845c06959f6dfc9547
46 |/ desc: mv a b, mv dir/ dir2/, phase: public
46 |/ desc: mv a b, mv dir/ dir2/, phase: public
47 o changeset: 3c482b16e54596fed340d05ffaf155f156cda7ee
47 o changeset: 3c482b16e54596fed340d05ffaf155f156cda7ee
48 desc: initial, phase: public
48 desc: initial, phase: public
49
49
50 $ hg rebase -s . -d 1
50 $ hg rebase -s . -d 1
51 rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
51 rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
52 merging b and a to b
52 merging b and a to b
53 merging dir2/file.txt and dir/file.txt to dir2/file.txt
53 merging dir2/file.txt and dir/file.txt to dir2/file.txt
54 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg (glob)
54 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg (glob)
55 $ cd ..
55 $ cd ..
56 $ rm -rf server
56 $ rm -rf server
57 $ rm -rf repo
57 $ rm -rf repo
58
58
59 Make sure filename heuristics do not when they are not related
59 Make sure filename heuristics do not when they are not related
60 $ hg init server
60 $ hg init server
61 $ cd server
61 $ cd server
62 $ echo 'somecontent' > a
62 $ echo 'somecontent' > a
63 $ hg add a
63 $ hg add a
64 $ hg ci -m initial
64 $ hg ci -m initial
65 $ hg rm a
65 $ hg rm a
66 $ echo 'completelydifferentcontext' > b
66 $ echo 'completelydifferentcontext' > b
67 $ hg add b
67 $ hg add b
68 $ hg ci -m 'rm a, add b'
68 $ hg ci -m 'rm a, add b'
69 $ cd ..
69 $ cd ..
70 $ hg clone -q server repo
70 $ hg clone -q server repo
71 $ initclient repo
71 $ initclient repo
72 $ cd repo
72 $ cd repo
73 $ hg up -q 0
73 $ hg up -q 0
74 $ printf 'somecontent\nmoarcontent' > a
74 $ printf 'somecontent\nmoarcontent' > a
75 $ hg ci -qm 'mode a'
75 $ hg ci -qm 'mode a'
76
76
77 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
77 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
78 @ changeset: d526312210b9e8f795d576a77dc643796384d86e
78 @ changeset: d526312210b9e8f795d576a77dc643796384d86e
79 | desc: mode a, phase: draft
79 | desc: mode a, phase: draft
80 | o changeset: 46985f76c7e5e5123433527f5c8526806145650b
80 | o changeset: 46985f76c7e5e5123433527f5c8526806145650b
81 |/ desc: rm a, add b, phase: public
81 |/ desc: rm a, add b, phase: public
82 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
82 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
83 desc: initial, phase: public
83 desc: initial, phase: public
84
84
85 $ hg rebase -s . -d 1
85 $ hg rebase -s . -d 1
86 rebasing 2:d526312210b9 "mode a" (tip)
86 rebasing 2:d526312210b9 "mode a" (tip)
87 other [source] changed a which local [dest] deleted
87 other [source] changed a which local [dest] deleted
88 use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
88 use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
89 unresolved conflicts (see hg resolve, then hg rebase --continue)
89 unresolved conflicts (see hg resolve, then hg rebase --continue)
90 [1]
90 [1]
91
91
92 $ cd ..
92 $ cd ..
93 $ rm -rf server
93 $ rm -rf server
94 $ rm -rf repo
94 $ rm -rf repo
95
95
96 Test when lca didn't modified the file that was moved
96 Test when lca didn't modified the file that was moved
97 $ hg init server
97 $ hg init server
98 $ cd server
98 $ cd server
99 $ echo 'somecontent' > a
99 $ echo 'somecontent' > a
100 $ hg add a
100 $ hg add a
101 $ hg ci -m initial
101 $ hg ci -m initial
102 $ echo c > c
102 $ echo c > c
103 $ hg add c
103 $ hg add c
104 $ hg ci -m randomcommit
104 $ hg ci -m randomcommit
105 $ hg mv a b
105 $ hg mv a b
106 $ hg ci -m 'mv a b'
106 $ hg ci -m 'mv a b'
107 $ cd ..
107 $ cd ..
108 $ hg clone -q server repo
108 $ hg clone -q server repo
109 $ initclient repo
109 $ initclient repo
110 $ cd repo
110 $ cd repo
111 $ hg up -q 1
111 $ hg up -q 1
112 $ echo b > a
112 $ echo b > a
113 $ hg ci -qm 'mod a'
113 $ hg ci -qm 'mod a'
114
114
115 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
115 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
116 @ changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
116 @ changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
117 | desc: mod a, phase: draft
117 | desc: mod a, phase: draft
118 | o changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
118 | o changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
119 |/ desc: mv a b, phase: public
119 |/ desc: mv a b, phase: public
120 o changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
120 o changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
121 | desc: randomcommit, phase: public
121 | desc: randomcommit, phase: public
122 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
122 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
123 desc: initial, phase: public
123 desc: initial, phase: public
124
124
125 $ hg rebase -s . -d 2
125 $ hg rebase -s . -d 2
126 rebasing 3:9d5cf99c3d9f "mod a" (tip)
126 rebasing 3:9d5cf99c3d9f "mod a" (tip)
127 merging b and a to b
127 merging b and a to b
128 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
128 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
129 $ cd ..
129 $ cd ..
130 $ rm -rf server
130 $ rm -rf server
131 $ rm -rf repo
131 $ rm -rf repo
132
132
133 Rebase "backwards"
133 Rebase "backwards"
134 $ hg init server
134 $ hg init server
135 $ cd server
135 $ cd server
136 $ echo 'somecontent' > a
136 $ echo 'somecontent' > a
137 $ hg add a
137 $ hg add a
138 $ hg ci -m initial
138 $ hg ci -m initial
139 $ echo c > c
139 $ echo c > c
140 $ hg add c
140 $ hg add c
141 $ hg ci -m randomcommit
141 $ hg ci -m randomcommit
142 $ hg mv a b
142 $ hg mv a b
143 $ hg ci -m 'mv a b'
143 $ hg ci -m 'mv a b'
144 $ cd ..
144 $ cd ..
145 $ hg clone -q server repo
145 $ hg clone -q server repo
146 $ initclient repo
146 $ initclient repo
147 $ cd repo
147 $ cd repo
148 $ hg up -q 2
148 $ hg up -q 2
149 $ echo b > b
149 $ echo b > b
150 $ hg ci -qm 'mod b'
150 $ hg ci -qm 'mod b'
151
151
152 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
152 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
153 @ changeset: fbe97126b3969056795c462a67d93faf13e4d298
153 @ changeset: fbe97126b3969056795c462a67d93faf13e4d298
154 | desc: mod b, phase: draft
154 | desc: mod b, phase: draft
155 o changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
155 o changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
156 | desc: mv a b, phase: public
156 | desc: mv a b, phase: public
157 o changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
157 o changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
158 | desc: randomcommit, phase: public
158 | desc: randomcommit, phase: public
159 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
159 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
160 desc: initial, phase: public
160 desc: initial, phase: public
161
161
162 $ hg rebase -s . -d 0
162 $ hg rebase -s . -d 0
163 rebasing 3:fbe97126b396 "mod b" (tip)
163 rebasing 3:fbe97126b396 "mod b" (tip)
164 merging a and b to a
164 merging a and b to a
165 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg (glob)
165 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg (glob)
166 $ cd ..
166 $ cd ..
167 $ rm -rf server
167 $ rm -rf server
168 $ rm -rf repo
168 $ rm -rf repo
169
169
170 Rebase draft commit on top of draft commit
170 Rebase draft commit on top of draft commit
171 $ hg init repo
171 $ hg init repo
172 $ initclient repo
172 $ initclient repo
173 $ cd repo
173 $ cd repo
174 $ echo 'somecontent' > a
174 $ echo 'somecontent' > a
175 $ hg add a
175 $ hg add a
176 $ hg ci -m initial
176 $ hg ci -m initial
177 $ hg mv a b
177 $ hg mv a b
178 $ hg ci -m 'mv a b'
178 $ hg ci -m 'mv a b'
179 $ hg up -q ".^"
179 $ hg up -q ".^"
180 $ echo b > a
180 $ echo b > a
181 $ hg ci -qm 'mod a'
181 $ hg ci -qm 'mod a'
182
182
183 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
183 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
184 @ changeset: 5268f05aa1684cfb5741e9eb05eddcc1c5ee7508
184 @ changeset: 5268f05aa1684cfb5741e9eb05eddcc1c5ee7508
185 | desc: mod a, phase: draft
185 | desc: mod a, phase: draft
186 | o changeset: 542cb58df733ee48fa74729bd2cdb94c9310d362
186 | o changeset: 542cb58df733ee48fa74729bd2cdb94c9310d362
187 |/ desc: mv a b, phase: draft
187 |/ desc: mv a b, phase: draft
188 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
188 o changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
189 desc: initial, phase: draft
189 desc: initial, phase: draft
190
190
191 $ hg rebase -s . -d 1
191 $ hg rebase -s . -d 1
192 rebasing 2:5268f05aa168 "mod a" (tip)
192 rebasing 2:5268f05aa168 "mod a" (tip)
193 merging b and a to b
193 merging b and a to b
194 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5268f05aa168-284f6515-rebase.hg (glob)
194 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5268f05aa168-284f6515-rebase.hg (glob)
195 $ cd ..
195 $ cd ..
196 $ rm -rf server
196 $ rm -rf server
197 $ rm -rf repo
197 $ rm -rf repo
198
198
199 Check a few potential move candidates
199 Check a few potential move candidates
200 $ hg init server
200 $ hg init server
201 $ initclient server
201 $ initclient server
202 $ cd server
202 $ cd server
203 $ mkdir dir
203 $ mkdir dir
204 $ echo a > dir/a
204 $ echo a > dir/a
205 $ hg add dir/a
205 $ hg add dir/a
206 $ hg ci -qm initial
206 $ hg ci -qm initial
207 $ hg mv dir/a dir/b
207 $ hg mv dir/a dir/b
208 $ hg ci -qm 'mv dir/a dir/b'
208 $ hg ci -qm 'mv dir/a dir/b'
209 $ mkdir dir2
209 $ mkdir dir2
210 $ echo b > dir2/a
210 $ echo b > dir2/a
211 $ hg add dir2/a
211 $ hg add dir2/a
212 $ hg ci -qm 'create dir2/a'
212 $ hg ci -qm 'create dir2/a'
213 $ cd ..
213 $ cd ..
214 $ hg clone -q server repo
214 $ hg clone -q server repo
215 $ initclient repo
215 $ initclient repo
216 $ cd repo
216 $ cd repo
217 $ hg up -q 0
217 $ hg up -q 0
218 $ echo b > dir/a
218 $ echo b > dir/a
219 $ hg ci -qm 'mod dir/a'
219 $ hg ci -qm 'mod dir/a'
220
220
221 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
221 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
222 @ changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
222 @ changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
223 | desc: mod dir/a, phase: draft
223 | desc: mod dir/a, phase: draft
224 | o changeset: 4494bf7efd2e0dfdd388e767fb913a8a3731e3fa
224 | o changeset: 4494bf7efd2e0dfdd388e767fb913a8a3731e3fa
225 | | desc: create dir2/a, phase: public
225 | | desc: create dir2/a, phase: public
226 | o changeset: b1784dfab6ea6bfafeb11c0ac50a2981b0fe6ade
226 | o changeset: b1784dfab6ea6bfafeb11c0ac50a2981b0fe6ade
227 |/ desc: mv dir/a dir/b, phase: public
227 |/ desc: mv dir/a dir/b, phase: public
228 o changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
228 o changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
229 desc: initial, phase: public
229 desc: initial, phase: public
230
230
231 $ hg rebase -s . -d 2
231 $ hg rebase -s . -d 2
232 rebasing 3:6b2f4cece40f "mod dir/a" (tip)
232 rebasing 3:6b2f4cece40f "mod dir/a" (tip)
233 merging dir/b and dir/a to dir/b
233 merging dir/b and dir/a to dir/b
234 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg (glob)
234 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg (glob)
235 $ cd ..
235 $ cd ..
236 $ rm -rf server
236 $ rm -rf server
237 $ rm -rf repo
237 $ rm -rf repo
238
238
239 Move file in one branch and delete it in another
239 Move file in one branch and delete it in another
240 $ hg init server
240 $ hg init server
241 $ initclient server
241 $ initclient server
242 $ cd server
242 $ cd server
243 $ echo a > a
243 $ echo a > a
244 $ hg add a
244 $ hg add a
245 $ hg ci -m initial
245 $ hg ci -m initial
246 $ cd ..
246 $ cd ..
247 $ hg clone -q server repo
247 $ hg clone -q server repo
248 $ initclient repo
248 $ initclient repo
249 $ cd repo
249 $ cd repo
250 $ hg mv a b
250 $ hg mv a b
251 $ hg ci -m 'mv a b'
251 $ hg ci -m 'mv a b'
252 $ hg up -q ".^"
252 $ hg up -q ".^"
253 $ hg rm a
253 $ hg rm a
254 $ hg ci -m 'del a'
254 $ hg ci -m 'del a'
255 created new head
255 created new head
256
256
257 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
257 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
258 @ changeset: 7d61ee3b1e48577891a072024968428ba465c47b
258 @ changeset: 7d61ee3b1e48577891a072024968428ba465c47b
259 | desc: del a, phase: draft
259 | desc: del a, phase: draft
260 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
260 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
261 |/ desc: mv a b, phase: draft
261 |/ desc: mv a b, phase: draft
262 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
262 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
263 desc: initial, phase: public
263 desc: initial, phase: public
264
264
265 $ hg rebase -s 1 -d 2
265 $ hg rebase -s 1 -d 2
266 rebasing 1:472e38d57782 "mv a b"
266 rebasing 1:472e38d57782 "mv a b"
267 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg (glob)
267 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg (glob)
268 $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062
268 $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062
269 $ ls
269 $ ls
270 b
270 b
271 $ cd ..
271 $ cd ..
272 $ rm -rf server
272 $ rm -rf server
273 $ rm -rf repo
273 $ rm -rf repo
274
274
275 Move a directory in draft branch
275 Move a directory in draft branch
276 $ hg init server
276 $ hg init server
277 $ initclient server
277 $ initclient server
278 $ cd server
278 $ cd server
279 $ mkdir dir
279 $ mkdir dir
280 $ echo a > dir/a
280 $ echo a > dir/a
281 $ hg add dir/a
281 $ hg add dir/a
282 $ hg ci -qm initial
282 $ hg ci -qm initial
283 $ cd ..
283 $ cd ..
284 $ hg clone -q server repo
284 $ hg clone -q server repo
285 $ initclient repo
285 $ initclient repo
286 $ cd repo
286 $ cd repo
287 $ echo b > dir/a
287 $ echo b > dir/a
288 $ hg ci -qm 'mod dir/a'
288 $ hg ci -qm 'mod dir/a'
289 $ hg up -q ".^"
289 $ hg up -q ".^"
290 $ hg mv -q dir/ dir2
290 $ hg mv -q dir/ dir2
291 $ hg ci -qm 'mv dir/ dir2/'
291 $ hg ci -qm 'mv dir/ dir2/'
292
292
293 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
293 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
294 @ changeset: a33d80b6e352591dfd82784e1ad6cdd86b25a239
294 @ changeset: a33d80b6e352591dfd82784e1ad6cdd86b25a239
295 | desc: mv dir/ dir2/, phase: draft
295 | desc: mv dir/ dir2/, phase: draft
296 | o changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
296 | o changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
297 |/ desc: mod dir/a, phase: draft
297 |/ desc: mod dir/a, phase: draft
298 o changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
298 o changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
299 desc: initial, phase: public
299 desc: initial, phase: public
300
300
301 $ hg rebase -s . -d 1
301 $ hg rebase -s . -d 1
302 rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
302 rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
303 merging dir/a and dir2/a to dir2/a
303 merging dir/a and dir2/a to dir2/a
304 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg (glob)
304 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg (glob)
305 $ cd ..
305 $ cd ..
306 $ rm -rf server
306 $ rm -rf server
307 $ rm -rf repo
307 $ rm -rf repo
308
308
309 Move file twice and rebase mod on top of moves
309 Move file twice and rebase mod on top of moves
310 $ hg init server
310 $ hg init server
311 $ initclient server
311 $ initclient server
312 $ cd server
312 $ cd server
313 $ echo a > a
313 $ echo a > a
314 $ hg add a
314 $ hg add a
315 $ hg ci -m initial
315 $ hg ci -m initial
316 $ hg mv a b
316 $ hg mv a b
317 $ hg ci -m 'mv a b'
317 $ hg ci -m 'mv a b'
318 $ hg mv b c
318 $ hg mv b c
319 $ hg ci -m 'mv b c'
319 $ hg ci -m 'mv b c'
320 $ cd ..
320 $ cd ..
321 $ hg clone -q server repo
321 $ hg clone -q server repo
322 $ initclient repo
322 $ initclient repo
323 $ cd repo
323 $ cd repo
324 $ hg up -q 0
324 $ hg up -q 0
325 $ echo c > a
325 $ echo c > a
326 $ hg ci -m 'mod a'
326 $ hg ci -m 'mod a'
327 created new head
327 created new head
328 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
328 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
329 @ changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
329 @ changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
330 | desc: mod a, phase: draft
330 | desc: mod a, phase: draft
331 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
331 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
332 | | desc: mv b c, phase: public
332 | | desc: mv b c, phase: public
333 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
333 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
334 |/ desc: mv a b, phase: public
334 |/ desc: mv a b, phase: public
335 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
335 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
336 desc: initial, phase: public
336 desc: initial, phase: public
337 $ hg rebase -s . -d 2
337 $ hg rebase -s . -d 2
338 rebasing 3:d41316942216 "mod a" (tip)
338 rebasing 3:d41316942216 "mod a" (tip)
339 merging c and a to c
339 merging c and a to c
340 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
340 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
341
341
342 $ cd ..
342 $ cd ..
343 $ rm -rf server
343 $ rm -rf server
344 $ rm -rf repo
344 $ rm -rf repo
345
345
346 Move file twice and rebase moves on top of mods
346 Move file twice and rebase moves on top of mods
347 $ hg init server
347 $ hg init server
348 $ initclient server
348 $ initclient server
349 $ cd server
349 $ cd server
350 $ echo a > a
350 $ echo a > a
351 $ hg add a
351 $ hg add a
352 $ hg ci -m initial
352 $ hg ci -m initial
353 $ cd ..
353 $ cd ..
354 $ hg clone -q server repo
354 $ hg clone -q server repo
355 $ initclient repo
355 $ initclient repo
356 $ cd repo
356 $ cd repo
357 $ hg mv a b
357 $ hg mv a b
358 $ hg ci -m 'mv a b'
358 $ hg ci -m 'mv a b'
359 $ hg mv b c
359 $ hg mv b c
360 $ hg ci -m 'mv b c'
360 $ hg ci -m 'mv b c'
361 $ hg up -q 0
361 $ hg up -q 0
362 $ echo c > a
362 $ echo c > a
363 $ hg ci -m 'mod a'
363 $ hg ci -m 'mod a'
364 created new head
364 created new head
365 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
365 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
366 @ changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
366 @ changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
367 | desc: mod a, phase: draft
367 | desc: mod a, phase: draft
368 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
368 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
369 | | desc: mv b c, phase: draft
369 | | desc: mv b c, phase: draft
370 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
370 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
371 |/ desc: mv a b, phase: draft
371 |/ desc: mv a b, phase: draft
372 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
372 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
373 desc: initial, phase: public
373 desc: initial, phase: public
374 $ hg rebase -s 1 -d .
374 $ hg rebase -s 1 -d .
375 rebasing 1:472e38d57782 "mv a b"
375 rebasing 1:472e38d57782 "mv a b"
376 merging a and b to b
376 merging a and b to b
377 rebasing 2:d3efd280421d "mv b c"
377 rebasing 2:d3efd280421d "mv b c"
378 merging b and c to c
378 merging b and c to c
379 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg (glob)
379 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg (glob)
380
380
381 $ cd ..
381 $ cd ..
382 $ rm -rf server
382 $ rm -rf server
383 $ rm -rf repo
383 $ rm -rf repo
384
384
385 Move one file and add another file in the same folder in one branch, modify file in another branch
385 Move one file and add another file in the same folder in one branch, modify file in another branch
386 $ hg init server
386 $ hg init server
387 $ initclient server
387 $ initclient server
388 $ cd server
388 $ cd server
389 $ echo a > a
389 $ echo a > a
390 $ hg add a
390 $ hg add a
391 $ hg ci -m initial
391 $ hg ci -m initial
392 $ hg mv a b
392 $ hg mv a b
393 $ hg ci -m 'mv a b'
393 $ hg ci -m 'mv a b'
394 $ echo c > c
394 $ echo c > c
395 $ hg add c
395 $ hg add c
396 $ hg ci -m 'add c'
396 $ hg ci -m 'add c'
397 $ cd ..
397 $ cd ..
398 $ hg clone -q server repo
398 $ hg clone -q server repo
399 $ initclient repo
399 $ initclient repo
400 $ cd repo
400 $ cd repo
401 $ hg up -q 0
401 $ hg up -q 0
402 $ echo b > a
402 $ echo b > a
403 $ hg ci -m 'mod a'
403 $ hg ci -m 'mod a'
404 created new head
404 created new head
405
405
406 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
406 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
407 @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
407 @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
408 | desc: mod a, phase: draft
408 | desc: mod a, phase: draft
409 | o changeset: b1a6187e79fbce851bb584eadcb0cc4a80290fd9
409 | o changeset: b1a6187e79fbce851bb584eadcb0cc4a80290fd9
410 | | desc: add c, phase: public
410 | | desc: add c, phase: public
411 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
411 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
412 |/ desc: mv a b, phase: public
412 |/ desc: mv a b, phase: public
413 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
413 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
414 desc: initial, phase: public
414 desc: initial, phase: public
415
415
416 $ hg rebase -s . -d 2
416 $ hg rebase -s . -d 2
417 rebasing 3:ef716627c70b "mod a" (tip)
417 rebasing 3:ef716627c70b "mod a" (tip)
418 merging b and a to b
418 merging b and a to b
419 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
419 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
420 $ ls
420 $ ls
421 b
421 b
422 c
422 c
423 $ cat b
423 $ cat b
424 b
424 b
425
425
426 Merge test
426 Merge test
427 $ hg init server
427 $ hg init server
428 $ cd server
428 $ cd server
429 $ echo a > a
429 $ echo a > a
430 $ hg add a
430 $ hg add a
431 $ hg ci -m initial
431 $ hg ci -m initial
432 $ echo b > a
432 $ echo b > a
433 $ hg ci -m 'modify a'
433 $ hg ci -m 'modify a'
434 $ hg up -q 0
434 $ hg up -q 0
435 $ hg mv a b
435 $ hg mv a b
436 $ hg ci -m 'mv a b'
436 $ hg ci -m 'mv a b'
437 created new head
437 created new head
438 $ cd ..
438 $ cd ..
439 $ hg clone -q server repo
439 $ hg clone -q server repo
440 $ initclient repo
440 $ initclient repo
441 $ cd repo
441 $ cd repo
442 $ hg up -q 2
442 $ hg up -q 2
443
443
444 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
444 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
445 @ changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
445 @ changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
446 | desc: mv a b, phase: public
446 | desc: mv a b, phase: public
447 | o changeset: b0357b07f79129a3d08a68621271ca1352ae8a09
447 | o changeset: b0357b07f79129a3d08a68621271ca1352ae8a09
448 |/ desc: modify a, phase: public
448 |/ desc: modify a, phase: public
449 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
449 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
450 desc: initial, phase: public
450 desc: initial, phase: public
451
451
452 $ hg merge 1
452 $ hg merge 1
453 merging b and a to b
453 merging b and a to b
454 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
454 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
455 (branch merge, don't forget to commit)
455 (branch merge, don't forget to commit)
456 $ hg ci -m merge
456 $ hg ci -m merge
457 $ ls
457 $ ls
458 b
458 b
459 $ cd ..
459 $ cd ..
460 $ rm -rf server
460 $ rm -rf server
461 $ rm -rf repo
461 $ rm -rf repo
462
462
463 Copy and move file
463 Copy and move file
464 $ hg init server
464 $ hg init server
465 $ initclient server
465 $ initclient server
466 $ cd server
466 $ cd server
467 $ echo a > a
467 $ echo a > a
468 $ hg add a
468 $ hg add a
469 $ hg ci -m initial
469 $ hg ci -m initial
470 $ hg cp a c
470 $ hg cp a c
471 $ hg mv a b
471 $ hg mv a b
472 $ hg ci -m 'cp a c, mv a b'
472 $ hg ci -m 'cp a c, mv a b'
473 $ cd ..
473 $ cd ..
474 $ hg clone -q server repo
474 $ hg clone -q server repo
475 $ initclient repo
475 $ initclient repo
476 $ cd repo
476 $ cd repo
477 $ hg up -q 0
477 $ hg up -q 0
478 $ echo b > a
478 $ echo b > a
479 $ hg ci -m 'mod a'
479 $ hg ci -m 'mod a'
480 created new head
480 created new head
481
481
482 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
482 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
483 @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
483 @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
484 | desc: mod a, phase: draft
484 | desc: mod a, phase: draft
485 | o changeset: 4fc3fd13fbdb89ada6b75bfcef3911a689a0dde8
485 | o changeset: 4fc3fd13fbdb89ada6b75bfcef3911a689a0dde8
486 |/ desc: cp a c, mv a b, phase: public
486 |/ desc: cp a c, mv a b, phase: public
487 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
487 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
488 desc: initial, phase: public
488 desc: initial, phase: public
489
489
490 $ hg rebase -s . -d 1
490 $ hg rebase -s . -d 1
491 rebasing 2:ef716627c70b "mod a" (tip)
491 rebasing 2:ef716627c70b "mod a" (tip)
492 merging b and a to b
492 merging b and a to b
493 merging c and a to c
493 merging c and a to c
494 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
494 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
495 $ ls
495 $ ls
496 b
496 b
497 c
497 c
498 $ cat b
498 $ cat b
499 b
499 b
500 $ cat c
500 $ cat c
501 b
501 b
502 $ cd ..
502 $ cd ..
503 $ rm -rf server
503 $ rm -rf server
504 $ rm -rf repo
504 $ rm -rf repo
505
505
506 Do a merge commit with many consequent moves in one branch
506 Do a merge commit with many consequent moves in one branch
507 $ hg init server
507 $ hg init server
508 $ initclient server
508 $ initclient server
509 $ cd server
509 $ cd server
510 $ echo a > a
510 $ echo a > a
511 $ hg add a
511 $ hg add a
512 $ hg ci -m initial
512 $ hg ci -m initial
513 $ echo b > a
513 $ echo b > a
514 $ hg ci -qm 'mod a'
514 $ hg ci -qm 'mod a'
515 $ cd ..
515 $ cd ..
516 $ hg clone -q server repo
516 $ hg clone -q server repo
517 $ initclient repo
517 $ initclient repo
518 $ cd repo
518 $ cd repo
519 $ hg up -q ".^"
519 $ hg up -q ".^"
520 $ hg mv a b
520 $ hg mv a b
521 $ hg ci -qm 'mv a b'
521 $ hg ci -qm 'mv a b'
522 $ hg mv b c
522 $ hg mv b c
523 $ hg ci -qm 'mv b c'
523 $ hg ci -qm 'mv b c'
524 $ hg up -q 1
524 $ hg up -q 1
525 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
525 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
526 o changeset: d3efd280421d24f9f229997c19e654761c942a71
526 o changeset: d3efd280421d24f9f229997c19e654761c942a71
527 | desc: mv b c, phase: draft
527 | desc: mv b c, phase: draft
528 o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
528 o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
529 | desc: mv a b, phase: draft
529 | desc: mv a b, phase: draft
530 | @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
530 | @ changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
531 |/ desc: mod a, phase: public
531 |/ desc: mod a, phase: public
532 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
532 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
533 desc: initial, phase: public
533 desc: initial, phase: public
534
534
535 $ hg merge 3
535 $ hg merge 3
536 merging a and c to c
536 merging a and c to c
537 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
537 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
538 (branch merge, don't forget to commit)
538 (branch merge, don't forget to commit)
539 $ hg ci -qm 'merge'
539 $ hg ci -qm 'merge'
540 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
540 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
541 @ changeset: cd29b0d08c0f39bfed4cde1b40e30f419db0c825
541 @ changeset: cd29b0d08c0f39bfed4cde1b40e30f419db0c825
542 |\ desc: merge, phase: draft
542 |\ desc: merge, phase: draft
543 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
543 | o changeset: d3efd280421d24f9f229997c19e654761c942a71
544 | | desc: mv b c, phase: draft
544 | | desc: mv b c, phase: draft
545 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
545 | o changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
546 | | desc: mv a b, phase: draft
546 | | desc: mv a b, phase: draft
547 o | changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
547 o | changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
548 |/ desc: mod a, phase: public
548 |/ desc: mod a, phase: public
549 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
549 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
550 desc: initial, phase: public
550 desc: initial, phase: public
551 $ ls
551 $ ls
552 c
552 c
553 $ cd ..
553 $ cd ..
554 $ rm -rf server
554 $ rm -rf server
555 $ rm -rf repo
555 $ rm -rf repo
556
556
557 Test shelve/unshelve
557 Test shelve/unshelve
558 $ hg init server
558 $ hg init server
559 $ initclient server
559 $ initclient server
560 $ cd server
560 $ cd server
561 $ echo a > a
561 $ echo a > a
562 $ hg add a
562 $ hg add a
563 $ hg ci -m initial
563 $ hg ci -m initial
564 $ cd ..
564 $ cd ..
565 $ hg clone -q server repo
565 $ hg clone -q server repo
566 $ initclient repo
566 $ initclient repo
567 $ cd repo
567 $ cd repo
568 $ echo b > a
568 $ echo b > a
569 $ hg shelve
569 $ hg shelve
570 shelved as default
570 shelved as default
571 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
571 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
572 $ hg mv a b
572 $ hg mv a b
573 $ hg ci -m 'mv a b'
573 $ hg ci -m 'mv a b'
574
574
575 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
575 $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
576 @ changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
576 @ changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
577 | desc: mv a b, phase: draft
577 | desc: mv a b, phase: draft
578 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
578 o changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
579 desc: initial, phase: public
579 desc: initial, phase: public
580 $ hg unshelve
580 $ hg unshelve
581 unshelving change 'default'
581 unshelving change 'default'
582 rebasing shelved changes
582 rebasing shelved changes
583 rebasing 2:45f63161acea "changes to: initial" (tip)
583 rebasing 2:45f63161acea "changes to: initial" (tip)
584 merging b and a to b
584 merging b and a to b
585 $ ls
585 $ ls
586 b
586 b
587 $ cat b
587 $ cat b
588 b
588 b
589 $ cd ..
589 $ cd ..
590 $ rm -rf server
590 $ rm -rf server
591 $ rm -rf repo
591 $ rm -rf repo
592
593 Test full copytrace ability on draft branch
594 -------------------------------------------
595
596 File directory and base name changed in same move
597 $ hg init repo
598 $ initclient repo
599 $ mkdir repo/dir1
600 $ cd repo/dir1
601 $ echo a > a
602 $ hg add a
603 $ hg ci -qm initial
604 $ cd ..
605 $ hg mv -q dir1 dir2
606 $ hg mv dir2/a dir2/b
607 $ hg ci -qm 'mv a b; mv dir1 dir2'
608 $ hg up -q '.^'
609 $ cd dir1
610 $ echo b >> a
611 $ cd ..
612 $ hg ci -qm 'mod a'
613
614 $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
615 @ changeset 6207d2d318e710b882e3d5ada2a89770efc42c96
616 | desc mod a, phase: draft
617 | o changeset abffdd4e3dfc04bc375034b970299b2a309a1cce
618 |/ desc mv a b; mv dir1 dir2, phase: draft
619 o changeset 81973cd24b58db2fdf18ce3d64fb2cc3284e9ab3
620 desc initial, phase: draft
621
622 $ hg rebase -s . -d 1
623 rebasing 2:6207d2d318e7 "mod a" (tip)
624 merging dir2/b and dir1/a to dir2/b
625 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg (glob)
626 $ cat dir2/b
627 a
628 b
629 $ cd ..
630 $ rm -rf server
631 $ rm -rf repo
632
633 Move directory in one merge parent, while adding file to original directory
634 in other merge parent. File moved on rebase.
635 $ hg init repo
636 $ initclient repo
637 $ mkdir repo/dir1
638 $ cd repo/dir1
639 $ echo dummy > dummy
640 $ hg add dummy
641 $ cd ..
642 $ hg ci -qm initial
643 $ cd dir1
644 $ echo a > a
645 $ hg add a
646 $ cd ..
647 $ hg ci -qm 'hg add dir1/a'
648 $ hg up -q '.^'
649 $ hg mv -q dir1 dir2
650 $ hg ci -qm 'mv dir1 dir2'
651
652 $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
653 @ changeset e8919e7df8d036e07b906045eddcd4a42ff1915f
654 | desc mv dir1 dir2, phase: draft
655 | o changeset 7c7c6f339be00f849c3cb2df738ca91db78b32c8
656 |/ desc hg add dir1/a, phase: draft
657 o changeset a235dcce55dcf42034c4e374cb200662d0bb4a13
658 desc initial, phase: draft
659
660 $ hg rebase -s . -d 1
661 rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip)
662 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg (glob)
663 $ ls dir2
664 a
665 dummy
666 $ rm -rf server
667 $ rm -rf repo
General Comments 0
You need to be logged in to leave comments. Login now