##// END OF EJS Templates
copies: separate moves via directory renames from explicit copies...
Siddharth Agarwal -
r18134:6c35b53c default
parent child Browse files
Show More
@@ -1,372 +1,379
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 four dicts: "copy", "movewithdir", "diverge", and
174 "renamedelete".
174
175
175 "copy" is a mapping from destination name -> source name,
176 "copy" is a mapping from destination name -> source name,
176 where source is in c1 and destination is in c2 or vice-versa.
177 where source is in c1 and destination is in c2 or vice-versa.
177
178
179 "movewithdir" is a mapping from source name -> destination name,
180 where the file at source present in one context but not the other
181 needs to be moved to destination by the merge process, because the
182 other context moved the directory it is in.
183
178 "diverge" is a mapping of source name -> list of destination names
184 "diverge" is a mapping of source name -> list of destination names
179 for divergent renames.
185 for divergent renames.
180
186
181 "renamedelete" is a mapping of source name -> list of destination
187 "renamedelete" is a mapping of source name -> list of destination
182 names for files deleted in c1 that were renamed in c2 or vice-versa.
188 names for files deleted in c1 that were renamed in c2 or vice-versa.
183 """
189 """
184 # avoid silly behavior for update from empty dir
190 # avoid silly behavior for update from empty dir
185 if not c1 or not c2 or c1 == c2:
191 if not c1 or not c2 or c1 == c2:
186 return {}, {}, {}
192 return {}, {}, {}, {}
187
193
188 # avoid silly behavior for parent -> working dir
194 # avoid silly behavior for parent -> working dir
189 if c2.node() is None and c1.node() == repo.dirstate.p1():
195 if c2.node() is None and c1.node() == repo.dirstate.p1():
190 return repo.dirstate.copies(), {}, {}
196 return repo.dirstate.copies(), {}, {}, {}
191
197
192 limit = _findlimit(repo, c1.rev(), c2.rev())
198 limit = _findlimit(repo, c1.rev(), c2.rev())
193 if limit is None:
199 if limit is None:
194 # no common ancestor, no copies
200 # no common ancestor, no copies
195 return {}, {}, {}
201 return {}, {}, {}, {}
196 m1 = c1.manifest()
202 m1 = c1.manifest()
197 m2 = c2.manifest()
203 m2 = c2.manifest()
198 ma = ca.manifest()
204 ma = ca.manifest()
199
205
200 def makectx(f, n):
206 def makectx(f, n):
201 if len(n) != 20: # in a working context?
207 if len(n) != 20: # in a working context?
202 if c1.rev() is None:
208 if c1.rev() is None:
203 return c1.filectx(f)
209 return c1.filectx(f)
204 return c2.filectx(f)
210 return c2.filectx(f)
205 return repo.filectx(f, fileid=n)
211 return repo.filectx(f, fileid=n)
206
212
207 ctx = util.lrucachefunc(makectx)
213 ctx = util.lrucachefunc(makectx)
208 copy = {}
214 copy = {}
215 movewithdir = {}
209 fullcopy = {}
216 fullcopy = {}
210 diverge = {}
217 diverge = {}
211
218
212 def related(f1, f2, limit):
219 def related(f1, f2, limit):
213 # Walk back to common ancestor to see if the two files originate
220 # Walk back to common ancestor to see if the two files originate
214 # from the same file. Since workingfilectx's rev() is None it messes
221 # from the same file. Since workingfilectx's rev() is None it messes
215 # up the integer comparison logic, hence the pre-step check for
222 # up the integer comparison logic, hence the pre-step check for
216 # None (f1 and f2 can only be workingfilectx's initially).
223 # None (f1 and f2 can only be workingfilectx's initially).
217
224
218 if f1 == f2:
225 if f1 == f2:
219 return f1 # a match
226 return f1 # a match
220
227
221 g1, g2 = f1.ancestors(), f2.ancestors()
228 g1, g2 = f1.ancestors(), f2.ancestors()
222 try:
229 try:
223 f1r, f2r = f1.rev(), f2.rev()
230 f1r, f2r = f1.rev(), f2.rev()
224
231
225 if f1r is None:
232 if f1r is None:
226 f1 = g1.next()
233 f1 = g1.next()
227 if f2r is None:
234 if f2r is None:
228 f2 = g2.next()
235 f2 = g2.next()
229
236
230 while True:
237 while True:
231 f1r, f2r = f1.rev(), f2.rev()
238 f1r, f2r = f1.rev(), f2.rev()
232 if f1r > f2r:
239 if f1r > f2r:
233 f1 = g1.next()
240 f1 = g1.next()
234 elif f2r > f1r:
241 elif f2r > f1r:
235 f2 = g2.next()
242 f2 = g2.next()
236 elif f1 == f2:
243 elif f1 == f2:
237 return f1 # a match
244 return f1 # a match
238 elif f1r == f2r or f1r < limit or f2r < limit:
245 elif f1r == f2r or f1r < limit or f2r < limit:
239 return False # copy no longer relevant
246 return False # copy no longer relevant
240 except StopIteration:
247 except StopIteration:
241 return False
248 return False
242
249
243 def checkcopies(f, m1, m2):
250 def checkcopies(f, m1, m2):
244 '''check possible copies of f from m1 to m2'''
251 '''check possible copies of f from m1 to m2'''
245 of = None
252 of = None
246 seen = set([f])
253 seen = set([f])
247 for oc in ctx(f, m1[f]).ancestors():
254 for oc in ctx(f, m1[f]).ancestors():
248 ocr = oc.rev()
255 ocr = oc.rev()
249 of = oc.path()
256 of = oc.path()
250 if of in seen:
257 if of in seen:
251 # check limit late - grab last rename before
258 # check limit late - grab last rename before
252 if ocr < limit:
259 if ocr < limit:
253 break
260 break
254 continue
261 continue
255 seen.add(of)
262 seen.add(of)
256
263
257 fullcopy[f] = of # remember for dir rename detection
264 fullcopy[f] = of # remember for dir rename detection
258 if of not in m2:
265 if of not in m2:
259 continue # no match, keep looking
266 continue # no match, keep looking
260 if m2[of] == ma.get(of):
267 if m2[of] == ma.get(of):
261 break # no merge needed, quit early
268 break # no merge needed, quit early
262 c2 = ctx(of, m2[of])
269 c2 = ctx(of, m2[of])
263 cr = related(oc, c2, ca.rev())
270 cr = related(oc, c2, ca.rev())
264 if cr and (of == f or of == c2.path()): # non-divergent
271 if cr and (of == f or of == c2.path()): # non-divergent
265 copy[f] = of
272 copy[f] = of
266 of = None
273 of = None
267 break
274 break
268
275
269 if of in ma:
276 if of in ma:
270 diverge.setdefault(of, []).append(f)
277 diverge.setdefault(of, []).append(f)
271
278
272 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
279 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
273
280
274 u1 = _nonoverlap(m1, m2, ma)
281 u1 = _nonoverlap(m1, m2, ma)
275 u2 = _nonoverlap(m2, m1, ma)
282 u2 = _nonoverlap(m2, m1, ma)
276
283
277 if u1:
284 if u1:
278 repo.ui.debug(" unmatched files in local:\n %s\n"
285 repo.ui.debug(" unmatched files in local:\n %s\n"
279 % "\n ".join(u1))
286 % "\n ".join(u1))
280 if u2:
287 if u2:
281 repo.ui.debug(" unmatched files in other:\n %s\n"
288 repo.ui.debug(" unmatched files in other:\n %s\n"
282 % "\n ".join(u2))
289 % "\n ".join(u2))
283
290
284 for f in u1:
291 for f in u1:
285 checkcopies(f, m1, m2)
292 checkcopies(f, m1, m2)
286 for f in u2:
293 for f in u2:
287 checkcopies(f, m2, m1)
294 checkcopies(f, m2, m1)
288
295
289 renamedelete = {}
296 renamedelete = {}
290 renamedelete2 = set()
297 renamedelete2 = set()
291 diverge2 = set()
298 diverge2 = set()
292 for of, fl in diverge.items():
299 for of, fl in diverge.items():
293 if len(fl) == 1 or of in c1 or of in c2:
300 if len(fl) == 1 or of in c1 or of in c2:
294 del diverge[of] # not actually divergent, or not a rename
301 del diverge[of] # not actually divergent, or not a rename
295 if of not in c1 and of not in c2:
302 if of not in c1 and of not in c2:
296 # renamed on one side, deleted on the other side, but filter
303 # renamed on one side, deleted on the other side, but filter
297 # out files that have been renamed and then deleted
304 # out files that have been renamed and then deleted
298 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
305 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
299 renamedelete2.update(fl) # reverse map for below
306 renamedelete2.update(fl) # reverse map for below
300 else:
307 else:
301 diverge2.update(fl) # reverse map for below
308 diverge2.update(fl) # reverse map for below
302
309
303 if fullcopy:
310 if fullcopy:
304 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
311 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
305 "% = renamed and deleted):\n")
312 "% = renamed and deleted):\n")
306 for f in fullcopy:
313 for f in fullcopy:
307 note = ""
314 note = ""
308 if f in copy:
315 if f in copy:
309 note += "*"
316 note += "*"
310 if f in diverge2:
317 if f in diverge2:
311 note += "!"
318 note += "!"
312 if f in renamedelete2:
319 if f in renamedelete2:
313 note += "%"
320 note += "%"
314 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
321 repo.ui.debug(" %s -> %s %s\n" % (f, fullcopy[f], note))
315 del diverge2
322 del diverge2
316
323
317 if not fullcopy:
324 if not fullcopy:
318 return copy, diverge, renamedelete
325 return copy, movewithdir, diverge, renamedelete
319
326
320 repo.ui.debug(" checking for directory renames\n")
327 repo.ui.debug(" checking for directory renames\n")
321
328
322 # generate a directory move map
329 # generate a directory move map
323 d1, d2 = c1.dirs(), c2.dirs()
330 d1, d2 = c1.dirs(), c2.dirs()
324 d1.add('')
331 d1.add('')
325 d2.add('')
332 d2.add('')
326 invalid = set()
333 invalid = set()
327 dirmove = {}
334 dirmove = {}
328
335
329 # examine each file copy for a potential directory move, which is
336 # examine each file copy for a potential directory move, which is
330 # when all the files in a directory are moved to a new directory
337 # when all the files in a directory are moved to a new directory
331 for dst, src in fullcopy.iteritems():
338 for dst, src in fullcopy.iteritems():
332 dsrc, ddst = _dirname(src), _dirname(dst)
339 dsrc, ddst = _dirname(src), _dirname(dst)
333 if dsrc in invalid:
340 if dsrc in invalid:
334 # already seen to be uninteresting
341 # already seen to be uninteresting
335 continue
342 continue
336 elif dsrc in d1 and ddst in d1:
343 elif dsrc in d1 and ddst in d1:
337 # directory wasn't entirely moved locally
344 # directory wasn't entirely moved locally
338 invalid.add(dsrc)
345 invalid.add(dsrc)
339 elif dsrc in d2 and ddst in d2:
346 elif dsrc in d2 and ddst in d2:
340 # directory wasn't entirely moved remotely
347 # directory wasn't entirely moved remotely
341 invalid.add(dsrc)
348 invalid.add(dsrc)
342 elif dsrc in dirmove and dirmove[dsrc] != ddst:
349 elif dsrc in dirmove and dirmove[dsrc] != ddst:
343 # files from the same directory moved to two different places
350 # files from the same directory moved to two different places
344 invalid.add(dsrc)
351 invalid.add(dsrc)
345 else:
352 else:
346 # looks good so far
353 # looks good so far
347 dirmove[dsrc + "/"] = ddst + "/"
354 dirmove[dsrc + "/"] = ddst + "/"
348
355
349 for i in invalid:
356 for i in invalid:
350 if i in dirmove:
357 if i in dirmove:
351 del dirmove[i]
358 del dirmove[i]
352 del d1, d2, invalid
359 del d1, d2, invalid
353
360
354 if not dirmove:
361 if not dirmove:
355 return copy, diverge, renamedelete
362 return copy, movewithdir, diverge, renamedelete
356
363
357 for d in dirmove:
364 for d in dirmove:
358 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
365 repo.ui.debug(" dir %s -> %s\n" % (d, dirmove[d]))
359
366
360 # check unaccounted nonoverlapping files against directory moves
367 # check unaccounted nonoverlapping files against directory moves
361 for f in u1 + u2:
368 for f in u1 + u2:
362 if f not in fullcopy:
369 if f not in fullcopy:
363 for d in dirmove:
370 for d in dirmove:
364 if f.startswith(d):
371 if f.startswith(d):
365 # new file added in a directory that was moved, move it
372 # new file added in a directory that was moved, move it
366 df = dirmove[d] + f[len(d):]
373 df = dirmove[d] + f[len(d):]
367 if df not in copy:
374 if df not in copy:
368 copy[f] = df
375 movewithdir[f] = df
369 repo.ui.debug(" file %s -> %s\n" % (f, copy[f]))
376 repo.ui.debug(" file %s -> %s\n" % (f, df))
370 break
377 break
371
378
372 return copy, diverge, renamedelete
379 return copy, movewithdir, diverge, renamedelete
@@ -1,653 +1,656
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 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 node import nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import error, scmutil, util, filemerge, copies, subrepo
10 import error, scmutil, util, filemerge, copies, subrepo
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._dirty = False
17 self._dirty = False
18 self._read()
18 self._read()
19 def reset(self, node=None):
19 def reset(self, node=None):
20 self._state = {}
20 self._state = {}
21 if node:
21 if node:
22 self._local = node
22 self._local = node
23 shutil.rmtree(self._repo.join("merge"), True)
23 shutil.rmtree(self._repo.join("merge"), True)
24 self._dirty = False
24 self._dirty = False
25 def _read(self):
25 def _read(self):
26 self._state = {}
26 self._state = {}
27 try:
27 try:
28 f = self._repo.opener("merge/state")
28 f = self._repo.opener("merge/state")
29 for i, l in enumerate(f):
29 for i, l in enumerate(f):
30 if i == 0:
30 if i == 0:
31 self._local = bin(l[:-1])
31 self._local = bin(l[:-1])
32 else:
32 else:
33 bits = l[:-1].split("\0")
33 bits = l[:-1].split("\0")
34 self._state[bits[0]] = bits[1:]
34 self._state[bits[0]] = bits[1:]
35 f.close()
35 f.close()
36 except IOError, err:
36 except IOError, err:
37 if err.errno != errno.ENOENT:
37 if err.errno != errno.ENOENT:
38 raise
38 raise
39 self._dirty = False
39 self._dirty = False
40 def commit(self):
40 def commit(self):
41 if self._dirty:
41 if self._dirty:
42 f = self._repo.opener("merge/state", "w")
42 f = self._repo.opener("merge/state", "w")
43 f.write(hex(self._local) + "\n")
43 f.write(hex(self._local) + "\n")
44 for d, v in self._state.iteritems():
44 for d, v in self._state.iteritems():
45 f.write("\0".join([d] + v) + "\n")
45 f.write("\0".join([d] + v) + "\n")
46 f.close()
46 f.close()
47 self._dirty = False
47 self._dirty = False
48 def add(self, fcl, fco, fca, fd, flags):
48 def add(self, fcl, fco, fca, fd, flags):
49 hash = util.sha1(fcl.path()).hexdigest()
49 hash = util.sha1(fcl.path()).hexdigest()
50 self._repo.opener.write("merge/" + hash, fcl.data())
50 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 hex(fca.filenode()), fco.path(), flags]
52 hex(fca.filenode()), fco.path(), flags]
53 self._dirty = True
53 self._dirty = True
54 def __contains__(self, dfile):
54 def __contains__(self, dfile):
55 return dfile in self._state
55 return dfile in self._state
56 def __getitem__(self, dfile):
56 def __getitem__(self, dfile):
57 return self._state[dfile][0]
57 return self._state[dfile][0]
58 def __iter__(self):
58 def __iter__(self):
59 l = self._state.keys()
59 l = self._state.keys()
60 l.sort()
60 l.sort()
61 for f in l:
61 for f in l:
62 yield f
62 yield f
63 def mark(self, dfile, state):
63 def mark(self, dfile, state):
64 self._state[dfile][0] = state
64 self._state[dfile][0] = state
65 self._dirty = True
65 self._dirty = True
66 def resolve(self, dfile, wctx, octx):
66 def resolve(self, dfile, wctx, octx):
67 if self[dfile] == 'r':
67 if self[dfile] == 'r':
68 return 0
68 return 0
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 f = self._repo.opener("merge/" + hash)
70 f = self._repo.opener("merge/" + hash)
71 self._repo.wwrite(dfile, f.read(), flags)
71 self._repo.wwrite(dfile, f.read(), flags)
72 f.close()
72 f.close()
73 fcd = wctx[dfile]
73 fcd = wctx[dfile]
74 fco = octx[ofile]
74 fco = octx[ofile]
75 fca = self._repo.filectx(afile, fileid=anode)
75 fca = self._repo.filectx(afile, fileid=anode)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 if r is None:
77 if r is None:
78 # no real conflict
78 # no real conflict
79 del self._state[dfile]
79 del self._state[dfile]
80 elif not r:
80 elif not r:
81 self.mark(dfile, 'r')
81 self.mark(dfile, 'r')
82 return r
82 return r
83
83
84 def _checkunknownfile(repo, wctx, mctx, f):
84 def _checkunknownfile(repo, wctx, mctx, f):
85 return (not repo.dirstate._ignore(f)
85 return (not repo.dirstate._ignore(f)
86 and os.path.isfile(repo.wjoin(f))
86 and os.path.isfile(repo.wjoin(f))
87 and repo.dirstate.normalize(f) not in repo.dirstate
87 and repo.dirstate.normalize(f) not in repo.dirstate
88 and mctx[f].cmp(wctx[f]))
88 and mctx[f].cmp(wctx[f]))
89
89
90 def _checkunknown(repo, wctx, mctx):
90 def _checkunknown(repo, wctx, mctx):
91 "check for collisions between unknown files and files in mctx"
91 "check for collisions between unknown files and files in mctx"
92
92
93 error = False
93 error = False
94 for f in mctx:
94 for f in mctx:
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
96 error = True
96 error = True
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
98 if error:
98 if error:
99 raise util.Abort(_("untracked files in working directory differ "
99 raise util.Abort(_("untracked files in working directory differ "
100 "from files in requested revision"))
100 "from files in requested revision"))
101
101
102 def _remains(f, m, ma, workingctx=False):
102 def _remains(f, m, ma, workingctx=False):
103 """check whether specified file remains after merge.
103 """check whether specified file remains after merge.
104
104
105 It is assumed that specified file is not contained in the manifest
105 It is assumed that specified file is not contained in the manifest
106 of the other context.
106 of the other context.
107 """
107 """
108 if f in ma:
108 if f in ma:
109 n = m[f]
109 n = m[f]
110 if n != ma[f]:
110 if n != ma[f]:
111 return True # because it is changed locally
111 return True # because it is changed locally
112 # even though it doesn't remain, if "remote deleted" is
112 # even though it doesn't remain, if "remote deleted" is
113 # chosen in manifestmerge()
113 # chosen in manifestmerge()
114 elif workingctx and n[20:] == "a":
114 elif workingctx and n[20:] == "a":
115 return True # because it is added locally (linear merge specific)
115 return True # because it is added locally (linear merge specific)
116 else:
116 else:
117 return False # because it is removed remotely
117 return False # because it is removed remotely
118 else:
118 else:
119 return True # because it is added locally
119 return True # because it is added locally
120
120
121 def _checkcollision(mctx, extractxs):
121 def _checkcollision(mctx, extractxs):
122 "check for case folding collisions in the destination context"
122 "check for case folding collisions in the destination context"
123 folded = {}
123 folded = {}
124 for fn in mctx:
124 for fn in mctx:
125 fold = util.normcase(fn)
125 fold = util.normcase(fn)
126 if fold in folded:
126 if fold in folded:
127 raise util.Abort(_("case-folding collision between %s and %s")
127 raise util.Abort(_("case-folding collision between %s and %s")
128 % (fn, folded[fold]))
128 % (fn, folded[fold]))
129 folded[fold] = fn
129 folded[fold] = fn
130
130
131 if extractxs:
131 if extractxs:
132 wctx, actx = extractxs
132 wctx, actx = extractxs
133 # class to delay looking up copy mapping
133 # class to delay looking up copy mapping
134 class pathcopies(object):
134 class pathcopies(object):
135 @util.propertycache
135 @util.propertycache
136 def map(self):
136 def map(self):
137 # {dst@mctx: src@wctx} copy mapping
137 # {dst@mctx: src@wctx} copy mapping
138 return copies.pathcopies(wctx, mctx)
138 return copies.pathcopies(wctx, mctx)
139 pc = pathcopies()
139 pc = pathcopies()
140
140
141 for fn in wctx:
141 for fn in wctx:
142 fold = util.normcase(fn)
142 fold = util.normcase(fn)
143 mfn = folded.get(fold, None)
143 mfn = folded.get(fold, None)
144 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
144 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
145 _remains(fn, wctx.manifest(), actx.manifest(), True) and
145 _remains(fn, wctx.manifest(), actx.manifest(), True) and
146 _remains(mfn, mctx.manifest(), actx.manifest())):
146 _remains(mfn, mctx.manifest(), actx.manifest())):
147 raise util.Abort(_("case-folding collision between %s and %s")
147 raise util.Abort(_("case-folding collision between %s and %s")
148 % (mfn, fn))
148 % (mfn, fn))
149
149
150 def _forgetremoved(wctx, mctx, branchmerge):
150 def _forgetremoved(wctx, mctx, branchmerge):
151 """
151 """
152 Forget removed files
152 Forget removed files
153
153
154 If we're jumping between revisions (as opposed to merging), and if
154 If we're jumping between revisions (as opposed to merging), and if
155 neither the working directory nor the target rev has the file,
155 neither the working directory nor the target rev has the file,
156 then we need to remove it from the dirstate, to prevent the
156 then we need to remove it from the dirstate, to prevent the
157 dirstate from listing the file when it is no longer in the
157 dirstate from listing the file when it is no longer in the
158 manifest.
158 manifest.
159
159
160 If we're merging, and the other revision has removed a file
160 If we're merging, and the other revision has removed a file
161 that is not present in the working directory, we need to mark it
161 that is not present in the working directory, we need to mark it
162 as removed.
162 as removed.
163 """
163 """
164
164
165 action = []
165 action = []
166 state = branchmerge and 'r' or 'f'
166 state = branchmerge and 'r' or 'f'
167 for f in wctx.deleted():
167 for f in wctx.deleted():
168 if f not in mctx:
168 if f not in mctx:
169 action.append((f, state))
169 action.append((f, state))
170
170
171 if not branchmerge:
171 if not branchmerge:
172 for f in wctx.removed():
172 for f in wctx.removed():
173 if f not in mctx:
173 if f not in mctx:
174 action.append((f, "f"))
174 action.append((f, "f"))
175
175
176 return action
176 return action
177
177
178 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
178 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
179 """
179 """
180 Merge p1 and p2 with ancestor pa and generate merge action list
180 Merge p1 and p2 with ancestor pa and generate merge action list
181
181
182 overwrite = whether we clobber working files
182 overwrite = whether we clobber working files
183 partial = function to filter file lists
183 partial = function to filter file lists
184 """
184 """
185
185
186 def fmerge(f, f2, fa):
186 def fmerge(f, f2, fa):
187 """merge flags"""
187 """merge flags"""
188 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
188 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
189 if m == n: # flags agree
189 if m == n: # flags agree
190 return m # unchanged
190 return m # unchanged
191 if m and n and not a: # flags set, don't agree, differ from parent
191 if m and n and not a: # flags set, don't agree, differ from parent
192 r = repo.ui.promptchoice(
192 r = repo.ui.promptchoice(
193 _(" conflicting flags for %s\n"
193 _(" conflicting flags for %s\n"
194 "(n)one, e(x)ec or sym(l)ink?") % f,
194 "(n)one, e(x)ec or sym(l)ink?") % f,
195 (_("&None"), _("E&xec"), _("Sym&link")), 0)
195 (_("&None"), _("E&xec"), _("Sym&link")), 0)
196 if r == 1:
196 if r == 1:
197 return "x" # Exec
197 return "x" # Exec
198 if r == 2:
198 if r == 2:
199 return "l" # Symlink
199 return "l" # Symlink
200 return ""
200 return ""
201 if m and m != a: # changed from a to m
201 if m and m != a: # changed from a to m
202 return m
202 return m
203 if n and n != a: # changed from a to n
203 if n and n != a: # changed from a to n
204 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
204 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
205 # can't automatically merge symlink flag when there
205 # can't automatically merge symlink flag when there
206 # are file-level conflicts here, let filemerge take
206 # are file-level conflicts here, let filemerge take
207 # care of it
207 # care of it
208 return m
208 return m
209 return n
209 return n
210 return '' # flag was cleared
210 return '' # flag was cleared
211
211
212 def act(msg, m, f, *args):
212 def act(msg, m, f, *args):
213 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
213 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
214 action.append((f, m) + args)
214 action.append((f, m) + args)
215
215
216 action, copy = [], {}
216 action, copy, movewithdir = [], {}, {}
217
217
218 if overwrite:
218 if overwrite:
219 pa = p1
219 pa = p1
220 elif pa == p2: # backwards
220 elif pa == p2: # backwards
221 pa = p1.p1()
221 pa = p1.p1()
222 elif pa and repo.ui.configbool("merge", "followcopies", True):
222 elif pa and repo.ui.configbool("merge", "followcopies", True):
223 copy, diverge, renamedelete = copies.mergecopies(repo, p1, p2, pa)
223 ret = copies.mergecopies(repo, p1, p2, pa)
224 copy, movewithdir, diverge, renamedelete = ret
224 for of, fl in diverge.iteritems():
225 for of, fl in diverge.iteritems():
225 act("divergent renames", "dr", of, fl)
226 act("divergent renames", "dr", of, fl)
226 for of, fl in renamedelete.iteritems():
227 for of, fl in renamedelete.iteritems():
227 act("rename and delete", "rd", of, fl)
228 act("rename and delete", "rd", of, fl)
228
229
229 repo.ui.note(_("resolving manifests\n"))
230 repo.ui.note(_("resolving manifests\n"))
230 repo.ui.debug(" overwrite: %s, partial: %s\n"
231 repo.ui.debug(" overwrite: %s, partial: %s\n"
231 % (bool(overwrite), bool(partial)))
232 % (bool(overwrite), bool(partial)))
232 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
233 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
233
234
234 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
235 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
235 copied = set(copy.values())
236 copied = set(copy.values())
237 copied.update(movewithdir.values())
236
238
237 if '.hgsubstate' in m1:
239 if '.hgsubstate' in m1:
238 # check whether sub state is modified
240 # check whether sub state is modified
239 for s in p1.substate:
241 for s in p1.substate:
240 if p1.sub(s).dirty():
242 if p1.sub(s).dirty():
241 m1['.hgsubstate'] += "+"
243 m1['.hgsubstate'] += "+"
242 break
244 break
243
245
244 # Compare manifests
246 # Compare manifests
245 for f, n in m1.iteritems():
247 for f, n in m1.iteritems():
246 if partial and not partial(f):
248 if partial and not partial(f):
247 continue
249 continue
248 if f in m2:
250 if f in m2:
249 rflags = fmerge(f, f, f)
251 rflags = fmerge(f, f, f)
250 a = ma.get(f, nullid)
252 a = ma.get(f, nullid)
251 if n == m2[f] or m2[f] == a: # same or local newer
253 if n == m2[f] or m2[f] == a: # same or local newer
252 # is file locally modified or flags need changing?
254 # is file locally modified or flags need changing?
253 # dirstate flags may need to be made current
255 # dirstate flags may need to be made current
254 if m1.flags(f) != rflags or n[20:]:
256 if m1.flags(f) != rflags or n[20:]:
255 act("update permissions", "e", f, rflags)
257 act("update permissions", "e", f, rflags)
256 elif n == a: # remote newer
258 elif n == a: # remote newer
257 act("remote is newer", "g", f, rflags)
259 act("remote is newer", "g", f, rflags)
258 else: # both changed
260 else: # both changed
259 act("versions differ", "m", f, f, f, rflags, False)
261 act("versions differ", "m", f, f, f, rflags, False)
260 elif f in copied: # files we'll deal with on m2 side
262 elif f in copied: # files we'll deal with on m2 side
261 pass
263 pass
262 elif f in copy:
264 elif f in movewithdir: # directory rename
265 f2 = movewithdir[f]
266 act("remote renamed directory to " + f2, "d", f, None, f2,
267 m1.flags(f))
268 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
263 f2 = copy[f]
269 f2 = copy[f]
264 if f2 not in m2: # directory rename
270 act("local copied/moved to " + f2, "m", f, f2, f,
265 act("remote renamed directory to " + f2, "d",
271 fmerge(f, f2, f2), False)
266 f, None, f2, m1.flags(f))
267 else: # case 2 A,B/B/B or case 4,21 A/B/B
268 act("local copied/moved to " + f2, "m",
269 f, f2, f, fmerge(f, f2, f2), False)
270 elif f in ma: # clean, a different, no remote
272 elif f in ma: # clean, a different, no remote
271 if n != ma[f]:
273 if n != ma[f]:
272 if repo.ui.promptchoice(
274 if repo.ui.promptchoice(
273 _(" local changed %s which remote deleted\n"
275 _(" local changed %s which remote deleted\n"
274 "use (c)hanged version or (d)elete?") % f,
276 "use (c)hanged version or (d)elete?") % f,
275 (_("&Changed"), _("&Delete")), 0):
277 (_("&Changed"), _("&Delete")), 0):
276 act("prompt delete", "r", f)
278 act("prompt delete", "r", f)
277 else:
279 else:
278 act("prompt keep", "a", f)
280 act("prompt keep", "a", f)
279 elif n[20:] == "a": # added, no remote
281 elif n[20:] == "a": # added, no remote
280 act("remote deleted", "f", f)
282 act("remote deleted", "f", f)
281 else:
283 else:
282 act("other deleted", "r", f)
284 act("other deleted", "r", f)
283
285
284 for f, n in m2.iteritems():
286 for f, n in m2.iteritems():
285 if partial and not partial(f):
287 if partial and not partial(f):
286 continue
288 continue
287 if f in m1 or f in copied: # files already visited
289 if f in m1 or f in copied: # files already visited
288 continue
290 continue
289 if f in copy:
291 if f in movewithdir:
292 f2 = movewithdir[f]
293 act("local renamed directory to " + f2, "d", None, f, f2,
294 m2.flags(f))
295 elif f in copy:
290 f2 = copy[f]
296 f2 = copy[f]
291 if f2 not in m1: # directory rename
297 if f2 in m2: # rename case 1, A/A,B/A
292 act("local renamed directory to " + f2, "d",
293 None, f, f2, m2.flags(f))
294 elif f2 in m2: # rename case 1, A/A,B/A
295 act("remote copied to " + f, "m",
298 act("remote copied to " + f, "m",
296 f2, f, f, fmerge(f2, f, f2), False)
299 f2, f, f, fmerge(f2, f, f2), False)
297 else: # case 3,20 A/B/A
300 else: # case 3,20 A/B/A
298 act("remote moved to " + f, "m",
301 act("remote moved to " + f, "m",
299 f2, f, f, fmerge(f2, f, f2), True)
302 f2, f, f, fmerge(f2, f, f2), True)
300 elif f not in ma:
303 elif f not in ma:
301 if (not overwrite
304 if (not overwrite
302 and _checkunknownfile(repo, p1, p2, f)):
305 and _checkunknownfile(repo, p1, p2, f)):
303 rflags = fmerge(f, f, f)
306 rflags = fmerge(f, f, f)
304 act("remote differs from untracked local",
307 act("remote differs from untracked local",
305 "m", f, f, f, rflags, False)
308 "m", f, f, f, rflags, False)
306 else:
309 else:
307 act("remote created", "g", f, m2.flags(f))
310 act("remote created", "g", f, m2.flags(f))
308 elif n != ma[f]:
311 elif n != ma[f]:
309 if repo.ui.promptchoice(
312 if repo.ui.promptchoice(
310 _("remote changed %s which local deleted\n"
313 _("remote changed %s which local deleted\n"
311 "use (c)hanged version or leave (d)eleted?") % f,
314 "use (c)hanged version or leave (d)eleted?") % f,
312 (_("&Changed"), _("&Deleted")), 0) == 0:
315 (_("&Changed"), _("&Deleted")), 0) == 0:
313 act("prompt recreating", "g", f, m2.flags(f))
316 act("prompt recreating", "g", f, m2.flags(f))
314
317
315 return action
318 return action
316
319
317 def actionkey(a):
320 def actionkey(a):
318 return a[1] == 'r' and -1 or 0, a
321 return a[1] == 'r' and -1 or 0, a
319
322
320 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
323 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
321 """apply the merge action list to the working directory
324 """apply the merge action list to the working directory
322
325
323 wctx is the working copy context
326 wctx is the working copy context
324 mctx is the context to be merged into the working copy
327 mctx is the context to be merged into the working copy
325 actx is the context of the common ancestor
328 actx is the context of the common ancestor
326
329
327 Return a tuple of counts (updated, merged, removed, unresolved) that
330 Return a tuple of counts (updated, merged, removed, unresolved) that
328 describes how many files were affected by the update.
331 describes how many files were affected by the update.
329 """
332 """
330
333
331 updated, merged, removed, unresolved = 0, 0, 0, 0
334 updated, merged, removed, unresolved = 0, 0, 0, 0
332 ms = mergestate(repo)
335 ms = mergestate(repo)
333 ms.reset(wctx.p1().node())
336 ms.reset(wctx.p1().node())
334 moves = []
337 moves = []
335 action.sort(key=actionkey)
338 action.sort(key=actionkey)
336
339
337 # prescan for merges
340 # prescan for merges
338 for a in action:
341 for a in action:
339 f, m = a[:2]
342 f, m = a[:2]
340 if m == 'm': # merge
343 if m == 'm': # merge
341 f2, fd, flags, move = a[2:]
344 f2, fd, flags, move = a[2:]
342 if f == '.hgsubstate': # merged internally
345 if f == '.hgsubstate': # merged internally
343 continue
346 continue
344 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
347 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
345 fcl = wctx[f]
348 fcl = wctx[f]
346 fco = mctx[f2]
349 fco = mctx[f2]
347 if mctx == actx: # backwards, use working dir parent as ancestor
350 if mctx == actx: # backwards, use working dir parent as ancestor
348 if fcl.parents():
351 if fcl.parents():
349 fca = fcl.p1()
352 fca = fcl.p1()
350 else:
353 else:
351 fca = repo.filectx(f, fileid=nullrev)
354 fca = repo.filectx(f, fileid=nullrev)
352 else:
355 else:
353 fca = fcl.ancestor(fco, actx)
356 fca = fcl.ancestor(fco, actx)
354 if not fca:
357 if not fca:
355 fca = repo.filectx(f, fileid=nullrev)
358 fca = repo.filectx(f, fileid=nullrev)
356 ms.add(fcl, fco, fca, fd, flags)
359 ms.add(fcl, fco, fca, fd, flags)
357 if f != fd and move:
360 if f != fd and move:
358 moves.append(f)
361 moves.append(f)
359
362
360 audit = scmutil.pathauditor(repo.root)
363 audit = scmutil.pathauditor(repo.root)
361
364
362 # remove renamed files after safely stored
365 # remove renamed files after safely stored
363 for f in moves:
366 for f in moves:
364 if os.path.lexists(repo.wjoin(f)):
367 if os.path.lexists(repo.wjoin(f)):
365 repo.ui.debug("removing %s\n" % f)
368 repo.ui.debug("removing %s\n" % f)
366 audit(f)
369 audit(f)
367 os.unlink(repo.wjoin(f))
370 os.unlink(repo.wjoin(f))
368
371
369 numupdates = len(action)
372 numupdates = len(action)
370 for i, a in enumerate(action):
373 for i, a in enumerate(action):
371 f, m = a[:2]
374 f, m = a[:2]
372 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
375 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
373 unit=_('files'))
376 unit=_('files'))
374 if f and f[0] == "/":
377 if f and f[0] == "/":
375 continue
378 continue
376 if m == "r": # remove
379 if m == "r": # remove
377 repo.ui.note(_("removing %s\n") % f)
380 repo.ui.note(_("removing %s\n") % f)
378 audit(f)
381 audit(f)
379 if f == '.hgsubstate': # subrepo states need updating
382 if f == '.hgsubstate': # subrepo states need updating
380 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
383 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
381 try:
384 try:
382 util.unlinkpath(repo.wjoin(f))
385 util.unlinkpath(repo.wjoin(f))
383 except OSError, inst:
386 except OSError, inst:
384 if inst.errno != errno.ENOENT:
387 if inst.errno != errno.ENOENT:
385 repo.ui.warn(_("update failed to remove %s: %s!\n") %
388 repo.ui.warn(_("update failed to remove %s: %s!\n") %
386 (f, inst.strerror))
389 (f, inst.strerror))
387 removed += 1
390 removed += 1
388 elif m == "m": # merge
391 elif m == "m": # merge
389 if f == '.hgsubstate': # subrepo states need updating
392 if f == '.hgsubstate': # subrepo states need updating
390 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
393 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
391 overwrite)
394 overwrite)
392 continue
395 continue
393 f2, fd, flags, move = a[2:]
396 f2, fd, flags, move = a[2:]
394 repo.wopener.audit(fd)
397 repo.wopener.audit(fd)
395 r = ms.resolve(fd, wctx, mctx)
398 r = ms.resolve(fd, wctx, mctx)
396 if r is not None and r > 0:
399 if r is not None and r > 0:
397 unresolved += 1
400 unresolved += 1
398 else:
401 else:
399 if r is None:
402 if r is None:
400 updated += 1
403 updated += 1
401 else:
404 else:
402 merged += 1
405 merged += 1
403 if (move and repo.dirstate.normalize(fd) != f
406 if (move and repo.dirstate.normalize(fd) != f
404 and os.path.lexists(repo.wjoin(f))):
407 and os.path.lexists(repo.wjoin(f))):
405 repo.ui.debug("removing %s\n" % f)
408 repo.ui.debug("removing %s\n" % f)
406 audit(f)
409 audit(f)
407 os.unlink(repo.wjoin(f))
410 os.unlink(repo.wjoin(f))
408 elif m == "g": # get
411 elif m == "g": # get
409 flags = a[2]
412 flags = a[2]
410 repo.ui.note(_("getting %s\n") % f)
413 repo.ui.note(_("getting %s\n") % f)
411 t = mctx.filectx(f).data()
414 t = mctx.filectx(f).data()
412 repo.wwrite(f, t, flags)
415 repo.wwrite(f, t, flags)
413 t = None
416 t = None
414 updated += 1
417 updated += 1
415 if f == '.hgsubstate': # subrepo states need updating
418 if f == '.hgsubstate': # subrepo states need updating
416 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
419 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
417 elif m == "d": # directory rename
420 elif m == "d": # directory rename
418 f2, fd, flags = a[2:]
421 f2, fd, flags = a[2:]
419 if f:
422 if f:
420 repo.ui.note(_("moving %s to %s\n") % (f, fd))
423 repo.ui.note(_("moving %s to %s\n") % (f, fd))
421 audit(f)
424 audit(f)
422 t = wctx.filectx(f).data()
425 t = wctx.filectx(f).data()
423 repo.wwrite(fd, t, flags)
426 repo.wwrite(fd, t, flags)
424 util.unlinkpath(repo.wjoin(f))
427 util.unlinkpath(repo.wjoin(f))
425 if f2:
428 if f2:
426 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
429 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
427 t = mctx.filectx(f2).data()
430 t = mctx.filectx(f2).data()
428 repo.wwrite(fd, t, flags)
431 repo.wwrite(fd, t, flags)
429 updated += 1
432 updated += 1
430 elif m == "dr": # divergent renames
433 elif m == "dr": # divergent renames
431 fl = a[2]
434 fl = a[2]
432 repo.ui.warn(_("note: possible conflict - %s was renamed "
435 repo.ui.warn(_("note: possible conflict - %s was renamed "
433 "multiple times to:\n") % f)
436 "multiple times to:\n") % f)
434 for nf in fl:
437 for nf in fl:
435 repo.ui.warn(" %s\n" % nf)
438 repo.ui.warn(" %s\n" % nf)
436 elif m == "rd": # rename and delete
439 elif m == "rd": # rename and delete
437 fl = a[2]
440 fl = a[2]
438 repo.ui.warn(_("note: possible conflict - %s was deleted "
441 repo.ui.warn(_("note: possible conflict - %s was deleted "
439 "and renamed to:\n") % f)
442 "and renamed to:\n") % f)
440 for nf in fl:
443 for nf in fl:
441 repo.ui.warn(" %s\n" % nf)
444 repo.ui.warn(" %s\n" % nf)
442 elif m == "e": # exec
445 elif m == "e": # exec
443 flags = a[2]
446 flags = a[2]
444 repo.wopener.audit(f)
447 repo.wopener.audit(f)
445 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
448 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
446 ms.commit()
449 ms.commit()
447 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
450 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
448
451
449 return updated, merged, removed, unresolved
452 return updated, merged, removed, unresolved
450
453
451 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
454 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
452 "Calculate the actions needed to merge mctx into tctx"
455 "Calculate the actions needed to merge mctx into tctx"
453 action = []
456 action = []
454 folding = not util.checkcase(repo.path)
457 folding = not util.checkcase(repo.path)
455 if folding:
458 if folding:
456 # collision check is not needed for clean update
459 # collision check is not needed for clean update
457 if (not branchmerge and
460 if (not branchmerge and
458 (force or not tctx.dirty(missing=True, branch=False))):
461 (force or not tctx.dirty(missing=True, branch=False))):
459 _checkcollision(mctx, None)
462 _checkcollision(mctx, None)
460 else:
463 else:
461 _checkcollision(mctx, (tctx, ancestor))
464 _checkcollision(mctx, (tctx, ancestor))
462 if not force:
465 if not force:
463 _checkunknown(repo, tctx, mctx)
466 _checkunknown(repo, tctx, mctx)
464 if tctx.rev() is None:
467 if tctx.rev() is None:
465 action += _forgetremoved(tctx, mctx, branchmerge)
468 action += _forgetremoved(tctx, mctx, branchmerge)
466 action += manifestmerge(repo, tctx, mctx,
469 action += manifestmerge(repo, tctx, mctx,
467 ancestor,
470 ancestor,
468 force and not branchmerge,
471 force and not branchmerge,
469 partial)
472 partial)
470 return action
473 return action
471
474
472 def recordupdates(repo, action, branchmerge):
475 def recordupdates(repo, action, branchmerge):
473 "record merge actions to the dirstate"
476 "record merge actions to the dirstate"
474
477
475 for a in action:
478 for a in action:
476 f, m = a[:2]
479 f, m = a[:2]
477 if m == "r": # remove
480 if m == "r": # remove
478 if branchmerge:
481 if branchmerge:
479 repo.dirstate.remove(f)
482 repo.dirstate.remove(f)
480 else:
483 else:
481 repo.dirstate.drop(f)
484 repo.dirstate.drop(f)
482 elif m == "a": # re-add
485 elif m == "a": # re-add
483 if not branchmerge:
486 if not branchmerge:
484 repo.dirstate.add(f)
487 repo.dirstate.add(f)
485 elif m == "f": # forget
488 elif m == "f": # forget
486 repo.dirstate.drop(f)
489 repo.dirstate.drop(f)
487 elif m == "e": # exec change
490 elif m == "e": # exec change
488 repo.dirstate.normallookup(f)
491 repo.dirstate.normallookup(f)
489 elif m == "g": # get
492 elif m == "g": # get
490 if branchmerge:
493 if branchmerge:
491 repo.dirstate.otherparent(f)
494 repo.dirstate.otherparent(f)
492 else:
495 else:
493 repo.dirstate.normal(f)
496 repo.dirstate.normal(f)
494 elif m == "m": # merge
497 elif m == "m": # merge
495 f2, fd, flag, move = a[2:]
498 f2, fd, flag, move = a[2:]
496 if branchmerge:
499 if branchmerge:
497 # We've done a branch merge, mark this file as merged
500 # We've done a branch merge, mark this file as merged
498 # so that we properly record the merger later
501 # so that we properly record the merger later
499 repo.dirstate.merge(fd)
502 repo.dirstate.merge(fd)
500 if f != f2: # copy/rename
503 if f != f2: # copy/rename
501 if move:
504 if move:
502 repo.dirstate.remove(f)
505 repo.dirstate.remove(f)
503 if f != fd:
506 if f != fd:
504 repo.dirstate.copy(f, fd)
507 repo.dirstate.copy(f, fd)
505 else:
508 else:
506 repo.dirstate.copy(f2, fd)
509 repo.dirstate.copy(f2, fd)
507 else:
510 else:
508 # We've update-merged a locally modified file, so
511 # We've update-merged a locally modified file, so
509 # we set the dirstate to emulate a normal checkout
512 # we set the dirstate to emulate a normal checkout
510 # of that file some time in the past. Thus our
513 # of that file some time in the past. Thus our
511 # merge will appear as a normal local file
514 # merge will appear as a normal local file
512 # modification.
515 # modification.
513 if f2 == fd: # file not locally copied/moved
516 if f2 == fd: # file not locally copied/moved
514 repo.dirstate.normallookup(fd)
517 repo.dirstate.normallookup(fd)
515 if move:
518 if move:
516 repo.dirstate.drop(f)
519 repo.dirstate.drop(f)
517 elif m == "d": # directory rename
520 elif m == "d": # directory rename
518 f2, fd, flag = a[2:]
521 f2, fd, flag = a[2:]
519 if not f2 and f not in repo.dirstate:
522 if not f2 and f not in repo.dirstate:
520 # untracked file moved
523 # untracked file moved
521 continue
524 continue
522 if branchmerge:
525 if branchmerge:
523 repo.dirstate.add(fd)
526 repo.dirstate.add(fd)
524 if f:
527 if f:
525 repo.dirstate.remove(f)
528 repo.dirstate.remove(f)
526 repo.dirstate.copy(f, fd)
529 repo.dirstate.copy(f, fd)
527 if f2:
530 if f2:
528 repo.dirstate.copy(f2, fd)
531 repo.dirstate.copy(f2, fd)
529 else:
532 else:
530 repo.dirstate.normal(fd)
533 repo.dirstate.normal(fd)
531 if f:
534 if f:
532 repo.dirstate.drop(f)
535 repo.dirstate.drop(f)
533
536
534 def update(repo, node, branchmerge, force, partial, ancestor=None,
537 def update(repo, node, branchmerge, force, partial, ancestor=None,
535 mergeancestor=False):
538 mergeancestor=False):
536 """
539 """
537 Perform a merge between the working directory and the given node
540 Perform a merge between the working directory and the given node
538
541
539 node = the node to update to, or None if unspecified
542 node = the node to update to, or None if unspecified
540 branchmerge = whether to merge between branches
543 branchmerge = whether to merge between branches
541 force = whether to force branch merging or file overwriting
544 force = whether to force branch merging or file overwriting
542 partial = a function to filter file lists (dirstate not updated)
545 partial = a function to filter file lists (dirstate not updated)
543 mergeancestor = if false, merging with an ancestor (fast-forward)
546 mergeancestor = if false, merging with an ancestor (fast-forward)
544 is only allowed between different named branches. This flag
547 is only allowed between different named branches. This flag
545 is used by rebase extension as a temporary fix and should be
548 is used by rebase extension as a temporary fix and should be
546 avoided in general.
549 avoided in general.
547
550
548 The table below shows all the behaviors of the update command
551 The table below shows all the behaviors of the update command
549 given the -c and -C or no options, whether the working directory
552 given the -c and -C or no options, whether the working directory
550 is dirty, whether a revision is specified, and the relationship of
553 is dirty, whether a revision is specified, and the relationship of
551 the parent rev to the target rev (linear, on the same named
554 the parent rev to the target rev (linear, on the same named
552 branch, or on another named branch).
555 branch, or on another named branch).
553
556
554 This logic is tested by test-update-branches.t.
557 This logic is tested by test-update-branches.t.
555
558
556 -c -C dirty rev | linear same cross
559 -c -C dirty rev | linear same cross
557 n n n n | ok (1) x
560 n n n n | ok (1) x
558 n n n y | ok ok ok
561 n n n y | ok ok ok
559 n n y * | merge (2) (2)
562 n n y * | merge (2) (2)
560 n y * * | --- discard ---
563 n y * * | --- discard ---
561 y n y * | --- (3) ---
564 y n y * | --- (3) ---
562 y n n * | --- ok ---
565 y n n * | --- ok ---
563 y y * * | --- (4) ---
566 y y * * | --- (4) ---
564
567
565 x = can't happen
568 x = can't happen
566 * = don't-care
569 * = don't-care
567 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
570 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
568 2 = abort: crosses branches (use 'hg merge' to merge or
571 2 = abort: crosses branches (use 'hg merge' to merge or
569 use 'hg update -C' to discard changes)
572 use 'hg update -C' to discard changes)
570 3 = abort: uncommitted local changes
573 3 = abort: uncommitted local changes
571 4 = incompatible options (checked in commands.py)
574 4 = incompatible options (checked in commands.py)
572
575
573 Return the same tuple as applyupdates().
576 Return the same tuple as applyupdates().
574 """
577 """
575
578
576 onode = node
579 onode = node
577 wlock = repo.wlock()
580 wlock = repo.wlock()
578 try:
581 try:
579 wc = repo[None]
582 wc = repo[None]
580 if node is None:
583 if node is None:
581 # tip of current branch
584 # tip of current branch
582 try:
585 try:
583 node = repo.branchtip(wc.branch())
586 node = repo.branchtip(wc.branch())
584 except error.RepoLookupError:
587 except error.RepoLookupError:
585 if wc.branch() == "default": # no default branch!
588 if wc.branch() == "default": # no default branch!
586 node = repo.lookup("tip") # update to tip
589 node = repo.lookup("tip") # update to tip
587 else:
590 else:
588 raise util.Abort(_("branch %s not found") % wc.branch())
591 raise util.Abort(_("branch %s not found") % wc.branch())
589 overwrite = force and not branchmerge
592 overwrite = force and not branchmerge
590 pl = wc.parents()
593 pl = wc.parents()
591 p1, p2 = pl[0], repo[node]
594 p1, p2 = pl[0], repo[node]
592 if ancestor:
595 if ancestor:
593 pa = repo[ancestor]
596 pa = repo[ancestor]
594 else:
597 else:
595 pa = p1.ancestor(p2)
598 pa = p1.ancestor(p2)
596
599
597 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
600 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
598
601
599 ### check phase
602 ### check phase
600 if not overwrite and len(pl) > 1:
603 if not overwrite and len(pl) > 1:
601 raise util.Abort(_("outstanding uncommitted merges"))
604 raise util.Abort(_("outstanding uncommitted merges"))
602 if branchmerge:
605 if branchmerge:
603 if pa == p2:
606 if pa == p2:
604 raise util.Abort(_("merging with a working directory ancestor"
607 raise util.Abort(_("merging with a working directory ancestor"
605 " has no effect"))
608 " has no effect"))
606 elif pa == p1:
609 elif pa == p1:
607 if not mergeancestor and p1.branch() == p2.branch():
610 if not mergeancestor and p1.branch() == p2.branch():
608 raise util.Abort(_("nothing to merge"),
611 raise util.Abort(_("nothing to merge"),
609 hint=_("use 'hg update' "
612 hint=_("use 'hg update' "
610 "or check 'hg heads'"))
613 "or check 'hg heads'"))
611 if not force and (wc.files() or wc.deleted()):
614 if not force and (wc.files() or wc.deleted()):
612 raise util.Abort(_("outstanding uncommitted changes"),
615 raise util.Abort(_("outstanding uncommitted changes"),
613 hint=_("use 'hg status' to list changes"))
616 hint=_("use 'hg status' to list changes"))
614 for s in wc.substate:
617 for s in wc.substate:
615 if wc.sub(s).dirty():
618 if wc.sub(s).dirty():
616 raise util.Abort(_("outstanding uncommitted changes in "
619 raise util.Abort(_("outstanding uncommitted changes in "
617 "subrepository '%s'") % s)
620 "subrepository '%s'") % s)
618
621
619 elif not overwrite:
622 elif not overwrite:
620 if pa == p1 or pa == p2: # linear
623 if pa == p1 or pa == p2: # linear
621 pass # all good
624 pass # all good
622 elif wc.dirty(missing=True):
625 elif wc.dirty(missing=True):
623 raise util.Abort(_("crosses branches (merge branches or use"
626 raise util.Abort(_("crosses branches (merge branches or use"
624 " --clean to discard changes)"))
627 " --clean to discard changes)"))
625 elif onode is None:
628 elif onode is None:
626 raise util.Abort(_("crosses branches (merge branches or update"
629 raise util.Abort(_("crosses branches (merge branches or update"
627 " --check to force update)"))
630 " --check to force update)"))
628 else:
631 else:
629 # Allow jumping branches if clean and specific rev given
632 # Allow jumping branches if clean and specific rev given
630 pa = p1
633 pa = p1
631
634
632 ### calculate phase
635 ### calculate phase
633 action = calculateupdates(repo, wc, p2, pa, branchmerge, force, partial)
636 action = calculateupdates(repo, wc, p2, pa, branchmerge, force, partial)
634
637
635 ### apply phase
638 ### apply phase
636 if not branchmerge: # just jump to the new rev
639 if not branchmerge: # just jump to the new rev
637 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
640 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
638 if not partial:
641 if not partial:
639 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
642 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
640
643
641 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
644 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
642
645
643 if not partial:
646 if not partial:
644 repo.setparents(fp1, fp2)
647 repo.setparents(fp1, fp2)
645 recordupdates(repo, action, branchmerge)
648 recordupdates(repo, action, branchmerge)
646 if not branchmerge:
649 if not branchmerge:
647 repo.dirstate.setbranch(p2.branch())
650 repo.dirstate.setbranch(p2.branch())
648 finally:
651 finally:
649 wlock.release()
652 wlock.release()
650
653
651 if not partial:
654 if not partial:
652 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
655 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
653 return stats
656 return stats
General Comments 0
You need to be logged in to leave comments. Login now