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