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