##// END OF EJS Templates
merge: don't call hooks for revert...
Matt Mackall -
r3296:20087b4b default
parent child Browse files
Show More
@@ -1,466 +1,466
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 else:
57 else:
58 if fd != fw:
58 if fd != fw:
59 repo.ui.debug(_("copying %s to %s\n") % (fw, fd))
59 repo.ui.debug(_("copying %s to %s\n") % (fw, fd))
60 repo.wwrite(fd, repo.wread(fw))
60 repo.wwrite(fd, repo.wread(fw))
61 if move:
61 if move:
62 repo.ui.debug(_("removing %s\n") % fw)
62 repo.ui.debug(_("removing %s\n") % fw)
63 os.unlink(a)
63 os.unlink(a)
64
64
65 os.unlink(b)
65 os.unlink(b)
66 os.unlink(c)
66 os.unlink(c)
67 return r
67 return r
68
68
69 def checkunknown(repo, m2, wctx):
69 def checkunknown(repo, m2, wctx):
70 """
70 """
71 check for collisions between unknown files and files in m2
71 check for collisions between unknown files and files in m2
72 """
72 """
73 for f in wctx.unknown():
73 for f in wctx.unknown():
74 if f in m2:
74 if f in m2:
75 if repo.file(f).cmp(m2[f], repo.wread(f)):
75 if repo.file(f).cmp(m2[f], repo.wread(f)):
76 raise util.Abort(_("'%s' already exists in the working"
76 raise util.Abort(_("'%s' already exists in the working"
77 " dir and differs from remote") % f)
77 " dir and differs from remote") % f)
78
78
79 def forgetremoved(m2, wctx):
79 def forgetremoved(m2, wctx):
80 """
80 """
81 Forget removed files
81 Forget removed files
82
82
83 If we're jumping between revisions (as opposed to merging), and if
83 If we're jumping between revisions (as opposed to merging), and if
84 neither the working directory nor the target rev has the file,
84 neither the working directory nor the target rev has the file,
85 then we need to remove it from the dirstate, to prevent the
85 then we need to remove it from the dirstate, to prevent the
86 dirstate from listing the file when it is no longer in the
86 dirstate from listing the file when it is no longer in the
87 manifest.
87 manifest.
88 """
88 """
89
89
90 action = []
90 action = []
91
91
92 for f in wctx.deleted() + wctx.removed():
92 for f in wctx.deleted() + wctx.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 if not repo.ui.config("merge", "followcopies"):
136 if not repo.ui.config("merge", "followcopies"):
137 return {}
137 return {}
138
138
139 # avoid silly behavior for update from empty dir
139 # avoid silly behavior for update from empty dir
140 if not m1:
140 if not m1:
141 return {}
141 return {}
142
142
143 dcopies = repo.dirstate.copies()
143 dcopies = repo.dirstate.copies()
144 copy = {}
144 copy = {}
145 match = {}
145 match = {}
146 u1 = nonoverlap(m1, m2)
146 u1 = nonoverlap(m1, m2)
147 u2 = nonoverlap(m2, m1)
147 u2 = nonoverlap(m2, m1)
148 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
148 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
149
149
150 def checkpair(c, f2, man):
150 def checkpair(c, f2, man):
151 ''' check if an apparent pair actually matches '''
151 ''' check if an apparent pair actually matches '''
152 c2 = ctx(f2, man[f2])
152 c2 = ctx(f2, man[f2])
153 ca = c.ancestor(c2)
153 ca = c.ancestor(c2)
154 if ca and ca.path() == c.path() or ca.path() == c2.path():
154 if ca and ca.path() == c.path() or ca.path() == c2.path():
155 copy[c.path()] = f2
155 copy[c.path()] = f2
156 copy[f2] = c.path()
156 copy[f2] = c.path()
157
157
158 for f in u1:
158 for f in u1:
159 c = ctx(dcopies.get(f, f), m1[f])
159 c = ctx(dcopies.get(f, f), m1[f])
160 for of in findold(c, limit):
160 for of in findold(c, limit):
161 if of in m2:
161 if of in m2:
162 checkpair(c, of, m2)
162 checkpair(c, of, m2)
163 else:
163 else:
164 match.setdefault(of, []).append(f)
164 match.setdefault(of, []).append(f)
165
165
166 for f in u2:
166 for f in u2:
167 c = ctx(f, m2[f])
167 c = ctx(f, m2[f])
168 for of in findold(c, limit):
168 for of in findold(c, limit):
169 if of in m1:
169 if of in m1:
170 checkpair(c, of, m1)
170 checkpair(c, of, m1)
171 elif of in match:
171 elif of in match:
172 for mf in match[of]:
172 for mf in match[of]:
173 checkpair(c, mf, m1)
173 checkpair(c, mf, m1)
174
174
175 return copy
175 return copy
176
176
177 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
177 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
178 """
178 """
179 Merge manifest m1 with m2 using ancestor ma and generate merge action list
179 Merge manifest m1 with m2 using ancestor ma and generate merge action list
180 """
180 """
181
181
182 m1 = p1.manifest()
182 m1 = p1.manifest()
183 m2 = p2.manifest()
183 m2 = p2.manifest()
184 ma = pa.manifest()
184 ma = pa.manifest()
185 backwards = (pa == p2)
185 backwards = (pa == p2)
186
186
187 def fmerge(f, f2=None, fa=None):
187 def fmerge(f, f2=None, fa=None):
188 """merge executable flags"""
188 """merge executable flags"""
189 if not f2:
189 if not f2:
190 f2 = f
190 f2 = f
191 fa = f
191 fa = f
192 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
192 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
193 return ((a^b) | (a^c)) ^ a
193 return ((a^b) | (a^c)) ^ a
194
194
195 action = []
195 action = []
196
196
197 def act(msg, f, m, *args):
197 def act(msg, f, m, *args):
198 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
198 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
199 action.append((f, m) + args)
199 action.append((f, m) + args)
200
200
201 copy = {}
201 copy = {}
202 if not (backwards or overwrite):
202 if not (backwards or overwrite):
203 copy = findcopies(repo, m1, m2, pa.rev())
203 copy = findcopies(repo, m1, m2, pa.rev())
204
204
205 # Compare manifests
205 # Compare manifests
206 for f, n in m1.iteritems():
206 for f, n in m1.iteritems():
207 if partial and not partial(f):
207 if partial and not partial(f):
208 continue
208 continue
209 if f in m2:
209 if f in m2:
210 # are files different?
210 # are files different?
211 if n != m2[f]:
211 if n != m2[f]:
212 a = ma.get(f, nullid)
212 a = ma.get(f, nullid)
213 # are both different from the ancestor?
213 # are both different from the ancestor?
214 if not overwrite and n != a and m2[f] != a:
214 if not overwrite and n != a and m2[f] != a:
215 act("versions differ", f, "m", fmerge(f), n[:20], m2[f])
215 act("versions differ", f, "m", fmerge(f), n[:20], m2[f])
216 # are we clobbering?
216 # are we clobbering?
217 # is remote's version newer?
217 # is remote's version newer?
218 # or are we going back in time and clean?
218 # or are we going back in time and clean?
219 elif overwrite or m2[f] != a or (backwards and not n[20:]):
219 elif overwrite or m2[f] != a or (backwards and not n[20:]):
220 act("remote is newer", f, "g", m2.execf(f), m2[f])
220 act("remote is newer", f, "g", m2.execf(f), m2[f])
221 # local is newer, not overwrite, check mode bits
221 # local is newer, not overwrite, check mode bits
222 elif fmerge(f) != m1.execf(f):
222 elif fmerge(f) != m1.execf(f):
223 act("update permissions", f, "e", m2.execf(f))
223 act("update permissions", f, "e", m2.execf(f))
224 # contents same, check mode bits
224 # contents same, check mode bits
225 elif m1.execf(f) != m2.execf(f):
225 elif m1.execf(f) != m2.execf(f):
226 if overwrite or fmerge(f) != m1.execf(f):
226 if overwrite or fmerge(f) != m1.execf(f):
227 act("update permissions", f, "e", m2.execf(f))
227 act("update permissions", f, "e", m2.execf(f))
228 elif f in copy:
228 elif f in copy:
229 f2 = copy[f]
229 f2 = copy[f]
230 if f in ma: # case 3,20 A/B/A
230 if f in ma: # case 3,20 A/B/A
231 act("remote moved",
231 act("remote moved",
232 f, "c", f2, f2, m1[f], m2[f2], fmerge(f, f2, f), True)
232 f, "c", f2, f2, m1[f], m2[f2], fmerge(f, f2, f), True)
233 else:
233 else:
234 if f2 in m1: # case 2 A,B/B/B
234 if f2 in m1: # case 2 A,B/B/B
235 act("local copied",
235 act("local copied",
236 f, "c", f2, f, m1[f], m2[f2], fmerge(f, f2, f2), False)
236 f, "c", f2, f, m1[f], m2[f2], fmerge(f, f2, f2), False)
237 else: # case 4,21 A/B/B
237 else: # case 4,21 A/B/B
238 act("local moved",
238 act("local moved",
239 f, "c", f2, f, m1[f], m2[f2], fmerge(f, f2, f2), False)
239 f, "c", f2, f, m1[f], m2[f2], fmerge(f, f2, f2), False)
240 elif f in ma:
240 elif f in ma:
241 if n != ma[f] and not overwrite:
241 if n != ma[f] and not overwrite:
242 if repo.ui.prompt(
242 if repo.ui.prompt(
243 (_(" local changed %s which remote deleted\n") % f) +
243 (_(" local changed %s which remote deleted\n") % f) +
244 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
244 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
245 act("prompt delete", f, "r")
245 act("prompt delete", f, "r")
246 else:
246 else:
247 act("other deleted", f, "r")
247 act("other deleted", f, "r")
248 else:
248 else:
249 # file is created on branch or in working directory
249 # file is created on branch or in working directory
250 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
250 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
251 act("remote deleted", f, "r")
251 act("remote deleted", f, "r")
252
252
253 for f, n in m2.iteritems():
253 for f, n in m2.iteritems():
254 if partial and not partial(f):
254 if partial and not partial(f):
255 continue
255 continue
256 if f in m1:
256 if f in m1:
257 continue
257 continue
258 if f in copy:
258 if f in copy:
259 f2 = copy[f]
259 f2 = copy[f]
260 if f2 not in m2: # already seen
260 if f2 not in m2: # already seen
261 continue
261 continue
262 # rename case 1, A/A,B/A
262 # rename case 1, A/A,B/A
263 act("remote copied",
263 act("remote copied",
264 f2, "c", f, f, m1[f2], m2[f], fmerge(f2, f, f2), False)
264 f2, "c", f, f, m1[f2], m2[f], fmerge(f2, f, f2), False)
265 elif f in ma:
265 elif f in ma:
266 if overwrite or backwards:
266 if overwrite or backwards:
267 act("recreating", f, "g", m2.execf(f), n)
267 act("recreating", f, "g", m2.execf(f), n)
268 elif n != ma[f]:
268 elif n != ma[f]:
269 if repo.ui.prompt(
269 if repo.ui.prompt(
270 (_("remote changed %s which local deleted\n") % f) +
270 (_("remote changed %s which local deleted\n") % f) +
271 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
271 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
272 act("prompt recreating", f, "g", m2.execf(f), n)
272 act("prompt recreating", f, "g", m2.execf(f), n)
273 else:
273 else:
274 act("remote created", f, "g", m2.execf(f), n)
274 act("remote created", f, "g", m2.execf(f), n)
275
275
276 return action
276 return action
277
277
278 def applyupdates(repo, action, xp1, xp2):
278 def applyupdates(repo, action, xp1, xp2):
279 updated, merged, removed, unresolved = 0, 0, 0, 0
279 updated, merged, removed, unresolved = 0, 0, 0, 0
280 action.sort()
280 action.sort()
281 for a in action:
281 for a in action:
282 f, m = a[:2]
282 f, m = a[:2]
283 if f[0] == "/":
283 if f[0] == "/":
284 continue
284 continue
285 if m == "r": # remove
285 if m == "r": # remove
286 repo.ui.note(_("removing %s\n") % f)
286 repo.ui.note(_("removing %s\n") % f)
287 util.audit_path(f)
287 util.audit_path(f)
288 try:
288 try:
289 util.unlink(repo.wjoin(f))
289 util.unlink(repo.wjoin(f))
290 except OSError, inst:
290 except OSError, inst:
291 if inst.errno != errno.ENOENT:
291 if inst.errno != errno.ENOENT:
292 repo.ui.warn(_("update failed to remove %s: %s!\n") %
292 repo.ui.warn(_("update failed to remove %s: %s!\n") %
293 (f, inst.strerror))
293 (f, inst.strerror))
294 removed +=1
294 removed +=1
295 elif m == "c": # copy
295 elif m == "c": # copy
296 f2, fd, my, other, flag, move = a[2:]
296 f2, fd, my, other, flag, move = a[2:]
297 repo.ui.status(_("merging %s and %s to %s\n") % (f, f2, fd))
297 repo.ui.status(_("merging %s and %s to %s\n") % (f, f2, fd))
298 if filemerge(repo, f, f2, fd, my, other, xp1, xp2, move):
298 if filemerge(repo, f, f2, fd, my, other, xp1, xp2, move):
299 unresolved += 1
299 unresolved += 1
300 util.set_exec(repo.wjoin(fd), flag)
300 util.set_exec(repo.wjoin(fd), flag)
301 merged += 1
301 merged += 1
302 elif m == "m": # merge
302 elif m == "m": # merge
303 flag, my, other = a[2:]
303 flag, my, other = a[2:]
304 repo.ui.status(_("merging %s\n") % f)
304 repo.ui.status(_("merging %s\n") % f)
305 if filemerge(repo, f, f, f, my, other, xp1, xp2, False):
305 if filemerge(repo, f, f, f, my, other, xp1, xp2, False):
306 unresolved += 1
306 unresolved += 1
307 util.set_exec(repo.wjoin(f), flag)
307 util.set_exec(repo.wjoin(f), flag)
308 merged += 1
308 merged += 1
309 elif m == "g": # get
309 elif m == "g": # get
310 flag, node = a[2:]
310 flag, node = a[2:]
311 repo.ui.note(_("getting %s\n") % f)
311 repo.ui.note(_("getting %s\n") % f)
312 t = repo.file(f).read(node)
312 t = repo.file(f).read(node)
313 repo.wwrite(f, t)
313 repo.wwrite(f, t)
314 util.set_exec(repo.wjoin(f), flag)
314 util.set_exec(repo.wjoin(f), flag)
315 updated += 1
315 updated += 1
316 elif m == "e": # exec
316 elif m == "e": # exec
317 flag = a[2:]
317 flag = a[2:]
318 util.set_exec(repo.wjoin(f), flag)
318 util.set_exec(repo.wjoin(f), flag)
319
319
320 return updated, merged, removed, unresolved
320 return updated, merged, removed, unresolved
321
321
322 def recordupdates(repo, action, branchmerge):
322 def recordupdates(repo, action, branchmerge):
323 for a in action:
323 for a in action:
324 f, m = a[:2]
324 f, m = a[:2]
325 if m == "r": # remove
325 if m == "r": # remove
326 if branchmerge:
326 if branchmerge:
327 repo.dirstate.update([f], 'r')
327 repo.dirstate.update([f], 'r')
328 else:
328 else:
329 repo.dirstate.forget([f])
329 repo.dirstate.forget([f])
330 elif m == "f": # forget
330 elif m == "f": # forget
331 repo.dirstate.forget([f])
331 repo.dirstate.forget([f])
332 elif m == "g": # get
332 elif m == "g": # get
333 if branchmerge:
333 if branchmerge:
334 repo.dirstate.update([f], 'n', st_mtime=-1)
334 repo.dirstate.update([f], 'n', st_mtime=-1)
335 else:
335 else:
336 repo.dirstate.update([f], 'n')
336 repo.dirstate.update([f], 'n')
337 elif m == "m": # merge
337 elif m == "m": # merge
338 flag, my, other = a[2:]
338 flag, my, other = a[2:]
339 if branchmerge:
339 if branchmerge:
340 # We've done a branch merge, mark this file as merged
340 # We've done a branch merge, mark this file as merged
341 # so that we properly record the merger later
341 # so that we properly record the merger later
342 repo.dirstate.update([f], 'm')
342 repo.dirstate.update([f], 'm')
343 else:
343 else:
344 # We've update-merged a locally modified file, so
344 # We've update-merged a locally modified file, so
345 # we set the dirstate to emulate a normal checkout
345 # we set the dirstate to emulate a normal checkout
346 # of that file some time in the past. Thus our
346 # of that file some time in the past. Thus our
347 # merge will appear as a normal local file
347 # merge will appear as a normal local file
348 # modification.
348 # modification.
349 fl = repo.file(f)
349 fl = repo.file(f)
350 f_len = fl.size(fl.rev(other))
350 f_len = fl.size(fl.rev(other))
351 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
351 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
352 elif m == "c": # copy
352 elif m == "c": # copy
353 f2, fd, my, other, flag, move = a[2:]
353 f2, fd, my, other, flag, move = a[2:]
354 if branchmerge:
354 if branchmerge:
355 # We've done a branch merge, mark this file as merged
355 # We've done a branch merge, mark this file as merged
356 # so that we properly record the merger later
356 # so that we properly record the merger later
357 repo.dirstate.update([fd], 'm')
357 repo.dirstate.update([fd], 'm')
358 else:
358 else:
359 # We've update-merged a locally modified file, so
359 # We've update-merged a locally modified file, so
360 # we set the dirstate to emulate a normal checkout
360 # we set the dirstate to emulate a normal checkout
361 # of that file some time in the past. Thus our
361 # of that file some time in the past. Thus our
362 # merge will appear as a normal local file
362 # merge will appear as a normal local file
363 # modification.
363 # modification.
364 fl = repo.file(f)
364 fl = repo.file(f)
365 f_len = fl.size(fl.rev(other))
365 f_len = fl.size(fl.rev(other))
366 repo.dirstate.update([fd], 'n', st_size=f_len, st_mtime=-1)
366 repo.dirstate.update([fd], 'n', st_size=f_len, st_mtime=-1)
367 if move:
367 if move:
368 repo.dirstate.update([f], 'r')
368 repo.dirstate.update([f], 'r')
369 if f != fd:
369 if f != fd:
370 repo.dirstate.copy(f, fd)
370 repo.dirstate.copy(f, fd)
371 else:
371 else:
372 repo.dirstate.copy(f2, fd)
372 repo.dirstate.copy(f2, fd)
373
373
374 def update(repo, node, branchmerge=False, force=False, partial=None,
374 def update(repo, node, branchmerge=False, force=False, partial=None,
375 wlock=None, show_stats=True, remind=True):
375 wlock=None, show_stats=True, remind=True):
376
376
377 overwrite = force and not branchmerge
377 overwrite = force and not branchmerge
378 forcemerge = force and branchmerge
378 forcemerge = force and branchmerge
379
379
380 if not wlock:
380 if not wlock:
381 wlock = repo.wlock()
381 wlock = repo.wlock()
382
382
383 ### check phase
383 ### check phase
384
384
385 wc = repo.workingctx()
385 wc = repo.workingctx()
386 pl = wc.parents()
386 pl = wc.parents()
387 if not overwrite and len(pl) > 1:
387 if not overwrite and len(pl) > 1:
388 raise util.Abort(_("outstanding uncommitted merges"))
388 raise util.Abort(_("outstanding uncommitted merges"))
389
389
390 p1, p2 = pl[0], repo.changectx(node)
390 p1, p2 = pl[0], repo.changectx(node)
391 pa = p1.ancestor(p2)
391 pa = p1.ancestor(p2)
392
392
393 # is there a linear path from p1 to p2?
393 # is there a linear path from p1 to p2?
394 if pa == p1 or pa == p2:
394 if pa == p1 or pa == p2:
395 if branchmerge:
395 if branchmerge:
396 raise util.Abort(_("there is nothing to merge, just use "
396 raise util.Abort(_("there is nothing to merge, just use "
397 "'hg update' or look at 'hg heads'"))
397 "'hg update' or look at 'hg heads'"))
398 elif not (overwrite or branchmerge):
398 elif not (overwrite or branchmerge):
399 raise util.Abort(_("update spans branches, use 'hg merge' "
399 raise util.Abort(_("update spans branches, use 'hg merge' "
400 "or 'hg update -C' to lose changes"))
400 "or 'hg update -C' to lose changes"))
401
401
402 if branchmerge and not forcemerge:
402 if branchmerge and not forcemerge:
403 if wc.modified() or wc.added() or wc.removed():
403 if wc.modified() or wc.added() or wc.removed():
404 raise util.Abort(_("outstanding uncommitted changes"))
404 raise util.Abort(_("outstanding uncommitted changes"))
405
405
406 m1 = wc.manifest()
406 m1 = wc.manifest()
407 m2 = p2.manifest()
407 m2 = p2.manifest()
408
408
409 # resolve the manifest to determine which files
409 # resolve the manifest to determine which files
410 # we care about merging
410 # we care about merging
411 repo.ui.note(_("resolving manifests\n"))
411 repo.ui.note(_("resolving manifests\n"))
412 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s\n") %
412 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s\n") %
413 (overwrite, branchmerge, bool(partial)))
413 (overwrite, branchmerge, bool(partial)))
414 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (p1, p2, pa))
414 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (p1, p2, pa))
415
415
416 action = []
416 action = []
417
417
418 if not force:
418 if not force:
419 checkunknown(repo, m2, wc)
419 checkunknown(repo, m2, wc)
420 if not branchmerge:
420 if not branchmerge:
421 action += forgetremoved(m2, wc)
421 action += forgetremoved(m2, wc)
422
422
423 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
423 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
424
424
425 ### apply phase
425 ### apply phase
426
426
427 if not branchmerge:
427 if not branchmerge:
428 # we don't need to do any magic, just jump to the new rev
428 # just jump to the new rev
429 p1, p2 = p2, repo.changectx(nullid)
429 fp1, fp2, xp1, xp2 = p2.node(), nullid, str(p2), ''
430 else:
431 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
430
432
431 xp1, xp2 = str(p1), str(p2)
433 if not partial:
432 if not p2: xp2 = ''
433
434 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
434 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
435
435
436 updated, merged, removed, unresolved = applyupdates(repo, action, xp1, xp2)
436 updated, merged, removed, unresolved = applyupdates(repo, action, xp1, xp2)
437
437
438 # update dirstate
438 # update dirstate
439 if not partial:
439 if not partial:
440 recordupdates(repo, action, branchmerge)
440 recordupdates(repo, action, branchmerge)
441 repo.dirstate.setparents(p1.node(), p2.node())
441 repo.dirstate.setparents(fp1, fp2)
442 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
442
443
443 if show_stats:
444 if show_stats:
444 stats = ((updated, _("updated")),
445 stats = ((updated, _("updated")),
445 (merged - unresolved, _("merged")),
446 (merged - unresolved, _("merged")),
446 (removed, _("removed")),
447 (removed, _("removed")),
447 (unresolved, _("unresolved")))
448 (unresolved, _("unresolved")))
448 note = ", ".join([_("%d files %s") % s for s in stats])
449 note = ", ".join([_("%d files %s") % s for s in stats])
449 repo.ui.status("%s\n" % note)
450 repo.ui.status("%s\n" % note)
450 if not partial:
451 if not partial:
451 if branchmerge:
452 if branchmerge:
452 if unresolved:
453 if unresolved:
453 repo.ui.status(_("There are unresolved merges,"
454 repo.ui.status(_("There are unresolved merges,"
454 " you can redo the full merge using:\n"
455 " you can redo the full merge using:\n"
455 " hg update -C %s\n"
456 " hg update -C %s\n"
456 " hg merge %s\n"
457 " hg merge %s\n"
457 % (p1.rev(), p2.rev())))
458 % (p1.rev(), p2.rev())))
458 elif remind:
459 elif remind:
459 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
460 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
460 elif unresolved:
461 elif unresolved:
461 repo.ui.status(_("There are unresolved merges with"
462 repo.ui.status(_("There are unresolved merges with"
462 " locally modified files.\n"))
463 " locally modified files.\n"))
463
464
464 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
465 return unresolved
465 return unresolved
466
466
General Comments 0
You need to be logged in to leave comments. Login now