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