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