##// END OF EJS Templates
manifestmerge: fix order in which manifests are fetched...
Siddharth Agarwal -
r18651:e5566593 default
parent child Browse files
Show More
@@ -1,708 +1,716
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, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import error, util, filemerge, copies, subrepo, worker
10 import error, util, filemerge, copies, subrepo, worker
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._dirty = False
17 self._dirty = False
18 self._read()
18 self._read()
19 def reset(self, node=None):
19 def reset(self, node=None):
20 self._state = {}
20 self._state = {}
21 if node:
21 if node:
22 self._local = node
22 self._local = node
23 shutil.rmtree(self._repo.join("merge"), True)
23 shutil.rmtree(self._repo.join("merge"), True)
24 self._dirty = False
24 self._dirty = False
25 def _read(self):
25 def _read(self):
26 self._state = {}
26 self._state = {}
27 try:
27 try:
28 f = self._repo.opener("merge/state")
28 f = self._repo.opener("merge/state")
29 for i, l in enumerate(f):
29 for i, l in enumerate(f):
30 if i == 0:
30 if i == 0:
31 self._local = bin(l[:-1])
31 self._local = bin(l[:-1])
32 else:
32 else:
33 bits = l[:-1].split("\0")
33 bits = l[:-1].split("\0")
34 self._state[bits[0]] = bits[1:]
34 self._state[bits[0]] = bits[1:]
35 f.close()
35 f.close()
36 except IOError, err:
36 except IOError, err:
37 if err.errno != errno.ENOENT:
37 if err.errno != errno.ENOENT:
38 raise
38 raise
39 self._dirty = False
39 self._dirty = False
40 def commit(self):
40 def commit(self):
41 if self._dirty:
41 if self._dirty:
42 f = self._repo.opener("merge/state", "w")
42 f = self._repo.opener("merge/state", "w")
43 f.write(hex(self._local) + "\n")
43 f.write(hex(self._local) + "\n")
44 for d, v in self._state.iteritems():
44 for d, v in self._state.iteritems():
45 f.write("\0".join([d] + v) + "\n")
45 f.write("\0".join([d] + v) + "\n")
46 f.close()
46 f.close()
47 self._dirty = False
47 self._dirty = False
48 def add(self, fcl, fco, fca, fd):
48 def add(self, fcl, fco, fca, fd):
49 hash = util.sha1(fcl.path()).hexdigest()
49 hash = util.sha1(fcl.path()).hexdigest()
50 self._repo.opener.write("merge/" + hash, fcl.data())
50 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 hex(fca.filenode()), fco.path(), fcl.flags()]
52 hex(fca.filenode()), fco.path(), fcl.flags()]
53 self._dirty = True
53 self._dirty = True
54 def __contains__(self, dfile):
54 def __contains__(self, dfile):
55 return dfile in self._state
55 return dfile in self._state
56 def __getitem__(self, dfile):
56 def __getitem__(self, dfile):
57 return self._state[dfile][0]
57 return self._state[dfile][0]
58 def __iter__(self):
58 def __iter__(self):
59 l = self._state.keys()
59 l = self._state.keys()
60 l.sort()
60 l.sort()
61 for f in l:
61 for f in l:
62 yield f
62 yield f
63 def mark(self, dfile, state):
63 def mark(self, dfile, state):
64 self._state[dfile][0] = state
64 self._state[dfile][0] = state
65 self._dirty = True
65 self._dirty = True
66 def resolve(self, dfile, wctx, octx):
66 def resolve(self, dfile, wctx, octx):
67 if self[dfile] == 'r':
67 if self[dfile] == 'r':
68 return 0
68 return 0
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 fcd = wctx[dfile]
70 fcd = wctx[dfile]
71 fco = octx[ofile]
71 fco = octx[ofile]
72 fca = self._repo.filectx(afile, fileid=anode)
72 fca = self._repo.filectx(afile, fileid=anode)
73 # "premerge" x flags
73 # "premerge" x flags
74 flo = fco.flags()
74 flo = fco.flags()
75 fla = fca.flags()
75 fla = fca.flags()
76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
77 if fca.node() == nullid:
77 if fca.node() == nullid:
78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
79 afile)
79 afile)
80 elif flags == fla:
80 elif flags == fla:
81 flags = flo
81 flags = flo
82 # restore local
82 # restore local
83 f = self._repo.opener("merge/" + hash)
83 f = self._repo.opener("merge/" + hash)
84 self._repo.wwrite(dfile, f.read(), flags)
84 self._repo.wwrite(dfile, f.read(), flags)
85 f.close()
85 f.close()
86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
87 if r is None:
87 if r is None:
88 # no real conflict
88 # no real conflict
89 del self._state[dfile]
89 del self._state[dfile]
90 elif not r:
90 elif not r:
91 self.mark(dfile, 'r')
91 self.mark(dfile, 'r')
92 return r
92 return r
93
93
94 def _checkunknownfile(repo, wctx, mctx, f):
94 def _checkunknownfile(repo, wctx, mctx, f):
95 return (not repo.dirstate._ignore(f)
95 return (not repo.dirstate._ignore(f)
96 and os.path.isfile(repo.wjoin(f))
96 and os.path.isfile(repo.wjoin(f))
97 and repo.dirstate.normalize(f) not in repo.dirstate
97 and repo.dirstate.normalize(f) not in repo.dirstate
98 and mctx[f].cmp(wctx[f]))
98 and mctx[f].cmp(wctx[f]))
99
99
100 def _checkunknown(repo, wctx, mctx):
100 def _checkunknown(repo, wctx, mctx):
101 "check for collisions between unknown files and files in mctx"
101 "check for collisions between unknown files and files in mctx"
102
102
103 error = False
103 error = False
104 for f in mctx:
104 for f in mctx:
105 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
105 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
106 error = True
106 error = True
107 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
107 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
108 if error:
108 if error:
109 raise util.Abort(_("untracked files in working directory differ "
109 raise util.Abort(_("untracked files in working directory differ "
110 "from files in requested revision"))
110 "from files in requested revision"))
111
111
112 def _remains(f, m, ma, workingctx=False):
112 def _remains(f, m, ma, workingctx=False):
113 """check whether specified file remains after merge.
113 """check whether specified file remains after merge.
114
114
115 It is assumed that specified file is not contained in the manifest
115 It is assumed that specified file is not contained in the manifest
116 of the other context.
116 of the other context.
117 """
117 """
118 if f in ma:
118 if f in ma:
119 n = m[f]
119 n = m[f]
120 if n != ma[f]:
120 if n != ma[f]:
121 return True # because it is changed locally
121 return True # because it is changed locally
122 # even though it doesn't remain, if "remote deleted" is
122 # even though it doesn't remain, if "remote deleted" is
123 # chosen in manifestmerge()
123 # chosen in manifestmerge()
124 elif workingctx and n[20:] == "a":
124 elif workingctx and n[20:] == "a":
125 return True # because it is added locally (linear merge specific)
125 return True # because it is added locally (linear merge specific)
126 else:
126 else:
127 return False # because it is removed remotely
127 return False # because it is removed remotely
128 else:
128 else:
129 return True # because it is added locally
129 return True # because it is added locally
130
130
131 def _checkcollision(mctx, extractxs):
131 def _checkcollision(mctx, extractxs):
132 "check for case folding collisions in the destination context"
132 "check for case folding collisions in the destination context"
133 folded = {}
133 folded = {}
134 for fn in mctx:
134 for fn in mctx:
135 fold = util.normcase(fn)
135 fold = util.normcase(fn)
136 if fold in folded:
136 if fold in folded:
137 raise util.Abort(_("case-folding collision between %s and %s")
137 raise util.Abort(_("case-folding collision between %s and %s")
138 % (fn, folded[fold]))
138 % (fn, folded[fold]))
139 folded[fold] = fn
139 folded[fold] = fn
140
140
141 if extractxs:
141 if extractxs:
142 wctx, actx = extractxs
142 wctx, actx = extractxs
143 # class to delay looking up copy mapping
143 # class to delay looking up copy mapping
144 class pathcopies(object):
144 class pathcopies(object):
145 @util.propertycache
145 @util.propertycache
146 def map(self):
146 def map(self):
147 # {dst@mctx: src@wctx} copy mapping
147 # {dst@mctx: src@wctx} copy mapping
148 return copies.pathcopies(wctx, mctx)
148 return copies.pathcopies(wctx, mctx)
149 pc = pathcopies()
149 pc = pathcopies()
150
150
151 for fn in wctx:
151 for fn in wctx:
152 fold = util.normcase(fn)
152 fold = util.normcase(fn)
153 mfn = folded.get(fold, None)
153 mfn = folded.get(fold, None)
154 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
154 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
155 _remains(fn, wctx.manifest(), actx.manifest(), True) and
155 _remains(fn, wctx.manifest(), actx.manifest(), True) and
156 _remains(mfn, mctx.manifest(), actx.manifest())):
156 _remains(mfn, mctx.manifest(), actx.manifest())):
157 raise util.Abort(_("case-folding collision between %s and %s")
157 raise util.Abort(_("case-folding collision between %s and %s")
158 % (mfn, fn))
158 % (mfn, fn))
159
159
160 def _forgetremoved(wctx, mctx, branchmerge):
160 def _forgetremoved(wctx, mctx, branchmerge):
161 """
161 """
162 Forget removed files
162 Forget removed files
163
163
164 If we're jumping between revisions (as opposed to merging), and if
164 If we're jumping between revisions (as opposed to merging), and if
165 neither the working directory nor the target rev has the file,
165 neither the working directory nor the target rev has the file,
166 then we need to remove it from the dirstate, to prevent the
166 then we need to remove it from the dirstate, to prevent the
167 dirstate from listing the file when it is no longer in the
167 dirstate from listing the file when it is no longer in the
168 manifest.
168 manifest.
169
169
170 If we're merging, and the other revision has removed a file
170 If we're merging, and the other revision has removed a file
171 that is not present in the working directory, we need to mark it
171 that is not present in the working directory, we need to mark it
172 as removed.
172 as removed.
173 """
173 """
174
174
175 actions = []
175 actions = []
176 state = branchmerge and 'r' or 'f'
176 state = branchmerge and 'r' or 'f'
177 for f in wctx.deleted():
177 for f in wctx.deleted():
178 if f not in mctx:
178 if f not in mctx:
179 actions.append((f, state, None, "forget deleted"))
179 actions.append((f, state, None, "forget deleted"))
180
180
181 if not branchmerge:
181 if not branchmerge:
182 for f in wctx.removed():
182 for f in wctx.removed():
183 if f not in mctx:
183 if f not in mctx:
184 actions.append((f, "f", None, "forget removed"))
184 actions.append((f, "f", None, "forget removed"))
185
185
186 return actions
186 return actions
187
187
188 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial):
188 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial):
189 """
189 """
190 Merge p1 and p2 with ancestor pa and generate merge action list
190 Merge p1 and p2 with ancestor pa and generate merge action list
191
191
192 branchmerge and force are as passed in to update
192 branchmerge and force are as passed in to update
193 partial = function to filter file lists
193 partial = function to filter file lists
194 """
194 """
195
195
196 overwrite = force and not branchmerge
196 overwrite = force and not branchmerge
197 actions, copy, movewithdir = [], {}, {}
197 actions, copy, movewithdir = [], {}, {}
198
198
199 followcopies = False
199 if overwrite:
200 if overwrite:
200 pa = wctx
201 pa = wctx
201 elif pa == p2: # backwards
202 elif pa == p2: # backwards
202 pa = wctx.p1()
203 pa = wctx.p1()
203 elif not branchmerge and not wctx.dirty(missing=True):
204 elif not branchmerge and not wctx.dirty(missing=True):
204 pass
205 pass
205 elif pa and repo.ui.configbool("merge", "followcopies", True):
206 elif pa and repo.ui.configbool("merge", "followcopies", True):
207 followcopies = True
208
209 # manifests fetched in order are going to be faster, so prime the caches
210 [x.manifest() for x in
211 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
212
213 if followcopies:
206 ret = copies.mergecopies(repo, wctx, p2, pa)
214 ret = copies.mergecopies(repo, wctx, p2, pa)
207 copy, movewithdir, diverge, renamedelete = ret
215 copy, movewithdir, diverge, renamedelete = ret
208 for of, fl in diverge.iteritems():
216 for of, fl in diverge.iteritems():
209 actions.append((of, "dr", (fl,), "divergent renames"))
217 actions.append((of, "dr", (fl,), "divergent renames"))
210 for of, fl in renamedelete.iteritems():
218 for of, fl in renamedelete.iteritems():
211 actions.append((of, "rd", (fl,), "rename and delete"))
219 actions.append((of, "rd", (fl,), "rename and delete"))
212
220
213 repo.ui.note(_("resolving manifests\n"))
221 repo.ui.note(_("resolving manifests\n"))
214 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
222 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
215 % (bool(branchmerge), bool(force), bool(partial)))
223 % (bool(branchmerge), bool(force), bool(partial)))
216 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
224 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
217
225
218 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
226 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
219 copied = set(copy.values())
227 copied = set(copy.values())
220 copied.update(movewithdir.values())
228 copied.update(movewithdir.values())
221
229
222 if '.hgsubstate' in m1:
230 if '.hgsubstate' in m1:
223 # check whether sub state is modified
231 # check whether sub state is modified
224 for s in sorted(wctx.substate):
232 for s in sorted(wctx.substate):
225 if wctx.sub(s).dirty():
233 if wctx.sub(s).dirty():
226 m1['.hgsubstate'] += "+"
234 m1['.hgsubstate'] += "+"
227 break
235 break
228
236
229 aborts, prompts = [], []
237 aborts, prompts = [], []
230 # Compare manifests
238 # Compare manifests
231 for f, n in m1.iteritems():
239 for f, n in m1.iteritems():
232 if partial and not partial(f):
240 if partial and not partial(f):
233 continue
241 continue
234 if f in m2:
242 if f in m2:
235 n2 = m2[f]
243 n2 = m2[f]
236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
244 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 nol = 'l' not in fl1 + fl2 + fla
245 nol = 'l' not in fl1 + fl2 + fla
238 a = ma.get(f, nullid)
246 a = ma.get(f, nullid)
239 if n == n2 and fl1 == fl2:
247 if n == n2 and fl1 == fl2:
240 pass # same - keep local
248 pass # same - keep local
241 elif n2 == a and fl2 == fla:
249 elif n2 == a and fl2 == fla:
242 pass # remote unchanged - keep local
250 pass # remote unchanged - keep local
243 elif n == a and fl1 == fla: # local unchanged - use remote
251 elif n == a and fl1 == fla: # local unchanged - use remote
244 if n == n2: # optimization: keep local content
252 if n == n2: # optimization: keep local content
245 actions.append((f, "e", (fl2,), "update permissions"))
253 actions.append((f, "e", (fl2,), "update permissions"))
246 else:
254 else:
247 actions.append((f, "g", (fl2,), "remote is newer"))
255 actions.append((f, "g", (fl2,), "remote is newer"))
248 elif nol and n2 == a: # remote only changed 'x'
256 elif nol and n2 == a: # remote only changed 'x'
249 actions.append((f, "e", (fl2,), "update permissions"))
257 actions.append((f, "e", (fl2,), "update permissions"))
250 elif nol and n == a: # local only changed 'x'
258 elif nol and n == a: # local only changed 'x'
251 actions.append((f, "g", (fl1,), "remote is newer"))
259 actions.append((f, "g", (fl1,), "remote is newer"))
252 else: # both changed something
260 else: # both changed something
253 actions.append((f, "m", (f, f, False), "versions differ"))
261 actions.append((f, "m", (f, f, False), "versions differ"))
254 elif f in copied: # files we'll deal with on m2 side
262 elif f in copied: # files we'll deal with on m2 side
255 pass
263 pass
256 elif f in movewithdir: # directory rename
264 elif f in movewithdir: # directory rename
257 f2 = movewithdir[f]
265 f2 = movewithdir[f]
258 actions.append((f, "d", (None, f2, m1.flags(f)),
266 actions.append((f, "d", (None, f2, m1.flags(f)),
259 "remote renamed directory to " + f2))
267 "remote renamed directory to " + f2))
260 elif f in copy:
268 elif f in copy:
261 f2 = copy[f]
269 f2 = copy[f]
262 actions.append((f, "m", (f2, f, False),
270 actions.append((f, "m", (f2, f, False),
263 "local copied/moved to " + f2))
271 "local copied/moved to " + f2))
264 elif f in ma: # clean, a different, no remote
272 elif f in ma: # clean, a different, no remote
265 if n != ma[f]:
273 if n != ma[f]:
266 prompts.append((f, "cd")) # prompt changed/deleted
274 prompts.append((f, "cd")) # prompt changed/deleted
267 elif n[20:] == "a": # added, no remote
275 elif n[20:] == "a": # added, no remote
268 actions.append((f, "f", None, "remote deleted"))
276 actions.append((f, "f", None, "remote deleted"))
269 else:
277 else:
270 actions.append((f, "r", None, "other deleted"))
278 actions.append((f, "r", None, "other deleted"))
271
279
272 for f, n in m2.iteritems():
280 for f, n in m2.iteritems():
273 if partial and not partial(f):
281 if partial and not partial(f):
274 continue
282 continue
275 if f in m1 or f in copied: # files already visited
283 if f in m1 or f in copied: # files already visited
276 continue
284 continue
277 if f in movewithdir:
285 if f in movewithdir:
278 f2 = movewithdir[f]
286 f2 = movewithdir[f]
279 actions.append((None, "d", (f, f2, m2.flags(f)),
287 actions.append((None, "d", (f, f2, m2.flags(f)),
280 "local renamed directory to " + f2))
288 "local renamed directory to " + f2))
281 elif f in copy:
289 elif f in copy:
282 f2 = copy[f]
290 f2 = copy[f]
283 if f2 in m2:
291 if f2 in m2:
284 actions.append((f2, "m", (f, f, False),
292 actions.append((f2, "m", (f, f, False),
285 "remote copied to " + f))
293 "remote copied to " + f))
286 else:
294 else:
287 actions.append((f2, "m", (f, f, True),
295 actions.append((f2, "m", (f, f, True),
288 "remote moved to " + f))
296 "remote moved to " + f))
289 elif f not in ma:
297 elif f not in ma:
290 # local unknown, remote created: the logic is described by the
298 # local unknown, remote created: the logic is described by the
291 # following table:
299 # following table:
292 #
300 #
293 # force branchmerge different | action
301 # force branchmerge different | action
294 # n * n | get
302 # n * n | get
295 # n * y | abort
303 # n * y | abort
296 # y n * | get
304 # y n * | get
297 # y y n | get
305 # y y n | get
298 # y y y | merge
306 # y y y | merge
299 #
307 #
300 # Checking whether the files are different is expensive, so we
308 # Checking whether the files are different is expensive, so we
301 # don't do that when we can avoid it.
309 # don't do that when we can avoid it.
302 if force and not branchmerge:
310 if force and not branchmerge:
303 actions.append((f, "g", (m2.flags(f),), "remote created"))
311 actions.append((f, "g", (m2.flags(f),), "remote created"))
304 else:
312 else:
305 different = _checkunknownfile(repo, wctx, p2, f)
313 different = _checkunknownfile(repo, wctx, p2, f)
306 if force and branchmerge and different:
314 if force and branchmerge and different:
307 actions.append((f, "m", (f, f, False),
315 actions.append((f, "m", (f, f, False),
308 "remote differs from untracked local"))
316 "remote differs from untracked local"))
309 elif not force and different:
317 elif not force and different:
310 aborts.append((f, "ud"))
318 aborts.append((f, "ud"))
311 else:
319 else:
312 actions.append((f, "g", (m2.flags(f),), "remote created"))
320 actions.append((f, "g", (m2.flags(f),), "remote created"))
313 elif n != ma[f]:
321 elif n != ma[f]:
314 prompts.append((f, "dc")) # prompt deleted/changed
322 prompts.append((f, "dc")) # prompt deleted/changed
315
323
316 for f, m in sorted(aborts):
324 for f, m in sorted(aborts):
317 if m == "ud":
325 if m == "ud":
318 repo.ui.warn(_("%s: untracked file differs\n") % f)
326 repo.ui.warn(_("%s: untracked file differs\n") % f)
319 else: assert False, m
327 else: assert False, m
320 if aborts:
328 if aborts:
321 raise util.Abort(_("untracked files in working directory differ "
329 raise util.Abort(_("untracked files in working directory differ "
322 "from files in requested revision"))
330 "from files in requested revision"))
323
331
324 for f, m in sorted(prompts):
332 for f, m in sorted(prompts):
325 if m == "cd":
333 if m == "cd":
326 if repo.ui.promptchoice(
334 if repo.ui.promptchoice(
327 _("local changed %s which remote deleted\n"
335 _("local changed %s which remote deleted\n"
328 "use (c)hanged version or (d)elete?") % f,
336 "use (c)hanged version or (d)elete?") % f,
329 (_("&Changed"), _("&Delete")), 0):
337 (_("&Changed"), _("&Delete")), 0):
330 actions.append((f, "r", None, "prompt delete"))
338 actions.append((f, "r", None, "prompt delete"))
331 else:
339 else:
332 actions.append((f, "a", None, "prompt keep"))
340 actions.append((f, "a", None, "prompt keep"))
333 elif m == "dc":
341 elif m == "dc":
334 if repo.ui.promptchoice(
342 if repo.ui.promptchoice(
335 _("remote changed %s which local deleted\n"
343 _("remote changed %s which local deleted\n"
336 "use (c)hanged version or leave (d)eleted?") % f,
344 "use (c)hanged version or leave (d)eleted?") % f,
337 (_("&Changed"), _("&Deleted")), 0) == 0:
345 (_("&Changed"), _("&Deleted")), 0) == 0:
338 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
346 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
339 else: assert False, m
347 else: assert False, m
340 return actions
348 return actions
341
349
342 def actionkey(a):
350 def actionkey(a):
343 return a[1] == "r" and -1 or 0, a
351 return a[1] == "r" and -1 or 0, a
344
352
345 def getremove(repo, mctx, overwrite, args):
353 def getremove(repo, mctx, overwrite, args):
346 """apply usually-non-interactive updates to the working directory
354 """apply usually-non-interactive updates to the working directory
347
355
348 mctx is the context to be merged into the working copy
356 mctx is the context to be merged into the working copy
349
357
350 yields tuples for progress updates
358 yields tuples for progress updates
351 """
359 """
352 verbose = repo.ui.verbose
360 verbose = repo.ui.verbose
353 unlink = util.unlinkpath
361 unlink = util.unlinkpath
354 wjoin = repo.wjoin
362 wjoin = repo.wjoin
355 fctx = mctx.filectx
363 fctx = mctx.filectx
356 wwrite = repo.wwrite
364 wwrite = repo.wwrite
357 audit = repo.wopener.audit
365 audit = repo.wopener.audit
358 i = 0
366 i = 0
359 for arg in args:
367 for arg in args:
360 f = arg[0]
368 f = arg[0]
361 if arg[1] == 'r':
369 if arg[1] == 'r':
362 if verbose:
370 if verbose:
363 repo.ui.note(_("removing %s\n") % f)
371 repo.ui.note(_("removing %s\n") % f)
364 audit(f)
372 audit(f)
365 try:
373 try:
366 unlink(wjoin(f), ignoremissing=True)
374 unlink(wjoin(f), ignoremissing=True)
367 except OSError, inst:
375 except OSError, inst:
368 repo.ui.warn(_("update failed to remove %s: %s!\n") %
376 repo.ui.warn(_("update failed to remove %s: %s!\n") %
369 (f, inst.strerror))
377 (f, inst.strerror))
370 else:
378 else:
371 if verbose:
379 if verbose:
372 repo.ui.note(_("getting %s\n") % f)
380 repo.ui.note(_("getting %s\n") % f)
373 wwrite(f, fctx(f).data(), arg[2][0])
381 wwrite(f, fctx(f).data(), arg[2][0])
374 if i == 100:
382 if i == 100:
375 yield i, f
383 yield i, f
376 i = 0
384 i = 0
377 i += 1
385 i += 1
378 if i > 0:
386 if i > 0:
379 yield i, f
387 yield i, f
380
388
381 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
389 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
382 """apply the merge action list to the working directory
390 """apply the merge action list to the working directory
383
391
384 wctx is the working copy context
392 wctx is the working copy context
385 mctx is the context to be merged into the working copy
393 mctx is the context to be merged into the working copy
386 actx is the context of the common ancestor
394 actx is the context of the common ancestor
387
395
388 Return a tuple of counts (updated, merged, removed, unresolved) that
396 Return a tuple of counts (updated, merged, removed, unresolved) that
389 describes how many files were affected by the update.
397 describes how many files were affected by the update.
390 """
398 """
391
399
392 updated, merged, removed, unresolved = 0, 0, 0, 0
400 updated, merged, removed, unresolved = 0, 0, 0, 0
393 ms = mergestate(repo)
401 ms = mergestate(repo)
394 ms.reset(wctx.p1().node())
402 ms.reset(wctx.p1().node())
395 moves = []
403 moves = []
396 actions.sort(key=actionkey)
404 actions.sort(key=actionkey)
397
405
398 # prescan for merges
406 # prescan for merges
399 for a in actions:
407 for a in actions:
400 f, m, args, msg = a
408 f, m, args, msg = a
401 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
409 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
402 if m == "m": # merge
410 if m == "m": # merge
403 f2, fd, move = args
411 f2, fd, move = args
404 if fd == '.hgsubstate': # merged internally
412 if fd == '.hgsubstate': # merged internally
405 continue
413 continue
406 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
414 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
407 fcl = wctx[f]
415 fcl = wctx[f]
408 fco = mctx[f2]
416 fco = mctx[f2]
409 if mctx == actx: # backwards, use working dir parent as ancestor
417 if mctx == actx: # backwards, use working dir parent as ancestor
410 if fcl.parents():
418 if fcl.parents():
411 fca = fcl.p1()
419 fca = fcl.p1()
412 else:
420 else:
413 fca = repo.filectx(f, fileid=nullrev)
421 fca = repo.filectx(f, fileid=nullrev)
414 else:
422 else:
415 fca = fcl.ancestor(fco, actx)
423 fca = fcl.ancestor(fco, actx)
416 if not fca:
424 if not fca:
417 fca = repo.filectx(f, fileid=nullrev)
425 fca = repo.filectx(f, fileid=nullrev)
418 ms.add(fcl, fco, fca, fd)
426 ms.add(fcl, fco, fca, fd)
419 if f != fd and move:
427 if f != fd and move:
420 moves.append(f)
428 moves.append(f)
421
429
422 audit = repo.wopener.audit
430 audit = repo.wopener.audit
423
431
424 # remove renamed files after safely stored
432 # remove renamed files after safely stored
425 for f in moves:
433 for f in moves:
426 if os.path.lexists(repo.wjoin(f)):
434 if os.path.lexists(repo.wjoin(f)):
427 repo.ui.debug("removing %s\n" % f)
435 repo.ui.debug("removing %s\n" % f)
428 audit(f)
436 audit(f)
429 util.unlinkpath(repo.wjoin(f))
437 util.unlinkpath(repo.wjoin(f))
430
438
431 numupdates = len(actions)
439 numupdates = len(actions)
432 workeractions = [a for a in actions if a[1] in 'gr']
440 workeractions = [a for a in actions if a[1] in 'gr']
433 updated = len([a for a in workeractions if a[1] == 'g'])
441 updated = len([a for a in workeractions if a[1] == 'g'])
434 removed = len([a for a in workeractions if a[1] == 'r'])
442 removed = len([a for a in workeractions if a[1] == 'r'])
435 actions = [a for a in actions if a[1] not in 'gr']
443 actions = [a for a in actions if a[1] not in 'gr']
436
444
437 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
445 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
438 if hgsub and hgsub[0] == 'r':
446 if hgsub and hgsub[0] == 'r':
439 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
447 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
440
448
441 z = 0
449 z = 0
442 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
450 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
443 workeractions)
451 workeractions)
444 for i, item in prog:
452 for i, item in prog:
445 z += i
453 z += i
446 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
454 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
447 unit=_('files'))
455 unit=_('files'))
448
456
449 if hgsub and hgsub[0] == 'g':
457 if hgsub and hgsub[0] == 'g':
450 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
458 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
451
459
452 _updating = _('updating')
460 _updating = _('updating')
453 _files = _('files')
461 _files = _('files')
454 progress = repo.ui.progress
462 progress = repo.ui.progress
455
463
456 for i, a in enumerate(actions):
464 for i, a in enumerate(actions):
457 f, m, args, msg = a
465 f, m, args, msg = a
458 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
466 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
459 if m == "m": # merge
467 if m == "m": # merge
460 if fd == '.hgsubstate': # subrepo states need updating
468 if fd == '.hgsubstate': # subrepo states need updating
461 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
469 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
462 overwrite)
470 overwrite)
463 continue
471 continue
464 f2, fd, move = args
472 f2, fd, move = args
465 audit(fd)
473 audit(fd)
466 r = ms.resolve(fd, wctx, mctx)
474 r = ms.resolve(fd, wctx, mctx)
467 if r is not None and r > 0:
475 if r is not None and r > 0:
468 unresolved += 1
476 unresolved += 1
469 else:
477 else:
470 if r is None:
478 if r is None:
471 updated += 1
479 updated += 1
472 else:
480 else:
473 merged += 1
481 merged += 1
474 elif m == "d": # directory rename
482 elif m == "d": # directory rename
475 f2, fd, flags = args
483 f2, fd, flags = args
476 if f:
484 if f:
477 repo.ui.note(_("moving %s to %s\n") % (f, fd))
485 repo.ui.note(_("moving %s to %s\n") % (f, fd))
478 audit(f)
486 audit(f)
479 repo.wwrite(fd, wctx.filectx(f).data(), flags)
487 repo.wwrite(fd, wctx.filectx(f).data(), flags)
480 util.unlinkpath(repo.wjoin(f))
488 util.unlinkpath(repo.wjoin(f))
481 if f2:
489 if f2:
482 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
490 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
483 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
491 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
484 updated += 1
492 updated += 1
485 elif m == "dr": # divergent renames
493 elif m == "dr": # divergent renames
486 fl, = args
494 fl, = args
487 repo.ui.warn(_("note: possible conflict - %s was renamed "
495 repo.ui.warn(_("note: possible conflict - %s was renamed "
488 "multiple times to:\n") % f)
496 "multiple times to:\n") % f)
489 for nf in fl:
497 for nf in fl:
490 repo.ui.warn(" %s\n" % nf)
498 repo.ui.warn(" %s\n" % nf)
491 elif m == "rd": # rename and delete
499 elif m == "rd": # rename and delete
492 fl, = args
500 fl, = args
493 repo.ui.warn(_("note: possible conflict - %s was deleted "
501 repo.ui.warn(_("note: possible conflict - %s was deleted "
494 "and renamed to:\n") % f)
502 "and renamed to:\n") % f)
495 for nf in fl:
503 for nf in fl:
496 repo.ui.warn(" %s\n" % nf)
504 repo.ui.warn(" %s\n" % nf)
497 elif m == "e": # exec
505 elif m == "e": # exec
498 flags, = args
506 flags, = args
499 audit(f)
507 audit(f)
500 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
508 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
501 updated += 1
509 updated += 1
502 ms.commit()
510 ms.commit()
503 progress(_updating, None, total=numupdates, unit=_files)
511 progress(_updating, None, total=numupdates, unit=_files)
504
512
505 return updated, merged, removed, unresolved
513 return updated, merged, removed, unresolved
506
514
507 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
515 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
508 "Calculate the actions needed to merge mctx into tctx"
516 "Calculate the actions needed to merge mctx into tctx"
509 actions = []
517 actions = []
510 folding = not util.checkcase(repo.path)
518 folding = not util.checkcase(repo.path)
511 if folding:
519 if folding:
512 # collision check is not needed for clean update
520 # collision check is not needed for clean update
513 if (not branchmerge and
521 if (not branchmerge and
514 (force or not tctx.dirty(missing=True, branch=False))):
522 (force or not tctx.dirty(missing=True, branch=False))):
515 _checkcollision(mctx, None)
523 _checkcollision(mctx, None)
516 else:
524 else:
517 _checkcollision(mctx, (tctx, ancestor))
525 _checkcollision(mctx, (tctx, ancestor))
518 actions += manifestmerge(repo, tctx, mctx,
526 actions += manifestmerge(repo, tctx, mctx,
519 ancestor,
527 ancestor,
520 branchmerge, force,
528 branchmerge, force,
521 partial)
529 partial)
522 if tctx.rev() is None:
530 if tctx.rev() is None:
523 actions += _forgetremoved(tctx, mctx, branchmerge)
531 actions += _forgetremoved(tctx, mctx, branchmerge)
524 return actions
532 return actions
525
533
526 def recordupdates(repo, actions, branchmerge):
534 def recordupdates(repo, actions, branchmerge):
527 "record merge actions to the dirstate"
535 "record merge actions to the dirstate"
528
536
529 for a in actions:
537 for a in actions:
530 f, m, args, msg = a
538 f, m, args, msg = a
531 if m == "r": # remove
539 if m == "r": # remove
532 if branchmerge:
540 if branchmerge:
533 repo.dirstate.remove(f)
541 repo.dirstate.remove(f)
534 else:
542 else:
535 repo.dirstate.drop(f)
543 repo.dirstate.drop(f)
536 elif m == "a": # re-add
544 elif m == "a": # re-add
537 if not branchmerge:
545 if not branchmerge:
538 repo.dirstate.add(f)
546 repo.dirstate.add(f)
539 elif m == "f": # forget
547 elif m == "f": # forget
540 repo.dirstate.drop(f)
548 repo.dirstate.drop(f)
541 elif m == "e": # exec change
549 elif m == "e": # exec change
542 repo.dirstate.normallookup(f)
550 repo.dirstate.normallookup(f)
543 elif m == "g": # get
551 elif m == "g": # get
544 if branchmerge:
552 if branchmerge:
545 repo.dirstate.otherparent(f)
553 repo.dirstate.otherparent(f)
546 else:
554 else:
547 repo.dirstate.normal(f)
555 repo.dirstate.normal(f)
548 elif m == "m": # merge
556 elif m == "m": # merge
549 f2, fd, move = args
557 f2, fd, move = args
550 if branchmerge:
558 if branchmerge:
551 # We've done a branch merge, mark this file as merged
559 # We've done a branch merge, mark this file as merged
552 # so that we properly record the merger later
560 # so that we properly record the merger later
553 repo.dirstate.merge(fd)
561 repo.dirstate.merge(fd)
554 if f != f2: # copy/rename
562 if f != f2: # copy/rename
555 if move:
563 if move:
556 repo.dirstate.remove(f)
564 repo.dirstate.remove(f)
557 if f != fd:
565 if f != fd:
558 repo.dirstate.copy(f, fd)
566 repo.dirstate.copy(f, fd)
559 else:
567 else:
560 repo.dirstate.copy(f2, fd)
568 repo.dirstate.copy(f2, fd)
561 else:
569 else:
562 # We've update-merged a locally modified file, so
570 # We've update-merged a locally modified file, so
563 # we set the dirstate to emulate a normal checkout
571 # we set the dirstate to emulate a normal checkout
564 # of that file some time in the past. Thus our
572 # of that file some time in the past. Thus our
565 # merge will appear as a normal local file
573 # merge will appear as a normal local file
566 # modification.
574 # modification.
567 if f2 == fd: # file not locally copied/moved
575 if f2 == fd: # file not locally copied/moved
568 repo.dirstate.normallookup(fd)
576 repo.dirstate.normallookup(fd)
569 if move:
577 if move:
570 repo.dirstate.drop(f)
578 repo.dirstate.drop(f)
571 elif m == "d": # directory rename
579 elif m == "d": # directory rename
572 f2, fd, flag = args
580 f2, fd, flag = args
573 if not f2 and f not in repo.dirstate:
581 if not f2 and f not in repo.dirstate:
574 # untracked file moved
582 # untracked file moved
575 continue
583 continue
576 if branchmerge:
584 if branchmerge:
577 repo.dirstate.add(fd)
585 repo.dirstate.add(fd)
578 if f:
586 if f:
579 repo.dirstate.remove(f)
587 repo.dirstate.remove(f)
580 repo.dirstate.copy(f, fd)
588 repo.dirstate.copy(f, fd)
581 if f2:
589 if f2:
582 repo.dirstate.copy(f2, fd)
590 repo.dirstate.copy(f2, fd)
583 else:
591 else:
584 repo.dirstate.normal(fd)
592 repo.dirstate.normal(fd)
585 if f:
593 if f:
586 repo.dirstate.drop(f)
594 repo.dirstate.drop(f)
587
595
588 def update(repo, node, branchmerge, force, partial, ancestor=None,
596 def update(repo, node, branchmerge, force, partial, ancestor=None,
589 mergeancestor=False):
597 mergeancestor=False):
590 """
598 """
591 Perform a merge between the working directory and the given node
599 Perform a merge between the working directory and the given node
592
600
593 node = the node to update to, or None if unspecified
601 node = the node to update to, or None if unspecified
594 branchmerge = whether to merge between branches
602 branchmerge = whether to merge between branches
595 force = whether to force branch merging or file overwriting
603 force = whether to force branch merging or file overwriting
596 partial = a function to filter file lists (dirstate not updated)
604 partial = a function to filter file lists (dirstate not updated)
597 mergeancestor = if false, merging with an ancestor (fast-forward)
605 mergeancestor = if false, merging with an ancestor (fast-forward)
598 is only allowed between different named branches. This flag
606 is only allowed between different named branches. This flag
599 is used by rebase extension as a temporary fix and should be
607 is used by rebase extension as a temporary fix and should be
600 avoided in general.
608 avoided in general.
601
609
602 The table below shows all the behaviors of the update command
610 The table below shows all the behaviors of the update command
603 given the -c and -C or no options, whether the working directory
611 given the -c and -C or no options, whether the working directory
604 is dirty, whether a revision is specified, and the relationship of
612 is dirty, whether a revision is specified, and the relationship of
605 the parent rev to the target rev (linear, on the same named
613 the parent rev to the target rev (linear, on the same named
606 branch, or on another named branch).
614 branch, or on another named branch).
607
615
608 This logic is tested by test-update-branches.t.
616 This logic is tested by test-update-branches.t.
609
617
610 -c -C dirty rev | linear same cross
618 -c -C dirty rev | linear same cross
611 n n n n | ok (1) x
619 n n n n | ok (1) x
612 n n n y | ok ok ok
620 n n n y | ok ok ok
613 n n y * | merge (2) (2)
621 n n y * | merge (2) (2)
614 n y * * | --- discard ---
622 n y * * | --- discard ---
615 y n y * | --- (3) ---
623 y n y * | --- (3) ---
616 y n n * | --- ok ---
624 y n n * | --- ok ---
617 y y * * | --- (4) ---
625 y y * * | --- (4) ---
618
626
619 x = can't happen
627 x = can't happen
620 * = don't-care
628 * = don't-care
621 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
629 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
622 2 = abort: crosses branches (use 'hg merge' to merge or
630 2 = abort: crosses branches (use 'hg merge' to merge or
623 use 'hg update -C' to discard changes)
631 use 'hg update -C' to discard changes)
624 3 = abort: uncommitted local changes
632 3 = abort: uncommitted local changes
625 4 = incompatible options (checked in commands.py)
633 4 = incompatible options (checked in commands.py)
626
634
627 Return the same tuple as applyupdates().
635 Return the same tuple as applyupdates().
628 """
636 """
629
637
630 onode = node
638 onode = node
631 wlock = repo.wlock()
639 wlock = repo.wlock()
632 try:
640 try:
633 wc = repo[None]
641 wc = repo[None]
634 if node is None:
642 if node is None:
635 # tip of current branch
643 # tip of current branch
636 try:
644 try:
637 node = repo.branchtip(wc.branch())
645 node = repo.branchtip(wc.branch())
638 except error.RepoLookupError:
646 except error.RepoLookupError:
639 if wc.branch() == "default": # no default branch!
647 if wc.branch() == "default": # no default branch!
640 node = repo.lookup("tip") # update to tip
648 node = repo.lookup("tip") # update to tip
641 else:
649 else:
642 raise util.Abort(_("branch %s not found") % wc.branch())
650 raise util.Abort(_("branch %s not found") % wc.branch())
643 overwrite = force and not branchmerge
651 overwrite = force and not branchmerge
644 pl = wc.parents()
652 pl = wc.parents()
645 p1, p2 = pl[0], repo[node]
653 p1, p2 = pl[0], repo[node]
646 if ancestor:
654 if ancestor:
647 pa = repo[ancestor]
655 pa = repo[ancestor]
648 else:
656 else:
649 pa = p1.ancestor(p2)
657 pa = p1.ancestor(p2)
650
658
651 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
659 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
652
660
653 ### check phase
661 ### check phase
654 if not overwrite and len(pl) > 1:
662 if not overwrite and len(pl) > 1:
655 raise util.Abort(_("outstanding uncommitted merges"))
663 raise util.Abort(_("outstanding uncommitted merges"))
656 if branchmerge:
664 if branchmerge:
657 if pa == p2:
665 if pa == p2:
658 raise util.Abort(_("merging with a working directory ancestor"
666 raise util.Abort(_("merging with a working directory ancestor"
659 " has no effect"))
667 " has no effect"))
660 elif pa == p1:
668 elif pa == p1:
661 if not mergeancestor and p1.branch() == p2.branch():
669 if not mergeancestor and p1.branch() == p2.branch():
662 raise util.Abort(_("nothing to merge"),
670 raise util.Abort(_("nothing to merge"),
663 hint=_("use 'hg update' "
671 hint=_("use 'hg update' "
664 "or check 'hg heads'"))
672 "or check 'hg heads'"))
665 if not force and (wc.files() or wc.deleted()):
673 if not force and (wc.files() or wc.deleted()):
666 raise util.Abort(_("outstanding uncommitted changes"),
674 raise util.Abort(_("outstanding uncommitted changes"),
667 hint=_("use 'hg status' to list changes"))
675 hint=_("use 'hg status' to list changes"))
668 for s in sorted(wc.substate):
676 for s in sorted(wc.substate):
669 if wc.sub(s).dirty():
677 if wc.sub(s).dirty():
670 raise util.Abort(_("outstanding uncommitted changes in "
678 raise util.Abort(_("outstanding uncommitted changes in "
671 "subrepository '%s'") % s)
679 "subrepository '%s'") % s)
672
680
673 elif not overwrite:
681 elif not overwrite:
674 if pa == p1 or pa == p2: # linear
682 if pa == p1 or pa == p2: # linear
675 pass # all good
683 pass # all good
676 elif wc.dirty(missing=True):
684 elif wc.dirty(missing=True):
677 raise util.Abort(_("crosses branches (merge branches or use"
685 raise util.Abort(_("crosses branches (merge branches or use"
678 " --clean to discard changes)"))
686 " --clean to discard changes)"))
679 elif onode is None:
687 elif onode is None:
680 raise util.Abort(_("crosses branches (merge branches or update"
688 raise util.Abort(_("crosses branches (merge branches or update"
681 " --check to force update)"))
689 " --check to force update)"))
682 else:
690 else:
683 # Allow jumping branches if clean and specific rev given
691 # Allow jumping branches if clean and specific rev given
684 pa = p1
692 pa = p1
685
693
686 ### calculate phase
694 ### calculate phase
687 actions = calculateupdates(repo, wc, p2, pa,
695 actions = calculateupdates(repo, wc, p2, pa,
688 branchmerge, force, partial)
696 branchmerge, force, partial)
689
697
690 ### apply phase
698 ### apply phase
691 if not branchmerge: # just jump to the new rev
699 if not branchmerge: # just jump to the new rev
692 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
700 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
693 if not partial:
701 if not partial:
694 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
702 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
695
703
696 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
704 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
697
705
698 if not partial:
706 if not partial:
699 repo.setparents(fp1, fp2)
707 repo.setparents(fp1, fp2)
700 recordupdates(repo, actions, branchmerge)
708 recordupdates(repo, actions, branchmerge)
701 if not branchmerge:
709 if not branchmerge:
702 repo.dirstate.setbranch(p2.branch())
710 repo.dirstate.setbranch(p2.branch())
703 finally:
711 finally:
704 wlock.release()
712 wlock.release()
705
713
706 if not partial:
714 if not partial:
707 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
715 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
708 return stats
716 return stats
General Comments 0
You need to be logged in to leave comments. Login now