##// END OF EJS Templates
merge: don't fiddle with name lookups or i18n in hot loops...
Bryan O'Sullivan -
r18640:a8648f32 default
parent child Browse files
Show More
@@ -1,698 +1,708 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, 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 if overwrite:
199 if overwrite:
200 pa = wctx
200 pa = wctx
201 elif pa == p2: # backwards
201 elif pa == p2: # backwards
202 pa = wctx.p1()
202 pa = wctx.p1()
203 elif not branchmerge and not wctx.dirty(missing=True):
203 elif not branchmerge and not wctx.dirty(missing=True):
204 pass
204 pass
205 elif pa and repo.ui.configbool("merge", "followcopies", True):
205 elif pa and repo.ui.configbool("merge", "followcopies", True):
206 ret = copies.mergecopies(repo, wctx, p2, pa)
206 ret = copies.mergecopies(repo, wctx, p2, pa)
207 copy, movewithdir, diverge, renamedelete = ret
207 copy, movewithdir, diverge, renamedelete = ret
208 for of, fl in diverge.iteritems():
208 for of, fl in diverge.iteritems():
209 actions.append((of, "dr", (fl,), "divergent renames"))
209 actions.append((of, "dr", (fl,), "divergent renames"))
210 for of, fl in renamedelete.iteritems():
210 for of, fl in renamedelete.iteritems():
211 actions.append((of, "rd", (fl,), "rename and delete"))
211 actions.append((of, "rd", (fl,), "rename and delete"))
212
212
213 repo.ui.note(_("resolving manifests\n"))
213 repo.ui.note(_("resolving manifests\n"))
214 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
214 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
215 % (bool(branchmerge), bool(force), bool(partial)))
215 % (bool(branchmerge), bool(force), bool(partial)))
216 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
216 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
217
217
218 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
218 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
219 copied = set(copy.values())
219 copied = set(copy.values())
220 copied.update(movewithdir.values())
220 copied.update(movewithdir.values())
221
221
222 if '.hgsubstate' in m1:
222 if '.hgsubstate' in m1:
223 # check whether sub state is modified
223 # check whether sub state is modified
224 for s in sorted(wctx.substate):
224 for s in sorted(wctx.substate):
225 if wctx.sub(s).dirty():
225 if wctx.sub(s).dirty():
226 m1['.hgsubstate'] += "+"
226 m1['.hgsubstate'] += "+"
227 break
227 break
228
228
229 aborts, prompts = [], []
229 aborts, prompts = [], []
230 # Compare manifests
230 # Compare manifests
231 for f, n in m1.iteritems():
231 for f, n in m1.iteritems():
232 if partial and not partial(f):
232 if partial and not partial(f):
233 continue
233 continue
234 if f in m2:
234 if f in m2:
235 n2 = m2[f]
235 n2 = m2[f]
236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 nol = 'l' not in fl1 + fl2 + fla
237 nol = 'l' not in fl1 + fl2 + fla
238 a = ma.get(f, nullid)
238 a = ma.get(f, nullid)
239 if n == n2 and fl1 == fl2:
239 if n == n2 and fl1 == fl2:
240 pass # same - keep local
240 pass # same - keep local
241 elif n2 == a and fl2 == fla:
241 elif n2 == a and fl2 == fla:
242 pass # remote unchanged - keep local
242 pass # remote unchanged - keep local
243 elif n == a and fl1 == fla: # local unchanged - use remote
243 elif n == a and fl1 == fla: # local unchanged - use remote
244 if n == n2: # optimization: keep local content
244 if n == n2: # optimization: keep local content
245 actions.append((f, "e", (fl2,), "update permissions"))
245 actions.append((f, "e", (fl2,), "update permissions"))
246 else:
246 else:
247 actions.append((f, "g", (fl2,), "remote is newer"))
247 actions.append((f, "g", (fl2,), "remote is newer"))
248 elif nol and n2 == a: # remote only changed 'x'
248 elif nol and n2 == a: # remote only changed 'x'
249 actions.append((f, "e", (fl2,), "update permissions"))
249 actions.append((f, "e", (fl2,), "update permissions"))
250 elif nol and n == a: # local only changed 'x'
250 elif nol and n == a: # local only changed 'x'
251 actions.append((f, "g", (fl1,), "remote is newer"))
251 actions.append((f, "g", (fl1,), "remote is newer"))
252 else: # both changed something
252 else: # both changed something
253 actions.append((f, "m", (f, f, False), "versions differ"))
253 actions.append((f, "m", (f, f, False), "versions differ"))
254 elif f in copied: # files we'll deal with on m2 side
254 elif f in copied: # files we'll deal with on m2 side
255 pass
255 pass
256 elif f in movewithdir: # directory rename
256 elif f in movewithdir: # directory rename
257 f2 = movewithdir[f]
257 f2 = movewithdir[f]
258 actions.append((f, "d", (None, f2, m1.flags(f)),
258 actions.append((f, "d", (None, f2, m1.flags(f)),
259 "remote renamed directory to " + f2))
259 "remote renamed directory to " + f2))
260 elif f in copy:
260 elif f in copy:
261 f2 = copy[f]
261 f2 = copy[f]
262 actions.append((f, "m", (f2, f, False),
262 actions.append((f, "m", (f2, f, False),
263 "local copied/moved to " + f2))
263 "local copied/moved to " + f2))
264 elif f in ma: # clean, a different, no remote
264 elif f in ma: # clean, a different, no remote
265 if n != ma[f]:
265 if n != ma[f]:
266 prompts.append((f, "cd")) # prompt changed/deleted
266 prompts.append((f, "cd")) # prompt changed/deleted
267 elif n[20:] == "a": # added, no remote
267 elif n[20:] == "a": # added, no remote
268 actions.append((f, "f", None, "remote deleted"))
268 actions.append((f, "f", None, "remote deleted"))
269 else:
269 else:
270 actions.append((f, "r", None, "other deleted"))
270 actions.append((f, "r", None, "other deleted"))
271
271
272 for f, n in m2.iteritems():
272 for f, n in m2.iteritems():
273 if partial and not partial(f):
273 if partial and not partial(f):
274 continue
274 continue
275 if f in m1 or f in copied: # files already visited
275 if f in m1 or f in copied: # files already visited
276 continue
276 continue
277 if f in movewithdir:
277 if f in movewithdir:
278 f2 = movewithdir[f]
278 f2 = movewithdir[f]
279 actions.append((None, "d", (f, f2, m2.flags(f)),
279 actions.append((None, "d", (f, f2, m2.flags(f)),
280 "local renamed directory to " + f2))
280 "local renamed directory to " + f2))
281 elif f in copy:
281 elif f in copy:
282 f2 = copy[f]
282 f2 = copy[f]
283 if f2 in m2:
283 if f2 in m2:
284 actions.append((f2, "m", (f, f, False),
284 actions.append((f2, "m", (f, f, False),
285 "remote copied to " + f))
285 "remote copied to " + f))
286 else:
286 else:
287 actions.append((f2, "m", (f, f, True),
287 actions.append((f2, "m", (f, f, True),
288 "remote moved to " + f))
288 "remote moved to " + f))
289 elif f not in ma:
289 elif f not in ma:
290 # local unknown, remote created: the logic is described by the
290 # local unknown, remote created: the logic is described by the
291 # following table:
291 # following table:
292 #
292 #
293 # force branchmerge different | action
293 # force branchmerge different | action
294 # n * n | get
294 # n * n | get
295 # n * y | abort
295 # n * y | abort
296 # y n * | get
296 # y n * | get
297 # y y n | get
297 # y y n | get
298 # y y y | merge
298 # y y y | merge
299 #
299 #
300 # Checking whether the files are different is expensive, so we
300 # Checking whether the files are different is expensive, so we
301 # don't do that when we can avoid it.
301 # don't do that when we can avoid it.
302 if force and not branchmerge:
302 if force and not branchmerge:
303 actions.append((f, "g", (m2.flags(f),), "remote created"))
303 actions.append((f, "g", (m2.flags(f),), "remote created"))
304 else:
304 else:
305 different = _checkunknownfile(repo, wctx, p2, f)
305 different = _checkunknownfile(repo, wctx, p2, f)
306 if force and branchmerge and different:
306 if force and branchmerge and different:
307 actions.append((f, "m", (f, f, False),
307 actions.append((f, "m", (f, f, False),
308 "remote differs from untracked local"))
308 "remote differs from untracked local"))
309 elif not force and different:
309 elif not force and different:
310 aborts.append((f, "ud"))
310 aborts.append((f, "ud"))
311 else:
311 else:
312 actions.append((f, "g", (m2.flags(f),), "remote created"))
312 actions.append((f, "g", (m2.flags(f),), "remote created"))
313 elif n != ma[f]:
313 elif n != ma[f]:
314 prompts.append((f, "dc")) # prompt deleted/changed
314 prompts.append((f, "dc")) # prompt deleted/changed
315
315
316 for f, m in sorted(aborts):
316 for f, m in sorted(aborts):
317 if m == "ud":
317 if m == "ud":
318 repo.ui.warn(_("%s: untracked file differs\n") % f)
318 repo.ui.warn(_("%s: untracked file differs\n") % f)
319 else: assert False, m
319 else: assert False, m
320 if aborts:
320 if aborts:
321 raise util.Abort(_("untracked files in working directory differ "
321 raise util.Abort(_("untracked files in working directory differ "
322 "from files in requested revision"))
322 "from files in requested revision"))
323
323
324 for f, m in sorted(prompts):
324 for f, m in sorted(prompts):
325 if m == "cd":
325 if m == "cd":
326 if repo.ui.promptchoice(
326 if repo.ui.promptchoice(
327 _("local changed %s which remote deleted\n"
327 _("local changed %s which remote deleted\n"
328 "use (c)hanged version or (d)elete?") % f,
328 "use (c)hanged version or (d)elete?") % f,
329 (_("&Changed"), _("&Delete")), 0):
329 (_("&Changed"), _("&Delete")), 0):
330 actions.append((f, "r", None, "prompt delete"))
330 actions.append((f, "r", None, "prompt delete"))
331 else:
331 else:
332 actions.append((f, "a", None, "prompt keep"))
332 actions.append((f, "a", None, "prompt keep"))
333 elif m == "dc":
333 elif m == "dc":
334 if repo.ui.promptchoice(
334 if repo.ui.promptchoice(
335 _("remote changed %s which local deleted\n"
335 _("remote changed %s which local deleted\n"
336 "use (c)hanged version or leave (d)eleted?") % f,
336 "use (c)hanged version or leave (d)eleted?") % f,
337 (_("&Changed"), _("&Deleted")), 0) == 0:
337 (_("&Changed"), _("&Deleted")), 0) == 0:
338 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
338 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
339 else: assert False, m
339 else: assert False, m
340 return actions
340 return actions
341
341
342 def actionkey(a):
342 def actionkey(a):
343 return a[1] == "r" and -1 or 0, a
343 return a[1] == "r" and -1 or 0, a
344
344
345 def getremove(repo, mctx, overwrite, args):
345 def getremove(repo, mctx, overwrite, args):
346 """apply usually-non-interactive updates to the working directory
346 """apply usually-non-interactive updates to the working directory
347
347
348 mctx is the context to be merged into the working copy
348 mctx is the context to be merged into the working copy
349
349
350 yields tuples for progress updates
350 yields tuples for progress updates
351 """
351 """
352 verbose = repo.ui.verbose
353 unlink = util.unlinkpath
354 wjoin = repo.wjoin
355 fctx = mctx.filectx
356 wwrite = repo.wwrite
352 audit = repo.wopener.audit
357 audit = repo.wopener.audit
353 i = 0
358 i = 0
354 for arg in args:
359 for arg in args:
355 f = arg[0]
360 f = arg[0]
356 if arg[1] == 'r':
361 if arg[1] == 'r':
357 repo.ui.note(_("removing %s\n") % f)
362 if verbose:
363 repo.ui.note(_("removing %s\n") % f)
358 audit(f)
364 audit(f)
359 try:
365 try:
360 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
366 unlink(wjoin(f), ignoremissing=True)
361 except OSError, inst:
367 except OSError, inst:
362 repo.ui.warn(_("update failed to remove %s: %s!\n") %
368 repo.ui.warn(_("update failed to remove %s: %s!\n") %
363 (f, inst.strerror))
369 (f, inst.strerror))
364 else:
370 else:
365 repo.ui.note(_("getting %s\n") % f)
371 if verbose:
366 repo.wwrite(f, mctx.filectx(f).data(), arg[2][0])
372 repo.ui.note(_("getting %s\n") % f)
373 wwrite(f, fctx(f).data(), arg[2][0])
367 if i == 100:
374 if i == 100:
368 yield i, f
375 yield i, f
369 i = 0
376 i = 0
370 i += 1
377 i += 1
371 if i > 0:
378 if i > 0:
372 yield i, f
379 yield i, f
373
380
374 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
381 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
375 """apply the merge action list to the working directory
382 """apply the merge action list to the working directory
376
383
377 wctx is the working copy context
384 wctx is the working copy context
378 mctx is the context to be merged into the working copy
385 mctx is the context to be merged into the working copy
379 actx is the context of the common ancestor
386 actx is the context of the common ancestor
380
387
381 Return a tuple of counts (updated, merged, removed, unresolved) that
388 Return a tuple of counts (updated, merged, removed, unresolved) that
382 describes how many files were affected by the update.
389 describes how many files were affected by the update.
383 """
390 """
384
391
385 updated, merged, removed, unresolved = 0, 0, 0, 0
392 updated, merged, removed, unresolved = 0, 0, 0, 0
386 ms = mergestate(repo)
393 ms = mergestate(repo)
387 ms.reset(wctx.p1().node())
394 ms.reset(wctx.p1().node())
388 moves = []
395 moves = []
389 actions.sort(key=actionkey)
396 actions.sort(key=actionkey)
390
397
391 # prescan for merges
398 # prescan for merges
392 for a in actions:
399 for a in actions:
393 f, m, args, msg = a
400 f, m, args, msg = a
394 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
401 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
395 if m == "m": # merge
402 if m == "m": # merge
396 f2, fd, move = args
403 f2, fd, move = args
397 if fd == '.hgsubstate': # merged internally
404 if fd == '.hgsubstate': # merged internally
398 continue
405 continue
399 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
406 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
400 fcl = wctx[f]
407 fcl = wctx[f]
401 fco = mctx[f2]
408 fco = mctx[f2]
402 if mctx == actx: # backwards, use working dir parent as ancestor
409 if mctx == actx: # backwards, use working dir parent as ancestor
403 if fcl.parents():
410 if fcl.parents():
404 fca = fcl.p1()
411 fca = fcl.p1()
405 else:
412 else:
406 fca = repo.filectx(f, fileid=nullrev)
413 fca = repo.filectx(f, fileid=nullrev)
407 else:
414 else:
408 fca = fcl.ancestor(fco, actx)
415 fca = fcl.ancestor(fco, actx)
409 if not fca:
416 if not fca:
410 fca = repo.filectx(f, fileid=nullrev)
417 fca = repo.filectx(f, fileid=nullrev)
411 ms.add(fcl, fco, fca, fd)
418 ms.add(fcl, fco, fca, fd)
412 if f != fd and move:
419 if f != fd and move:
413 moves.append(f)
420 moves.append(f)
414
421
415 audit = repo.wopener.audit
422 audit = repo.wopener.audit
416
423
417 # remove renamed files after safely stored
424 # remove renamed files after safely stored
418 for f in moves:
425 for f in moves:
419 if os.path.lexists(repo.wjoin(f)):
426 if os.path.lexists(repo.wjoin(f)):
420 repo.ui.debug("removing %s\n" % f)
427 repo.ui.debug("removing %s\n" % f)
421 audit(f)
428 audit(f)
422 util.unlinkpath(repo.wjoin(f))
429 util.unlinkpath(repo.wjoin(f))
423
430
424 numupdates = len(actions)
431 numupdates = len(actions)
425 workeractions = [a for a in actions if a[1] in 'gr']
432 workeractions = [a for a in actions if a[1] in 'gr']
426 updated = len([a for a in workeractions if a[1] == 'g'])
433 updated = len([a for a in workeractions if a[1] == 'g'])
427 removed = len([a for a in workeractions if a[1] == 'r'])
434 removed = len([a for a in workeractions if a[1] == 'r'])
428 actions = [a for a in actions if a[1] not in 'gr']
435 actions = [a for a in actions if a[1] not in 'gr']
429
436
430 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
437 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
431 if hgsub and hgsub[0] == 'r':
438 if hgsub and hgsub[0] == 'r':
432 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
439 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
433
440
434 z = 0
441 z = 0
435 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
442 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
436 workeractions)
443 workeractions)
437 for i, item in prog:
444 for i, item in prog:
438 z += i
445 z += i
439 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
446 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
440 unit=_('files'))
447 unit=_('files'))
441
448
442 if hgsub and hgsub[0] == 'g':
449 if hgsub and hgsub[0] == 'g':
443 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
450 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
444
451
452 _updating = _('updating')
453 _files = _('files')
454 progress = repo.ui.progress
455
445 for i, a in enumerate(actions):
456 for i, a in enumerate(actions):
446 f, m, args, msg = a
457 f, m, args, msg = a
447 repo.ui.progress(_('updating'), z + i + 1, item=f, total=numupdates,
458 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
448 unit=_('files'))
449 if m == "m": # merge
459 if m == "m": # merge
450 if fd == '.hgsubstate': # subrepo states need updating
460 if fd == '.hgsubstate': # subrepo states need updating
451 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
461 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
452 overwrite)
462 overwrite)
453 continue
463 continue
454 f2, fd, move = args
464 f2, fd, move = args
455 audit(fd)
465 audit(fd)
456 r = ms.resolve(fd, wctx, mctx)
466 r = ms.resolve(fd, wctx, mctx)
457 if r is not None and r > 0:
467 if r is not None and r > 0:
458 unresolved += 1
468 unresolved += 1
459 else:
469 else:
460 if r is None:
470 if r is None:
461 updated += 1
471 updated += 1
462 else:
472 else:
463 merged += 1
473 merged += 1
464 elif m == "d": # directory rename
474 elif m == "d": # directory rename
465 f2, fd, flags = args
475 f2, fd, flags = args
466 if f:
476 if f:
467 repo.ui.note(_("moving %s to %s\n") % (f, fd))
477 repo.ui.note(_("moving %s to %s\n") % (f, fd))
468 audit(f)
478 audit(f)
469 repo.wwrite(fd, wctx.filectx(f).data(), flags)
479 repo.wwrite(fd, wctx.filectx(f).data(), flags)
470 util.unlinkpath(repo.wjoin(f))
480 util.unlinkpath(repo.wjoin(f))
471 if f2:
481 if f2:
472 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
482 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
473 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
483 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
474 updated += 1
484 updated += 1
475 elif m == "dr": # divergent renames
485 elif m == "dr": # divergent renames
476 fl, = args
486 fl, = args
477 repo.ui.warn(_("note: possible conflict - %s was renamed "
487 repo.ui.warn(_("note: possible conflict - %s was renamed "
478 "multiple times to:\n") % f)
488 "multiple times to:\n") % f)
479 for nf in fl:
489 for nf in fl:
480 repo.ui.warn(" %s\n" % nf)
490 repo.ui.warn(" %s\n" % nf)
481 elif m == "rd": # rename and delete
491 elif m == "rd": # rename and delete
482 fl, = args
492 fl, = args
483 repo.ui.warn(_("note: possible conflict - %s was deleted "
493 repo.ui.warn(_("note: possible conflict - %s was deleted "
484 "and renamed to:\n") % f)
494 "and renamed to:\n") % f)
485 for nf in fl:
495 for nf in fl:
486 repo.ui.warn(" %s\n" % nf)
496 repo.ui.warn(" %s\n" % nf)
487 elif m == "e": # exec
497 elif m == "e": # exec
488 flags, = args
498 flags, = args
489 audit(f)
499 audit(f)
490 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
500 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
491 updated += 1
501 updated += 1
492 ms.commit()
502 ms.commit()
493 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
503 progress(_updating, None, total=numupdates, unit=_files)
494
504
495 return updated, merged, removed, unresolved
505 return updated, merged, removed, unresolved
496
506
497 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
507 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
498 "Calculate the actions needed to merge mctx into tctx"
508 "Calculate the actions needed to merge mctx into tctx"
499 actions = []
509 actions = []
500 folding = not util.checkcase(repo.path)
510 folding = not util.checkcase(repo.path)
501 if folding:
511 if folding:
502 # collision check is not needed for clean update
512 # collision check is not needed for clean update
503 if (not branchmerge and
513 if (not branchmerge and
504 (force or not tctx.dirty(missing=True, branch=False))):
514 (force or not tctx.dirty(missing=True, branch=False))):
505 _checkcollision(mctx, None)
515 _checkcollision(mctx, None)
506 else:
516 else:
507 _checkcollision(mctx, (tctx, ancestor))
517 _checkcollision(mctx, (tctx, ancestor))
508 if tctx.rev() is None:
518 if tctx.rev() is None:
509 actions += _forgetremoved(tctx, mctx, branchmerge)
519 actions += _forgetremoved(tctx, mctx, branchmerge)
510 actions += manifestmerge(repo, tctx, mctx,
520 actions += manifestmerge(repo, tctx, mctx,
511 ancestor,
521 ancestor,
512 branchmerge, force,
522 branchmerge, force,
513 partial)
523 partial)
514 return actions
524 return actions
515
525
516 def recordupdates(repo, actions, branchmerge):
526 def recordupdates(repo, actions, branchmerge):
517 "record merge actions to the dirstate"
527 "record merge actions to the dirstate"
518
528
519 for a in actions:
529 for a in actions:
520 f, m, args, msg = a
530 f, m, args, msg = a
521 if m == "r": # remove
531 if m == "r": # remove
522 if branchmerge:
532 if branchmerge:
523 repo.dirstate.remove(f)
533 repo.dirstate.remove(f)
524 else:
534 else:
525 repo.dirstate.drop(f)
535 repo.dirstate.drop(f)
526 elif m == "a": # re-add
536 elif m == "a": # re-add
527 if not branchmerge:
537 if not branchmerge:
528 repo.dirstate.add(f)
538 repo.dirstate.add(f)
529 elif m == "f": # forget
539 elif m == "f": # forget
530 repo.dirstate.drop(f)
540 repo.dirstate.drop(f)
531 elif m == "e": # exec change
541 elif m == "e": # exec change
532 repo.dirstate.normallookup(f)
542 repo.dirstate.normallookup(f)
533 elif m == "g": # get
543 elif m == "g": # get
534 if branchmerge:
544 if branchmerge:
535 repo.dirstate.otherparent(f)
545 repo.dirstate.otherparent(f)
536 else:
546 else:
537 repo.dirstate.normal(f)
547 repo.dirstate.normal(f)
538 elif m == "m": # merge
548 elif m == "m": # merge
539 f2, fd, move = args
549 f2, fd, move = args
540 if branchmerge:
550 if branchmerge:
541 # We've done a branch merge, mark this file as merged
551 # We've done a branch merge, mark this file as merged
542 # so that we properly record the merger later
552 # so that we properly record the merger later
543 repo.dirstate.merge(fd)
553 repo.dirstate.merge(fd)
544 if f != f2: # copy/rename
554 if f != f2: # copy/rename
545 if move:
555 if move:
546 repo.dirstate.remove(f)
556 repo.dirstate.remove(f)
547 if f != fd:
557 if f != fd:
548 repo.dirstate.copy(f, fd)
558 repo.dirstate.copy(f, fd)
549 else:
559 else:
550 repo.dirstate.copy(f2, fd)
560 repo.dirstate.copy(f2, fd)
551 else:
561 else:
552 # We've update-merged a locally modified file, so
562 # We've update-merged a locally modified file, so
553 # we set the dirstate to emulate a normal checkout
563 # we set the dirstate to emulate a normal checkout
554 # of that file some time in the past. Thus our
564 # of that file some time in the past. Thus our
555 # merge will appear as a normal local file
565 # merge will appear as a normal local file
556 # modification.
566 # modification.
557 if f2 == fd: # file not locally copied/moved
567 if f2 == fd: # file not locally copied/moved
558 repo.dirstate.normallookup(fd)
568 repo.dirstate.normallookup(fd)
559 if move:
569 if move:
560 repo.dirstate.drop(f)
570 repo.dirstate.drop(f)
561 elif m == "d": # directory rename
571 elif m == "d": # directory rename
562 f2, fd, flag = args
572 f2, fd, flag = args
563 if not f2 and f not in repo.dirstate:
573 if not f2 and f not in repo.dirstate:
564 # untracked file moved
574 # untracked file moved
565 continue
575 continue
566 if branchmerge:
576 if branchmerge:
567 repo.dirstate.add(fd)
577 repo.dirstate.add(fd)
568 if f:
578 if f:
569 repo.dirstate.remove(f)
579 repo.dirstate.remove(f)
570 repo.dirstate.copy(f, fd)
580 repo.dirstate.copy(f, fd)
571 if f2:
581 if f2:
572 repo.dirstate.copy(f2, fd)
582 repo.dirstate.copy(f2, fd)
573 else:
583 else:
574 repo.dirstate.normal(fd)
584 repo.dirstate.normal(fd)
575 if f:
585 if f:
576 repo.dirstate.drop(f)
586 repo.dirstate.drop(f)
577
587
578 def update(repo, node, branchmerge, force, partial, ancestor=None,
588 def update(repo, node, branchmerge, force, partial, ancestor=None,
579 mergeancestor=False):
589 mergeancestor=False):
580 """
590 """
581 Perform a merge between the working directory and the given node
591 Perform a merge between the working directory and the given node
582
592
583 node = the node to update to, or None if unspecified
593 node = the node to update to, or None if unspecified
584 branchmerge = whether to merge between branches
594 branchmerge = whether to merge between branches
585 force = whether to force branch merging or file overwriting
595 force = whether to force branch merging or file overwriting
586 partial = a function to filter file lists (dirstate not updated)
596 partial = a function to filter file lists (dirstate not updated)
587 mergeancestor = if false, merging with an ancestor (fast-forward)
597 mergeancestor = if false, merging with an ancestor (fast-forward)
588 is only allowed between different named branches. This flag
598 is only allowed between different named branches. This flag
589 is used by rebase extension as a temporary fix and should be
599 is used by rebase extension as a temporary fix and should be
590 avoided in general.
600 avoided in general.
591
601
592 The table below shows all the behaviors of the update command
602 The table below shows all the behaviors of the update command
593 given the -c and -C or no options, whether the working directory
603 given the -c and -C or no options, whether the working directory
594 is dirty, whether a revision is specified, and the relationship of
604 is dirty, whether a revision is specified, and the relationship of
595 the parent rev to the target rev (linear, on the same named
605 the parent rev to the target rev (linear, on the same named
596 branch, or on another named branch).
606 branch, or on another named branch).
597
607
598 This logic is tested by test-update-branches.t.
608 This logic is tested by test-update-branches.t.
599
609
600 -c -C dirty rev | linear same cross
610 -c -C dirty rev | linear same cross
601 n n n n | ok (1) x
611 n n n n | ok (1) x
602 n n n y | ok ok ok
612 n n n y | ok ok ok
603 n n y * | merge (2) (2)
613 n n y * | merge (2) (2)
604 n y * * | --- discard ---
614 n y * * | --- discard ---
605 y n y * | --- (3) ---
615 y n y * | --- (3) ---
606 y n n * | --- ok ---
616 y n n * | --- ok ---
607 y y * * | --- (4) ---
617 y y * * | --- (4) ---
608
618
609 x = can't happen
619 x = can't happen
610 * = don't-care
620 * = don't-care
611 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
621 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
612 2 = abort: crosses branches (use 'hg merge' to merge or
622 2 = abort: crosses branches (use 'hg merge' to merge or
613 use 'hg update -C' to discard changes)
623 use 'hg update -C' to discard changes)
614 3 = abort: uncommitted local changes
624 3 = abort: uncommitted local changes
615 4 = incompatible options (checked in commands.py)
625 4 = incompatible options (checked in commands.py)
616
626
617 Return the same tuple as applyupdates().
627 Return the same tuple as applyupdates().
618 """
628 """
619
629
620 onode = node
630 onode = node
621 wlock = repo.wlock()
631 wlock = repo.wlock()
622 try:
632 try:
623 wc = repo[None]
633 wc = repo[None]
624 if node is None:
634 if node is None:
625 # tip of current branch
635 # tip of current branch
626 try:
636 try:
627 node = repo.branchtip(wc.branch())
637 node = repo.branchtip(wc.branch())
628 except error.RepoLookupError:
638 except error.RepoLookupError:
629 if wc.branch() == "default": # no default branch!
639 if wc.branch() == "default": # no default branch!
630 node = repo.lookup("tip") # update to tip
640 node = repo.lookup("tip") # update to tip
631 else:
641 else:
632 raise util.Abort(_("branch %s not found") % wc.branch())
642 raise util.Abort(_("branch %s not found") % wc.branch())
633 overwrite = force and not branchmerge
643 overwrite = force and not branchmerge
634 pl = wc.parents()
644 pl = wc.parents()
635 p1, p2 = pl[0], repo[node]
645 p1, p2 = pl[0], repo[node]
636 if ancestor:
646 if ancestor:
637 pa = repo[ancestor]
647 pa = repo[ancestor]
638 else:
648 else:
639 pa = p1.ancestor(p2)
649 pa = p1.ancestor(p2)
640
650
641 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
651 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
642
652
643 ### check phase
653 ### check phase
644 if not overwrite and len(pl) > 1:
654 if not overwrite and len(pl) > 1:
645 raise util.Abort(_("outstanding uncommitted merges"))
655 raise util.Abort(_("outstanding uncommitted merges"))
646 if branchmerge:
656 if branchmerge:
647 if pa == p2:
657 if pa == p2:
648 raise util.Abort(_("merging with a working directory ancestor"
658 raise util.Abort(_("merging with a working directory ancestor"
649 " has no effect"))
659 " has no effect"))
650 elif pa == p1:
660 elif pa == p1:
651 if not mergeancestor and p1.branch() == p2.branch():
661 if not mergeancestor and p1.branch() == p2.branch():
652 raise util.Abort(_("nothing to merge"),
662 raise util.Abort(_("nothing to merge"),
653 hint=_("use 'hg update' "
663 hint=_("use 'hg update' "
654 "or check 'hg heads'"))
664 "or check 'hg heads'"))
655 if not force and (wc.files() or wc.deleted()):
665 if not force and (wc.files() or wc.deleted()):
656 raise util.Abort(_("outstanding uncommitted changes"),
666 raise util.Abort(_("outstanding uncommitted changes"),
657 hint=_("use 'hg status' to list changes"))
667 hint=_("use 'hg status' to list changes"))
658 for s in sorted(wc.substate):
668 for s in sorted(wc.substate):
659 if wc.sub(s).dirty():
669 if wc.sub(s).dirty():
660 raise util.Abort(_("outstanding uncommitted changes in "
670 raise util.Abort(_("outstanding uncommitted changes in "
661 "subrepository '%s'") % s)
671 "subrepository '%s'") % s)
662
672
663 elif not overwrite:
673 elif not overwrite:
664 if pa == p1 or pa == p2: # linear
674 if pa == p1 or pa == p2: # linear
665 pass # all good
675 pass # all good
666 elif wc.dirty(missing=True):
676 elif wc.dirty(missing=True):
667 raise util.Abort(_("crosses branches (merge branches or use"
677 raise util.Abort(_("crosses branches (merge branches or use"
668 " --clean to discard changes)"))
678 " --clean to discard changes)"))
669 elif onode is None:
679 elif onode is None:
670 raise util.Abort(_("crosses branches (merge branches or update"
680 raise util.Abort(_("crosses branches (merge branches or update"
671 " --check to force update)"))
681 " --check to force update)"))
672 else:
682 else:
673 # Allow jumping branches if clean and specific rev given
683 # Allow jumping branches if clean and specific rev given
674 pa = p1
684 pa = p1
675
685
676 ### calculate phase
686 ### calculate phase
677 actions = calculateupdates(repo, wc, p2, pa,
687 actions = calculateupdates(repo, wc, p2, pa,
678 branchmerge, force, partial)
688 branchmerge, force, partial)
679
689
680 ### apply phase
690 ### apply phase
681 if not branchmerge: # just jump to the new rev
691 if not branchmerge: # just jump to the new rev
682 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
692 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
683 if not partial:
693 if not partial:
684 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
694 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
685
695
686 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
696 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
687
697
688 if not partial:
698 if not partial:
689 repo.setparents(fp1, fp2)
699 repo.setparents(fp1, fp2)
690 recordupdates(repo, actions, branchmerge)
700 recordupdates(repo, actions, branchmerge)
691 if not branchmerge:
701 if not branchmerge:
692 repo.dirstate.setbranch(p2.branch())
702 repo.dirstate.setbranch(p2.branch())
693 finally:
703 finally:
694 wlock.release()
704 wlock.release()
695
705
696 if not partial:
706 if not partial:
697 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
707 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
698 return stats
708 return stats
General Comments 0
You need to be logged in to leave comments. Login now