##// END OF EJS Templates
merge: simplify a check in checkcopies
Matt Mackall -
r4399:93652499 default
parent child Browse files
Show More
@@ -1,524 +1,524 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 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "errno util os tempfile")
11 demandload(globals(), "errno util os tempfile")
12
12
13 def filemerge(repo, fw, fo, wctx, mctx):
13 def filemerge(repo, fw, fo, wctx, mctx):
14 """perform a 3-way merge in the working directory
14 """perform a 3-way merge in the working directory
15
15
16 fw = filename in the working directory
16 fw = 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 f = os.fdopen(fd, "wb")
24 f = os.fdopen(fd, "wb")
25 repo.wwrite(ctx.path(), ctx.data(), f)
25 repo.wwrite(ctx.path(), ctx.data(), f)
26 f.close()
26 f.close()
27 return name
27 return name
28
28
29 fcm = wctx.filectx(fw)
29 fcm = wctx.filectx(fw)
30 fco = mctx.filectx(fo)
30 fco = mctx.filectx(fo)
31
31
32 if not fco.cmp(fcm.data()): # files identical?
32 if not fco.cmp(fcm.data()): # files identical?
33 return None
33 return None
34
34
35 fca = fcm.ancestor(fco)
35 fca = fcm.ancestor(fco)
36 if not fca:
36 if not fca:
37 fca = repo.filectx(fw, fileid=nullrev)
37 fca = repo.filectx(fw, fileid=nullrev)
38 a = repo.wjoin(fw)
38 a = repo.wjoin(fw)
39 b = temp("base", fca)
39 b = temp("base", fca)
40 c = temp("other", fco)
40 c = temp("other", fco)
41
41
42 if fw != fo:
42 if fw != fo:
43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
43 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
44 else:
44 else:
45 repo.ui.status(_("merging %s\n") % fw)
45 repo.ui.status(_("merging %s\n") % fw)
46
46
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
48
48
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
50 or "hgmerge")
50 or "hgmerge")
51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
52 environ={'HG_FILE': fw,
52 environ={'HG_FILE': fw,
53 'HG_MY_NODE': str(wctx.parents()[0]),
53 'HG_MY_NODE': str(wctx.parents()[0]),
54 'HG_OTHER_NODE': str(mctx)})
54 'HG_OTHER_NODE': str(mctx)})
55 if r:
55 if r:
56 repo.ui.warn(_("merging %s failed!\n") % fw)
56 repo.ui.warn(_("merging %s failed!\n") % fw)
57
57
58 os.unlink(b)
58 os.unlink(b)
59 os.unlink(c)
59 os.unlink(c)
60 return r
60 return r
61
61
62 def checkunknown(wctx, mctx):
62 def checkunknown(wctx, mctx):
63 "check for collisions between unknown files and files in mctx"
63 "check for collisions between unknown files and files in mctx"
64 man = mctx.manifest()
64 man = mctx.manifest()
65 for f in wctx.unknown():
65 for f in wctx.unknown():
66 if f in man:
66 if f in man:
67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
67 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
68 raise util.Abort(_("untracked local file '%s' differs"\
68 raise util.Abort(_("untracked local file '%s' differs"\
69 " from remote version") % f)
69 " from remote version") % f)
70
70
71 def checkcollision(mctx):
71 def checkcollision(mctx):
72 "check for case folding collisions in the destination context"
72 "check for case folding collisions in the destination context"
73 folded = {}
73 folded = {}
74 for fn in mctx.manifest():
74 for fn in mctx.manifest():
75 fold = fn.lower()
75 fold = fn.lower()
76 if fold in folded:
76 if fold in folded:
77 raise util.Abort(_("case-folding collision between %s and %s")
77 raise util.Abort(_("case-folding collision between %s and %s")
78 % (fn, folded[fold]))
78 % (fn, folded[fold]))
79 folded[fold] = fn
79 folded[fold] = fn
80
80
81 def forgetremoved(wctx, mctx):
81 def forgetremoved(wctx, mctx):
82 """
82 """
83 Forget removed files
83 Forget removed files
84
84
85 If we're jumping between revisions (as opposed to merging), and if
85 If we're jumping between revisions (as opposed to merging), and if
86 neither the working directory nor the target rev has the file,
86 neither the working directory nor the target rev has the file,
87 then we need to remove it from the dirstate, to prevent the
87 then we need to remove it from the dirstate, to prevent the
88 dirstate from listing the file when it is no longer in the
88 dirstate from listing the file when it is no longer in the
89 manifest.
89 manifest.
90 """
90 """
91
91
92 action = []
92 action = []
93 man = mctx.manifest()
93 man = mctx.manifest()
94 for f in wctx.deleted() + wctx.removed():
94 for f in wctx.deleted() + wctx.removed():
95 if f not in man:
95 if f not in man:
96 action.append((f, "f"))
96 action.append((f, "f"))
97
97
98 return action
98 return action
99
99
100 def findcopies(repo, m1, m2, ma, limit):
100 def findcopies(repo, m1, m2, ma, limit):
101 """
101 """
102 Find moves and copies between m1 and m2 back to limit linkrev
102 Find moves and copies between m1 and m2 back to limit linkrev
103 """
103 """
104
104
105 def dirname(f):
105 def dirname(f):
106 s = f.rfind("/")
106 s = f.rfind("/")
107 if s == -1:
107 if s == -1:
108 return ""
108 return ""
109 return f[:s]
109 return f[:s]
110
110
111 def dirs(files):
111 def dirs(files):
112 d = {}
112 d = {}
113 for f in files:
113 for f in files:
114 f = dirname(f)
114 f = dirname(f)
115 while f not in d:
115 while f not in d:
116 d[f] = True
116 d[f] = True
117 f = dirname(f)
117 f = dirname(f)
118 return d
118 return d
119
119
120 def findold(fctx):
120 def findold(fctx):
121 "find files that path was copied from, back to linkrev limit"
121 "find files that path was copied from, back to linkrev limit"
122 old = {}
122 old = {}
123 seen = {}
123 seen = {}
124 orig = fctx.path()
124 orig = fctx.path()
125 visit = [fctx]
125 visit = [fctx]
126 while visit:
126 while visit:
127 fc = visit.pop()
127 fc = visit.pop()
128 s = str(fc)
128 s = str(fc)
129 if s in seen:
129 if s in seen:
130 continue
130 continue
131 seen[s] = 1
131 seen[s] = 1
132 if fc.path() != orig and fc.path() not in old:
132 if fc.path() != orig and fc.path() not in old:
133 old[fc.path()] = 1
133 old[fc.path()] = 1
134 if fc.rev() < limit:
134 if fc.rev() < limit:
135 continue
135 continue
136 visit += fc.parents()
136 visit += fc.parents()
137
137
138 old = old.keys()
138 old = old.keys()
139 old.sort()
139 old.sort()
140 return old
140 return old
141
141
142 def nonoverlap(d1, d2, d3):
142 def nonoverlap(d1, d2, d3):
143 "Return list of elements in d1 not in d2 or d3"
143 "Return list of elements in d1 not in d2 or d3"
144 l = [d for d in d1 if d not in d3 and d not in d2]
144 l = [d for d in d1 if d not in d3 and d not in d2]
145 l.sort()
145 l.sort()
146 return l
146 return l
147
147
148 def checkcopies(c, man):
148 def checkcopies(c, man):
149 '''check possible copies for filectx c'''
149 '''check possible copies for filectx c'''
150 for of in findold(c):
150 for of in findold(c):
151 if of not in man: # original file not in other manifest?
151 if of not in man: # original file not in other manifest?
152 continue
152 continue
153 c2 = ctx(of, man[of])
153 c2 = ctx(of, man[of])
154 ca = c.ancestor(c2)
154 ca = c.ancestor(c2)
155 if not ca: # unrelated?
155 if not ca: # unrelated?
156 continue
156 continue
157 # named changed on only one side?
157 # named changed on only one side?
158 if ca.path() == c.path() or ca.path() == c2.path():
158 if ca.path() == c.path() or ca.path() == c2.path():
159 fullcopy[c.path()] = of # remember for dir rename detection
159 fullcopy[c.path()] = of # remember for dir rename detection
160 if c == ca and c2 == ca: # no merge needed, ignore copy
160 if c == c2: # no merge needed, ignore copy
161 continue
161 continue
162 copy[c.path()] = of
162 copy[c.path()] = of
163
163
164 if not repo.ui.configbool("merge", "followcopies", True):
164 if not repo.ui.configbool("merge", "followcopies", True):
165 return {}
165 return {}
166
166
167 # avoid silly behavior for update from empty dir
167 # avoid silly behavior for update from empty dir
168 if not m1 or not m2 or not ma:
168 if not m1 or not m2 or not ma:
169 return {}
169 return {}
170
170
171 dcopies = repo.dirstate.copies()
171 dcopies = repo.dirstate.copies()
172 copy = {}
172 copy = {}
173 fullcopy = {}
173 fullcopy = {}
174 u1 = nonoverlap(m1, m2, ma)
174 u1 = nonoverlap(m1, m2, ma)
175 u2 = nonoverlap(m2, m1, ma)
175 u2 = nonoverlap(m2, m1, ma)
176 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
176 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
177
177
178 for f in u1:
178 for f in u1:
179 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2)
179 checkcopies(ctx(dcopies.get(f, f), m1[f]), m2)
180
180
181 for f in u2:
181 for f in u2:
182 checkcopies(ctx(f, m2[f]), m1)
182 checkcopies(ctx(f, m2[f]), m1)
183
183
184 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
184 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
185 return copy
185 return copy
186
186
187 # generate a directory move map
187 # generate a directory move map
188 d1, d2 = dirs(m1), dirs(m2)
188 d1, d2 = dirs(m1), dirs(m2)
189 invalid = {}
189 invalid = {}
190 dirmove = {}
190 dirmove = {}
191
191
192 # examine each file copy for a potential directory move, which is
192 # examine each file copy for a potential directory move, which is
193 # when all the files in a directory are moved to a new directory
193 # when all the files in a directory are moved to a new directory
194 for dst, src in fullcopy.items():
194 for dst, src in fullcopy.items():
195 dsrc, ddst = dirname(src), dirname(dst)
195 dsrc, ddst = dirname(src), dirname(dst)
196 if dsrc in invalid:
196 if dsrc in invalid:
197 # already seen to be uninteresting
197 # already seen to be uninteresting
198 continue
198 continue
199 elif dsrc in d1 and ddst in d1:
199 elif dsrc in d1 and ddst in d1:
200 # directory wasn't entirely moved locally
200 # directory wasn't entirely moved locally
201 invalid[dsrc] = True
201 invalid[dsrc] = True
202 elif dsrc in d2 and ddst in d2:
202 elif dsrc in d2 and ddst in d2:
203 # directory wasn't entirely moved remotely
203 # directory wasn't entirely moved remotely
204 invalid[dsrc] = True
204 invalid[dsrc] = True
205 elif dsrc in dirmove and dirmove[dsrc] != ddst:
205 elif dsrc in dirmove and dirmove[dsrc] != ddst:
206 # files from the same directory moved to two different places
206 # files from the same directory moved to two different places
207 invalid[dsrc] = True
207 invalid[dsrc] = True
208 else:
208 else:
209 # looks good so far
209 # looks good so far
210 dirmove[dsrc + "/"] = ddst + "/"
210 dirmove[dsrc + "/"] = ddst + "/"
211
211
212 for i in invalid:
212 for i in invalid:
213 if i in dirmove:
213 if i in dirmove:
214 del dirmove[i]
214 del dirmove[i]
215
215
216 del d1, d2, invalid
216 del d1, d2, invalid
217
217
218 if not dirmove:
218 if not dirmove:
219 return copy
219 return copy
220
220
221 # check unaccounted nonoverlapping files against directory moves
221 # check unaccounted nonoverlapping files against directory moves
222 for f in u1 + u2:
222 for f in u1 + u2:
223 if f not in fullcopy:
223 if f not in fullcopy:
224 for d in dirmove:
224 for d in dirmove:
225 if f.startswith(d):
225 if f.startswith(d):
226 # new file added in a directory that was moved, move it
226 # new file added in a directory that was moved, move it
227 copy[f] = dirmove[d] + f[len(d):]
227 copy[f] = dirmove[d] + f[len(d):]
228 break
228 break
229
229
230 return copy
230 return copy
231
231
232 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
232 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
233 """
233 """
234 Merge p1 and p2 with ancestor ma and generate merge action list
234 Merge p1 and p2 with ancestor ma and generate merge action list
235
235
236 overwrite = whether we clobber working files
236 overwrite = whether we clobber working files
237 partial = function to filter file lists
237 partial = function to filter file lists
238 """
238 """
239
239
240 repo.ui.note(_("resolving manifests\n"))
240 repo.ui.note(_("resolving manifests\n"))
241 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
241 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
242 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
242 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
243
243
244 m1 = p1.manifest()
244 m1 = p1.manifest()
245 m2 = p2.manifest()
245 m2 = p2.manifest()
246 ma = pa.manifest()
246 ma = pa.manifest()
247 backwards = (pa == p2)
247 backwards = (pa == p2)
248 action = []
248 action = []
249 copy = {}
249 copy = {}
250
250
251 def fmerge(f, f2=None, fa=None):
251 def fmerge(f, f2=None, fa=None):
252 """merge executable flags"""
252 """merge executable flags"""
253 if not f2:
253 if not f2:
254 f2 = f
254 f2 = f
255 fa = f
255 fa = f
256 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
256 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
257 return ((a^b) | (a^c)) ^ a
257 return ((a^b) | (a^c)) ^ a
258
258
259 def act(msg, m, f, *args):
259 def act(msg, m, f, *args):
260 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
260 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
261 action.append((f, m) + args)
261 action.append((f, m) + args)
262
262
263 if not (backwards or overwrite):
263 if not (backwards or overwrite):
264 copy = findcopies(repo, m1, m2, ma, pa.rev())
264 copy = findcopies(repo, m1, m2, ma, pa.rev())
265 copied = dict.fromkeys(copy.values())
265 copied = dict.fromkeys(copy.values())
266
266
267 # Compare manifests
267 # Compare manifests
268 for f, n in m1.iteritems():
268 for f, n in m1.iteritems():
269 if partial and not partial(f):
269 if partial and not partial(f):
270 continue
270 continue
271 if f in m2:
271 if f in m2:
272 # are files different?
272 # are files different?
273 if n != m2[f]:
273 if n != m2[f]:
274 a = ma.get(f, nullid)
274 a = ma.get(f, nullid)
275 # are both different from the ancestor?
275 # are both different from the ancestor?
276 if not overwrite and n != a and m2[f] != a:
276 if not overwrite and n != a and m2[f] != a:
277 act("versions differ", "m", f, f, f, fmerge(f), False)
277 act("versions differ", "m", f, f, f, fmerge(f), False)
278 # are we clobbering?
278 # are we clobbering?
279 # is remote's version newer?
279 # is remote's version newer?
280 # or are we going back in time and clean?
280 # or are we going back in time and clean?
281 elif overwrite or m2[f] != a or (backwards and not n[20:]):
281 elif overwrite or m2[f] != a or (backwards and not n[20:]):
282 act("remote is newer", "g", f, m2.execf(f))
282 act("remote is newer", "g", f, m2.execf(f))
283 # local is newer, not overwrite, check mode bits
283 # local is newer, not overwrite, check mode bits
284 elif fmerge(f) != m1.execf(f):
284 elif fmerge(f) != m1.execf(f):
285 act("update permissions", "e", f, m2.execf(f))
285 act("update permissions", "e", f, m2.execf(f))
286 # contents same, check mode bits
286 # contents same, check mode bits
287 elif m1.execf(f) != m2.execf(f):
287 elif m1.execf(f) != m2.execf(f):
288 if overwrite or fmerge(f) != m1.execf(f):
288 if overwrite or fmerge(f) != m1.execf(f):
289 act("update permissions", "e", f, m2.execf(f))
289 act("update permissions", "e", f, m2.execf(f))
290 elif f in copied:
290 elif f in copied:
291 continue
291 continue
292 elif f in copy:
292 elif f in copy:
293 f2 = copy[f]
293 f2 = copy[f]
294 if f2 not in m2: # directory rename
294 if f2 not in m2: # directory rename
295 act("remote renamed directory to " + f2, "d",
295 act("remote renamed directory to " + f2, "d",
296 f, None, f2, m1.execf(f))
296 f, None, f2, m1.execf(f))
297 elif f2 in m1: # case 2 A,B/B/B
297 elif f2 in m1: # case 2 A,B/B/B
298 act("local copied to " + f2, "m",
298 act("local copied to " + f2, "m",
299 f, f2, f, fmerge(f, f2, f2), False)
299 f, f2, f, fmerge(f, f2, f2), False)
300 else: # case 4,21 A/B/B
300 else: # case 4,21 A/B/B
301 act("local moved to " + f2, "m",
301 act("local moved to " + f2, "m",
302 f, f2, f, fmerge(f, f2, f2), False)
302 f, f2, f, fmerge(f, f2, f2), False)
303 elif f in ma:
303 elif f in ma:
304 if n != ma[f] and not overwrite:
304 if n != ma[f] and not overwrite:
305 if repo.ui.prompt(
305 if repo.ui.prompt(
306 (_(" local changed %s which remote deleted\n") % f) +
306 (_(" local changed %s which remote deleted\n") % f) +
307 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
307 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
308 act("prompt delete", "r", f)
308 act("prompt delete", "r", f)
309 else:
309 else:
310 act("other deleted", "r", f)
310 act("other deleted", "r", f)
311 else:
311 else:
312 # file is created on branch or in working directory
312 # file is created on branch or in working directory
313 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
313 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
314 act("remote deleted", "r", f)
314 act("remote deleted", "r", f)
315
315
316 for f, n in m2.iteritems():
316 for f, n in m2.iteritems():
317 if partial and not partial(f):
317 if partial and not partial(f):
318 continue
318 continue
319 if f in m1:
319 if f in m1:
320 continue
320 continue
321 if f in copied:
321 if f in copied:
322 continue
322 continue
323 if f in copy:
323 if f in copy:
324 f2 = copy[f]
324 f2 = copy[f]
325 if f2 not in m1: # directory rename
325 if f2 not in m1: # directory rename
326 act("local renamed directory to " + f2, "d",
326 act("local renamed directory to " + f2, "d",
327 None, f, f2, m2.execf(f))
327 None, f, f2, m2.execf(f))
328 elif f2 in m2: # rename case 1, A/A,B/A
328 elif f2 in m2: # rename case 1, A/A,B/A
329 act("remote copied to " + f, "m",
329 act("remote copied to " + f, "m",
330 f2, f, f, fmerge(f2, f, f2), False)
330 f2, f, f, fmerge(f2, f, f2), False)
331 else: # case 3,20 A/B/A
331 else: # case 3,20 A/B/A
332 act("remote moved to " + f, "m",
332 act("remote moved to " + f, "m",
333 f2, f, f, fmerge(f2, f, f2), True)
333 f2, f, f, fmerge(f2, f, f2), True)
334 elif f in ma:
334 elif f in ma:
335 if overwrite or backwards:
335 if overwrite or backwards:
336 act("recreating", "g", f, m2.execf(f))
336 act("recreating", "g", f, m2.execf(f))
337 elif n != ma[f]:
337 elif n != ma[f]:
338 if repo.ui.prompt(
338 if repo.ui.prompt(
339 (_("remote changed %s which local deleted\n") % f) +
339 (_("remote changed %s which local deleted\n") % f) +
340 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
340 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
341 act("prompt recreating", "g", f, m2.execf(f))
341 act("prompt recreating", "g", f, m2.execf(f))
342 else:
342 else:
343 act("remote created", "g", f, m2.execf(f))
343 act("remote created", "g", f, m2.execf(f))
344
344
345 return action
345 return action
346
346
347 def applyupdates(repo, action, wctx, mctx):
347 def applyupdates(repo, action, wctx, mctx):
348 "apply the merge action list to the working directory"
348 "apply the merge action list to the working directory"
349
349
350 updated, merged, removed, unresolved = 0, 0, 0, 0
350 updated, merged, removed, unresolved = 0, 0, 0, 0
351 action.sort()
351 action.sort()
352 for a in action:
352 for a in action:
353 f, m = a[:2]
353 f, m = a[:2]
354 if f and f[0] == "/":
354 if f and f[0] == "/":
355 continue
355 continue
356 if m == "r": # remove
356 if m == "r": # remove
357 repo.ui.note(_("removing %s\n") % f)
357 repo.ui.note(_("removing %s\n") % f)
358 util.audit_path(f)
358 util.audit_path(f)
359 try:
359 try:
360 util.unlink(repo.wjoin(f))
360 util.unlink(repo.wjoin(f))
361 except OSError, inst:
361 except OSError, inst:
362 if inst.errno != errno.ENOENT:
362 if inst.errno != errno.ENOENT:
363 repo.ui.warn(_("update failed to remove %s: %s!\n") %
363 repo.ui.warn(_("update failed to remove %s: %s!\n") %
364 (f, inst.strerror))
364 (f, inst.strerror))
365 removed += 1
365 removed += 1
366 elif m == "m": # merge
366 elif m == "m": # merge
367 f2, fd, flag, move = a[2:]
367 f2, fd, flag, move = a[2:]
368 r = filemerge(repo, f, f2, wctx, mctx)
368 r = filemerge(repo, f, f2, wctx, mctx)
369 if r > 0:
369 if r > 0:
370 unresolved += 1
370 unresolved += 1
371 else:
371 else:
372 if r is None:
372 if r is None:
373 updated += 1
373 updated += 1
374 else:
374 else:
375 merged += 1
375 merged += 1
376 if f != fd:
376 if f != fd:
377 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
377 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
378 repo.wwrite(fd, repo.wread(f))
378 repo.wwrite(fd, repo.wread(f))
379 if move:
379 if move:
380 repo.ui.debug(_("removing %s\n") % f)
380 repo.ui.debug(_("removing %s\n") % f)
381 os.unlink(repo.wjoin(f))
381 os.unlink(repo.wjoin(f))
382 util.set_exec(repo.wjoin(fd), flag)
382 util.set_exec(repo.wjoin(fd), flag)
383 elif m == "g": # get
383 elif m == "g": # get
384 flag = a[2]
384 flag = a[2]
385 repo.ui.note(_("getting %s\n") % f)
385 repo.ui.note(_("getting %s\n") % f)
386 t = mctx.filectx(f).data()
386 t = mctx.filectx(f).data()
387 repo.wwrite(f, t)
387 repo.wwrite(f, t)
388 util.set_exec(repo.wjoin(f), flag)
388 util.set_exec(repo.wjoin(f), flag)
389 updated += 1
389 updated += 1
390 elif m == "d": # directory rename
390 elif m == "d": # directory rename
391 f2, fd, flag = a[2:]
391 f2, fd, flag = a[2:]
392 if f:
392 if f:
393 repo.ui.note(_("moving %s to %s\n") % (f, fd))
393 repo.ui.note(_("moving %s to %s\n") % (f, fd))
394 t = wctx.filectx(f).data()
394 t = wctx.filectx(f).data()
395 repo.wwrite(fd, t)
395 repo.wwrite(fd, t)
396 util.set_exec(repo.wjoin(fd), flag)
396 util.set_exec(repo.wjoin(fd), flag)
397 util.unlink(repo.wjoin(f))
397 util.unlink(repo.wjoin(f))
398 if f2:
398 if f2:
399 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
399 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
400 t = mctx.filectx(f2).data()
400 t = mctx.filectx(f2).data()
401 repo.wwrite(fd, t)
401 repo.wwrite(fd, t)
402 util.set_exec(repo.wjoin(fd), flag)
402 util.set_exec(repo.wjoin(fd), flag)
403 updated += 1
403 updated += 1
404 elif m == "e": # exec
404 elif m == "e": # exec
405 flag = a[2]
405 flag = a[2]
406 util.set_exec(repo.wjoin(f), flag)
406 util.set_exec(repo.wjoin(f), flag)
407
407
408 return updated, merged, removed, unresolved
408 return updated, merged, removed, unresolved
409
409
410 def recordupdates(repo, action, branchmerge):
410 def recordupdates(repo, action, branchmerge):
411 "record merge actions to the dirstate"
411 "record merge actions to the dirstate"
412
412
413 for a in action:
413 for a in action:
414 f, m = a[:2]
414 f, m = a[:2]
415 if m == "r": # remove
415 if m == "r": # remove
416 if branchmerge:
416 if branchmerge:
417 repo.dirstate.update([f], 'r')
417 repo.dirstate.update([f], 'r')
418 else:
418 else:
419 repo.dirstate.forget([f])
419 repo.dirstate.forget([f])
420 elif m == "f": # forget
420 elif m == "f": # forget
421 repo.dirstate.forget([f])
421 repo.dirstate.forget([f])
422 elif m == "g": # get
422 elif m == "g": # get
423 if branchmerge:
423 if branchmerge:
424 repo.dirstate.update([f], 'n', st_mtime=-1)
424 repo.dirstate.update([f], 'n', st_mtime=-1)
425 else:
425 else:
426 repo.dirstate.update([f], 'n')
426 repo.dirstate.update([f], 'n')
427 elif m == "m": # merge
427 elif m == "m": # merge
428 f2, fd, flag, move = a[2:]
428 f2, fd, flag, move = a[2:]
429 if branchmerge:
429 if branchmerge:
430 # We've done a branch merge, mark this file as merged
430 # We've done a branch merge, mark this file as merged
431 # so that we properly record the merger later
431 # so that we properly record the merger later
432 repo.dirstate.update([fd], 'm')
432 repo.dirstate.update([fd], 'm')
433 if f != f2: # copy/rename
433 if f != f2: # copy/rename
434 if move:
434 if move:
435 repo.dirstate.update([f], 'r')
435 repo.dirstate.update([f], 'r')
436 if f != fd:
436 if f != fd:
437 repo.dirstate.copy(f, fd)
437 repo.dirstate.copy(f, fd)
438 else:
438 else:
439 repo.dirstate.copy(f2, fd)
439 repo.dirstate.copy(f2, fd)
440 else:
440 else:
441 # We've update-merged a locally modified file, so
441 # We've update-merged a locally modified file, so
442 # we set the dirstate to emulate a normal checkout
442 # we set the dirstate to emulate a normal checkout
443 # of that file some time in the past. Thus our
443 # of that file some time in the past. Thus our
444 # merge will appear as a normal local file
444 # merge will appear as a normal local file
445 # modification.
445 # modification.
446 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
446 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
447 if move:
447 if move:
448 repo.dirstate.forget([f])
448 repo.dirstate.forget([f])
449 elif m == "d": # directory rename
449 elif m == "d": # directory rename
450 f2, fd, flag = a[2:]
450 f2, fd, flag = a[2:]
451 if branchmerge:
451 if branchmerge:
452 repo.dirstate.update([fd], 'a')
452 repo.dirstate.update([fd], 'a')
453 if f:
453 if f:
454 repo.dirstate.update([f], 'r')
454 repo.dirstate.update([f], 'r')
455 repo.dirstate.copy(f, fd)
455 repo.dirstate.copy(f, fd)
456 if f2:
456 if f2:
457 repo.dirstate.copy(f2, fd)
457 repo.dirstate.copy(f2, fd)
458 else:
458 else:
459 repo.dirstate.update([fd], 'n')
459 repo.dirstate.update([fd], 'n')
460 if f:
460 if f:
461 repo.dirstate.forget([f])
461 repo.dirstate.forget([f])
462
462
463 def update(repo, node, branchmerge, force, partial, wlock):
463 def update(repo, node, branchmerge, force, partial, wlock):
464 """
464 """
465 Perform a merge between the working directory and the given node
465 Perform a merge between the working directory and the given node
466
466
467 branchmerge = whether to merge between branches
467 branchmerge = whether to merge between branches
468 force = whether to force branch merging or file overwriting
468 force = whether to force branch merging or file overwriting
469 partial = a function to filter file lists (dirstate not updated)
469 partial = a function to filter file lists (dirstate not updated)
470 wlock = working dir lock, if already held
470 wlock = working dir lock, if already held
471 """
471 """
472
472
473 if not wlock:
473 if not wlock:
474 wlock = repo.wlock()
474 wlock = repo.wlock()
475
475
476 overwrite = force and not branchmerge
476 overwrite = force and not branchmerge
477 forcemerge = force and branchmerge
477 forcemerge = force and branchmerge
478 wc = repo.workingctx()
478 wc = repo.workingctx()
479 pl = wc.parents()
479 pl = wc.parents()
480 p1, p2 = pl[0], repo.changectx(node)
480 p1, p2 = pl[0], repo.changectx(node)
481 pa = p1.ancestor(p2)
481 pa = p1.ancestor(p2)
482 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
482 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
483
483
484 ### check phase
484 ### check phase
485 if not overwrite and len(pl) > 1:
485 if not overwrite and len(pl) > 1:
486 raise util.Abort(_("outstanding uncommitted merges"))
486 raise util.Abort(_("outstanding uncommitted merges"))
487 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
487 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
488 if branchmerge:
488 if branchmerge:
489 raise util.Abort(_("there is nothing to merge, just use "
489 raise util.Abort(_("there is nothing to merge, just use "
490 "'hg update' or look at 'hg heads'"))
490 "'hg update' or look at 'hg heads'"))
491 elif not (overwrite or branchmerge):
491 elif not (overwrite or branchmerge):
492 raise util.Abort(_("update spans branches, use 'hg merge' "
492 raise util.Abort(_("update spans branches, use 'hg merge' "
493 "or 'hg update -C' to lose changes"))
493 "or 'hg update -C' to lose changes"))
494 if branchmerge and not forcemerge:
494 if branchmerge and not forcemerge:
495 if wc.files():
495 if wc.files():
496 raise util.Abort(_("outstanding uncommitted changes"))
496 raise util.Abort(_("outstanding uncommitted changes"))
497
497
498 ### calculate phase
498 ### calculate phase
499 action = []
499 action = []
500 if not force:
500 if not force:
501 checkunknown(wc, p2)
501 checkunknown(wc, p2)
502 if not util.checkfolding(repo.path):
502 if not util.checkfolding(repo.path):
503 checkcollision(p2)
503 checkcollision(p2)
504 if not branchmerge:
504 if not branchmerge:
505 action += forgetremoved(wc, p2)
505 action += forgetremoved(wc, p2)
506 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
506 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
507
507
508 ### apply phase
508 ### apply phase
509 if not branchmerge: # just jump to the new rev
509 if not branchmerge: # just jump to the new rev
510 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
510 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
511 if not partial:
511 if not partial:
512 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
512 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
513
513
514 stats = applyupdates(repo, action, wc, p2)
514 stats = applyupdates(repo, action, wc, p2)
515
515
516 if not partial:
516 if not partial:
517 recordupdates(repo, action, branchmerge)
517 recordupdates(repo, action, branchmerge)
518 repo.dirstate.setparents(fp1, fp2)
518 repo.dirstate.setparents(fp1, fp2)
519 if not branchmerge:
519 if not branchmerge:
520 repo.dirstate.setbranch(p2.branch())
520 repo.dirstate.setbranch(p2.branch())
521 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
521 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
522
522
523 return stats
523 return stats
524
524
General Comments 0
You need to be logged in to leave comments. Login now