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