##// END OF EJS Templates
merge: reorder dirstate update slightly for correctness
Matt Mackall -
r3247:7a0d70b6 default
parent child Browse files
Show More
@@ -1,408 +1,408 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 filtermanifest(man, partial):
168 if partial:
168 if partial:
169 for k in man.keys():
169 for k in man.keys():
170 if not partial(k): del man[k]
170 if not partial(k): del man[k]
171
171
172 def manifestmerge(ui, m1, m2, ma, overwrite, backwards):
172 def manifestmerge(ui, m1, m2, ma, overwrite, backwards):
173 """
173 """
174 Merge manifest m1 with m2 using ancestor ma and generate merge action list
174 Merge manifest m1 with m2 using ancestor ma and generate merge action list
175 """
175 """
176
176
177 def fmerge(f):
177 def fmerge(f):
178 """merge executable flags"""
178 """merge executable flags"""
179 a, b, c = ma.execf(f), m1.execf(f), m2.execf(f)
179 a, b, c = ma.execf(f), m1.execf(f), m2.execf(f)
180 return ((a^b) | (a^c)) ^ a
180 return ((a^b) | (a^c)) ^ a
181
181
182 action = []
182 action = []
183
183
184 def act(msg, f, m, *args):
184 def act(msg, f, m, *args):
185 ui.debug(" %s: %s -> %s\n" % (f, msg, m))
185 ui.debug(" %s: %s -> %s\n" % (f, msg, m))
186 action.append((f, m) + args)
186 action.append((f, m) + args)
187
187
188 # Compare manifests
188 # Compare manifests
189 for f, n in m1.iteritems():
189 for f, n in m1.iteritems():
190 if f in m2:
190 if f in m2:
191 # are files different?
191 # are files different?
192 if n != m2[f]:
192 if n != m2[f]:
193 a = ma.get(f, nullid)
193 a = ma.get(f, nullid)
194 # are both different from the ancestor?
194 # are both different from the ancestor?
195 if not overwrite and n != a and m2[f] != a:
195 if not overwrite and n != a and m2[f] != a:
196 act("versions differ", f, "m", fmerge(f), n[:20], m2[f])
196 act("versions differ", f, "m", fmerge(f), n[:20], m2[f])
197 # are we clobbering?
197 # are we clobbering?
198 # is remote's version newer?
198 # is remote's version newer?
199 # or are we going back in time and clean?
199 # or are we going back in time and clean?
200 elif overwrite or m2[f] != a or (backwards and not n[20:]):
200 elif overwrite or m2[f] != a or (backwards and not n[20:]):
201 act("remote is newer", f, "g", m2.execf(f), m2[f])
201 act("remote is newer", f, "g", m2.execf(f), m2[f])
202 # local is newer, not overwrite, check mode bits
202 # local is newer, not overwrite, check mode bits
203 elif fmerge(f) != m1.execf(f):
203 elif fmerge(f) != m1.execf(f):
204 act("update permissions", f, "e", m2.execf(f))
204 act("update permissions", f, "e", m2.execf(f))
205 # contents same, check mode bits
205 # contents same, check mode bits
206 elif m1.execf(f) != m2.execf(f):
206 elif m1.execf(f) != m2.execf(f):
207 if overwrite or fmerge(f) != m1.execf(f):
207 if overwrite or fmerge(f) != m1.execf(f):
208 act("update permissions", f, "e", m2.execf(f))
208 act("update permissions", f, "e", m2.execf(f))
209 del m2[f]
209 del m2[f]
210 elif f in ma:
210 elif f in ma:
211 if n != ma[f] and not overwrite:
211 if n != ma[f] and not overwrite:
212 if ui.prompt(
212 if ui.prompt(
213 (_(" local changed %s which remote deleted\n") % f) +
213 (_(" local changed %s which remote deleted\n") % f) +
214 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
214 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
215 act("prompt delete", f, "r")
215 act("prompt delete", f, "r")
216 else:
216 else:
217 act("other deleted", f, "r")
217 act("other deleted", f, "r")
218 else:
218 else:
219 # file is created on branch or in working directory
219 # file is created on branch or in working directory
220 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
220 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
221 act("remote deleted", f, "r")
221 act("remote deleted", f, "r")
222
222
223 for f, n in m2.iteritems():
223 for f, n in m2.iteritems():
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().copy()
341 m2 = p2.manifest().copy()
341 m2 = p2.manifest().copy()
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)
354 filtermanifest(m1, partial)
355 filtermanifest(m2, partial)
355 filtermanifest(m2, partial)
356
356
357 if not force:
357 if not force:
358 checkunknown(repo, m2, wc)
358 checkunknown(repo, m2, wc)
359 if not branchmerge:
359 if not branchmerge:
360 action += forgetremoved(m2, wc)
360 action += forgetremoved(m2, wc)
361 if not (backwards or overwrite):
361 if not (backwards or overwrite):
362 copy = findcopies(repo, m1, m2, pa.rev())
362 copy = findcopies(repo, m1, m2, pa.rev())
363
363
364 action += manifestmerge(repo.ui, m1, m2, ma, overwrite, backwards)
364 action += manifestmerge(repo.ui, m1, m2, ma, overwrite, backwards)
365 del m1, m2, ma
365 del m1, m2, ma
366
366
367 ### apply phase
367 ### apply phase
368
368
369 if not branchmerge:
369 if not branchmerge:
370 # we don't need to do any magic, just jump to the new rev
370 # we don't need to do any magic, just jump to the new rev
371 p1, p2 = p2, repo.changectx(nullid)
371 p1, p2 = p2, repo.changectx(nullid)
372
372
373 xp1, xp2 = str(p1), str(p2)
373 xp1, xp2 = str(p1), str(p2)
374 if not p2: xp2 = ''
374 if not p2: xp2 = ''
375
375
376 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
376 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
377
377
378 updated, merged, removed, unresolved = applyupdates(repo, action, xp1, xp2)
378 updated, merged, removed, unresolved = applyupdates(repo, action, xp1, xp2)
379
379
380 # update dirstate
380 # update dirstate
381 if not partial:
381 if not partial:
382 recordupdates(repo, action, branchmerge)
382 repo.dirstate.setparents(p1.node(), p2.node())
383 repo.dirstate.setparents(p1.node(), p2.node())
383 recordupdates(repo, action, branchmerge)
384
384
385 if show_stats:
385 if show_stats:
386 stats = ((updated, _("updated")),
386 stats = ((updated, _("updated")),
387 (merged - unresolved, _("merged")),
387 (merged - unresolved, _("merged")),
388 (removed, _("removed")),
388 (removed, _("removed")),
389 (unresolved, _("unresolved")))
389 (unresolved, _("unresolved")))
390 note = ", ".join([_("%d files %s") % s for s in stats])
390 note = ", ".join([_("%d files %s") % s for s in stats])
391 repo.ui.status("%s\n" % note)
391 repo.ui.status("%s\n" % note)
392 if not partial:
392 if not partial:
393 if branchmerge:
393 if branchmerge:
394 if unresolved:
394 if unresolved:
395 repo.ui.status(_("There are unresolved merges,"
395 repo.ui.status(_("There are unresolved merges,"
396 " you can redo the full merge using:\n"
396 " you can redo the full merge using:\n"
397 " hg update -C %s\n"
397 " hg update -C %s\n"
398 " hg merge %s\n"
398 " hg merge %s\n"
399 % (p1.rev(), p2.rev())))
399 % (p1.rev(), p2.rev())))
400 elif remind:
400 elif remind:
401 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
401 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
402 elif unresolved:
402 elif unresolved:
403 repo.ui.status(_("There are unresolved merges with"
403 repo.ui.status(_("There are unresolved merges with"
404 " locally modified files.\n"))
404 " locally modified files.\n"))
405
405
406 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
406 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
407 return unresolved
407 return unresolved
408
408
General Comments 0
You need to be logged in to leave comments. Login now