##// END OF EJS Templates
update: add comments and test cases for updating across branches...
Stuart W Marks -
r9716:ea8c207a default
parent child Browse files
Show More
@@ -0,0 +1,81 b''
1 #!/bin/sh
2
3 # Construct the following history tree:
4 #
5 # @ 5:e1bb631146ca b1
6 # |
7 # o 4:a4fdb3b883c4 0:b608b9236435 b1
8 # |
9 # | o 3:4b57d2520816 1:44592833ba9f
10 # | |
11 # | | o 2:063f31070f65
12 # | |/
13 # | o 1:44592833ba9f
14 # |/
15 # o 0:b608b9236435
16
17 hg init
18 echo foo > foo
19 echo zero > a
20 hg ci -qAm0
21 echo one > a ; hg ci -m1
22 echo two > a ; hg ci -m2
23 hg up -q 1
24 echo three > a ; hg ci -qm3
25 hg up -q 0
26 hg branch -q b1
27 echo four > a ; hg ci -qm4
28 echo five > a ; hg ci -qm5
29
30 echo % initial repo state
31 echo
32 hg --config 'extensions.graphlog=' \
33 glog --template '{rev}:{node|short} {parents} {branches}\n'
34
35 # Test helper functions.
36
37 revtest () {
38 msg=$1
39 dirtyflag=$2 # 'clean' or 'dirty'
40 startrev=$3
41 targetrev=$4
42 opt=$5
43 echo % revtest $msg $startrev $targetrev
44 hg up -qC $startrev
45 test $dirtyflag = dirty && echo dirty > foo
46 hg up $opt $targetrev
47 hg parent --template 'parent={rev}\n'
48 hg stat
49 }
50
51 norevtest () {
52 msg=$1
53 dirtyflag=$2 # 'clean' or 'dirty'
54 startrev=$3
55 opt=$4
56 echo % norevtest $msg $startrev
57 hg up -qC $startrev
58 test $dirtyflag = dirty && echo dirty > foo
59 hg up $opt
60 hg parent --template 'parent={rev}\n'
61 hg stat
62 }
63
64 # Test cases are documented in a table in the update function of merge.py.
65 # Cases are run as shown in that table, row by row.
66
67 norevtest 'none clean linear' clean 4
68 norevtest 'none clean same' clean 2
69
70 revtest 'none clean linear' clean 1 2
71 revtest 'none clean same' clean 2 3
72 revtest 'none clean cross' clean 3 4
73
74 revtest 'none dirty linear' dirty 1 2
75 revtest 'none dirty same' dirty 2 3
76 revtest 'none dirty cross' dirty 3 4
77
78 revtest '-C dirty linear' dirty 1 2 -C
79 revtest '-c dirty linear' dirty 1 2 -c
80 norevtest '-c clean same' clean 2 -c
81 revtest '-cC dirty linear' dirty 1 2 -cC
@@ -0,0 +1,55 b''
1 % initial repo state
2
3 @ 5:e1bb631146ca b1
4 |
5 o 4:a4fdb3b883c4 0:b608b9236435 b1
6 |
7 | o 3:4b57d2520816 1:44592833ba9f
8 | |
9 | | o 2:063f31070f65
10 | |/
11 | o 1:44592833ba9f
12 |/
13 o 0:b608b9236435
14
15 % norevtest none clean linear 4
16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17 parent=5
18 % norevtest none clean same 2
19 abort: crosses branches (use 'hg merge' or 'hg update -C')
20 parent=2
21 % revtest none clean linear 1 2
22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 parent=2
24 % revtest none clean same 2 3
25 abort: crosses branches (use 'hg merge' or 'hg update -C')
26 parent=2
27 % revtest none clean cross 3 4
28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 parent=4
30 % revtest none dirty linear 1 2
31 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 parent=2
33 M foo
34 % revtest none dirty same 2 3
35 abort: crosses branches (use 'hg merge' or 'hg update -C' to discard changes)
36 parent=2
37 M foo
38 % revtest none dirty cross 3 4
39 abort: crosses named branches (use 'hg update -C' to discard changes)
40 parent=3
41 M foo
42 % revtest -C dirty linear 1 2
43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 parent=2
45 % revtest -c dirty linear 1 2
46 abort: uncommitted local changes
47 parent=1
48 M foo
49 % norevtest -c clean same 2
50 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
51 parent=3
52 % revtest -cC dirty linear 1 2
53 abort: cannot specify both -c/--check and -C/--clean
54 parent=1
55 M foo
@@ -1,481 +1,509 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._read()
18 18 def reset(self, node=None):
19 19 self._state = {}
20 20 if node:
21 21 self._local = node
22 22 shutil.rmtree(self._repo.join("merge"), True)
23 23 def _read(self):
24 24 self._state = {}
25 25 try:
26 26 localnode = None
27 27 f = self._repo.opener("merge/state")
28 28 for i, l in enumerate(f):
29 29 if i == 0:
30 30 localnode = l[:-1]
31 31 else:
32 32 bits = l[:-1].split("\0")
33 33 self._state[bits[0]] = bits[1:]
34 34 self._local = bin(localnode)
35 35 except IOError, err:
36 36 if err.errno != errno.ENOENT:
37 37 raise
38 38 def _write(self):
39 39 f = self._repo.opener("merge/state", "w")
40 40 f.write(hex(self._local) + "\n")
41 41 for d, v in self._state.iteritems():
42 42 f.write("\0".join([d] + v) + "\n")
43 43 def add(self, fcl, fco, fca, fd, flags):
44 44 hash = util.sha1(fcl.path()).hexdigest()
45 45 self._repo.opener("merge/" + hash, "w").write(fcl.data())
46 46 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
47 47 hex(fca.filenode()), fco.path(), flags]
48 48 self._write()
49 49 def __contains__(self, dfile):
50 50 return dfile in self._state
51 51 def __getitem__(self, dfile):
52 52 return self._state[dfile][0]
53 53 def __iter__(self):
54 54 l = self._state.keys()
55 55 l.sort()
56 56 for f in l:
57 57 yield f
58 58 def mark(self, dfile, state):
59 59 self._state[dfile][0] = state
60 60 self._write()
61 61 def resolve(self, dfile, wctx, octx):
62 62 if self[dfile] == 'r':
63 63 return 0
64 64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
65 65 f = self._repo.opener("merge/" + hash)
66 66 self._repo.wwrite(dfile, f.read(), flags)
67 67 fcd = wctx[dfile]
68 68 fco = octx[ofile]
69 69 fca = self._repo.filectx(afile, fileid=anode)
70 70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
71 71 if not r:
72 72 self.mark(dfile, 'r')
73 73 return r
74 74
75 75 def _checkunknown(wctx, mctx):
76 76 "check for collisions between unknown files and files in mctx"
77 77 for f in wctx.unknown():
78 78 if f in mctx and mctx[f].cmp(wctx[f].data()):
79 79 raise util.Abort(_("untracked file in working directory differs"
80 80 " from file in requested revision: '%s'") % f)
81 81
82 82 def _checkcollision(mctx):
83 83 "check for case folding collisions in the destination context"
84 84 folded = {}
85 85 for fn in mctx:
86 86 fold = fn.lower()
87 87 if fold in folded:
88 88 raise util.Abort(_("case-folding collision between %s and %s")
89 89 % (fn, folded[fold]))
90 90 folded[fold] = fn
91 91
92 92 def _forgetremoved(wctx, mctx, branchmerge):
93 93 """
94 94 Forget removed files
95 95
96 96 If we're jumping between revisions (as opposed to merging), and if
97 97 neither the working directory nor the target rev has the file,
98 98 then we need to remove it from the dirstate, to prevent the
99 99 dirstate from listing the file when it is no longer in the
100 100 manifest.
101 101
102 102 If we're merging, and the other revision has removed a file
103 103 that is not present in the working directory, we need to mark it
104 104 as removed.
105 105 """
106 106
107 107 action = []
108 108 state = branchmerge and 'r' or 'f'
109 109 for f in wctx.deleted():
110 110 if f not in mctx:
111 111 action.append((f, state))
112 112
113 113 if not branchmerge:
114 114 for f in wctx.removed():
115 115 if f not in mctx:
116 116 action.append((f, "f"))
117 117
118 118 return action
119 119
120 120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
121 121 """
122 122 Merge p1 and p2 with ancestor ma and generate merge action list
123 123
124 124 overwrite = whether we clobber working files
125 125 partial = function to filter file lists
126 126 """
127 127
128 128 def fmerge(f, f2, fa):
129 129 """merge flags"""
130 130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
131 131 if m == n: # flags agree
132 132 return m # unchanged
133 133 if m and n and not a: # flags set, don't agree, differ from parent
134 134 r = repo.ui.promptchoice(
135 135 _(" conflicting flags for %s\n"
136 136 "(n)one, e(x)ec or sym(l)ink?") % f,
137 137 (_("&None"), _("E&xec"), _("Sym&link")), 0)
138 138 if r == 1: return "x" # Exec
139 139 if r == 2: return "l" # Symlink
140 140 return ""
141 141 if m and m != a: # changed from a to m
142 142 return m
143 143 if n and n != a: # changed from a to n
144 144 return n
145 145 return '' # flag was cleared
146 146
147 147 def act(msg, m, f, *args):
148 148 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
149 149 action.append((f, m) + args)
150 150
151 151 action, copy = [], {}
152 152
153 153 if overwrite:
154 154 pa = p1
155 155 elif pa == p2: # backwards
156 156 pa = p1.p1()
157 157 elif pa and repo.ui.configbool("merge", "followcopies", True):
158 158 dirs = repo.ui.configbool("merge", "followdirs", True)
159 159 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
160 160 for of, fl in diverge.iteritems():
161 161 act("divergent renames", "dr", of, fl)
162 162
163 163 repo.ui.note(_("resolving manifests\n"))
164 164 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
165 165 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
166 166
167 167 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
168 168 copied = set(copy.values())
169 169
170 170 # Compare manifests
171 171 for f, n in m1.iteritems():
172 172 if partial and not partial(f):
173 173 continue
174 174 if f in m2:
175 175 rflags = fmerge(f, f, f)
176 176 a = ma.get(f, nullid)
177 177 if n == m2[f] or m2[f] == a: # same or local newer
178 178 if m1.flags(f) != rflags:
179 179 act("update permissions", "e", f, rflags)
180 180 elif n == a: # remote newer
181 181 act("remote is newer", "g", f, rflags)
182 182 else: # both changed
183 183 act("versions differ", "m", f, f, f, rflags, False)
184 184 elif f in copied: # files we'll deal with on m2 side
185 185 pass
186 186 elif f in copy:
187 187 f2 = copy[f]
188 188 if f2 not in m2: # directory rename
189 189 act("remote renamed directory to " + f2, "d",
190 190 f, None, f2, m1.flags(f))
191 191 else: # case 2 A,B/B/B or case 4,21 A/B/B
192 192 act("local copied/moved to " + f2, "m",
193 193 f, f2, f, fmerge(f, f2, f2), False)
194 194 elif f in ma: # clean, a different, no remote
195 195 if n != ma[f]:
196 196 if repo.ui.promptchoice(
197 197 _(" local changed %s which remote deleted\n"
198 198 "use (c)hanged version or (d)elete?") % f,
199 199 (_("&Changed"), _("&Delete")), 0):
200 200 act("prompt delete", "r", f)
201 201 else:
202 202 act("prompt keep", "a", f)
203 203 elif n[20:] == "a": # added, no remote
204 204 act("remote deleted", "f", f)
205 205 elif n[20:] != "u":
206 206 act("other deleted", "r", f)
207 207
208 208 for f, n in m2.iteritems():
209 209 if partial and not partial(f):
210 210 continue
211 211 if f in m1 or f in copied: # files already visited
212 212 continue
213 213 if f in copy:
214 214 f2 = copy[f]
215 215 if f2 not in m1: # directory rename
216 216 act("local renamed directory to " + f2, "d",
217 217 None, f, f2, m2.flags(f))
218 218 elif f2 in m2: # rename case 1, A/A,B/A
219 219 act("remote copied to " + f, "m",
220 220 f2, f, f, fmerge(f2, f, f2), False)
221 221 else: # case 3,20 A/B/A
222 222 act("remote moved to " + f, "m",
223 223 f2, f, f, fmerge(f2, f, f2), True)
224 224 elif f not in ma:
225 225 act("remote created", "g", f, m2.flags(f))
226 226 elif n != ma[f]:
227 227 if repo.ui.promptchoice(
228 228 _("remote changed %s which local deleted\n"
229 229 "use (c)hanged version or leave (d)eleted?") % f,
230 230 (_("&Changed"), _("&Deleted")), 0) == 0:
231 231 act("prompt recreating", "g", f, m2.flags(f))
232 232
233 233 return action
234 234
235 235 def actionkey(a):
236 236 return a[1] == 'r' and -1 or 0, a
237 237
238 238 def applyupdates(repo, action, wctx, mctx):
239 239 "apply the merge action list to the working directory"
240 240
241 241 updated, merged, removed, unresolved = 0, 0, 0, 0
242 242 ms = mergestate(repo)
243 243 ms.reset(wctx.parents()[0].node())
244 244 moves = []
245 245 action.sort(key=actionkey)
246 246 substate = wctx.substate # prime
247 247
248 248 # prescan for merges
249 249 for a in action:
250 250 f, m = a[:2]
251 251 if m == 'm': # merge
252 252 f2, fd, flags, move = a[2:]
253 253 if f == '.hgsubstate': # merged internally
254 254 continue
255 255 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
256 256 fcl = wctx[f]
257 257 fco = mctx[f2]
258 258 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
259 259 ms.add(fcl, fco, fca, fd, flags)
260 260 if f != fd and move:
261 261 moves.append(f)
262 262
263 263 # remove renamed files after safely stored
264 264 for f in moves:
265 265 if util.lexists(repo.wjoin(f)):
266 266 repo.ui.debug("removing %s\n" % f)
267 267 os.unlink(repo.wjoin(f))
268 268
269 269 audit_path = util.path_auditor(repo.root)
270 270
271 271 for a in action:
272 272 f, m = a[:2]
273 273 if f and f[0] == "/":
274 274 continue
275 275 if m == "r": # remove
276 276 repo.ui.note(_("removing %s\n") % f)
277 277 audit_path(f)
278 278 if f == '.hgsubstate': # subrepo states need updating
279 279 subrepo.submerge(repo, wctx, mctx, wctx)
280 280 try:
281 281 util.unlink(repo.wjoin(f))
282 282 except OSError, inst:
283 283 if inst.errno != errno.ENOENT:
284 284 repo.ui.warn(_("update failed to remove %s: %s!\n") %
285 285 (f, inst.strerror))
286 286 removed += 1
287 287 elif m == "m": # merge
288 288 if f == '.hgsubstate': # subrepo states need updating
289 289 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
290 290 continue
291 291 f2, fd, flags, move = a[2:]
292 292 r = ms.resolve(fd, wctx, mctx)
293 293 if r is not None and r > 0:
294 294 unresolved += 1
295 295 else:
296 296 if r is None:
297 297 updated += 1
298 298 else:
299 299 merged += 1
300 300 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
301 301 if f != fd and move and util.lexists(repo.wjoin(f)):
302 302 repo.ui.debug("removing %s\n" % f)
303 303 os.unlink(repo.wjoin(f))
304 304 elif m == "g": # get
305 305 flags = a[2]
306 306 repo.ui.note(_("getting %s\n") % f)
307 307 t = mctx.filectx(f).data()
308 308 repo.wwrite(f, t, flags)
309 309 updated += 1
310 310 if f == '.hgsubstate': # subrepo states need updating
311 311 subrepo.submerge(repo, wctx, mctx, wctx)
312 312 elif m == "d": # directory rename
313 313 f2, fd, flags = a[2:]
314 314 if f:
315 315 repo.ui.note(_("moving %s to %s\n") % (f, fd))
316 316 t = wctx.filectx(f).data()
317 317 repo.wwrite(fd, t, flags)
318 318 util.unlink(repo.wjoin(f))
319 319 if f2:
320 320 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
321 321 t = mctx.filectx(f2).data()
322 322 repo.wwrite(fd, t, flags)
323 323 updated += 1
324 324 elif m == "dr": # divergent renames
325 325 fl = a[2]
326 326 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
327 327 for nf in fl:
328 328 repo.ui.warn(" %s\n" % nf)
329 329 elif m == "e": # exec
330 330 flags = a[2]
331 331 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
332 332
333 333 return updated, merged, removed, unresolved
334 334
335 335 def recordupdates(repo, action, branchmerge):
336 336 "record merge actions to the dirstate"
337 337
338 338 for a in action:
339 339 f, m = a[:2]
340 340 if m == "r": # remove
341 341 if branchmerge:
342 342 repo.dirstate.remove(f)
343 343 else:
344 344 repo.dirstate.forget(f)
345 345 elif m == "a": # re-add
346 346 if not branchmerge:
347 347 repo.dirstate.add(f)
348 348 elif m == "f": # forget
349 349 repo.dirstate.forget(f)
350 350 elif m == "e": # exec change
351 351 repo.dirstate.normallookup(f)
352 352 elif m == "g": # get
353 353 if branchmerge:
354 354 repo.dirstate.normaldirty(f)
355 355 else:
356 356 repo.dirstate.normal(f)
357 357 elif m == "m": # merge
358 358 f2, fd, flag, move = a[2:]
359 359 if branchmerge:
360 360 # We've done a branch merge, mark this file as merged
361 361 # so that we properly record the merger later
362 362 repo.dirstate.merge(fd)
363 363 if f != f2: # copy/rename
364 364 if move:
365 365 repo.dirstate.remove(f)
366 366 if f != fd:
367 367 repo.dirstate.copy(f, fd)
368 368 else:
369 369 repo.dirstate.copy(f2, fd)
370 370 else:
371 371 # We've update-merged a locally modified file, so
372 372 # we set the dirstate to emulate a normal checkout
373 373 # of that file some time in the past. Thus our
374 374 # merge will appear as a normal local file
375 375 # modification.
376 376 repo.dirstate.normallookup(fd)
377 377 if move:
378 378 repo.dirstate.forget(f)
379 379 elif m == "d": # directory rename
380 380 f2, fd, flag = a[2:]
381 381 if not f2 and f not in repo.dirstate:
382 382 # untracked file moved
383 383 continue
384 384 if branchmerge:
385 385 repo.dirstate.add(fd)
386 386 if f:
387 387 repo.dirstate.remove(f)
388 388 repo.dirstate.copy(f, fd)
389 389 if f2:
390 390 repo.dirstate.copy(f2, fd)
391 391 else:
392 392 repo.dirstate.normal(fd)
393 393 if f:
394 394 repo.dirstate.forget(f)
395 395
396 396 def update(repo, node, branchmerge, force, partial):
397 397 """
398 398 Perform a merge between the working directory and the given node
399 399
400 node = the node to update to, or None if unspecified
400 401 branchmerge = whether to merge between branches
401 402 force = whether to force branch merging or file overwriting
402 403 partial = a function to filter file lists (dirstate not updated)
404
405 The table below shows all the behaviors of the update command
406 given the -c and -C or no options, whether the working directory
407 is dirty, whether a revision is specified, and the relationship of
408 the parent rev to the target rev (linear, on the same named
409 branch, or on another named branch).
410
411 This logic is tested by test-update-branches.
412
413 -c -C dirty rev | linear same cross
414 n n n n | ok (1) x
415 n n n y | ok (1) ok
416 n n y * | merge (2) (3)
417 n y * * | --- discard ---
418 y n y * | --- (4) ---
419 y n n * | --- ok ---
420 y y * * | --- (5) ---
421
422 x = can't happen
423 * = don't-care
424 1 = abort: crosses branches (use 'hg merge' or 'hg update -C')
425 2 = abort: crosses branches (use 'hg merge' or 'hg update -C'
426 to discard changes)
427 3 = abort: crosses named branches (use 'hg update -C' to
428 discard changes)
429 4 = abort: uncommitted local changes
430 5 = incompatible options (checked in commands.py)
403 431 """
404 432
405 433 wlock = repo.wlock()
406 434 try:
407 435 wc = repo[None]
408 436 if node is None:
409 437 # tip of current branch
410 438 try:
411 439 node = repo.branchtags()[wc.branch()]
412 440 except KeyError:
413 441 if wc.branch() == "default": # no default branch!
414 442 node = repo.lookup("tip") # update to tip
415 443 else:
416 444 raise util.Abort(_("branch %s not found") % wc.branch())
417 445 overwrite = force and not branchmerge
418 446 pl = wc.parents()
419 447 p1, p2 = pl[0], repo[node]
420 448 pa = p1.ancestor(p2)
421 449 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
422 450 fastforward = False
423 451
424 452 ### check phase
425 453 if not overwrite and len(pl) > 1:
426 454 raise util.Abort(_("outstanding uncommitted merges"))
427 455 if branchmerge:
428 456 if pa == p2:
429 457 raise util.Abort(_("can't merge with ancestor"))
430 458 elif pa == p1:
431 459 if p1.branch() != p2.branch():
432 460 fastforward = True
433 461 else:
434 462 raise util.Abort(_("nothing to merge (use 'hg update'"
435 463 " or check 'hg heads')"))
436 464 if not force and (wc.files() or wc.deleted()):
437 465 raise util.Abort(_("outstanding uncommitted changes "
438 466 "(use 'hg status' to list changes)"))
439 467 elif not overwrite:
440 468 if pa == p1 or pa == p2: # linear
441 469 pass # all good
442 470 elif p1.branch() == p2.branch():
443 471 if wc.files() or wc.deleted():
444 472 raise util.Abort(_("crosses branches (use 'hg merge' or "
445 473 "'hg update -C' to discard changes)"))
446 474 raise util.Abort(_("crosses branches (use 'hg merge' "
447 475 "or 'hg update -C')"))
448 476 elif wc.files() or wc.deleted():
449 477 raise util.Abort(_("crosses named branches (use "
450 478 "'hg update -C' to discard changes)"))
451 479 else:
452 480 # Allow jumping branches if there are no changes
453 481 overwrite = True
454 482
455 483 ### calculate phase
456 484 action = []
457 485 if not force:
458 486 _checkunknown(wc, p2)
459 487 if not util.checkcase(repo.path):
460 488 _checkcollision(p2)
461 489 action += _forgetremoved(wc, p2, branchmerge)
462 490 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
463 491
464 492 ### apply phase
465 493 if not branchmerge: # just jump to the new rev
466 494 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
467 495 if not partial:
468 496 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
469 497
470 498 stats = applyupdates(repo, action, wc, p2)
471 499
472 500 if not partial:
473 501 recordupdates(repo, action, branchmerge)
474 502 repo.dirstate.setparents(fp1, fp2)
475 503 if not branchmerge and not fastforward:
476 504 repo.dirstate.setbranch(p2.branch())
477 505 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
478 506
479 507 return stats
480 508 finally:
481 509 wlock.release()
General Comments 0
You need to be logged in to leave comments. Login now