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