##// END OF EJS Templates
merge: add copied hash to simplify copy logic
Matt Mackall -
r3729:581d2077 default
parent child Browse files
Show More
@@ -1,416 +1,418 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 None
33 return None
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=nullrev)
37 fca = repo.filectx(fw, fileid=nullrev)
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(_("untracked local file '%s' differs"\
68 raise util.Abort(_("untracked local file '%s' differs"\
69 " from remote version") % f)
69 " from remote version") % 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 u1 = nonoverlap(m1, m2, ma)
133 u1 = nonoverlap(m1, m2, ma)
134 u2 = nonoverlap(m2, m1, ma)
134 u2 = nonoverlap(m2, m1, ma)
135 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
135 ctx = util.cachefunc(lambda f, n: repo.filectx(f, fileid=n[:20]))
136
136
137 def checkpair(c, f2, man):
137 def checkpair(c, f2, man):
138 ''' check if an apparent pair actually matches '''
138 ''' check if an apparent pair actually matches '''
139 c2 = ctx(f2, man[f2])
139 c2 = ctx(f2, man[f2])
140 ca = c.ancestor(c2)
140 ca = c.ancestor(c2)
141 if not ca or c == ca or c2 == ca:
141 if not ca or c == ca or c2 == ca:
142 return
142 return
143 if ca.path() == c.path() or ca.path() == c2.path():
143 if ca.path() == c.path() or ca.path() == c2.path():
144 copy[c.path()] = f2
144 copy[c.path()] = f2
145 copy[f2] = c.path()
145 copy[f2] = c.path()
146
146
147 for f in u1:
147 for f in u1:
148 c = ctx(dcopies.get(f, f), m1[f])
148 c = ctx(dcopies.get(f, f), m1[f])
149 for of in findold(c, limit):
149 for of in findold(c, limit):
150 if of in m2:
150 if of in m2:
151 checkpair(c, of, m2)
151 checkpair(c, of, m2)
152
152
153 for f in u2:
153 for f in u2:
154 c = ctx(f, m2[f])
154 c = ctx(f, m2[f])
155 for of in findold(c, limit):
155 for of in findold(c, limit):
156 if of in m1:
156 if of in m1:
157 checkpair(c, of, m1)
157 checkpair(c, of, m1)
158
158
159 return copy
159 return copy
160
160
161 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
161 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
162 """
162 """
163 Merge p1 and p2 with ancestor ma and generate merge action list
163 Merge p1 and p2 with ancestor ma and generate merge action list
164
164
165 overwrite = whether we clobber working files
165 overwrite = whether we clobber working files
166 partial = function to filter file lists
166 partial = function to filter file lists
167 """
167 """
168
168
169 repo.ui.note(_("resolving manifests\n"))
169 repo.ui.note(_("resolving manifests\n"))
170 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
170 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
171 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
171 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
172
172
173 m1 = p1.manifest()
173 m1 = p1.manifest()
174 m2 = p2.manifest()
174 m2 = p2.manifest()
175 ma = pa.manifest()
175 ma = pa.manifest()
176 backwards = (pa == p2)
176 backwards = (pa == p2)
177 action = []
177 action = []
178 copy = {}
178 copy = {}
179 copied = {}
179
180
180 def fmerge(f, f2=None, fa=None):
181 def fmerge(f, f2=None, fa=None):
181 """merge executable flags"""
182 """merge executable flags"""
182 if not f2:
183 if not f2:
183 f2 = f
184 f2 = f
184 fa = f
185 fa = f
185 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
186 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
186 return ((a^b) | (a^c)) ^ a
187 return ((a^b) | (a^c)) ^ a
187
188
188 def act(msg, m, f, *args):
189 def act(msg, m, f, *args):
189 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
190 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
190 action.append((f, m) + args)
191 action.append((f, m) + args)
191
192
192 if pa and not (backwards or overwrite):
193 if pa and not (backwards or overwrite):
193 copy = findcopies(repo, m1, m2, ma, pa.rev())
194 copy = findcopies(repo, m1, m2, ma, pa.rev())
194
195
195 # Compare manifests
196 # Compare manifests
196 for f, n in m1.iteritems():
197 for f, n in m1.iteritems():
197 if partial and not partial(f):
198 if partial and not partial(f):
198 continue
199 continue
199 if f in m2:
200 if f in m2:
200 # are files different?
201 # are files different?
201 if n != m2[f]:
202 if n != m2[f]:
202 a = ma.get(f, nullid)
203 a = ma.get(f, nullid)
203 # are both different from the ancestor?
204 # are both different from the ancestor?
204 if not overwrite and n != a and m2[f] != a:
205 if not overwrite and n != a and m2[f] != a:
205 act("versions differ", "m", f, f, f, fmerge(f), False)
206 act("versions differ", "m", f, f, f, fmerge(f), False)
206 # are we clobbering?
207 # are we clobbering?
207 # is remote's version newer?
208 # is remote's version newer?
208 # or are we going back in time and clean?
209 # or are we going back in time and clean?
209 elif overwrite or m2[f] != a or (backwards and not n[20:]):
210 elif overwrite or m2[f] != a or (backwards and not n[20:]):
210 act("remote is newer", "g", f, m2.execf(f))
211 act("remote is newer", "g", f, m2.execf(f))
211 # local is newer, not overwrite, check mode bits
212 # local is newer, not overwrite, check mode bits
212 elif fmerge(f) != m1.execf(f):
213 elif fmerge(f) != m1.execf(f):
213 act("update permissions", "e", f, m2.execf(f))
214 act("update permissions", "e", f, m2.execf(f))
214 # contents same, check mode bits
215 # contents same, check mode bits
215 elif m1.execf(f) != m2.execf(f):
216 elif m1.execf(f) != m2.execf(f):
216 if overwrite or fmerge(f) != m1.execf(f):
217 if overwrite or fmerge(f) != m1.execf(f):
217 act("update permissions", "e", f, m2.execf(f))
218 act("update permissions", "e", f, m2.execf(f))
218 elif f in copy:
219 elif f in copy:
219 f2 = copy[f]
220 f2 = copy[f]
221 copied[f2] = True
220 if f in ma: # case 3,20 A/B/A
222 if f in ma: # case 3,20 A/B/A
221 act("remote moved to " + f2, "m",
223 act("remote moved to " + f2, "m",
222 f, f2, f2, fmerge(f, f2, f), True)
224 f, f2, f2, fmerge(f, f2, f), True)
223 else:
225 else:
224 if f2 in m1: # case 2 A,B/B/B
226 if f2 in m1: # case 2 A,B/B/B
225 act("local copied to " + f2, "m",
227 act("local copied to " + f2, "m",
226 f, f2, f, fmerge(f, f2, f2), False)
228 f, f2, f, fmerge(f, f2, f2), False)
227 else: # case 4,21 A/B/B
229 else: # case 4,21 A/B/B
228 act("local moved to " + f2, "m",
230 act("local moved to " + f2, "m",
229 f, f2, f, fmerge(f, f2, f2), False)
231 f, f2, f, fmerge(f, f2, f2), False)
230 elif f in ma:
232 elif f in ma:
231 if n != ma[f] and not overwrite:
233 if n != ma[f] and not overwrite:
232 if repo.ui.prompt(
234 if repo.ui.prompt(
233 (_(" local changed %s which remote deleted\n") % f) +
235 (_(" local changed %s which remote deleted\n") % f) +
234 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
236 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
235 act("prompt delete", "r", f)
237 act("prompt delete", "r", f)
236 else:
238 else:
237 act("other deleted", "r", f)
239 act("other deleted", "r", f)
238 else:
240 else:
239 # file is created on branch or in working directory
241 # file is created on branch or in working directory
240 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
242 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
241 act("remote deleted", "r", f)
243 act("remote deleted", "r", f)
242
244
243 for f, n in m2.iteritems():
245 for f, n in m2.iteritems():
244 if partial and not partial(f):
246 if partial and not partial(f):
245 continue
247 continue
246 if f in m1:
248 if f in m1:
247 continue
249 continue
250 if f in copied:
251 continue
248 if f in copy:
252 if f in copy:
249 f2 = copy[f]
253 f2 = copy[f]
250 if f2 not in m2: # already seen
251 continue
252 # rename case 1, A/A,B/A
254 # rename case 1, A/A,B/A
253 act("remote copied to " + f, "m",
255 act("remote copied to " + f, "m",
254 f2, f, f, fmerge(f2, f, f2), False)
256 f2, f, f, fmerge(f2, f, f2), False)
255 elif f in ma:
257 elif f in ma:
256 if overwrite or backwards:
258 if overwrite or backwards:
257 act("recreating", "g", f, m2.execf(f))
259 act("recreating", "g", f, m2.execf(f))
258 elif n != ma[f]:
260 elif n != ma[f]:
259 if repo.ui.prompt(
261 if repo.ui.prompt(
260 (_("remote changed %s which local deleted\n") % f) +
262 (_("remote changed %s which local deleted\n") % f) +
261 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
263 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
262 act("prompt recreating", "g", f, m2.execf(f))
264 act("prompt recreating", "g", f, m2.execf(f))
263 else:
265 else:
264 act("remote created", "g", f, m2.execf(f))
266 act("remote created", "g", f, m2.execf(f))
265
267
266 return action
268 return action
267
269
268 def applyupdates(repo, action, wctx, mctx):
270 def applyupdates(repo, action, wctx, mctx):
269 "apply the merge action list to the working directory"
271 "apply the merge action list to the working directory"
270
272
271 updated, merged, removed, unresolved = 0, 0, 0, 0
273 updated, merged, removed, unresolved = 0, 0, 0, 0
272 action.sort()
274 action.sort()
273 for a in action:
275 for a in action:
274 f, m = a[:2]
276 f, m = a[:2]
275 if f[0] == "/":
277 if f[0] == "/":
276 continue
278 continue
277 if m == "r": # remove
279 if m == "r": # remove
278 repo.ui.note(_("removing %s\n") % f)
280 repo.ui.note(_("removing %s\n") % f)
279 util.audit_path(f)
281 util.audit_path(f)
280 try:
282 try:
281 util.unlink(repo.wjoin(f))
283 util.unlink(repo.wjoin(f))
282 except OSError, inst:
284 except OSError, inst:
283 if inst.errno != errno.ENOENT:
285 if inst.errno != errno.ENOENT:
284 repo.ui.warn(_("update failed to remove %s: %s!\n") %
286 repo.ui.warn(_("update failed to remove %s: %s!\n") %
285 (f, inst.strerror))
287 (f, inst.strerror))
286 removed += 1
288 removed += 1
287 elif m == "m": # merge
289 elif m == "m": # merge
288 f2, fd, flag, move = a[2:]
290 f2, fd, flag, move = a[2:]
289 r = filemerge(repo, f, f2, wctx, mctx)
291 r = filemerge(repo, f, f2, wctx, mctx)
290 if r > 0:
292 if r > 0:
291 unresolved += 1
293 unresolved += 1
292 else:
294 else:
293 if r is None:
295 if r is None:
294 updated += 1
296 updated += 1
295 else:
297 else:
296 merged += 1
298 merged += 1
297 if f != fd:
299 if f != fd:
298 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
300 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
299 repo.wwrite(fd, repo.wread(f))
301 repo.wwrite(fd, repo.wread(f))
300 if move:
302 if move:
301 repo.ui.debug(_("removing %s\n") % f)
303 repo.ui.debug(_("removing %s\n") % f)
302 os.unlink(repo.wjoin(f))
304 os.unlink(repo.wjoin(f))
303 util.set_exec(repo.wjoin(fd), flag)
305 util.set_exec(repo.wjoin(fd), flag)
304 elif m == "g": # get
306 elif m == "g": # get
305 flag = a[2]
307 flag = a[2]
306 repo.ui.note(_("getting %s\n") % f)
308 repo.ui.note(_("getting %s\n") % f)
307 t = mctx.filectx(f).data()
309 t = mctx.filectx(f).data()
308 repo.wwrite(f, t)
310 repo.wwrite(f, t)
309 util.set_exec(repo.wjoin(f), flag)
311 util.set_exec(repo.wjoin(f), flag)
310 updated += 1
312 updated += 1
311 elif m == "e": # exec
313 elif m == "e": # exec
312 flag = a[2]
314 flag = a[2]
313 util.set_exec(repo.wjoin(f), flag)
315 util.set_exec(repo.wjoin(f), flag)
314
316
315 return updated, merged, removed, unresolved
317 return updated, merged, removed, unresolved
316
318
317 def recordupdates(repo, action, branchmerge):
319 def recordupdates(repo, action, branchmerge):
318 "record merge actions to the dirstate"
320 "record merge actions to the dirstate"
319
321
320 for a in action:
322 for a in action:
321 f, m = a[:2]
323 f, m = a[:2]
322 if m == "r": # remove
324 if m == "r": # remove
323 if branchmerge:
325 if branchmerge:
324 repo.dirstate.update([f], 'r')
326 repo.dirstate.update([f], 'r')
325 else:
327 else:
326 repo.dirstate.forget([f])
328 repo.dirstate.forget([f])
327 elif m == "f": # forget
329 elif m == "f": # forget
328 repo.dirstate.forget([f])
330 repo.dirstate.forget([f])
329 elif m == "g": # get
331 elif m == "g": # get
330 if branchmerge:
332 if branchmerge:
331 repo.dirstate.update([f], 'n', st_mtime=-1)
333 repo.dirstate.update([f], 'n', st_mtime=-1)
332 else:
334 else:
333 repo.dirstate.update([f], 'n')
335 repo.dirstate.update([f], 'n')
334 elif m == "m": # merge
336 elif m == "m": # merge
335 f2, fd, flag, move = a[2:]
337 f2, fd, flag, move = a[2:]
336 if branchmerge:
338 if branchmerge:
337 # We've done a branch merge, mark this file as merged
339 # We've done a branch merge, mark this file as merged
338 # so that we properly record the merger later
340 # so that we properly record the merger later
339 repo.dirstate.update([fd], 'm')
341 repo.dirstate.update([fd], 'm')
340 if f != f2: # copy/rename
342 if f != f2: # copy/rename
341 if move:
343 if move:
342 repo.dirstate.update([f], 'r')
344 repo.dirstate.update([f], 'r')
343 if f != fd:
345 if f != fd:
344 repo.dirstate.copy(f, fd)
346 repo.dirstate.copy(f, fd)
345 else:
347 else:
346 repo.dirstate.copy(f2, fd)
348 repo.dirstate.copy(f2, fd)
347 else:
349 else:
348 # We've update-merged a locally modified file, so
350 # We've update-merged a locally modified file, so
349 # we set the dirstate to emulate a normal checkout
351 # we set the dirstate to emulate a normal checkout
350 # of that file some time in the past. Thus our
352 # of that file some time in the past. Thus our
351 # merge will appear as a normal local file
353 # merge will appear as a normal local file
352 # modification.
354 # modification.
353 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
355 repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
354 if move:
356 if move:
355 repo.dirstate.forget([f])
357 repo.dirstate.forget([f])
356
358
357 def update(repo, node, branchmerge, force, partial, wlock):
359 def update(repo, node, branchmerge, force, partial, wlock):
358 """
360 """
359 Perform a merge between the working directory and the given node
361 Perform a merge between the working directory and the given node
360
362
361 branchmerge = whether to merge between branches
363 branchmerge = whether to merge between branches
362 force = whether to force branch merging or file overwriting
364 force = whether to force branch merging or file overwriting
363 partial = a function to filter file lists (dirstate not updated)
365 partial = a function to filter file lists (dirstate not updated)
364 wlock = working dir lock, if already held
366 wlock = working dir lock, if already held
365 """
367 """
366
368
367 if not wlock:
369 if not wlock:
368 wlock = repo.wlock()
370 wlock = repo.wlock()
369
371
370 overwrite = force and not branchmerge
372 overwrite = force and not branchmerge
371 forcemerge = force and branchmerge
373 forcemerge = force and branchmerge
372 wc = repo.workingctx()
374 wc = repo.workingctx()
373 pl = wc.parents()
375 pl = wc.parents()
374 p1, p2 = pl[0], repo.changectx(node)
376 p1, p2 = pl[0], repo.changectx(node)
375 pa = p1.ancestor(p2)
377 pa = p1.ancestor(p2)
376 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
378 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
377
379
378 ### check phase
380 ### check phase
379 if not overwrite and len(pl) > 1:
381 if not overwrite and len(pl) > 1:
380 raise util.Abort(_("outstanding uncommitted merges"))
382 raise util.Abort(_("outstanding uncommitted merges"))
381 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
383 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
382 if branchmerge:
384 if branchmerge:
383 raise util.Abort(_("there is nothing to merge, just use "
385 raise util.Abort(_("there is nothing to merge, just use "
384 "'hg update' or look at 'hg heads'"))
386 "'hg update' or look at 'hg heads'"))
385 elif not (overwrite or branchmerge):
387 elif not (overwrite or branchmerge):
386 raise util.Abort(_("update spans branches, use 'hg merge' "
388 raise util.Abort(_("update spans branches, use 'hg merge' "
387 "or 'hg update -C' to lose changes"))
389 "or 'hg update -C' to lose changes"))
388 if branchmerge and not forcemerge:
390 if branchmerge and not forcemerge:
389 if wc.files():
391 if wc.files():
390 raise util.Abort(_("outstanding uncommitted changes"))
392 raise util.Abort(_("outstanding uncommitted changes"))
391
393
392 ### calculate phase
394 ### calculate phase
393 action = []
395 action = []
394 if not force:
396 if not force:
395 checkunknown(wc, p2)
397 checkunknown(wc, p2)
396 if not branchmerge:
398 if not branchmerge:
397 action += forgetremoved(wc, p2)
399 action += forgetremoved(wc, p2)
398 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
400 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
399
401
400 ### apply phase
402 ### apply phase
401 if not branchmerge: # just jump to the new rev
403 if not branchmerge: # just jump to the new rev
402 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
404 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
403 if not partial:
405 if not partial:
404 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
406 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
405
407
406 stats = applyupdates(repo, action, wc, p2)
408 stats = applyupdates(repo, action, wc, p2)
407
409
408 if not partial:
410 if not partial:
409 recordupdates(repo, action, branchmerge)
411 recordupdates(repo, action, branchmerge)
410 repo.dirstate.setparents(fp1, fp2)
412 repo.dirstate.setparents(fp1, fp2)
411 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
413 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
412 if not branchmerge:
414 if not branchmerge:
413 repo.opener("branch", "w").write(p2.branch() + "\n")
415 repo.opener("branch", "w").write(p2.branch() + "\n")
414
416
415 return stats
417 return stats
416
418
General Comments 0
You need to be logged in to leave comments. Login now