##// END OF EJS Templates
merge: reduce manifest copying
Matt Mackall -
r3248:751840e7 default
parent child Browse files
Show More
@@ -1,408 +1,404 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, fd, my, other, p1, p2, move):
13 def filemerge(repo, fw, fo, fd, my, other, p1, p2, move):
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 and first parent
16 fw = filename in the working directory and first parent
17 fo = filename in other parent
17 fo = filename in other parent
18 fd = destination filename
18 fd = destination filename
19 my = fileid in first parent
19 my = fileid in first parent
20 other = fileid in second parent
20 other = fileid in second parent
21 p1, p2 = hex changeset ids for merge command
21 p1, p2 = hex changeset ids for merge command
22 move = whether to move or copy the file to the destination
22 move = whether to move or copy the file to the destination
23
23
24 TODO:
24 TODO:
25 if fw is copied in the working directory, we get confused
25 if fw is copied in the working directory, we get confused
26 implement move and fd
26 implement move and fd
27 """
27 """
28
28
29 def temp(prefix, ctx):
29 def temp(prefix, ctx):
30 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
30 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
31 (fd, name) = tempfile.mkstemp(prefix=pre)
31 (fd, name) = tempfile.mkstemp(prefix=pre)
32 f = os.fdopen(fd, "wb")
32 f = os.fdopen(fd, "wb")
33 repo.wwrite(ctx.path(), ctx.data(), f)
33 repo.wwrite(ctx.path(), ctx.data(), f)
34 f.close()
34 f.close()
35 return name
35 return name
36
36
37 fcm = repo.filectx(fw, fileid=my)
37 fcm = repo.filectx(fw, fileid=my)
38 fco = repo.filectx(fo, fileid=other)
38 fco = repo.filectx(fo, fileid=other)
39 fca = fcm.ancestor(fco)
39 fca = fcm.ancestor(fco)
40 if not fca:
40 if not fca:
41 fca = repo.filectx(fw, fileid=-1)
41 fca = repo.filectx(fw, fileid=-1)
42 a = repo.wjoin(fw)
42 a = repo.wjoin(fw)
43 b = temp("base", fca)
43 b = temp("base", fca)
44 c = temp("other", fco)
44 c = temp("other", fco)
45
45
46 repo.ui.note(_("resolving %s\n") % fw)
46 repo.ui.note(_("resolving %s\n") % fw)
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': p1,
53 'HG_MY_NODE': p1,
54 'HG_OTHER_NODE': p2})
54 'HG_OTHER_NODE': p2})
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(repo, m2, wctx):
62 def checkunknown(repo, m2, wctx):
63 """
63 """
64 check for collisions between unknown files and files in m2
64 check for collisions between unknown files and files in m2
65 """
65 """
66 for f in wctx.unknown():
66 for f in wctx.unknown():
67 if f in m2:
67 if f in m2:
68 if repo.file(f).cmp(m2[f], repo.wread(f)):
68 if repo.file(f).cmp(m2[f], repo.wread(f)):
69 raise util.Abort(_("'%s' already exists in the working"
69 raise util.Abort(_("'%s' already exists in the working"
70 " dir and differs from remote") % f)
70 " dir and differs from remote") % f)
71
71
72 def forgetremoved(m2, wctx):
72 def forgetremoved(m2, wctx):
73 """
73 """
74 Forget removed files
74 Forget removed files
75
75
76 If we're jumping between revisions (as opposed to merging), and if
76 If we're jumping between revisions (as opposed to merging), and if
77 neither the working directory nor the target rev has the file,
77 neither the working directory nor the target rev has the file,
78 then we need to remove it from the dirstate, to prevent the
78 then we need to remove it from the dirstate, to prevent the
79 dirstate from listing the file when it is no longer in the
79 dirstate from listing the file when it is no longer in the
80 manifest.
80 manifest.
81 """
81 """
82
82
83 action = []
83 action = []
84
84
85 for f in wctx.deleted() + wctx.removed():
85 for f in wctx.deleted() + wctx.removed():
86 if f not in m2:
86 if f not in m2:
87 action.append((f, "f"))
87 action.append((f, "f"))
88
88
89 return action
89 return action
90
90
91 def nonoverlap(d1, d2):
91 def nonoverlap(d1, d2):
92 """
92 """
93 Return list of elements in d1 not in d2
93 Return list of elements in d1 not in d2
94 """
94 """
95
95
96 l = []
96 l = []
97 for d in d1:
97 for d in d1:
98 if d not in d2:
98 if d not in d2:
99 l.append(d)
99 l.append(d)
100
100
101 l.sort()
101 l.sort()
102 return l
102 return l
103
103
104 def findold(fctx, limit):
104 def findold(fctx, limit):
105 """
105 """
106 find files that path was copied from, back to linkrev limit
106 find files that path was copied from, back to linkrev limit
107 """
107 """
108
108
109 old = {}
109 old = {}
110 orig = fctx.path()
110 orig = fctx.path()
111 visit = [fctx]
111 visit = [fctx]
112 while visit:
112 while visit:
113 fc = visit.pop()
113 fc = visit.pop()
114 if fc.rev() < limit:
114 if fc.rev() < limit:
115 continue
115 continue
116 if fc.path() != orig and fc.path() not in old:
116 if fc.path() != orig and fc.path() not in old:
117 old[fc.path()] = 1
117 old[fc.path()] = 1
118 visit += fc.parents()
118 visit += fc.parents()
119
119
120 old = old.keys()
120 old = old.keys()
121 old.sort()
121 old.sort()
122 return old
122 return old
123
123
124 def findcopies(repo, m1, m2, limit):
124 def findcopies(repo, m1, m2, limit):
125 """
125 """
126 Find moves and copies between m1 and m2 back to limit linkrev
126 Find moves and copies between m1 and m2 back to limit linkrev
127 """
127 """
128
128
129 # avoid silly behavior for update from empty dir
129 # avoid silly behavior for update from empty dir
130 if not m1:
130 if not m1:
131 return {}
131 return {}
132
132
133 dcopies = repo.dirstate.copies()
133 dcopies = repo.dirstate.copies()
134 copy = {}
134 copy = {}
135 match = {}
135 match = {}
136 u1 = nonoverlap(m1, m2)
136 u1 = nonoverlap(m1, m2)
137 u2 = nonoverlap(m2, m1)
137 u2 = nonoverlap(m2, m1)
138 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
138 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
139
139
140 def checkpair(c, f2, man):
140 def checkpair(c, f2, man):
141 ''' check if an apparent pair actually matches '''
141 ''' check if an apparent pair actually matches '''
142 c2 = ctx(f2, man[f2])
142 c2 = ctx(f2, man[f2])
143 ca = c.ancestor(c2)
143 ca = c.ancestor(c2)
144 if ca:
144 if ca:
145 copy[c.path()] = f2
145 copy[c.path()] = f2
146 copy[f2] = c.path()
146 copy[f2] = c.path()
147
147
148 for f in u1:
148 for f in u1:
149 c = ctx(dcopies.get(f, f), m1[f])
149 c = ctx(dcopies.get(f, f), m1[f])
150 for of in findold(c, limit):
150 for of in findold(c, limit):
151 if of in m2:
151 if of in m2:
152 checkpair(c, of, m2)
152 checkpair(c, of, m2)
153 else:
153 else:
154 match.setdefault(of, []).append(f)
154 match.setdefault(of, []).append(f)
155
155
156 for f in u2:
156 for f in u2:
157 c = ctx(f, m2[f])
157 c = ctx(f, m2[f])
158 for of in findold(c, limit):
158 for of in findold(c, limit):
159 if of in m1:
159 if of in m1:
160 checkpair(c, of, m1)
160 checkpair(c, of, m1)
161 elif of in match:
161 elif of in match:
162 for mf in match[of]:
162 for mf in match[of]:
163 checkpair(c, mf, m1)
163 checkpair(c, mf, m1)
164
164
165 return copy
165 return copy
166
166
167 def filtermanifest(man, partial):
167 def manifestmerge(ui, m1, m2, ma, overwrite, backwards, partial):
168 if partial:
169 for k in man.keys():
170 if not partial(k): del man[k]
171
172 def manifestmerge(ui, m1, m2, ma, overwrite, backwards):
173 """
168 """
174 Merge manifest m1 with m2 using ancestor ma and generate merge action list
169 Merge manifest m1 with m2 using ancestor ma and generate merge action list
175 """
170 """
176
171
177 def fmerge(f):
172 def fmerge(f):
178 """merge executable flags"""
173 """merge executable flags"""
179 a, b, c = ma.execf(f), m1.execf(f), m2.execf(f)
174 a, b, c = ma.execf(f), m1.execf(f), m2.execf(f)
180 return ((a^b) | (a^c)) ^ a
175 return ((a^b) | (a^c)) ^ a
181
176
182 action = []
177 action = []
183
178
184 def act(msg, f, m, *args):
179 def act(msg, f, m, *args):
185 ui.debug(" %s: %s -> %s\n" % (f, msg, m))
180 ui.debug(" %s: %s -> %s\n" % (f, msg, m))
186 action.append((f, m) + args)
181 action.append((f, m) + args)
187
182
188 # Compare manifests
183 # Compare manifests
189 for f, n in m1.iteritems():
184 for f, n in m1.iteritems():
185 if partial and not partial(f):
186 continue
190 if f in m2:
187 if f in m2:
191 # are files different?
188 # are files different?
192 if n != m2[f]:
189 if n != m2[f]:
193 a = ma.get(f, nullid)
190 a = ma.get(f, nullid)
194 # are both different from the ancestor?
191 # are both different from the ancestor?
195 if not overwrite and n != a and m2[f] != a:
192 if not overwrite and n != a and m2[f] != a:
196 act("versions differ", f, "m", fmerge(f), n[:20], m2[f])
193 act("versions differ", f, "m", fmerge(f), n[:20], m2[f])
197 # are we clobbering?
194 # are we clobbering?
198 # is remote's version newer?
195 # is remote's version newer?
199 # or are we going back in time and clean?
196 # or are we going back in time and clean?
200 elif overwrite or m2[f] != a or (backwards and not n[20:]):
197 elif overwrite or m2[f] != a or (backwards and not n[20:]):
201 act("remote is newer", f, "g", m2.execf(f), m2[f])
198 act("remote is newer", f, "g", m2.execf(f), m2[f])
202 # local is newer, not overwrite, check mode bits
199 # local is newer, not overwrite, check mode bits
203 elif fmerge(f) != m1.execf(f):
200 elif fmerge(f) != m1.execf(f):
204 act("update permissions", f, "e", m2.execf(f))
201 act("update permissions", f, "e", m2.execf(f))
205 # contents same, check mode bits
202 # contents same, check mode bits
206 elif m1.execf(f) != m2.execf(f):
203 elif m1.execf(f) != m2.execf(f):
207 if overwrite or fmerge(f) != m1.execf(f):
204 if overwrite or fmerge(f) != m1.execf(f):
208 act("update permissions", f, "e", m2.execf(f))
205 act("update permissions", f, "e", m2.execf(f))
209 del m2[f]
210 elif f in ma:
206 elif f in ma:
211 if n != ma[f] and not overwrite:
207 if n != ma[f] and not overwrite:
212 if ui.prompt(
208 if ui.prompt(
213 (_(" local changed %s which remote deleted\n") % f) +
209 (_(" local changed %s which remote deleted\n") % f) +
214 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
210 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
215 act("prompt delete", f, "r")
211 act("prompt delete", f, "r")
216 else:
212 else:
217 act("other deleted", f, "r")
213 act("other deleted", f, "r")
218 else:
214 else:
219 # file is created on branch or in working directory
215 # file is created on branch or in working directory
220 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
216 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
221 act("remote deleted", f, "r")
217 act("remote deleted", f, "r")
222
218
223 for f, n in m2.iteritems():
219 for f, n in m2.iteritems():
220 if partial and not partial(f):
221 continue
222 if f in m1:
223 continue
224 if f in ma:
224 if f in ma:
225 if overwrite or backwards:
225 if overwrite or backwards:
226 act("recreating", f, "g", m2.execf(f), n)
226 act("recreating", f, "g", m2.execf(f), n)
227 elif n != ma[f]:
227 elif n != ma[f]:
228 if ui.prompt(
228 if ui.prompt(
229 (_("remote changed %s which local deleted\n") % f) +
229 (_("remote changed %s which local deleted\n") % f) +
230 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
230 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
231 act("prompt recreating", f, "g", m2.execf(f), n)
231 act("prompt recreating", f, "g", m2.execf(f), n)
232 else:
232 else:
233 act("remote created", f, "g", m2.execf(f), n)
233 act("remote created", f, "g", m2.execf(f), n)
234
234
235 return action
235 return action
236
236
237 def applyupdates(repo, action, xp1, xp2):
237 def applyupdates(repo, action, xp1, xp2):
238 updated, merged, removed, unresolved = 0, 0, 0, 0
238 updated, merged, removed, unresolved = 0, 0, 0, 0
239 action.sort()
239 action.sort()
240 for a in action:
240 for a in action:
241 f, m = a[:2]
241 f, m = a[:2]
242 if f[0] == "/":
242 if f[0] == "/":
243 continue
243 continue
244 if m == "r": # remove
244 if m == "r": # remove
245 repo.ui.note(_("removing %s\n") % f)
245 repo.ui.note(_("removing %s\n") % f)
246 util.audit_path(f)
246 util.audit_path(f)
247 try:
247 try:
248 util.unlink(repo.wjoin(f))
248 util.unlink(repo.wjoin(f))
249 except OSError, inst:
249 except OSError, inst:
250 if inst.errno != errno.ENOENT:
250 if inst.errno != errno.ENOENT:
251 repo.ui.warn(_("update failed to remove %s: %s!\n") %
251 repo.ui.warn(_("update failed to remove %s: %s!\n") %
252 (f, inst.strerror))
252 (f, inst.strerror))
253 removed +=1
253 removed +=1
254 elif m == "m": # merge
254 elif m == "m": # merge
255 flag, my, other = a[2:]
255 flag, my, other = a[2:]
256 repo.ui.status(_("merging %s\n") % f)
256 repo.ui.status(_("merging %s\n") % f)
257 if filemerge(repo, f, f, f, my, other, xp1, xp2, False):
257 if filemerge(repo, f, f, f, my, other, xp1, xp2, False):
258 unresolved += 1
258 unresolved += 1
259 util.set_exec(repo.wjoin(f), flag)
259 util.set_exec(repo.wjoin(f), flag)
260 merged += 1
260 merged += 1
261 elif m == "g": # get
261 elif m == "g": # get
262 flag, node = a[2:]
262 flag, node = a[2:]
263 repo.ui.note(_("getting %s\n") % f)
263 repo.ui.note(_("getting %s\n") % f)
264 t = repo.file(f).read(node)
264 t = repo.file(f).read(node)
265 repo.wwrite(f, t)
265 repo.wwrite(f, t)
266 util.set_exec(repo.wjoin(f), flag)
266 util.set_exec(repo.wjoin(f), flag)
267 updated += 1
267 updated += 1
268 elif m == "e": # exec
268 elif m == "e": # exec
269 flag = a[2:]
269 flag = a[2:]
270 util.set_exec(repo.wjoin(f), flag)
270 util.set_exec(repo.wjoin(f), flag)
271
271
272 return updated, merged, removed, unresolved
272 return updated, merged, removed, unresolved
273
273
274 def recordupdates(repo, action, branchmerge):
274 def recordupdates(repo, action, branchmerge):
275 for a in action:
275 for a in action:
276 f, m = a[:2]
276 f, m = a[:2]
277 if m == "r": # remove
277 if m == "r": # remove
278 if branchmerge:
278 if branchmerge:
279 repo.dirstate.update([f], 'r')
279 repo.dirstate.update([f], 'r')
280 else:
280 else:
281 repo.dirstate.forget([f])
281 repo.dirstate.forget([f])
282 elif m == "f": # forget
282 elif m == "f": # forget
283 repo.dirstate.forget([f])
283 repo.dirstate.forget([f])
284 elif m == "g": # get
284 elif m == "g": # get
285 if branchmerge:
285 if branchmerge:
286 repo.dirstate.update([f], 'n', st_mtime=-1)
286 repo.dirstate.update([f], 'n', st_mtime=-1)
287 else:
287 else:
288 repo.dirstate.update([f], 'n')
288 repo.dirstate.update([f], 'n')
289 elif m == "m": # merge
289 elif m == "m": # merge
290 flag, my, other = a[2:]
290 flag, my, other = a[2:]
291 if branchmerge:
291 if branchmerge:
292 # We've done a branch merge, mark this file as merged
292 # We've done a branch merge, mark this file as merged
293 # so that we properly record the merger later
293 # so that we properly record the merger later
294 repo.dirstate.update([f], 'm')
294 repo.dirstate.update([f], 'm')
295 else:
295 else:
296 # We've update-merged a locally modified file, so
296 # We've update-merged a locally modified file, so
297 # we set the dirstate to emulate a normal checkout
297 # we set the dirstate to emulate a normal checkout
298 # of that file some time in the past. Thus our
298 # of that file some time in the past. Thus our
299 # merge will appear as a normal local file
299 # merge will appear as a normal local file
300 # modification.
300 # modification.
301 fl = repo.file(f)
301 fl = repo.file(f)
302 f_len = fl.size(fl.rev(other))
302 f_len = fl.size(fl.rev(other))
303 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
303 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
304
304
305 def update(repo, node, branchmerge=False, force=False, partial=None,
305 def update(repo, node, branchmerge=False, force=False, partial=None,
306 wlock=None, show_stats=True, remind=True):
306 wlock=None, show_stats=True, remind=True):
307
307
308 overwrite = force and not branchmerge
308 overwrite = force and not branchmerge
309 forcemerge = force and branchmerge
309 forcemerge = force and branchmerge
310
310
311 if not wlock:
311 if not wlock:
312 wlock = repo.wlock()
312 wlock = repo.wlock()
313
313
314 ### check phase
314 ### check phase
315
315
316 wc = repo.workingctx()
316 wc = repo.workingctx()
317 pl = wc.parents()
317 pl = wc.parents()
318 if not overwrite and len(pl) > 1:
318 if not overwrite and len(pl) > 1:
319 raise util.Abort(_("outstanding uncommitted merges"))
319 raise util.Abort(_("outstanding uncommitted merges"))
320
320
321 p1, p2 = pl[0], repo.changectx(node)
321 p1, p2 = pl[0], repo.changectx(node)
322 pa = p1.ancestor(p2)
322 pa = p1.ancestor(p2)
323
323
324 # are we going backwards?
324 # are we going backwards?
325 backwards = (pa == p2)
325 backwards = (pa == p2)
326
326
327 # is there a linear path from p1 to p2?
327 # is there a linear path from p1 to p2?
328 if pa == p1 or pa == p2:
328 if pa == p1 or pa == p2:
329 if branchmerge:
329 if branchmerge:
330 raise util.Abort(_("there is nothing to merge, just use "
330 raise util.Abort(_("there is nothing to merge, just use "
331 "'hg update' or look at 'hg heads'"))
331 "'hg update' or look at 'hg heads'"))
332 elif not (overwrite or branchmerge):
332 elif not (overwrite or branchmerge):
333 raise util.Abort(_("update spans branches, use 'hg merge' "
333 raise util.Abort(_("update spans branches, use 'hg merge' "
334 "or 'hg update -C' to lose changes"))
334 "or 'hg update -C' to lose changes"))
335
335
336 if branchmerge and not forcemerge:
336 if branchmerge and not forcemerge:
337 if wc.modified() or wc.added() or wc.removed():
337 if wc.modified() or wc.added() or wc.removed():
338 raise util.Abort(_("outstanding uncommitted changes"))
338 raise util.Abort(_("outstanding uncommitted changes"))
339
339
340 m1 = wc.manifest().copy()
340 m1 = wc.manifest()
341 m2 = p2.manifest().copy()
341 m2 = p2.manifest()
342 ma = pa.manifest()
342 ma = pa.manifest()
343
343
344 # resolve the manifest to determine which files
344 # resolve the manifest to determine which files
345 # we care about merging
345 # we care about merging
346 repo.ui.note(_("resolving manifests\n"))
346 repo.ui.note(_("resolving manifests\n"))
347 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s\n") %
347 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s\n") %
348 (overwrite, branchmerge, bool(partial)))
348 (overwrite, branchmerge, bool(partial)))
349 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (p1, p2, pa))
349 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (p1, p2, pa))
350
350
351 action = []
351 action = []
352 copy = {}
352 copy = {}
353
353
354 filtermanifest(m1, partial)
355 filtermanifest(m2, partial)
356
357 if not force:
354 if not force:
358 checkunknown(repo, m2, wc)
355 checkunknown(repo, m2, wc)
359 if not branchmerge:
356 if not branchmerge:
360 action += forgetremoved(m2, wc)
357 action += forgetremoved(m2, wc)
361 if not (backwards or overwrite):
358 if not (backwards or overwrite):
362 copy = findcopies(repo, m1, m2, pa.rev())
359 copy = findcopies(repo, m1, m2, pa.rev())
363
360
364 action += manifestmerge(repo.ui, m1, m2, ma, overwrite, backwards)
361 action += manifestmerge(repo.ui, m1, m2, ma, overwrite, backwards, partial)
365 del m1, m2, ma
366
362
367 ### apply phase
363 ### apply phase
368
364
369 if not branchmerge:
365 if not branchmerge:
370 # we don't need to do any magic, just jump to the new rev
366 # we don't need to do any magic, just jump to the new rev
371 p1, p2 = p2, repo.changectx(nullid)
367 p1, p2 = p2, repo.changectx(nullid)
372
368
373 xp1, xp2 = str(p1), str(p2)
369 xp1, xp2 = str(p1), str(p2)
374 if not p2: xp2 = ''
370 if not p2: xp2 = ''
375
371
376 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
372 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
377
373
378 updated, merged, removed, unresolved = applyupdates(repo, action, xp1, xp2)
374 updated, merged, removed, unresolved = applyupdates(repo, action, xp1, xp2)
379
375
380 # update dirstate
376 # update dirstate
381 if not partial:
377 if not partial:
382 recordupdates(repo, action, branchmerge)
378 recordupdates(repo, action, branchmerge)
383 repo.dirstate.setparents(p1.node(), p2.node())
379 repo.dirstate.setparents(p1.node(), p2.node())
384
380
385 if show_stats:
381 if show_stats:
386 stats = ((updated, _("updated")),
382 stats = ((updated, _("updated")),
387 (merged - unresolved, _("merged")),
383 (merged - unresolved, _("merged")),
388 (removed, _("removed")),
384 (removed, _("removed")),
389 (unresolved, _("unresolved")))
385 (unresolved, _("unresolved")))
390 note = ", ".join([_("%d files %s") % s for s in stats])
386 note = ", ".join([_("%d files %s") % s for s in stats])
391 repo.ui.status("%s\n" % note)
387 repo.ui.status("%s\n" % note)
392 if not partial:
388 if not partial:
393 if branchmerge:
389 if branchmerge:
394 if unresolved:
390 if unresolved:
395 repo.ui.status(_("There are unresolved merges,"
391 repo.ui.status(_("There are unresolved merges,"
396 " you can redo the full merge using:\n"
392 " you can redo the full merge using:\n"
397 " hg update -C %s\n"
393 " hg update -C %s\n"
398 " hg merge %s\n"
394 " hg merge %s\n"
399 % (p1.rev(), p2.rev())))
395 % (p1.rev(), p2.rev())))
400 elif remind:
396 elif remind:
401 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
397 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
402 elif unresolved:
398 elif unresolved:
403 repo.ui.status(_("There are unresolved merges with"
399 repo.ui.status(_("There are unresolved merges with"
404 " locally modified files.\n"))
400 " locally modified files.\n"))
405
401
406 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
402 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
407 return unresolved
403 return unresolved
408
404
General Comments 0
You need to be logged in to leave comments. Login now