##// END OF EJS Templates
merge: fix handling of deleted files
Alexis S. L. Carvalho -
r6242:a375ffc2 default
parent child Browse files
Show More
@@ -1,632 +1,641 b''
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import nullid, nullrev
8 from node import nullid, nullrev
9 from i18n import _
9 from i18n import _
10 import errno, util, os, heapq, filemerge
10 import errno, util, os, heapq, filemerge
11
11
12 def checkunknown(wctx, mctx):
12 def checkunknown(wctx, mctx):
13 "check for collisions between unknown files and files in mctx"
13 "check for collisions between unknown files and files in mctx"
14 man = mctx.manifest()
14 man = mctx.manifest()
15 for f in wctx.unknown():
15 for f in wctx.unknown():
16 if f in man:
16 if f in man:
17 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
17 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
18 raise util.Abort(_("untracked file in working directory differs"
18 raise util.Abort(_("untracked file in working directory differs"
19 " from file in requested revision: '%s'")
19 " from file in requested revision: '%s'")
20 % f)
20 % f)
21
21
22 def checkcollision(mctx):
22 def checkcollision(mctx):
23 "check for case folding collisions in the destination context"
23 "check for case folding collisions in the destination context"
24 folded = {}
24 folded = {}
25 for fn in mctx.manifest():
25 for fn in mctx.manifest():
26 fold = fn.lower()
26 fold = fn.lower()
27 if fold in folded:
27 if fold in folded:
28 raise util.Abort(_("case-folding collision between %s and %s")
28 raise util.Abort(_("case-folding collision between %s and %s")
29 % (fn, folded[fold]))
29 % (fn, folded[fold]))
30 folded[fold] = fn
30 folded[fold] = fn
31
31
32 def forgetremoved(wctx, mctx):
32 def forgetremoved(wctx, mctx, branchmerge):
33 """
33 """
34 Forget removed files
34 Forget removed files
35
35
36 If we're jumping between revisions (as opposed to merging), and if
36 If we're jumping between revisions (as opposed to merging), and if
37 neither the working directory nor the target rev has the file,
37 neither the working directory nor the target rev has the file,
38 then we need to remove it from the dirstate, to prevent the
38 then we need to remove it from the dirstate, to prevent the
39 dirstate from listing the file when it is no longer in the
39 dirstate from listing the file when it is no longer in the
40 manifest.
40 manifest.
41
42 If we're merging, and the other revision has removed a file
43 that is not present in the working directory, we need to mark it
44 as removed.
41 """
45 """
42
46
43 action = []
47 action = []
44 man = mctx.manifest()
48 man = mctx.manifest()
45 for f in wctx.deleted() + wctx.removed():
49 state = branchmerge and 'r' or 'f'
50 for f in wctx.deleted():
46 if f not in man:
51 if f not in man:
47 action.append((f, "f"))
52 action.append((f, state))
53
54 if not branchmerge:
55 for f in wctx.removed():
56 if f not in man:
57 action.append((f, "f"))
48
58
49 return action
59 return action
50
60
51 def findcopies(repo, m1, m2, ma, limit):
61 def findcopies(repo, m1, m2, ma, limit):
52 """
62 """
53 Find moves and copies between m1 and m2 back to limit linkrev
63 Find moves and copies between m1 and m2 back to limit linkrev
54 """
64 """
55
65
56 def nonoverlap(d1, d2, d3):
66 def nonoverlap(d1, d2, d3):
57 "Return list of elements in d1 not in d2 or d3"
67 "Return list of elements in d1 not in d2 or d3"
58 l = [d for d in d1 if d not in d3 and d not in d2]
68 l = [d for d in d1 if d not in d3 and d not in d2]
59 l.sort()
69 l.sort()
60 return l
70 return l
61
71
62 def dirname(f):
72 def dirname(f):
63 s = f.rfind("/")
73 s = f.rfind("/")
64 if s == -1:
74 if s == -1:
65 return ""
75 return ""
66 return f[:s]
76 return f[:s]
67
77
68 def dirs(files):
78 def dirs(files):
69 d = {}
79 d = {}
70 for f in files:
80 for f in files:
71 f = dirname(f)
81 f = dirname(f)
72 while f not in d:
82 while f not in d:
73 d[f] = True
83 d[f] = True
74 f = dirname(f)
84 f = dirname(f)
75 return d
85 return d
76
86
77 wctx = repo.workingctx()
87 wctx = repo.workingctx()
78
88
79 def makectx(f, n):
89 def makectx(f, n):
80 if len(n) == 20:
90 if len(n) == 20:
81 return repo.filectx(f, fileid=n)
91 return repo.filectx(f, fileid=n)
82 return wctx.filectx(f)
92 return wctx.filectx(f)
83 ctx = util.cachefunc(makectx)
93 ctx = util.cachefunc(makectx)
84
94
85 def findold(fctx):
95 def findold(fctx):
86 "find files that path was copied from, back to linkrev limit"
96 "find files that path was copied from, back to linkrev limit"
87 old = {}
97 old = {}
88 seen = {}
98 seen = {}
89 orig = fctx.path()
99 orig = fctx.path()
90 visit = [fctx]
100 visit = [fctx]
91 while visit:
101 while visit:
92 fc = visit.pop()
102 fc = visit.pop()
93 s = str(fc)
103 s = str(fc)
94 if s in seen:
104 if s in seen:
95 continue
105 continue
96 seen[s] = 1
106 seen[s] = 1
97 if fc.path() != orig and fc.path() not in old:
107 if fc.path() != orig and fc.path() not in old:
98 old[fc.path()] = 1
108 old[fc.path()] = 1
99 if fc.rev() < limit:
109 if fc.rev() < limit:
100 continue
110 continue
101 visit += fc.parents()
111 visit += fc.parents()
102
112
103 old = old.keys()
113 old = old.keys()
104 old.sort()
114 old.sort()
105 return old
115 return old
106
116
107 copy = {}
117 copy = {}
108 fullcopy = {}
118 fullcopy = {}
109 diverge = {}
119 diverge = {}
110
120
111 def checkcopies(c, man, aman):
121 def checkcopies(c, man, aman):
112 '''check possible copies for filectx c'''
122 '''check possible copies for filectx c'''
113 for of in findold(c):
123 for of in findold(c):
114 fullcopy[c.path()] = of # remember for dir rename detection
124 fullcopy[c.path()] = of # remember for dir rename detection
115 if of not in man: # original file not in other manifest?
125 if of not in man: # original file not in other manifest?
116 if of in ma:
126 if of in ma:
117 diverge.setdefault(of, []).append(c.path())
127 diverge.setdefault(of, []).append(c.path())
118 continue
128 continue
119 # if the original file is unchanged on the other branch,
129 # if the original file is unchanged on the other branch,
120 # no merge needed
130 # no merge needed
121 if man[of] == aman.get(of):
131 if man[of] == aman.get(of):
122 continue
132 continue
123 c2 = ctx(of, man[of])
133 c2 = ctx(of, man[of])
124 ca = c.ancestor(c2)
134 ca = c.ancestor(c2)
125 if not ca: # unrelated?
135 if not ca: # unrelated?
126 continue
136 continue
127 # named changed on only one side?
137 # named changed on only one side?
128 if ca.path() == c.path() or ca.path() == c2.path():
138 if ca.path() == c.path() or ca.path() == c2.path():
129 if c == ca and c2 == ca: # no merge needed, ignore copy
139 if c == ca and c2 == ca: # no merge needed, ignore copy
130 continue
140 continue
131 copy[c.path()] = of
141 copy[c.path()] = of
132
142
133 if not repo.ui.configbool("merge", "followcopies", True):
143 if not repo.ui.configbool("merge", "followcopies", True):
134 return {}, {}
144 return {}, {}
135
145
136 # avoid silly behavior for update from empty dir
146 # avoid silly behavior for update from empty dir
137 if not m1 or not m2 or not ma:
147 if not m1 or not m2 or not ma:
138 return {}, {}
148 return {}, {}
139
149
140 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
150 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
141
151
142 u1 = nonoverlap(m1, m2, ma)
152 u1 = nonoverlap(m1, m2, ma)
143 u2 = nonoverlap(m2, m1, ma)
153 u2 = nonoverlap(m2, m1, ma)
144
154
145 if u1:
155 if u1:
146 repo.ui.debug(_(" unmatched files in local:\n %s\n")
156 repo.ui.debug(_(" unmatched files in local:\n %s\n")
147 % "\n ".join(u1))
157 % "\n ".join(u1))
148 if u2:
158 if u2:
149 repo.ui.debug(_(" unmatched files in other:\n %s\n")
159 repo.ui.debug(_(" unmatched files in other:\n %s\n")
150 % "\n ".join(u2))
160 % "\n ".join(u2))
151
161
152 for f in u1:
162 for f in u1:
153 checkcopies(ctx(f, m1[f]), m2, ma)
163 checkcopies(ctx(f, m1[f]), m2, ma)
154
164
155 for f in u2:
165 for f in u2:
156 checkcopies(ctx(f, m2[f]), m1, ma)
166 checkcopies(ctx(f, m2[f]), m1, ma)
157
167
158 diverge2 = {}
168 diverge2 = {}
159 for of, fl in diverge.items():
169 for of, fl in diverge.items():
160 if len(fl) == 1:
170 if len(fl) == 1:
161 del diverge[of] # not actually divergent
171 del diverge[of] # not actually divergent
162 else:
172 else:
163 diverge2.update(dict.fromkeys(fl)) # reverse map for below
173 diverge2.update(dict.fromkeys(fl)) # reverse map for below
164
174
165 if fullcopy:
175 if fullcopy:
166 repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n"))
176 repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n"))
167 for f in fullcopy:
177 for f in fullcopy:
168 note = ""
178 note = ""
169 if f in copy: note += "*"
179 if f in copy: note += "*"
170 if f in diverge2: note += "!"
180 if f in diverge2: note += "!"
171 repo.ui.debug(_(" %s -> %s %s\n") % (f, fullcopy[f], note))
181 repo.ui.debug(_(" %s -> %s %s\n") % (f, fullcopy[f], note))
172
182
173 del diverge2
183 del diverge2
174
184
175 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
185 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
176 return copy, diverge
186 return copy, diverge
177
187
178 repo.ui.debug(_(" checking for directory renames\n"))
188 repo.ui.debug(_(" checking for directory renames\n"))
179
189
180 # generate a directory move map
190 # generate a directory move map
181 d1, d2 = dirs(m1), dirs(m2)
191 d1, d2 = dirs(m1), dirs(m2)
182 invalid = {}
192 invalid = {}
183 dirmove = {}
193 dirmove = {}
184
194
185 # examine each file copy for a potential directory move, which is
195 # examine each file copy for a potential directory move, which is
186 # when all the files in a directory are moved to a new directory
196 # when all the files in a directory are moved to a new directory
187 for dst, src in fullcopy.items():
197 for dst, src in fullcopy.items():
188 dsrc, ddst = dirname(src), dirname(dst)
198 dsrc, ddst = dirname(src), dirname(dst)
189 if dsrc in invalid:
199 if dsrc in invalid:
190 # already seen to be uninteresting
200 # already seen to be uninteresting
191 continue
201 continue
192 elif dsrc in d1 and ddst in d1:
202 elif dsrc in d1 and ddst in d1:
193 # directory wasn't entirely moved locally
203 # directory wasn't entirely moved locally
194 invalid[dsrc] = True
204 invalid[dsrc] = True
195 elif dsrc in d2 and ddst in d2:
205 elif dsrc in d2 and ddst in d2:
196 # directory wasn't entirely moved remotely
206 # directory wasn't entirely moved remotely
197 invalid[dsrc] = True
207 invalid[dsrc] = True
198 elif dsrc in dirmove and dirmove[dsrc] != ddst:
208 elif dsrc in dirmove and dirmove[dsrc] != ddst:
199 # files from the same directory moved to two different places
209 # files from the same directory moved to two different places
200 invalid[dsrc] = True
210 invalid[dsrc] = True
201 else:
211 else:
202 # looks good so far
212 # looks good so far
203 dirmove[dsrc + "/"] = ddst + "/"
213 dirmove[dsrc + "/"] = ddst + "/"
204
214
205 for i in invalid:
215 for i in invalid:
206 if i in dirmove:
216 if i in dirmove:
207 del dirmove[i]
217 del dirmove[i]
208
218
209 del d1, d2, invalid
219 del d1, d2, invalid
210
220
211 if not dirmove:
221 if not dirmove:
212 return copy, diverge
222 return copy, diverge
213
223
214 for d in dirmove:
224 for d in dirmove:
215 repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d]))
225 repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d]))
216
226
217 # check unaccounted nonoverlapping files against directory moves
227 # check unaccounted nonoverlapping files against directory moves
218 for f in u1 + u2:
228 for f in u1 + u2:
219 if f not in fullcopy:
229 if f not in fullcopy:
220 for d in dirmove:
230 for d in dirmove:
221 if f.startswith(d):
231 if f.startswith(d):
222 # new file added in a directory that was moved, move it
232 # new file added in a directory that was moved, move it
223 copy[f] = dirmove[d] + f[len(d):]
233 copy[f] = dirmove[d] + f[len(d):]
224 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f]))
234 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f]))
225 break
235 break
226
236
227 return copy, diverge
237 return copy, diverge
228
238
229 def symmetricdifference(repo, rev1, rev2):
239 def symmetricdifference(repo, rev1, rev2):
230 """symmetric difference of the sets of ancestors of rev1 and rev2
240 """symmetric difference of the sets of ancestors of rev1 and rev2
231
241
232 I.e. revisions that are ancestors of rev1 or rev2, but not both.
242 I.e. revisions that are ancestors of rev1 or rev2, but not both.
233 """
243 """
234 # basic idea:
244 # basic idea:
235 # - mark rev1 and rev2 with different colors
245 # - mark rev1 and rev2 with different colors
236 # - walk the graph in topological order with the help of a heap;
246 # - walk the graph in topological order with the help of a heap;
237 # for each revision r:
247 # for each revision r:
238 # - if r has only one color, we want to return it
248 # - if r has only one color, we want to return it
239 # - add colors[r] to its parents
249 # - add colors[r] to its parents
240 #
250 #
241 # We keep track of the number of revisions in the heap that
251 # We keep track of the number of revisions in the heap that
242 # we may be interested in. We stop walking the graph as soon
252 # we may be interested in. We stop walking the graph as soon
243 # as this number reaches 0.
253 # as this number reaches 0.
244 WHITE = 1
254 WHITE = 1
245 BLACK = 2
255 BLACK = 2
246 ALLCOLORS = WHITE | BLACK
256 ALLCOLORS = WHITE | BLACK
247 colors = {rev1: WHITE, rev2: BLACK}
257 colors = {rev1: WHITE, rev2: BLACK}
248
258
249 cl = repo.changelog
259 cl = repo.changelog
250
260
251 visit = [-rev1, -rev2]
261 visit = [-rev1, -rev2]
252 heapq.heapify(visit)
262 heapq.heapify(visit)
253 n_wanted = len(visit)
263 n_wanted = len(visit)
254 ret = []
264 ret = []
255
265
256 while n_wanted:
266 while n_wanted:
257 r = -heapq.heappop(visit)
267 r = -heapq.heappop(visit)
258 wanted = colors[r] != ALLCOLORS
268 wanted = colors[r] != ALLCOLORS
259 n_wanted -= wanted
269 n_wanted -= wanted
260 if wanted:
270 if wanted:
261 ret.append(r)
271 ret.append(r)
262
272
263 for p in cl.parentrevs(r):
273 for p in cl.parentrevs(r):
264 if p == nullrev:
274 if p == nullrev:
265 continue
275 continue
266 if p not in colors:
276 if p not in colors:
267 # first time we see p; add it to visit
277 # first time we see p; add it to visit
268 n_wanted += wanted
278 n_wanted += wanted
269 colors[p] = colors[r]
279 colors[p] = colors[r]
270 heapq.heappush(visit, -p)
280 heapq.heappush(visit, -p)
271 elif colors[p] != ALLCOLORS and colors[p] != colors[r]:
281 elif colors[p] != ALLCOLORS and colors[p] != colors[r]:
272 # at first we thought we wanted p, but now
282 # at first we thought we wanted p, but now
273 # we know we don't really want it
283 # we know we don't really want it
274 n_wanted -= 1
284 n_wanted -= 1
275 colors[p] |= colors[r]
285 colors[p] |= colors[r]
276
286
277 del colors[r]
287 del colors[r]
278
288
279 return ret
289 return ret
280
290
281 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
291 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
282 """
292 """
283 Merge p1 and p2 with ancestor ma and generate merge action list
293 Merge p1 and p2 with ancestor ma and generate merge action list
284
294
285 overwrite = whether we clobber working files
295 overwrite = whether we clobber working files
286 partial = function to filter file lists
296 partial = function to filter file lists
287 """
297 """
288
298
289 repo.ui.note(_("resolving manifests\n"))
299 repo.ui.note(_("resolving manifests\n"))
290 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
300 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
291 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
301 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
292
302
293 m1 = p1.manifest()
303 m1 = p1.manifest()
294 m2 = p2.manifest()
304 m2 = p2.manifest()
295 ma = pa.manifest()
305 ma = pa.manifest()
296 backwards = (pa == p2)
306 backwards = (pa == p2)
297 action = []
307 action = []
298 copy = {}
308 copy = {}
299 diverge = {}
309 diverge = {}
300
310
301 def fmerge(f, f2=None, fa=None):
311 def fmerge(f, f2=None, fa=None):
302 """merge flags"""
312 """merge flags"""
303 if not f2:
313 if not f2:
304 f2 = f
314 f2 = f
305 fa = f
315 fa = f
306 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
316 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
307 if m == n: # flags agree
317 if m == n: # flags agree
308 return m # unchanged
318 return m # unchanged
309 if m and n: # flags are set but don't agree
319 if m and n: # flags are set but don't agree
310 if not a: # both differ from parent
320 if not a: # both differ from parent
311 r = repo.ui.prompt(
321 r = repo.ui.prompt(
312 _(" conflicting flags for %s\n"
322 _(" conflicting flags for %s\n"
313 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
323 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
314 return r != "n" and r or ''
324 return r != "n" and r or ''
315 if m == a:
325 if m == a:
316 return n # changed from m to n
326 return n # changed from m to n
317 return m # changed from n to m
327 return m # changed from n to m
318 if m and m != a: # changed from a to m
328 if m and m != a: # changed from a to m
319 return m
329 return m
320 if n and n != a: # changed from a to n
330 if n and n != a: # changed from a to n
321 return n
331 return n
322 return '' # flag was cleared
332 return '' # flag was cleared
323
333
324 def act(msg, m, f, *args):
334 def act(msg, m, f, *args):
325 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
335 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
326 action.append((f, m) + args)
336 action.append((f, m) + args)
327
337
328 if not (backwards or overwrite):
338 if not (backwards or overwrite):
329 rev1 = p1.rev()
339 rev1 = p1.rev()
330 if rev1 is None:
340 if rev1 is None:
331 # p1 is a workingctx
341 # p1 is a workingctx
332 rev1 = p1.parents()[0].rev()
342 rev1 = p1.parents()[0].rev()
333 limit = min(symmetricdifference(repo, rev1, p2.rev()))
343 limit = min(symmetricdifference(repo, rev1, p2.rev()))
334 copy, diverge = findcopies(repo, m1, m2, ma, limit)
344 copy, diverge = findcopies(repo, m1, m2, ma, limit)
335
345
336 for of, fl in diverge.items():
346 for of, fl in diverge.items():
337 act("divergent renames", "dr", of, fl)
347 act("divergent renames", "dr", of, fl)
338
348
339 copied = dict.fromkeys(copy.values())
349 copied = dict.fromkeys(copy.values())
340
350
341 # Compare manifests
351 # Compare manifests
342 for f, n in m1.iteritems():
352 for f, n in m1.iteritems():
343 if partial and not partial(f):
353 if partial and not partial(f):
344 continue
354 continue
345 if f in m2:
355 if f in m2:
346 if overwrite or backwards:
356 if overwrite or backwards:
347 rflags = m2.flags(f)
357 rflags = m2.flags(f)
348 else:
358 else:
349 rflags = fmerge(f)
359 rflags = fmerge(f)
350 # are files different?
360 # are files different?
351 if n != m2[f]:
361 if n != m2[f]:
352 a = ma.get(f, nullid)
362 a = ma.get(f, nullid)
353 # are we clobbering?
363 # are we clobbering?
354 if overwrite:
364 if overwrite:
355 act("clobbering", "g", f, rflags)
365 act("clobbering", "g", f, rflags)
356 # or are we going back in time and clean?
366 # or are we going back in time and clean?
357 elif backwards and not n[20:]:
367 elif backwards and not n[20:]:
358 act("reverting", "g", f, rflags)
368 act("reverting", "g", f, rflags)
359 # are both different from the ancestor?
369 # are both different from the ancestor?
360 elif n != a and m2[f] != a:
370 elif n != a and m2[f] != a:
361 act("versions differ", "m", f, f, f, rflags, False)
371 act("versions differ", "m", f, f, f, rflags, False)
362 # is remote's version newer?
372 # is remote's version newer?
363 elif m2[f] != a:
373 elif m2[f] != a:
364 act("remote is newer", "g", f, rflags)
374 act("remote is newer", "g", f, rflags)
365 # local is newer, not overwrite, check mode bits
375 # local is newer, not overwrite, check mode bits
366 elif m1.flags(f) != rflags:
376 elif m1.flags(f) != rflags:
367 act("update permissions", "e", f, rflags)
377 act("update permissions", "e", f, rflags)
368 # contents same, check mode bits
378 # contents same, check mode bits
369 elif m1.flags(f) != rflags:
379 elif m1.flags(f) != rflags:
370 act("update permissions", "e", f, rflags)
380 act("update permissions", "e", f, rflags)
371 elif f in copied:
381 elif f in copied:
372 continue
382 continue
373 elif f in copy:
383 elif f in copy:
374 f2 = copy[f]
384 f2 = copy[f]
375 if f2 not in m2: # directory rename
385 if f2 not in m2: # directory rename
376 act("remote renamed directory to " + f2, "d",
386 act("remote renamed directory to " + f2, "d",
377 f, None, f2, m1.flags(f))
387 f, None, f2, m1.flags(f))
378 elif f2 in m1: # case 2 A,B/B/B
388 elif f2 in m1: # case 2 A,B/B/B
379 act("local copied to " + f2, "m",
389 act("local copied to " + f2, "m",
380 f, f2, f, fmerge(f, f2, f2), False)
390 f, f2, f, fmerge(f, f2, f2), False)
381 else: # case 4,21 A/B/B
391 else: # case 4,21 A/B/B
382 act("local moved to " + f2, "m",
392 act("local moved to " + f2, "m",
383 f, f2, f, fmerge(f, f2, f2), False)
393 f, f2, f, fmerge(f, f2, f2), False)
384 elif f in ma:
394 elif f in ma:
385 if n != ma[f] and not overwrite:
395 if n != ma[f] and not overwrite:
386 if repo.ui.prompt(
396 if repo.ui.prompt(
387 _(" local changed %s which remote deleted\n"
397 _(" local changed %s which remote deleted\n"
388 "use (c)hanged version or (d)elete?") % f,
398 "use (c)hanged version or (d)elete?") % f,
389 _("[cd]"), _("c")) == _("d"):
399 _("[cd]"), _("c")) == _("d"):
390 act("prompt delete", "r", f)
400 act("prompt delete", "r", f)
391 else:
401 else:
392 act("other deleted", "r", f)
402 act("other deleted", "r", f)
393 else:
403 else:
394 # file is created on branch or in working directory
404 # file is created on branch or in working directory
395 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
405 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
396 act("remote deleted", "r", f)
406 act("remote deleted", "r", f)
397
407
398 for f, n in m2.iteritems():
408 for f, n in m2.iteritems():
399 if partial and not partial(f):
409 if partial and not partial(f):
400 continue
410 continue
401 if f in m1:
411 if f in m1:
402 continue
412 continue
403 if f in copied:
413 if f in copied:
404 continue
414 continue
405 if f in copy:
415 if f in copy:
406 f2 = copy[f]
416 f2 = copy[f]
407 if f2 not in m1: # directory rename
417 if f2 not in m1: # directory rename
408 act("local renamed directory to " + f2, "d",
418 act("local renamed directory to " + f2, "d",
409 None, f, f2, m2.flags(f))
419 None, f, f2, m2.flags(f))
410 elif f2 in m2: # rename case 1, A/A,B/A
420 elif f2 in m2: # rename case 1, A/A,B/A
411 act("remote copied to " + f, "m",
421 act("remote copied to " + f, "m",
412 f2, f, f, fmerge(f2, f, f2), False)
422 f2, f, f, fmerge(f2, f, f2), False)
413 else: # case 3,20 A/B/A
423 else: # case 3,20 A/B/A
414 act("remote moved to " + f, "m",
424 act("remote moved to " + f, "m",
415 f2, f, f, fmerge(f2, f, f2), True)
425 f2, f, f, fmerge(f2, f, f2), True)
416 elif f in ma:
426 elif f in ma:
417 if overwrite or backwards:
427 if overwrite or backwards:
418 act("recreating", "g", f, m2.flags(f))
428 act("recreating", "g", f, m2.flags(f))
419 elif n != ma[f]:
429 elif n != ma[f]:
420 if repo.ui.prompt(
430 if repo.ui.prompt(
421 _("remote changed %s which local deleted\n"
431 _("remote changed %s which local deleted\n"
422 "use (c)hanged version or leave (d)eleted?") % f,
432 "use (c)hanged version or leave (d)eleted?") % f,
423 _("[cd]"), _("c")) == _("c"):
433 _("[cd]"), _("c")) == _("c"):
424 act("prompt recreating", "g", f, m2.flags(f))
434 act("prompt recreating", "g", f, m2.flags(f))
425 else:
435 else:
426 act("remote created", "g", f, m2.flags(f))
436 act("remote created", "g", f, m2.flags(f))
427
437
428 return action
438 return action
429
439
430 def applyupdates(repo, action, wctx, mctx):
440 def applyupdates(repo, action, wctx, mctx):
431 "apply the merge action list to the working directory"
441 "apply the merge action list to the working directory"
432
442
433 updated, merged, removed, unresolved = 0, 0, 0, 0
443 updated, merged, removed, unresolved = 0, 0, 0, 0
434 action.sort()
444 action.sort()
435 # prescan for copy/renames
445 # prescan for copy/renames
436 for a in action:
446 for a in action:
437 f, m = a[:2]
447 f, m = a[:2]
438 if m == 'm': # merge
448 if m == 'm': # merge
439 f2, fd, flags, move = a[2:]
449 f2, fd, flags, move = a[2:]
440 if f != fd:
450 if f != fd:
441 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
451 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
442 repo.wwrite(fd, repo.wread(f), flags)
452 repo.wwrite(fd, repo.wread(f), flags)
443
453
444 audit_path = util.path_auditor(repo.root)
454 audit_path = util.path_auditor(repo.root)
445
455
446 for a in action:
456 for a in action:
447 f, m = a[:2]
457 f, m = a[:2]
448 if f and f[0] == "/":
458 if f and f[0] == "/":
449 continue
459 continue
450 if m == "r": # remove
460 if m == "r": # remove
451 repo.ui.note(_("removing %s\n") % f)
461 repo.ui.note(_("removing %s\n") % f)
452 audit_path(f)
462 audit_path(f)
453 try:
463 try:
454 util.unlink(repo.wjoin(f))
464 util.unlink(repo.wjoin(f))
455 except OSError, inst:
465 except OSError, inst:
456 if inst.errno != errno.ENOENT:
466 if inst.errno != errno.ENOENT:
457 repo.ui.warn(_("update failed to remove %s: %s!\n") %
467 repo.ui.warn(_("update failed to remove %s: %s!\n") %
458 (f, inst.strerror))
468 (f, inst.strerror))
459 removed += 1
469 removed += 1
460 elif m == "m": # merge
470 elif m == "m": # merge
461 f2, fd, flags, move = a[2:]
471 f2, fd, flags, move = a[2:]
462 r = filemerge.filemerge(repo, f, fd, f2, wctx, mctx)
472 r = filemerge.filemerge(repo, f, fd, f2, wctx, mctx)
463 if r > 0:
473 if r > 0:
464 unresolved += 1
474 unresolved += 1
465 else:
475 else:
466 if r is None:
476 if r is None:
467 updated += 1
477 updated += 1
468 else:
478 else:
469 merged += 1
479 merged += 1
470 util.set_flags(repo.wjoin(fd), flags)
480 util.set_flags(repo.wjoin(fd), flags)
471 if f != fd and move and util.lexists(repo.wjoin(f)):
481 if f != fd and move and util.lexists(repo.wjoin(f)):
472 repo.ui.debug(_("removing %s\n") % f)
482 repo.ui.debug(_("removing %s\n") % f)
473 os.unlink(repo.wjoin(f))
483 os.unlink(repo.wjoin(f))
474 elif m == "g": # get
484 elif m == "g": # get
475 flags = a[2]
485 flags = a[2]
476 repo.ui.note(_("getting %s\n") % f)
486 repo.ui.note(_("getting %s\n") % f)
477 t = mctx.filectx(f).data()
487 t = mctx.filectx(f).data()
478 repo.wwrite(f, t, flags)
488 repo.wwrite(f, t, flags)
479 updated += 1
489 updated += 1
480 elif m == "d": # directory rename
490 elif m == "d": # directory rename
481 f2, fd, flags = a[2:]
491 f2, fd, flags = a[2:]
482 if f:
492 if f:
483 repo.ui.note(_("moving %s to %s\n") % (f, fd))
493 repo.ui.note(_("moving %s to %s\n") % (f, fd))
484 t = wctx.filectx(f).data()
494 t = wctx.filectx(f).data()
485 repo.wwrite(fd, t, flags)
495 repo.wwrite(fd, t, flags)
486 util.unlink(repo.wjoin(f))
496 util.unlink(repo.wjoin(f))
487 if f2:
497 if f2:
488 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
498 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
489 t = mctx.filectx(f2).data()
499 t = mctx.filectx(f2).data()
490 repo.wwrite(fd, t, flags)
500 repo.wwrite(fd, t, flags)
491 updated += 1
501 updated += 1
492 elif m == "dr": # divergent renames
502 elif m == "dr": # divergent renames
493 fl = a[2]
503 fl = a[2]
494 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
504 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
495 for nf in fl:
505 for nf in fl:
496 repo.ui.warn(" %s\n" % nf)
506 repo.ui.warn(" %s\n" % nf)
497 elif m == "e": # exec
507 elif m == "e": # exec
498 flags = a[2]
508 flags = a[2]
499 util.set_flags(repo.wjoin(f), flags)
509 util.set_flags(repo.wjoin(f), flags)
500
510
501 return updated, merged, removed, unresolved
511 return updated, merged, removed, unresolved
502
512
503 def recordupdates(repo, action, branchmerge):
513 def recordupdates(repo, action, branchmerge):
504 "record merge actions to the dirstate"
514 "record merge actions to the dirstate"
505
515
506 for a in action:
516 for a in action:
507 f, m = a[:2]
517 f, m = a[:2]
508 if m == "r": # remove
518 if m == "r": # remove
509 if branchmerge:
519 if branchmerge:
510 repo.dirstate.remove(f)
520 repo.dirstate.remove(f)
511 else:
521 else:
512 repo.dirstate.forget(f)
522 repo.dirstate.forget(f)
513 elif m == "f": # forget
523 elif m == "f": # forget
514 repo.dirstate.forget(f)
524 repo.dirstate.forget(f)
515 elif m in "ge": # get or exec change
525 elif m in "ge": # get or exec change
516 if branchmerge:
526 if branchmerge:
517 repo.dirstate.normaldirty(f)
527 repo.dirstate.normaldirty(f)
518 else:
528 else:
519 repo.dirstate.normal(f)
529 repo.dirstate.normal(f)
520 elif m == "m": # merge
530 elif m == "m": # merge
521 f2, fd, flag, move = a[2:]
531 f2, fd, flag, move = a[2:]
522 if branchmerge:
532 if branchmerge:
523 # We've done a branch merge, mark this file as merged
533 # We've done a branch merge, mark this file as merged
524 # so that we properly record the merger later
534 # so that we properly record the merger later
525 repo.dirstate.merge(fd)
535 repo.dirstate.merge(fd)
526 if f != f2: # copy/rename
536 if f != f2: # copy/rename
527 if move:
537 if move:
528 repo.dirstate.remove(f)
538 repo.dirstate.remove(f)
529 if f != fd:
539 if f != fd:
530 repo.dirstate.copy(f, fd)
540 repo.dirstate.copy(f, fd)
531 else:
541 else:
532 repo.dirstate.copy(f2, fd)
542 repo.dirstate.copy(f2, fd)
533 else:
543 else:
534 # We've update-merged a locally modified file, so
544 # We've update-merged a locally modified file, so
535 # we set the dirstate to emulate a normal checkout
545 # we set the dirstate to emulate a normal checkout
536 # of that file some time in the past. Thus our
546 # of that file some time in the past. Thus our
537 # merge will appear as a normal local file
547 # merge will appear as a normal local file
538 # modification.
548 # modification.
539 repo.dirstate.normallookup(fd)
549 repo.dirstate.normallookup(fd)
540 if move:
550 if move:
541 repo.dirstate.forget(f)
551 repo.dirstate.forget(f)
542 elif m == "d": # directory rename
552 elif m == "d": # directory rename
543 f2, fd, flag = a[2:]
553 f2, fd, flag = a[2:]
544 if not f2 and f not in repo.dirstate:
554 if not f2 and f not in repo.dirstate:
545 # untracked file moved
555 # untracked file moved
546 continue
556 continue
547 if branchmerge:
557 if branchmerge:
548 repo.dirstate.add(fd)
558 repo.dirstate.add(fd)
549 if f:
559 if f:
550 repo.dirstate.remove(f)
560 repo.dirstate.remove(f)
551 repo.dirstate.copy(f, fd)
561 repo.dirstate.copy(f, fd)
552 if f2:
562 if f2:
553 repo.dirstate.copy(f2, fd)
563 repo.dirstate.copy(f2, fd)
554 else:
564 else:
555 repo.dirstate.normal(fd)
565 repo.dirstate.normal(fd)
556 if f:
566 if f:
557 repo.dirstate.forget(f)
567 repo.dirstate.forget(f)
558
568
559 def update(repo, node, branchmerge, force, partial):
569 def update(repo, node, branchmerge, force, partial):
560 """
570 """
561 Perform a merge between the working directory and the given node
571 Perform a merge between the working directory and the given node
562
572
563 branchmerge = whether to merge between branches
573 branchmerge = whether to merge between branches
564 force = whether to force branch merging or file overwriting
574 force = whether to force branch merging or file overwriting
565 partial = a function to filter file lists (dirstate not updated)
575 partial = a function to filter file lists (dirstate not updated)
566 """
576 """
567
577
568 wlock = repo.wlock()
578 wlock = repo.wlock()
569 try:
579 try:
570 wc = repo.workingctx()
580 wc = repo.workingctx()
571 if node is None:
581 if node is None:
572 # tip of current branch
582 # tip of current branch
573 try:
583 try:
574 node = repo.branchtags()[wc.branch()]
584 node = repo.branchtags()[wc.branch()]
575 except KeyError:
585 except KeyError:
576 if wc.branch() == "default": # no default branch!
586 if wc.branch() == "default": # no default branch!
577 node = repo.lookup("tip") # update to tip
587 node = repo.lookup("tip") # update to tip
578 else:
588 else:
579 raise util.Abort(_("branch %s not found") % wc.branch())
589 raise util.Abort(_("branch %s not found") % wc.branch())
580 overwrite = force and not branchmerge
590 overwrite = force and not branchmerge
581 forcemerge = force and branchmerge
591 forcemerge = force and branchmerge
582 pl = wc.parents()
592 pl = wc.parents()
583 p1, p2 = pl[0], repo.changectx(node)
593 p1, p2 = pl[0], repo.changectx(node)
584 pa = p1.ancestor(p2)
594 pa = p1.ancestor(p2)
585 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
595 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
586 fastforward = False
596 fastforward = False
587
597
588 ### check phase
598 ### check phase
589 if not overwrite and len(pl) > 1:
599 if not overwrite and len(pl) > 1:
590 raise util.Abort(_("outstanding uncommitted merges"))
600 raise util.Abort(_("outstanding uncommitted merges"))
591 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
601 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
592 if branchmerge:
602 if branchmerge:
593 if p1.branch() != p2.branch() and pa != p2:
603 if p1.branch() != p2.branch() and pa != p2:
594 fastforward = True
604 fastforward = True
595 else:
605 else:
596 raise util.Abort(_("there is nothing to merge, just use "
606 raise util.Abort(_("there is nothing to merge, just use "
597 "'hg update' or look at 'hg heads'"))
607 "'hg update' or look at 'hg heads'"))
598 elif not (overwrite or branchmerge):
608 elif not (overwrite or branchmerge):
599 raise util.Abort(_("update spans branches, use 'hg merge' "
609 raise util.Abort(_("update spans branches, use 'hg merge' "
600 "or 'hg update -C' to lose changes"))
610 "or 'hg update -C' to lose changes"))
601 if branchmerge and not forcemerge:
611 if branchmerge and not forcemerge:
602 if wc.files():
612 if wc.files():
603 raise util.Abort(_("outstanding uncommitted changes"))
613 raise util.Abort(_("outstanding uncommitted changes"))
604
614
605 ### calculate phase
615 ### calculate phase
606 action = []
616 action = []
607 if not force:
617 if not force:
608 checkunknown(wc, p2)
618 checkunknown(wc, p2)
609 if not util.checkfolding(repo.path):
619 if not util.checkfolding(repo.path):
610 checkcollision(p2)
620 checkcollision(p2)
611 if not branchmerge:
621 action += forgetremoved(wc, p2, branchmerge)
612 action += forgetremoved(wc, p2)
613 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
622 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
614
623
615 ### apply phase
624 ### apply phase
616 if not branchmerge: # just jump to the new rev
625 if not branchmerge: # just jump to the new rev
617 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
626 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
618 if not partial:
627 if not partial:
619 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
628 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
620
629
621 stats = applyupdates(repo, action, wc, p2)
630 stats = applyupdates(repo, action, wc, p2)
622
631
623 if not partial:
632 if not partial:
624 recordupdates(repo, action, branchmerge)
633 recordupdates(repo, action, branchmerge)
625 repo.dirstate.setparents(fp1, fp2)
634 repo.dirstate.setparents(fp1, fp2)
626 if not branchmerge and not fastforward:
635 if not branchmerge and not fastforward:
627 repo.dirstate.setbranch(p2.branch())
636 repo.dirstate.setbranch(p2.branch())
628 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
637 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
629
638
630 return stats
639 return stats
631 finally:
640 finally:
632 del wlock
641 del wlock
General Comments 0
You need to be logged in to leave comments. Login now