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