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