##// END OF EJS Templates
merge: do not warn about copy and rename in the same transaction (issue2113)
Thomas Arendsen Hein -
r16792:ad394c89 default
parent child Browse files
Show More
@@ -1,357 +1,357 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 import util
8 import util
9 import heapq
9 import heapq
10
10
11 def _nonoverlap(d1, d2, d3):
11 def _nonoverlap(d1, d2, d3):
12 "Return list of elements in d1 not in d2 or d3"
12 "Return list of elements in d1 not in d2 or d3"
13 return sorted([d for d in d1 if d not in d3 and d not in d2])
13 return sorted([d for d in d1 if d not in d3 and d not in d2])
14
14
15 def _dirname(f):
15 def _dirname(f):
16 s = f.rfind("/")
16 s = f.rfind("/")
17 if s == -1:
17 if s == -1:
18 return ""
18 return ""
19 return f[:s]
19 return f[:s]
20
20
21 def _findlimit(repo, a, b):
21 def _findlimit(repo, a, b):
22 """Find the earliest revision that's an ancestor of a or b but not both,
22 """Find the earliest revision that's an ancestor of a or b but not both,
23 None if no such revision exists.
23 None if no such revision exists.
24 """
24 """
25 # basic idea:
25 # basic idea:
26 # - mark a and b with different sides
26 # - mark a and b with different sides
27 # - if a parent's children are all on the same side, the parent is
27 # - if a parent's children are all on the same side, the parent is
28 # on that side, otherwise it is on no side
28 # on that side, otherwise it is on no side
29 # - walk the graph in topological order with the help of a heap;
29 # - walk the graph in topological order with the help of a heap;
30 # - add unseen parents to side map
30 # - add unseen parents to side map
31 # - clear side of any parent that has children on different sides
31 # - clear side of any parent that has children on different sides
32 # - track number of interesting revs that might still be on a side
32 # - track number of interesting revs that might still be on a side
33 # - track the lowest interesting rev seen
33 # - track the lowest interesting rev seen
34 # - quit when interesting revs is zero
34 # - quit when interesting revs is zero
35
35
36 cl = repo.changelog
36 cl = repo.changelog
37 working = len(cl) # pseudo rev for the working directory
37 working = len(cl) # pseudo rev for the working directory
38 if a is None:
38 if a is None:
39 a = working
39 a = working
40 if b is None:
40 if b is None:
41 b = working
41 b = working
42
42
43 side = {a: -1, b: 1}
43 side = {a: -1, b: 1}
44 visit = [-a, -b]
44 visit = [-a, -b]
45 heapq.heapify(visit)
45 heapq.heapify(visit)
46 interesting = len(visit)
46 interesting = len(visit)
47 hascommonancestor = False
47 hascommonancestor = False
48 limit = working
48 limit = working
49
49
50 while interesting:
50 while interesting:
51 r = -heapq.heappop(visit)
51 r = -heapq.heappop(visit)
52 if r == working:
52 if r == working:
53 parents = [cl.rev(p) for p in repo.dirstate.parents()]
53 parents = [cl.rev(p) for p in repo.dirstate.parents()]
54 else:
54 else:
55 parents = cl.parentrevs(r)
55 parents = cl.parentrevs(r)
56 for p in parents:
56 for p in parents:
57 if p < 0:
57 if p < 0:
58 continue
58 continue
59 if p not in side:
59 if p not in side:
60 # first time we see p; add it to visit
60 # first time we see p; add it to visit
61 side[p] = side[r]
61 side[p] = side[r]
62 if side[p]:
62 if side[p]:
63 interesting += 1
63 interesting += 1
64 heapq.heappush(visit, -p)
64 heapq.heappush(visit, -p)
65 elif side[p] and side[p] != side[r]:
65 elif side[p] and side[p] != side[r]:
66 # p was interesting but now we know better
66 # p was interesting but now we know better
67 side[p] = 0
67 side[p] = 0
68 interesting -= 1
68 interesting -= 1
69 hascommonancestor = True
69 hascommonancestor = True
70 if side[r]:
70 if side[r]:
71 limit = r # lowest rev visited
71 limit = r # lowest rev visited
72 interesting -= 1
72 interesting -= 1
73
73
74 if not hascommonancestor:
74 if not hascommonancestor:
75 return None
75 return None
76 return limit
76 return limit
77
77
78 def _chain(src, dst, a, b):
78 def _chain(src, dst, a, b):
79 '''chain two sets of copies a->b'''
79 '''chain two sets of copies a->b'''
80 t = a.copy()
80 t = a.copy()
81 for k, v in b.iteritems():
81 for k, v in b.iteritems():
82 if v in t:
82 if v in t:
83 # found a chain
83 # found a chain
84 if t[v] != k:
84 if t[v] != k:
85 # file wasn't renamed back to itself
85 # file wasn't renamed back to itself
86 t[k] = t[v]
86 t[k] = t[v]
87 if v not in dst:
87 if v not in dst:
88 # chain was a rename, not a copy
88 # chain was a rename, not a copy
89 del t[v]
89 del t[v]
90 if v in src:
90 if v in src:
91 # file is a copy of an existing file
91 # file is a copy of an existing file
92 t[k] = v
92 t[k] = v
93
93
94 # remove criss-crossed copies
94 # remove criss-crossed copies
95 for k, v in t.items():
95 for k, v in t.items():
96 if k in src and v in dst:
96 if k in src and v in dst:
97 del t[k]
97 del t[k]
98
98
99 return t
99 return t
100
100
101 def _tracefile(fctx, actx):
101 def _tracefile(fctx, actx):
102 '''return file context that is the ancestor of fctx present in actx'''
102 '''return file context that is the ancestor of fctx present in actx'''
103 stop = actx.rev()
103 stop = actx.rev()
104 am = actx.manifest()
104 am = actx.manifest()
105
105
106 for f in fctx.ancestors():
106 for f in fctx.ancestors():
107 if am.get(f.path(), None) == f.filenode():
107 if am.get(f.path(), None) == f.filenode():
108 return f
108 return f
109 if f.rev() < stop:
109 if f.rev() < stop:
110 return None
110 return None
111
111
112 def _dirstatecopies(d):
112 def _dirstatecopies(d):
113 ds = d._repo.dirstate
113 ds = d._repo.dirstate
114 c = ds.copies().copy()
114 c = ds.copies().copy()
115 for k in c.keys():
115 for k in c.keys():
116 if ds[k] not in 'anm':
116 if ds[k] not in 'anm':
117 del c[k]
117 del c[k]
118 return c
118 return c
119
119
120 def _forwardcopies(a, b):
120 def _forwardcopies(a, b):
121 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
121 '''find {dst@b: src@a} copy mapping where a is an ancestor of b'''
122
122
123 # check for working copy
123 # check for working copy
124 w = None
124 w = None
125 if b.rev() is None:
125 if b.rev() is None:
126 w = b
126 w = b
127 b = w.p1()
127 b = w.p1()
128 if a == b:
128 if a == b:
129 # short-circuit to avoid issues with merge states
129 # short-circuit to avoid issues with merge states
130 return _dirstatecopies(w)
130 return _dirstatecopies(w)
131
131
132 # find where new files came from
132 # find where new files came from
133 # we currently don't try to find where old files went, too expensive
133 # we currently don't try to find where old files went, too expensive
134 # this means we can miss a case like 'hg rm b; hg cp a b'
134 # this means we can miss a case like 'hg rm b; hg cp a b'
135 cm = {}
135 cm = {}
136 for f in b:
136 for f in b:
137 if f not in a:
137 if f not in a:
138 ofctx = _tracefile(b[f], a)
138 ofctx = _tracefile(b[f], a)
139 if ofctx:
139 if ofctx:
140 cm[f] = ofctx.path()
140 cm[f] = ofctx.path()
141
141
142 # combine copies from dirstate if necessary
142 # combine copies from dirstate if necessary
143 if w is not None:
143 if w is not None:
144 cm = _chain(a, w, cm, _dirstatecopies(w))
144 cm = _chain(a, w, cm, _dirstatecopies(w))
145
145
146 return cm
146 return cm
147
147
148 def _backwardcopies(a, b):
148 def _backwardcopies(a, b):
149 # because the forward mapping is 1:n, we can lose renames here
149 # because the forward mapping is 1:n, we can lose renames here
150 # in particular, we find renames better than copies
150 # in particular, we find renames better than copies
151 f = _forwardcopies(b, a)
151 f = _forwardcopies(b, a)
152 r = {}
152 r = {}
153 for k, v in f.iteritems():
153 for k, v in f.iteritems():
154 r[v] = k
154 r[v] = k
155 return r
155 return r
156
156
157 def pathcopies(x, y):
157 def pathcopies(x, y):
158 '''find {dst@y: src@x} copy mapping for directed compare'''
158 '''find {dst@y: src@x} copy mapping for directed compare'''
159 if x == y or not x or not y:
159 if x == y or not x or not y:
160 return {}
160 return {}
161 a = y.ancestor(x)
161 a = y.ancestor(x)
162 if a == x:
162 if a == x:
163 return _forwardcopies(x, y)
163 return _forwardcopies(x, y)
164 if a == y:
164 if a == y:
165 return _backwardcopies(x, y)
165 return _backwardcopies(x, y)
166 return _chain(x, y, _backwardcopies(x, a), _forwardcopies(a, y))
166 return _chain(x, y, _backwardcopies(x, a), _forwardcopies(a, y))
167
167
168 def mergecopies(repo, c1, c2, ca):
168 def mergecopies(repo, c1, c2, ca):
169 """
169 """
170 Find moves and copies between context c1 and c2 that are relevant
170 Find moves and copies between context c1 and c2 that are relevant
171 for merging.
171 for merging.
172
172
173 Returns two dicts, "copy" and "diverge".
173 Returns two dicts, "copy" and "diverge".
174
174
175 "copy" is a mapping from destination name -> source name,
175 "copy" is a mapping from destination name -> source name,
176 where source is in c1 and destination is in c2 or vice-versa.
176 where source is in c1 and destination is in c2 or vice-versa.
177
177
178 "diverge" is a mapping of source name -> list of destination names
178 "diverge" is a mapping of source name -> list of destination names
179 for divergent renames.
179 for divergent renames.
180 """
180 """
181 # avoid silly behavior for update from empty dir
181 # avoid silly behavior for update from empty dir
182 if not c1 or not c2 or c1 == c2:
182 if not c1 or not c2 or c1 == c2:
183 return {}, {}
183 return {}, {}
184
184
185 # avoid silly behavior for parent -> working dir
185 # avoid silly behavior for parent -> working dir
186 if c2.node() is None and c1.node() == repo.dirstate.p1():
186 if c2.node() is None and c1.node() == repo.dirstate.p1():
187 return repo.dirstate.copies(), {}
187 return repo.dirstate.copies(), {}
188
188
189 limit = _findlimit(repo, c1.rev(), c2.rev())
189 limit = _findlimit(repo, c1.rev(), c2.rev())
190 if limit is None:
190 if limit is None:
191 # no common ancestor, no copies
191 # no common ancestor, no copies
192 return {}, {}
192 return {}, {}
193 m1 = c1.manifest()
193 m1 = c1.manifest()
194 m2 = c2.manifest()
194 m2 = c2.manifest()
195 ma = ca.manifest()
195 ma = ca.manifest()
196
196
197 def makectx(f, n):
197 def makectx(f, n):
198 if len(n) != 20: # in a working context?
198 if len(n) != 20: # in a working context?
199 if c1.rev() is None:
199 if c1.rev() is None:
200 return c1.filectx(f)
200 return c1.filectx(f)
201 return c2.filectx(f)
201 return c2.filectx(f)
202 return repo.filectx(f, fileid=n)
202 return repo.filectx(f, fileid=n)
203
203
204 ctx = util.lrucachefunc(makectx)
204 ctx = util.lrucachefunc(makectx)
205 copy = {}
205 copy = {}
206 fullcopy = {}
206 fullcopy = {}
207 diverge = {}
207 diverge = {}
208
208
209 def related(f1, f2, limit):
209 def related(f1, f2, limit):
210 # Walk back to common ancestor to see if the two files originate
210 # Walk back to common ancestor to see if the two files originate
211 # from the same file. Since workingfilectx's rev() is None it messes
211 # from the same file. Since workingfilectx's rev() is None it messes
212 # up the integer comparison logic, hence the pre-step check for
212 # up the integer comparison logic, hence the pre-step check for
213 # None (f1 and f2 can only be workingfilectx's initially).
213 # None (f1 and f2 can only be workingfilectx's initially).
214
214
215 if f1 == f2:
215 if f1 == f2:
216 return f1 # a match
216 return f1 # a match
217
217
218 g1, g2 = f1.ancestors(), f2.ancestors()
218 g1, g2 = f1.ancestors(), f2.ancestors()
219 try:
219 try:
220 f1r, f2r = f1.rev(), f2.rev()
220 f1r, f2r = f1.rev(), f2.rev()
221
221
222 if f1r is None:
222 if f1r is None:
223 f1 = g1.next()
223 f1 = g1.next()
224 if f2r is None:
224 if f2r is None:
225 f2 = g2.next()
225 f2 = g2.next()
226
226
227 while True:
227 while True:
228 f1r, f2r = f1.rev(), f2.rev()
228 f1r, f2r = f1.rev(), f2.rev()
229 if f1r > f2r:
229 if f1r > f2r:
230 f1 = g1.next()
230 f1 = g1.next()
231 elif f2r > f1r:
231 elif f2r > f1r:
232 f2 = g2.next()
232 f2 = g2.next()
233 elif f1 == f2:
233 elif f1 == f2:
234 return f1 # a match
234 return f1 # a match
235 elif f1r == f2r or f1r < limit or f2r < limit:
235 elif f1r == f2r or f1r < limit or f2r < limit:
236 return False # copy no longer relevant
236 return False # copy no longer relevant
237 except StopIteration:
237 except StopIteration:
238 return False
238 return False
239
239
240 def checkcopies(f, m1, m2):
240 def checkcopies(f, m1, m2):
241 '''check possible copies of f from m1 to m2'''
241 '''check possible copies of f from m1 to m2'''
242 of = None
242 of = None
243 seen = set([f])
243 seen = set([f])
244 for oc in ctx(f, m1[f]).ancestors():
244 for oc in ctx(f, m1[f]).ancestors():
245 ocr = oc.rev()
245 ocr = oc.rev()
246 of = oc.path()
246 of = oc.path()
247 if of in seen:
247 if of in seen:
248 # check limit late - grab last rename before
248 # check limit late - grab last rename before
249 if ocr < limit:
249 if ocr < limit:
250 break
250 break
251 continue
251 continue
252 seen.add(of)
252 seen.add(of)
253
253
254 fullcopy[f] = of # remember for dir rename detection
254 fullcopy[f] = of # remember for dir rename detection
255 if of not in m2:
255 if of not in m2:
256 continue # no match, keep looking
256 continue # no match, keep looking
257 if m2[of] == ma.get(of):
257 if m2[of] == ma.get(of):
258 break # no merge needed, quit early
258 break # no merge needed, quit early
259 c2 = ctx(of, m2[of])
259 c2 = ctx(of, m2[of])
260 cr = related(oc, c2, ca.rev())
260 cr = related(oc, c2, ca.rev())
261 if cr and (of == f or of == c2.path()): # non-divergent
261 if cr and (of == f or of == c2.path()): # non-divergent
262 copy[f] = of
262 copy[f] = of
263 of = None
263 of = None
264 break
264 break
265
265
266 if of in ma:
266 if of in ma:
267 diverge.setdefault(of, []).append(f)
267 diverge.setdefault(of, []).append(f)
268
268
269 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
269 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
270
270
271 u1 = _nonoverlap(m1, m2, ma)
271 u1 = _nonoverlap(m1, m2, ma)
272 u2 = _nonoverlap(m2, m1, ma)
272 u2 = _nonoverlap(m2, m1, ma)
273
273
274 if u1:
274 if u1:
275 repo.ui.debug(" unmatched files in local:\n %s\n"
275 repo.ui.debug(" unmatched files in local:\n %s\n"
276 % "\n ".join(u1))
276 % "\n ".join(u1))
277 if u2:
277 if u2:
278 repo.ui.debug(" unmatched files in other:\n %s\n"
278 repo.ui.debug(" unmatched files in other:\n %s\n"
279 % "\n ".join(u2))
279 % "\n ".join(u2))
280
280
281 for f in u1:
281 for f in u1:
282 checkcopies(f, m1, m2)
282 checkcopies(f, m1, m2)
283 for f in u2:
283 for f in u2:
284 checkcopies(f, m2, m1)
284 checkcopies(f, m2, m1)
285
285
286 diverge2 = set()
286 diverge2 = set()
287 for of, fl in diverge.items():
287 for of, fl in diverge.items():
288 if len(fl) == 1 or of in c2:
288 if len(fl) == 1 or of in c1 or of in c2:
289 del diverge[of] # not actually divergent, or not a rename
289 del diverge[of] # not actually divergent, or not a rename
290 else:
290 else:
291 diverge2.update(fl) # reverse map for below
291 diverge2.update(fl) # reverse map for below
292
292
293 if fullcopy:
293 if fullcopy:
294 repo.ui.debug(" all copies found (* = to merge, ! = divergent):\n")
294 repo.ui.debug(" all copies found (* = to merge, ! = divergent):\n")
295 for f in fullcopy:
295 for f in fullcopy:
296 note = ""
296 note = ""
297 if f in copy:
297 if f in copy:
298 note += "*"
298 note += "*"
299 if f in diverge2:
299 if f in diverge2:
300 note += "!"
300 note += "!"
301 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
301 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
302 del diverge2
302 del diverge2
303
303
304 if not fullcopy:
304 if not fullcopy:
305 return copy, diverge
305 return copy, diverge
306
306
307 repo.ui.debug(" checking for directory renames\n")
307 repo.ui.debug(" checking for directory renames\n")
308
308
309 # generate a directory move map
309 # generate a directory move map
310 d1, d2 = c1.dirs(), c2.dirs()
310 d1, d2 = c1.dirs(), c2.dirs()
311 invalid = set([""])
311 invalid = set([""])
312 dirmove = {}
312 dirmove = {}
313
313
314 # examine each file copy for a potential directory move, which is
314 # examine each file copy for a potential directory move, which is
315 # when all the files in a directory are moved to a new directory
315 # when all the files in a directory are moved to a new directory
316 for dst, src in fullcopy.iteritems():
316 for dst, src in fullcopy.iteritems():
317 dsrc, ddst = _dirname(src), _dirname(dst)
317 dsrc, ddst = _dirname(src), _dirname(dst)
318 if dsrc in invalid:
318 if dsrc in invalid:
319 # already seen to be uninteresting
319 # already seen to be uninteresting
320 continue
320 continue
321 elif dsrc in d1 and ddst in d1:
321 elif dsrc in d1 and ddst in d1:
322 # directory wasn't entirely moved locally
322 # directory wasn't entirely moved locally
323 invalid.add(dsrc)
323 invalid.add(dsrc)
324 elif dsrc in d2 and ddst in d2:
324 elif dsrc in d2 and ddst in d2:
325 # directory wasn't entirely moved remotely
325 # directory wasn't entirely moved remotely
326 invalid.add(dsrc)
326 invalid.add(dsrc)
327 elif dsrc in dirmove and dirmove[dsrc] != ddst:
327 elif dsrc in dirmove and dirmove[dsrc] != ddst:
328 # files from the same directory moved to two different places
328 # files from the same directory moved to two different places
329 invalid.add(dsrc)
329 invalid.add(dsrc)
330 else:
330 else:
331 # looks good so far
331 # looks good so far
332 dirmove[dsrc + "/"] = ddst + "/"
332 dirmove[dsrc + "/"] = ddst + "/"
333
333
334 for i in invalid:
334 for i in invalid:
335 if i in dirmove:
335 if i in dirmove:
336 del dirmove[i]
336 del dirmove[i]
337 del d1, d2, invalid
337 del d1, d2, invalid
338
338
339 if not dirmove:
339 if not dirmove:
340 return copy, diverge
340 return copy, diverge
341
341
342 for d in dirmove:
342 for d in dirmove:
343 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
343 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
344
344
345 # check unaccounted nonoverlapping files against directory moves
345 # check unaccounted nonoverlapping files against directory moves
346 for f in u1 + u2:
346 for f in u1 + u2:
347 if f not in fullcopy:
347 if f not in fullcopy:
348 for d in dirmove:
348 for d in dirmove:
349 if f.startswith(d):
349 if f.startswith(d):
350 # new file added in a directory that was moved, move it
350 # new file added in a directory that was moved, move it
351 df = dirmove[d] + f[len(d):]
351 df = dirmove[d] + f[len(d):]
352 if df not in copy:
352 if df not in copy:
353 copy[f] = df
353 copy[f] = df
354 repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
354 repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
355 break
355 break
356
356
357 return copy, diverge
357 return copy, diverge
@@ -1,157 +1,154 b''
1 $ hg init
1 $ hg init
2
2
3 $ echo "[merge]" >> .hg/hgrc
3 $ echo "[merge]" >> .hg/hgrc
4 $ echo "followcopies = 1" >> .hg/hgrc
4 $ echo "followcopies = 1" >> .hg/hgrc
5
5
6 $ echo foo > a
6 $ echo foo > a
7 $ echo foo > a2
7 $ echo foo > a2
8 $ hg add a a2
8 $ hg add a a2
9 $ hg ci -m "start"
9 $ hg ci -m "start"
10
10
11 $ hg mv a b
11 $ hg mv a b
12 $ hg mv a2 b2
12 $ hg mv a2 b2
13 $ hg ci -m "rename"
13 $ hg ci -m "rename"
14
14
15 $ hg co 0
15 $ hg co 0
16 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
16 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
17
17
18 $ echo blahblah > a
18 $ echo blahblah > a
19 $ echo blahblah > a2
19 $ echo blahblah > a2
20 $ hg mv a2 c2
20 $ hg mv a2 c2
21 $ hg ci -m "modify"
21 $ hg ci -m "modify"
22 created new head
22 created new head
23
23
24 $ hg merge -y --debug
24 $ hg merge -y --debug
25 searching for copies back to rev 1
25 searching for copies back to rev 1
26 unmatched files in local:
26 unmatched files in local:
27 c2
27 c2
28 unmatched files in other:
28 unmatched files in other:
29 b
29 b
30 b2
30 b2
31 all copies found (* = to merge, ! = divergent):
31 all copies found (* = to merge, ! = divergent):
32 c2 -> a2 !
32 c2 -> a2 !
33 b -> a *
33 b -> a *
34 b2 -> a2 !
34 b2 -> a2 !
35 checking for directory renames
35 checking for directory renames
36 a2: divergent renames -> dr
36 a2: divergent renames -> dr
37 resolving manifests
37 resolving manifests
38 overwrite: False, partial: False
38 overwrite: False, partial: False
39 ancestor: af1939970a1c, local: 044f8520aeeb+, remote: 85c198ef2f6c
39 ancestor: af1939970a1c, local: 044f8520aeeb+, remote: 85c198ef2f6c
40 a: remote moved to b -> m
40 a: remote moved to b -> m
41 b2: remote created -> g
41 b2: remote created -> g
42 preserving a for resolve of b
42 preserving a for resolve of b
43 removing a
43 removing a
44 updating: a 1/3 files (33.33%)
44 updating: a 1/3 files (33.33%)
45 picked tool 'internal:merge' for b (binary False symlink False)
45 picked tool 'internal:merge' for b (binary False symlink False)
46 merging a and b to b
46 merging a and b to b
47 my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c
47 my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c
48 premerge successful
48 premerge successful
49 updating: a2 2/3 files (66.67%)
49 updating: a2 2/3 files (66.67%)
50 note: possible conflict - a2 was renamed multiple times to:
50 note: possible conflict - a2 was renamed multiple times to:
51 c2
51 c2
52 b2
52 b2
53 updating: b2 3/3 files (100.00%)
53 updating: b2 3/3 files (100.00%)
54 getting b2
54 getting b2
55 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
55 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
56 (branch merge, don't forget to commit)
56 (branch merge, don't forget to commit)
57
57
58 $ hg status -AC
58 $ hg status -AC
59 M b
59 M b
60 a
60 a
61 M b2
61 M b2
62 R a
62 R a
63 C c2
63 C c2
64
64
65 $ cat b
65 $ cat b
66 blahblah
66 blahblah
67
67
68 $ hg ci -m "merge"
68 $ hg ci -m "merge"
69
69
70 $ hg debugindex b
70 $ hg debugindex b
71 rev offset length base linkrev nodeid p1 p2
71 rev offset length base linkrev nodeid p1 p2
72 0 0 67 0 1 57eacc201a7f 000000000000 000000000000
72 0 0 67 0 1 57eacc201a7f 000000000000 000000000000
73 1 67 72 1 3 4727ba907962 000000000000 57eacc201a7f
73 1 67 72 1 3 4727ba907962 000000000000 57eacc201a7f
74
74
75 $ hg debugrename b
75 $ hg debugrename b
76 b renamed from a:dd03b83622e78778b403775d0d074b9ac7387a66
76 b renamed from a:dd03b83622e78778b403775d0d074b9ac7387a66
77
77
78 This used to trigger a "divergent renames" warning, despite no renames
78 This used to trigger a "divergent renames" warning, despite no renames
79
79
80 $ hg cp b b3
80 $ hg cp b b3
81 $ hg cp b b4
81 $ hg cp b b4
82 $ hg ci -A -m 'copy b twice'
82 $ hg ci -A -m 'copy b twice'
83 $ hg up eb92d88a9712
83 $ hg up eb92d88a9712
84 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
84 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
85 $ hg up
85 $ hg up
86 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 $ hg rm b3 b4
87 $ hg rm b3 b4
88 $ hg ci -m 'clean up a bit of our mess'
88 $ hg ci -m 'clean up a bit of our mess'
89
89
90 We'd rather not warn on divergent renames done in the same changeset (issue2113)
90 We'd rather not warn on divergent renames done in the same changeset (issue2113)
91
91
92 $ hg cp b b3
92 $ hg cp b b3
93 $ hg mv b b4
93 $ hg mv b b4
94 $ hg ci -A -m 'divergent renames in same changeset'
94 $ hg ci -A -m 'divergent renames in same changeset'
95 $ hg up c761c6948de0
95 $ hg up c761c6948de0
96 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
96 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
97 $ hg up
97 $ hg up
98 note: possible conflict - b was renamed multiple times to:
99 b3
100 b4
101 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
98 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
102
99
103 Check for issue2642
100 Check for issue2642
104
101
105 $ hg init t
102 $ hg init t
106 $ cd t
103 $ cd t
107
104
108 $ echo c0 > f1
105 $ echo c0 > f1
109 $ hg ci -Aqm0
106 $ hg ci -Aqm0
110
107
111 $ hg up null -q
108 $ hg up null -q
112 $ echo c1 > f1 # backport
109 $ echo c1 > f1 # backport
113 $ hg ci -Aqm1
110 $ hg ci -Aqm1
114 $ hg mv f1 f2
111 $ hg mv f1 f2
115 $ hg ci -qm2
112 $ hg ci -qm2
116
113
117 $ hg up 0 -q
114 $ hg up 0 -q
118 $ hg merge 1 -q --tool internal:local
115 $ hg merge 1 -q --tool internal:local
119 $ hg ci -qm3
116 $ hg ci -qm3
120
117
121 $ hg merge 2
118 $ hg merge 2
122 merging f1 and f2 to f2
119 merging f1 and f2 to f2
123 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
120 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
124 (branch merge, don't forget to commit)
121 (branch merge, don't forget to commit)
125
122
126 $ cat f2
123 $ cat f2
127 c0
124 c0
128
125
129 Check for issue2089
126 Check for issue2089
130
127
131 $ hg init repo2089
128 $ hg init repo2089
132 $ cd repo2089
129 $ cd repo2089
133
130
134 $ echo c0 > f1
131 $ echo c0 > f1
135 $ hg ci -Aqm0
132 $ hg ci -Aqm0
136
133
137 $ hg up null -q
134 $ hg up null -q
138 $ echo c1 > f1
135 $ echo c1 > f1
139 $ hg ci -Aqm1
136 $ hg ci -Aqm1
140
137
141 $ hg up 0 -q
138 $ hg up 0 -q
142 $ hg merge 1 -q --tool internal:local
139 $ hg merge 1 -q --tool internal:local
143 $ echo c2 > f1
140 $ echo c2 > f1
144 $ hg ci -qm2
141 $ hg ci -qm2
145
142
146 $ hg up 1 -q
143 $ hg up 1 -q
147 $ hg mv f1 f2
144 $ hg mv f1 f2
148 $ hg ci -Aqm3
145 $ hg ci -Aqm3
149
146
150 $ hg up 2 -q
147 $ hg up 2 -q
151 $ hg merge 3
148 $ hg merge 3
152 merging f1 and f2 to f2
149 merging f1 and f2 to f2
153 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
150 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
154 (branch merge, don't forget to commit)
151 (branch merge, don't forget to commit)
155
152
156 $ cat f2
153 $ cat f2
157 c2
154 c2
General Comments 0
You need to be logged in to leave comments. Login now