##// END OF EJS Templates
merge: fix spelling of length
Olle Lundberg -
r20607:abd44876 default
parent child Browse files
Show More
@@ -1,942 +1,942 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 or any later version.
7 7
8 8 import struct
9 9
10 10 from node import nullid, nullrev, hex, bin
11 11 from i18n import _
12 12 from mercurial import obsolete
13 13 import error, util, filemerge, copies, subrepo, worker, dicthelpers
14 14 import errno, os, shutil
15 15
16 16 _pack = struct.pack
17 17 _unpack = struct.unpack
18 18
19 19 def _droponode(data):
20 20 # used for compatibility for v1
21 21 bits = data.split("\0")
22 22 bits = bits[:-2] + bits[-1:]
23 23 return "\0".join(bits)
24 24
25 25 class mergestate(object):
26 26 '''track 3-way merge state of individual files
27 27
28 28 it is stored on disk when needed. Two file are used, one with an old
29 29 format, one with a new format. Both contains similar data, but the new
30 30 format can store new kind of field.
31 31
32 32 Current new format is a list of arbitrary record of the form:
33 33
34 34 [type][length][content]
35 35
36 36 Type is a single character, length is a 4 bytes integer, content is an
37 arbitrary suites of bytes of lenght `length`.
37 arbitrary suites of bytes of length `length`.
38 38
39 39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 40 should abort if they are unknown. lower case record can be safely ignored.
41 41
42 42 Currently known record:
43 43
44 44 L: the node of the "local" part of the merge (hexified version)
45 45 O: the node of the "other" part of the merge (hexified version)
46 46 F: a file to be merged entry
47 47 '''
48 48 statepathv1 = "merge/state"
49 49 statepathv2 = "merge/state2"
50 50 def __init__(self, repo):
51 51 self._repo = repo
52 52 self._dirty = False
53 53 self._read()
54 54 def reset(self, node=None, other=None):
55 55 self._state = {}
56 56 if node:
57 57 self._local = node
58 58 self._other = other
59 59 shutil.rmtree(self._repo.join("merge"), True)
60 60 self._dirty = False
61 61 def _read(self):
62 62 self._state = {}
63 63 records = self._readrecords()
64 64 for rtype, record in records:
65 65 if rtype == 'L':
66 66 self._local = bin(record)
67 67 elif rtype == 'O':
68 68 self._other = bin(record)
69 69 elif rtype == "F":
70 70 bits = record.split("\0")
71 71 self._state[bits[0]] = bits[1:]
72 72 elif not rtype.islower():
73 73 raise util.Abort(_('unsupported merge state record:'
74 74 % rtype))
75 75 self._dirty = False
76 76 def _readrecords(self):
77 77 v1records = self._readrecordsv1()
78 78 v2records = self._readrecordsv2()
79 79 oldv2 = set() # old format version of v2 record
80 80 for rec in v2records:
81 81 if rec[0] == 'L':
82 82 oldv2.add(rec)
83 83 elif rec[0] == 'F':
84 84 # drop the onode data (not contained in v1)
85 85 oldv2.add(('F', _droponode(rec[1])))
86 86 for rec in v1records:
87 87 if rec not in oldv2:
88 88 # v1 file is newer than v2 file, use it
89 89 # we have to infer the "other" changeset of the merge
90 90 # we cannot do better than that with v1 of the format
91 91 mctx = self._repo[None].parents()[-1]
92 92 v1records.append(('O', mctx.hex()))
93 93 # add place holder "other" file node information
94 94 # nobody is using it yet so we do no need to fetch the data
95 95 # if mctx was wrong `mctx[bits[-2]]` may fails.
96 96 for idx, r in enumerate(v1records):
97 97 if r[0] == 'F':
98 98 bits = r[1].split("\0")
99 99 bits.insert(-2, '')
100 100 v1records[idx] = (r[0], "\0".join(bits))
101 101 return v1records
102 102 else:
103 103 return v2records
104 104 def _readrecordsv1(self):
105 105 records = []
106 106 try:
107 107 f = self._repo.opener(self.statepathv1)
108 108 for i, l in enumerate(f):
109 109 if i == 0:
110 110 records.append(('L', l[:-1]))
111 111 else:
112 112 records.append(('F', l[:-1]))
113 113 f.close()
114 114 except IOError, err:
115 115 if err.errno != errno.ENOENT:
116 116 raise
117 117 return records
118 118 def _readrecordsv2(self):
119 119 records = []
120 120 try:
121 121 f = self._repo.opener(self.statepathv2)
122 122 data = f.read()
123 123 off = 0
124 124 end = len(data)
125 125 while off < end:
126 126 rtype = data[off]
127 127 off += 1
128 lenght = _unpack('>I', data[off:(off + 4)])[0]
128 length = _unpack('>I', data[off:(off + 4)])[0]
129 129 off += 4
130 record = data[off:(off + lenght)]
131 off += lenght
130 record = data[off:(off + length)]
131 off += length
132 132 records.append((rtype, record))
133 133 f.close()
134 134 except IOError, err:
135 135 if err.errno != errno.ENOENT:
136 136 raise
137 137 return records
138 138 def commit(self):
139 139 if self._dirty:
140 140 records = []
141 141 records.append(("L", hex(self._local)))
142 142 records.append(("O", hex(self._other)))
143 143 for d, v in self._state.iteritems():
144 144 records.append(("F", "\0".join([d] + v)))
145 145 self._writerecords(records)
146 146 self._dirty = False
147 147 def _writerecords(self, records):
148 148 self._writerecordsv1(records)
149 149 self._writerecordsv2(records)
150 150 def _writerecordsv1(self, records):
151 151 f = self._repo.opener(self.statepathv1, "w")
152 152 irecords = iter(records)
153 153 lrecords = irecords.next()
154 154 assert lrecords[0] == 'L'
155 155 f.write(hex(self._local) + "\n")
156 156 for rtype, data in irecords:
157 157 if rtype == "F":
158 158 f.write("%s\n" % _droponode(data))
159 159 f.close()
160 160 def _writerecordsv2(self, records):
161 161 f = self._repo.opener(self.statepathv2, "w")
162 162 for key, data in records:
163 163 assert len(key) == 1
164 164 format = ">sI%is" % len(data)
165 165 f.write(_pack(format, key, len(data), data))
166 166 f.close()
167 167 def add(self, fcl, fco, fca, fd):
168 168 hash = util.sha1(fcl.path()).hexdigest()
169 169 self._repo.opener.write("merge/" + hash, fcl.data())
170 170 self._state[fd] = ['u', hash, fcl.path(),
171 171 fca.path(), hex(fca.filenode()),
172 172 fco.path(), hex(fco.filenode()),
173 173 fcl.flags()]
174 174 self._dirty = True
175 175 def __contains__(self, dfile):
176 176 return dfile in self._state
177 177 def __getitem__(self, dfile):
178 178 return self._state[dfile][0]
179 179 def __iter__(self):
180 180 l = self._state.keys()
181 181 l.sort()
182 182 for f in l:
183 183 yield f
184 184 def files(self):
185 185 return self._state.keys()
186 186 def mark(self, dfile, state):
187 187 self._state[dfile][0] = state
188 188 self._dirty = True
189 189 def resolve(self, dfile, wctx):
190 190 if self[dfile] == 'r':
191 191 return 0
192 192 stateentry = self._state[dfile]
193 193 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
194 194 octx = self._repo[self._other]
195 195 fcd = wctx[dfile]
196 196 fco = octx[ofile]
197 197 fca = self._repo.filectx(afile, fileid=anode)
198 198 # "premerge" x flags
199 199 flo = fco.flags()
200 200 fla = fca.flags()
201 201 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
202 202 if fca.node() == nullid:
203 203 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
204 204 afile)
205 205 elif flags == fla:
206 206 flags = flo
207 207 # restore local
208 208 f = self._repo.opener("merge/" + hash)
209 209 self._repo.wwrite(dfile, f.read(), flags)
210 210 f.close()
211 211 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
212 212 if r is None:
213 213 # no real conflict
214 214 del self._state[dfile]
215 215 elif not r:
216 216 self.mark(dfile, 'r')
217 217 return r
218 218
219 219 def _checkunknownfile(repo, wctx, mctx, f):
220 220 return (not repo.dirstate._ignore(f)
221 221 and os.path.isfile(repo.wjoin(f))
222 222 and repo.wopener.audit.check(f)
223 223 and repo.dirstate.normalize(f) not in repo.dirstate
224 224 and mctx[f].cmp(wctx[f]))
225 225
226 226 def _checkunknown(repo, wctx, mctx):
227 227 "check for collisions between unknown files and files in mctx"
228 228
229 229 error = False
230 230 for f in mctx:
231 231 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
232 232 error = True
233 233 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
234 234 if error:
235 235 raise util.Abort(_("untracked files in working directory differ "
236 236 "from files in requested revision"))
237 237
238 238 def _forgetremoved(wctx, mctx, branchmerge):
239 239 """
240 240 Forget removed files
241 241
242 242 If we're jumping between revisions (as opposed to merging), and if
243 243 neither the working directory nor the target rev has the file,
244 244 then we need to remove it from the dirstate, to prevent the
245 245 dirstate from listing the file when it is no longer in the
246 246 manifest.
247 247
248 248 If we're merging, and the other revision has removed a file
249 249 that is not present in the working directory, we need to mark it
250 250 as removed.
251 251 """
252 252
253 253 actions = []
254 254 state = branchmerge and 'r' or 'f'
255 255 for f in wctx.deleted():
256 256 if f not in mctx:
257 257 actions.append((f, state, None, "forget deleted"))
258 258
259 259 if not branchmerge:
260 260 for f in wctx.removed():
261 261 if f not in mctx:
262 262 actions.append((f, "f", None, "forget removed"))
263 263
264 264 return actions
265 265
266 266 def _checkcollision(repo, wmf, actions, prompts):
267 267 # build provisional merged manifest up
268 268 pmmf = set(wmf)
269 269
270 270 def addop(f, args):
271 271 pmmf.add(f)
272 272 def removeop(f, args):
273 273 pmmf.discard(f)
274 274 def nop(f, args):
275 275 pass
276 276
277 277 def renameop(f, args):
278 278 f2, fd, flags = args
279 279 if f:
280 280 pmmf.discard(f)
281 281 pmmf.add(fd)
282 282 def mergeop(f, args):
283 283 f2, fd, move = args
284 284 if move:
285 285 pmmf.discard(f)
286 286 pmmf.add(fd)
287 287
288 288 opmap = {
289 289 "a": addop,
290 290 "d": renameop,
291 291 "dr": nop,
292 292 "e": nop,
293 293 "f": addop, # untracked file should be kept in working directory
294 294 "g": addop,
295 295 "m": mergeop,
296 296 "r": removeop,
297 297 "rd": nop,
298 298 }
299 299 for f, m, args, msg in actions:
300 300 op = opmap.get(m)
301 301 assert op, m
302 302 op(f, args)
303 303
304 304 opmap = {
305 305 "cd": addop,
306 306 "dc": addop,
307 307 }
308 308 for f, m in prompts:
309 309 op = opmap.get(m)
310 310 assert op, m
311 311 op(f, None)
312 312
313 313 # check case-folding collision in provisional merged manifest
314 314 foldmap = {}
315 315 for f in sorted(pmmf):
316 316 fold = util.normcase(f)
317 317 if fold in foldmap:
318 318 raise util.Abort(_("case-folding collision between %s and %s")
319 319 % (f, foldmap[fold]))
320 320 foldmap[fold] = f
321 321
322 322 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
323 323 acceptremote=False):
324 324 """
325 325 Merge p1 and p2 with ancestor pa and generate merge action list
326 326
327 327 branchmerge and force are as passed in to update
328 328 partial = function to filter file lists
329 329 acceptremote = accept the incoming changes without prompting
330 330 """
331 331
332 332 overwrite = force and not branchmerge
333 333 actions, copy, movewithdir = [], {}, {}
334 334
335 335 followcopies = False
336 336 if overwrite:
337 337 pa = wctx
338 338 elif pa == p2: # backwards
339 339 pa = wctx.p1()
340 340 elif not branchmerge and not wctx.dirty(missing=True):
341 341 pass
342 342 elif pa and repo.ui.configbool("merge", "followcopies", True):
343 343 followcopies = True
344 344
345 345 # manifests fetched in order are going to be faster, so prime the caches
346 346 [x.manifest() for x in
347 347 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
348 348
349 349 if followcopies:
350 350 ret = copies.mergecopies(repo, wctx, p2, pa)
351 351 copy, movewithdir, diverge, renamedelete = ret
352 352 for of, fl in diverge.iteritems():
353 353 actions.append((of, "dr", (fl,), "divergent renames"))
354 354 for of, fl in renamedelete.iteritems():
355 355 actions.append((of, "rd", (fl,), "rename and delete"))
356 356
357 357 repo.ui.note(_("resolving manifests\n"))
358 358 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
359 359 % (bool(branchmerge), bool(force), bool(partial)))
360 360 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
361 361
362 362 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
363 363 copied = set(copy.values())
364 364 copied.update(movewithdir.values())
365 365
366 366 if '.hgsubstate' in m1:
367 367 # check whether sub state is modified
368 368 for s in sorted(wctx.substate):
369 369 if wctx.sub(s).dirty():
370 370 m1['.hgsubstate'] += "+"
371 371 break
372 372
373 373 aborts, prompts = [], []
374 374 # Compare manifests
375 375 fdiff = dicthelpers.diff(m1, m2)
376 376 flagsdiff = m1.flagsdiff(m2)
377 377 diff12 = dicthelpers.join(fdiff, flagsdiff)
378 378
379 379 for f, (n12, fl12) in diff12.iteritems():
380 380 if n12:
381 381 n1, n2 = n12
382 382 else: # file contents didn't change, but flags did
383 383 n1 = n2 = m1.get(f, None)
384 384 if n1 is None:
385 385 # Since n1 == n2, the file isn't present in m2 either. This
386 386 # means that the file was removed or deleted locally and
387 387 # removed remotely, but that residual entries remain in flags.
388 388 # This can happen in manifests generated by workingctx.
389 389 continue
390 390 if fl12:
391 391 fl1, fl2 = fl12
392 392 else: # flags didn't change, file contents did
393 393 fl1 = fl2 = m1.flags(f)
394 394
395 395 if partial and not partial(f):
396 396 continue
397 397 if n1 and n2:
398 398 fla = ma.flags(f)
399 399 nol = 'l' not in fl1 + fl2 + fla
400 400 a = ma.get(f, nullid)
401 401 if n2 == a and fl2 == fla:
402 402 pass # remote unchanged - keep local
403 403 elif n1 == a and fl1 == fla: # local unchanged - use remote
404 404 if n1 == n2: # optimization: keep local content
405 405 actions.append((f, "e", (fl2,), "update permissions"))
406 406 else:
407 407 actions.append((f, "g", (fl2,), "remote is newer"))
408 408 elif nol and n2 == a: # remote only changed 'x'
409 409 actions.append((f, "e", (fl2,), "update permissions"))
410 410 elif nol and n1 == a: # local only changed 'x'
411 411 actions.append((f, "g", (fl1,), "remote is newer"))
412 412 else: # both changed something
413 413 actions.append((f, "m", (f, f, False), "versions differ"))
414 414 elif f in copied: # files we'll deal with on m2 side
415 415 pass
416 416 elif n1 and f in movewithdir: # directory rename
417 417 f2 = movewithdir[f]
418 418 actions.append((f, "d", (None, f2, fl1),
419 419 "remote renamed directory to " + f2))
420 420 elif n1 and f in copy:
421 421 f2 = copy[f]
422 422 actions.append((f, "m", (f2, f, False),
423 423 "local copied/moved to " + f2))
424 424 elif n1 and f in ma: # clean, a different, no remote
425 425 if n1 != ma[f]:
426 426 prompts.append((f, "cd")) # prompt changed/deleted
427 427 elif n1[20:] == "a": # added, no remote
428 428 actions.append((f, "f", None, "remote deleted"))
429 429 else:
430 430 actions.append((f, "r", None, "other deleted"))
431 431 elif n2 and f in movewithdir:
432 432 f2 = movewithdir[f]
433 433 actions.append((None, "d", (f, f2, fl2),
434 434 "local renamed directory to " + f2))
435 435 elif n2 and f in copy:
436 436 f2 = copy[f]
437 437 if f2 in m2:
438 438 actions.append((f2, "m", (f, f, False),
439 439 "remote copied to " + f))
440 440 else:
441 441 actions.append((f2, "m", (f, f, True),
442 442 "remote moved to " + f))
443 443 elif n2 and f not in ma:
444 444 # local unknown, remote created: the logic is described by the
445 445 # following table:
446 446 #
447 447 # force branchmerge different | action
448 448 # n * n | get
449 449 # n * y | abort
450 450 # y n * | get
451 451 # y y n | get
452 452 # y y y | merge
453 453 #
454 454 # Checking whether the files are different is expensive, so we
455 455 # don't do that when we can avoid it.
456 456 if force and not branchmerge:
457 457 actions.append((f, "g", (fl2,), "remote created"))
458 458 else:
459 459 different = _checkunknownfile(repo, wctx, p2, f)
460 460 if force and branchmerge and different:
461 461 actions.append((f, "m", (f, f, False),
462 462 "remote differs from untracked local"))
463 463 elif not force and different:
464 464 aborts.append((f, "ud"))
465 465 else:
466 466 actions.append((f, "g", (fl2,), "remote created"))
467 467 elif n2 and n2 != ma[f]:
468 468 different = _checkunknownfile(repo, wctx, p2, f)
469 469 if not force and different:
470 470 aborts.append((f, "ud"))
471 471 else:
472 472 # if different: old untracked f may be overwritten and lost
473 473 prompts.append((f, "dc")) # prompt deleted/changed
474 474
475 475 for f, m in sorted(aborts):
476 476 if m == "ud":
477 477 repo.ui.warn(_("%s: untracked file differs\n") % f)
478 478 else: assert False, m
479 479 if aborts:
480 480 raise util.Abort(_("untracked files in working directory differ "
481 481 "from files in requested revision"))
482 482
483 483 if not util.checkcase(repo.path):
484 484 # check collision between files only in p2 for clean update
485 485 if (not branchmerge and
486 486 (force or not wctx.dirty(missing=True, branch=False))):
487 487 _checkcollision(repo, m2, [], [])
488 488 else:
489 489 _checkcollision(repo, m1, actions, prompts)
490 490
491 491 for f, m in sorted(prompts):
492 492 if m == "cd":
493 493 if acceptremote:
494 494 actions.append((f, "r", None, "remote delete"))
495 495 elif repo.ui.promptchoice(
496 496 _("local changed %s which remote deleted\n"
497 497 "use (c)hanged version or (d)elete?"
498 498 "$$ &Changed $$ &Delete") % f, 0):
499 499 actions.append((f, "r", None, "prompt delete"))
500 500 else:
501 501 actions.append((f, "a", None, "prompt keep"))
502 502 elif m == "dc":
503 503 if acceptremote:
504 504 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
505 505 elif repo.ui.promptchoice(
506 506 _("remote changed %s which local deleted\n"
507 507 "use (c)hanged version or leave (d)eleted?"
508 508 "$$ &Changed $$ &Deleted") % f, 0) == 0:
509 509 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
510 510 else: assert False, m
511 511 return actions
512 512
513 513 def actionkey(a):
514 514 return a[1] in "rf" and -1 or 0, a
515 515
516 516 def getremove(repo, mctx, overwrite, args):
517 517 """apply usually-non-interactive updates to the working directory
518 518
519 519 mctx is the context to be merged into the working copy
520 520
521 521 yields tuples for progress updates
522 522 """
523 523 verbose = repo.ui.verbose
524 524 unlink = util.unlinkpath
525 525 wjoin = repo.wjoin
526 526 fctx = mctx.filectx
527 527 wwrite = repo.wwrite
528 528 audit = repo.wopener.audit
529 529 i = 0
530 530 for arg in args:
531 531 f = arg[0]
532 532 if arg[1] == 'r':
533 533 if verbose:
534 534 repo.ui.note(_("removing %s\n") % f)
535 535 audit(f)
536 536 try:
537 537 unlink(wjoin(f), ignoremissing=True)
538 538 except OSError, inst:
539 539 repo.ui.warn(_("update failed to remove %s: %s!\n") %
540 540 (f, inst.strerror))
541 541 else:
542 542 if verbose:
543 543 repo.ui.note(_("getting %s\n") % f)
544 544 wwrite(f, fctx(f).data(), arg[2][0])
545 545 if i == 100:
546 546 yield i, f
547 547 i = 0
548 548 i += 1
549 549 if i > 0:
550 550 yield i, f
551 551
552 552 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
553 553 """apply the merge action list to the working directory
554 554
555 555 wctx is the working copy context
556 556 mctx is the context to be merged into the working copy
557 557 actx is the context of the common ancestor
558 558
559 559 Return a tuple of counts (updated, merged, removed, unresolved) that
560 560 describes how many files were affected by the update.
561 561 """
562 562
563 563 updated, merged, removed, unresolved = 0, 0, 0, 0
564 564 ms = mergestate(repo)
565 565 ms.reset(wctx.p1().node(), mctx.node())
566 566 moves = []
567 567 actions.sort(key=actionkey)
568 568
569 569 # prescan for merges
570 570 for a in actions:
571 571 f, m, args, msg = a
572 572 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
573 573 if m == "m": # merge
574 574 f2, fd, move = args
575 575 if fd == '.hgsubstate': # merged internally
576 576 continue
577 577 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
578 578 fcl = wctx[f]
579 579 fco = mctx[f2]
580 580 if mctx == actx: # backwards, use working dir parent as ancestor
581 581 if fcl.parents():
582 582 fca = fcl.p1()
583 583 else:
584 584 fca = repo.filectx(f, fileid=nullrev)
585 585 else:
586 586 fca = fcl.ancestor(fco, actx)
587 587 if not fca:
588 588 fca = repo.filectx(f, fileid=nullrev)
589 589 ms.add(fcl, fco, fca, fd)
590 590 if f != fd and move:
591 591 moves.append(f)
592 592
593 593 audit = repo.wopener.audit
594 594
595 595 # remove renamed files after safely stored
596 596 for f in moves:
597 597 if os.path.lexists(repo.wjoin(f)):
598 598 repo.ui.debug("removing %s\n" % f)
599 599 audit(f)
600 600 util.unlinkpath(repo.wjoin(f))
601 601
602 602 numupdates = len(actions)
603 603 workeractions = [a for a in actions if a[1] in 'gr']
604 604 updateactions = [a for a in workeractions if a[1] == 'g']
605 605 updated = len(updateactions)
606 606 removeactions = [a for a in workeractions if a[1] == 'r']
607 607 removed = len(removeactions)
608 608 actions = [a for a in actions if a[1] not in 'gr']
609 609
610 610 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
611 611 if hgsub and hgsub[0] == 'r':
612 612 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
613 613
614 614 z = 0
615 615 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
616 616 removeactions)
617 617 for i, item in prog:
618 618 z += i
619 619 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
620 620 unit=_('files'))
621 621 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
622 622 updateactions)
623 623 for i, item in prog:
624 624 z += i
625 625 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
626 626 unit=_('files'))
627 627
628 628 if hgsub and hgsub[0] == 'g':
629 629 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
630 630
631 631 _updating = _('updating')
632 632 _files = _('files')
633 633 progress = repo.ui.progress
634 634
635 635 for i, a in enumerate(actions):
636 636 f, m, args, msg = a
637 637 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
638 638 if m == "m": # merge
639 639 f2, fd, move = args
640 640 if fd == '.hgsubstate': # subrepo states need updating
641 641 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
642 642 overwrite)
643 643 continue
644 644 audit(fd)
645 645 r = ms.resolve(fd, wctx)
646 646 if r is not None and r > 0:
647 647 unresolved += 1
648 648 else:
649 649 if r is None:
650 650 updated += 1
651 651 else:
652 652 merged += 1
653 653 elif m == "d": # directory rename
654 654 f2, fd, flags = args
655 655 if f:
656 656 repo.ui.note(_("moving %s to %s\n") % (f, fd))
657 657 audit(f)
658 658 repo.wwrite(fd, wctx.filectx(f).data(), flags)
659 659 util.unlinkpath(repo.wjoin(f))
660 660 if f2:
661 661 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
662 662 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
663 663 updated += 1
664 664 elif m == "dr": # divergent renames
665 665 fl, = args
666 666 repo.ui.warn(_("note: possible conflict - %s was renamed "
667 667 "multiple times to:\n") % f)
668 668 for nf in fl:
669 669 repo.ui.warn(" %s\n" % nf)
670 670 elif m == "rd": # rename and delete
671 671 fl, = args
672 672 repo.ui.warn(_("note: possible conflict - %s was deleted "
673 673 "and renamed to:\n") % f)
674 674 for nf in fl:
675 675 repo.ui.warn(" %s\n" % nf)
676 676 elif m == "e": # exec
677 677 flags, = args
678 678 audit(f)
679 679 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
680 680 updated += 1
681 681 ms.commit()
682 682 progress(_updating, None, total=numupdates, unit=_files)
683 683
684 684 return updated, merged, removed, unresolved
685 685
686 686 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
687 687 acceptremote=False):
688 688 "Calculate the actions needed to merge mctx into tctx"
689 689 actions = []
690 690 actions += manifestmerge(repo, tctx, mctx,
691 691 ancestor,
692 692 branchmerge, force,
693 693 partial, acceptremote)
694 694 if tctx.rev() is None:
695 695 actions += _forgetremoved(tctx, mctx, branchmerge)
696 696 return actions
697 697
698 698 def recordupdates(repo, actions, branchmerge):
699 699 "record merge actions to the dirstate"
700 700
701 701 for a in actions:
702 702 f, m, args, msg = a
703 703 if m == "r": # remove
704 704 if branchmerge:
705 705 repo.dirstate.remove(f)
706 706 else:
707 707 repo.dirstate.drop(f)
708 708 elif m == "a": # re-add
709 709 if not branchmerge:
710 710 repo.dirstate.add(f)
711 711 elif m == "f": # forget
712 712 repo.dirstate.drop(f)
713 713 elif m == "e": # exec change
714 714 repo.dirstate.normallookup(f)
715 715 elif m == "g": # get
716 716 if branchmerge:
717 717 repo.dirstate.otherparent(f)
718 718 else:
719 719 repo.dirstate.normal(f)
720 720 elif m == "m": # merge
721 721 f2, fd, move = args
722 722 if branchmerge:
723 723 # We've done a branch merge, mark this file as merged
724 724 # so that we properly record the merger later
725 725 repo.dirstate.merge(fd)
726 726 if f != f2: # copy/rename
727 727 if move:
728 728 repo.dirstate.remove(f)
729 729 if f != fd:
730 730 repo.dirstate.copy(f, fd)
731 731 else:
732 732 repo.dirstate.copy(f2, fd)
733 733 else:
734 734 # We've update-merged a locally modified file, so
735 735 # we set the dirstate to emulate a normal checkout
736 736 # of that file some time in the past. Thus our
737 737 # merge will appear as a normal local file
738 738 # modification.
739 739 if f2 == fd: # file not locally copied/moved
740 740 repo.dirstate.normallookup(fd)
741 741 if move:
742 742 repo.dirstate.drop(f)
743 743 elif m == "d": # directory rename
744 744 f2, fd, flag = args
745 745 if not f2 and f not in repo.dirstate:
746 746 # untracked file moved
747 747 continue
748 748 if branchmerge:
749 749 repo.dirstate.add(fd)
750 750 if f:
751 751 repo.dirstate.remove(f)
752 752 repo.dirstate.copy(f, fd)
753 753 if f2:
754 754 repo.dirstate.copy(f2, fd)
755 755 else:
756 756 repo.dirstate.normal(fd)
757 757 if f:
758 758 repo.dirstate.drop(f)
759 759
760 760 def update(repo, node, branchmerge, force, partial, ancestor=None,
761 761 mergeancestor=False):
762 762 """
763 763 Perform a merge between the working directory and the given node
764 764
765 765 node = the node to update to, or None if unspecified
766 766 branchmerge = whether to merge between branches
767 767 force = whether to force branch merging or file overwriting
768 768 partial = a function to filter file lists (dirstate not updated)
769 769 mergeancestor = whether it is merging with an ancestor. If true,
770 770 we should accept the incoming changes for any prompts that occur.
771 771 If false, merging with an ancestor (fast-forward) is only allowed
772 772 between different named branches. This flag is used by rebase extension
773 773 as a temporary fix and should be avoided in general.
774 774
775 775 The table below shows all the behaviors of the update command
776 776 given the -c and -C or no options, whether the working directory
777 777 is dirty, whether a revision is specified, and the relationship of
778 778 the parent rev to the target rev (linear, on the same named
779 779 branch, or on another named branch).
780 780
781 781 This logic is tested by test-update-branches.t.
782 782
783 783 -c -C dirty rev | linear same cross
784 784 n n n n | ok (1) x
785 785 n n n y | ok ok ok
786 786 n n y n | merge (2) (2)
787 787 n n y y | merge (3) (3)
788 788 n y * * | --- discard ---
789 789 y n y * | --- (4) ---
790 790 y n n * | --- ok ---
791 791 y y * * | --- (5) ---
792 792
793 793 x = can't happen
794 794 * = don't-care
795 795 1 = abort: not a linear update (merge or update --check to force update)
796 796 2 = abort: uncommitted changes (commit and merge, or update --clean to
797 797 discard changes)
798 798 3 = abort: uncommitted changes (commit or update --clean to discard changes)
799 799 4 = abort: uncommitted changes (checked in commands.py)
800 800 5 = incompatible options (checked in commands.py)
801 801
802 802 Return the same tuple as applyupdates().
803 803 """
804 804
805 805 onode = node
806 806 wlock = repo.wlock()
807 807 try:
808 808 wc = repo[None]
809 809 pl = wc.parents()
810 810 p1 = pl[0]
811 811 pa = None
812 812 if ancestor:
813 813 pa = repo[ancestor]
814 814
815 815 if node is None:
816 816 # Here is where we should consider bookmarks, divergent bookmarks,
817 817 # foreground changesets (successors), and tip of current branch;
818 818 # but currently we are only checking the branch tips.
819 819 try:
820 820 node = repo.branchtip(wc.branch())
821 821 except error.RepoLookupError:
822 822 if wc.branch() == "default": # no default branch!
823 823 node = repo.lookup("tip") # update to tip
824 824 else:
825 825 raise util.Abort(_("branch %s not found") % wc.branch())
826 826
827 827 if p1.obsolete() and not p1.children():
828 828 # allow updating to successors
829 829 successors = obsolete.successorssets(repo, p1.node())
830 830
831 831 # behavior of certain cases is as follows,
832 832 #
833 833 # divergent changesets: update to highest rev, similar to what
834 834 # is currently done when there are more than one head
835 835 # (i.e. 'tip')
836 836 #
837 837 # replaced changesets: same as divergent except we know there
838 838 # is no conflict
839 839 #
840 840 # pruned changeset: no update is done; though, we could
841 841 # consider updating to the first non-obsolete parent,
842 842 # similar to what is current done for 'hg prune'
843 843
844 844 if successors:
845 845 # flatten the list here handles both divergent (len > 1)
846 846 # and the usual case (len = 1)
847 847 successors = [n for sub in successors for n in sub]
848 848
849 849 # get the max revision for the given successors set,
850 850 # i.e. the 'tip' of a set
851 851 node = repo.revs("max(%ln)", successors)[0]
852 852 pa = p1
853 853
854 854 overwrite = force and not branchmerge
855 855
856 856 p2 = repo[node]
857 857 if pa is None:
858 858 pa = p1.ancestor(p2)
859 859
860 860 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
861 861
862 862 ### check phase
863 863 if not overwrite and len(pl) > 1:
864 864 raise util.Abort(_("outstanding uncommitted merges"))
865 865 if branchmerge:
866 866 if pa == p2:
867 867 raise util.Abort(_("merging with a working directory ancestor"
868 868 " has no effect"))
869 869 elif pa == p1:
870 870 if not mergeancestor and p1.branch() == p2.branch():
871 871 raise util.Abort(_("nothing to merge"),
872 872 hint=_("use 'hg update' "
873 873 "or check 'hg heads'"))
874 874 if not force and (wc.files() or wc.deleted()):
875 875 raise util.Abort(_("uncommitted changes"),
876 876 hint=_("use 'hg status' to list changes"))
877 877 for s in sorted(wc.substate):
878 878 if wc.sub(s).dirty():
879 879 raise util.Abort(_("uncommitted changes in "
880 880 "subrepository '%s'") % s)
881 881
882 882 elif not overwrite:
883 883 if p1 == p2: # no-op update
884 884 # call the hooks and exit early
885 885 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
886 886 repo.hook('update', parent1=xp2, parent2='', error=0)
887 887 return 0, 0, 0, 0
888 888
889 889 if pa not in (p1, p2): # nonlinear
890 890 dirty = wc.dirty(missing=True)
891 891 if dirty or onode is None:
892 892 # Branching is a bit strange to ensure we do the minimal
893 893 # amount of call to obsolete.background.
894 894 foreground = obsolete.foreground(repo, [p1.node()])
895 895 # note: the <node> variable contains a random identifier
896 896 if repo[node].node() in foreground:
897 897 pa = p1 # allow updating to successors
898 898 elif dirty:
899 899 msg = _("uncommitted changes")
900 900 if onode is None:
901 901 hint = _("commit and merge, or update --clean to"
902 902 " discard changes")
903 903 else:
904 904 hint = _("commit or update --clean to discard"
905 905 " changes")
906 906 raise util.Abort(msg, hint=hint)
907 907 else: # node is none
908 908 msg = _("not a linear update")
909 909 hint = _("merge or update --check to force update")
910 910 raise util.Abort(msg, hint=hint)
911 911 else:
912 912 # Allow jumping branches if clean and specific rev given
913 913 pa = p1
914 914
915 915 ### calculate phase
916 916 actions = calculateupdates(repo, wc, p2, pa,
917 917 branchmerge, force, partial, mergeancestor)
918 918
919 919 ### apply phase
920 920 if not branchmerge: # just jump to the new rev
921 921 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
922 922 if not partial:
923 923 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
924 924 # note that we're in the middle of an update
925 925 repo.vfs.write('updatestate', p2.hex())
926 926
927 927 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
928 928
929 929 if not partial:
930 930 repo.setparents(fp1, fp2)
931 931 recordupdates(repo, actions, branchmerge)
932 932 # update completed, clear state
933 933 util.unlink(repo.join('updatestate'))
934 934
935 935 if not branchmerge:
936 936 repo.dirstate.setbranch(p2.branch())
937 937 finally:
938 938 wlock.release()
939 939
940 940 if not partial:
941 941 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
942 942 return stats
General Comments 0
You need to be logged in to leave comments. Login now