##// END OF EJS Templates
merge: separate worker functions for batch remove and batch get...
Mads Kiilerich -
r21392:b1ce47da default
parent child Browse files
Show More
@@ -1,1174 +1,1188
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 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
51 51 def __init__(self, repo):
52 52 self._repo = repo
53 53 self._dirty = False
54 54 self._read()
55 55
56 56 def reset(self, node=None, other=None):
57 57 self._state = {}
58 58 self._local = None
59 59 self._other = None
60 60 if node:
61 61 self._local = node
62 62 self._other = other
63 63 shutil.rmtree(self._repo.join("merge"), True)
64 64 self._dirty = False
65 65
66 66 def _read(self):
67 67 """Analyse each record content to restore a serialized state from disk
68 68
69 69 This function process "record" entry produced by the de-serialization
70 70 of on disk file.
71 71 """
72 72 self._state = {}
73 73 self._local = None
74 74 self._other = None
75 75 records = self._readrecords()
76 76 for rtype, record in records:
77 77 if rtype == 'L':
78 78 self._local = bin(record)
79 79 elif rtype == 'O':
80 80 self._other = bin(record)
81 81 elif rtype == "F":
82 82 bits = record.split("\0")
83 83 self._state[bits[0]] = bits[1:]
84 84 elif not rtype.islower():
85 85 raise util.Abort(_('unsupported merge state record: %s')
86 86 % rtype)
87 87 self._dirty = False
88 88
89 89 def _readrecords(self):
90 90 """Read merge state from disk and return a list of record (TYPE, data)
91 91
92 92 We read data from both v1 and v2 files and decide which one to use.
93 93
94 94 V1 has been used by version prior to 2.9.1 and contains less data than
95 95 v2. We read both versions and check if no data in v2 contradicts
96 96 v1. If there is not contradiction we can safely assume that both v1
97 97 and v2 were written at the same time and use the extract data in v2. If
98 98 there is contradiction we ignore v2 content as we assume an old version
99 99 of Mercurial has overwritten the mergestate file and left an old v2
100 100 file around.
101 101
102 102 returns list of record [(TYPE, data), ...]"""
103 103 v1records = self._readrecordsv1()
104 104 v2records = self._readrecordsv2()
105 105 oldv2 = set() # old format version of v2 record
106 106 for rec in v2records:
107 107 if rec[0] == 'L':
108 108 oldv2.add(rec)
109 109 elif rec[0] == 'F':
110 110 # drop the onode data (not contained in v1)
111 111 oldv2.add(('F', _droponode(rec[1])))
112 112 for rec in v1records:
113 113 if rec not in oldv2:
114 114 # v1 file is newer than v2 file, use it
115 115 # we have to infer the "other" changeset of the merge
116 116 # we cannot do better than that with v1 of the format
117 117 mctx = self._repo[None].parents()[-1]
118 118 v1records.append(('O', mctx.hex()))
119 119 # add place holder "other" file node information
120 120 # nobody is using it yet so we do no need to fetch the data
121 121 # if mctx was wrong `mctx[bits[-2]]` may fails.
122 122 for idx, r in enumerate(v1records):
123 123 if r[0] == 'F':
124 124 bits = r[1].split("\0")
125 125 bits.insert(-2, '')
126 126 v1records[idx] = (r[0], "\0".join(bits))
127 127 return v1records
128 128 else:
129 129 return v2records
130 130
131 131 def _readrecordsv1(self):
132 132 """read on disk merge state for version 1 file
133 133
134 134 returns list of record [(TYPE, data), ...]
135 135
136 136 Note: the "F" data from this file are one entry short
137 137 (no "other file node" entry)
138 138 """
139 139 records = []
140 140 try:
141 141 f = self._repo.opener(self.statepathv1)
142 142 for i, l in enumerate(f):
143 143 if i == 0:
144 144 records.append(('L', l[:-1]))
145 145 else:
146 146 records.append(('F', l[:-1]))
147 147 f.close()
148 148 except IOError, err:
149 149 if err.errno != errno.ENOENT:
150 150 raise
151 151 return records
152 152
153 153 def _readrecordsv2(self):
154 154 """read on disk merge state for version 2 file
155 155
156 156 returns list of record [(TYPE, data), ...]
157 157 """
158 158 records = []
159 159 try:
160 160 f = self._repo.opener(self.statepathv2)
161 161 data = f.read()
162 162 off = 0
163 163 end = len(data)
164 164 while off < end:
165 165 rtype = data[off]
166 166 off += 1
167 167 length = _unpack('>I', data[off:(off + 4)])[0]
168 168 off += 4
169 169 record = data[off:(off + length)]
170 170 off += length
171 171 records.append((rtype, record))
172 172 f.close()
173 173 except IOError, err:
174 174 if err.errno != errno.ENOENT:
175 175 raise
176 176 return records
177 177
178 178 def active(self):
179 179 """Whether mergestate is active.
180 180
181 181 Returns True if there appears to be mergestate. This is a rough proxy
182 182 for "is a merge in progress."
183 183 """
184 184 # Check local variables before looking at filesystem for performance
185 185 # reasons.
186 186 return bool(self._local) or bool(self._state) or \
187 187 self._repo.opener.exists(self.statepathv1) or \
188 188 self._repo.opener.exists(self.statepathv2)
189 189
190 190 def commit(self):
191 191 """Write current state on disk (if necessary)"""
192 192 if self._dirty:
193 193 records = []
194 194 records.append(("L", hex(self._local)))
195 195 records.append(("O", hex(self._other)))
196 196 for d, v in self._state.iteritems():
197 197 records.append(("F", "\0".join([d] + v)))
198 198 self._writerecords(records)
199 199 self._dirty = False
200 200
201 201 def _writerecords(self, records):
202 202 """Write current state on disk (both v1 and v2)"""
203 203 self._writerecordsv1(records)
204 204 self._writerecordsv2(records)
205 205
206 206 def _writerecordsv1(self, records):
207 207 """Write current state on disk in a version 1 file"""
208 208 f = self._repo.opener(self.statepathv1, "w")
209 209 irecords = iter(records)
210 210 lrecords = irecords.next()
211 211 assert lrecords[0] == 'L'
212 212 f.write(hex(self._local) + "\n")
213 213 for rtype, data in irecords:
214 214 if rtype == "F":
215 215 f.write("%s\n" % _droponode(data))
216 216 f.close()
217 217
218 218 def _writerecordsv2(self, records):
219 219 """Write current state on disk in a version 2 file"""
220 220 f = self._repo.opener(self.statepathv2, "w")
221 221 for key, data in records:
222 222 assert len(key) == 1
223 223 format = ">sI%is" % len(data)
224 224 f.write(_pack(format, key, len(data), data))
225 225 f.close()
226 226
227 227 def add(self, fcl, fco, fca, fd):
228 228 """add a new (potentially?) conflicting file the merge state
229 229 fcl: file context for local,
230 230 fco: file context for remote,
231 231 fca: file context for ancestors,
232 232 fd: file path of the resulting merge.
233 233
234 234 note: also write the local version to the `.hg/merge` directory.
235 235 """
236 236 hash = util.sha1(fcl.path()).hexdigest()
237 237 self._repo.opener.write("merge/" + hash, fcl.data())
238 238 self._state[fd] = ['u', hash, fcl.path(),
239 239 fca.path(), hex(fca.filenode()),
240 240 fco.path(), hex(fco.filenode()),
241 241 fcl.flags()]
242 242 self._dirty = True
243 243
244 244 def __contains__(self, dfile):
245 245 return dfile in self._state
246 246
247 247 def __getitem__(self, dfile):
248 248 return self._state[dfile][0]
249 249
250 250 def __iter__(self):
251 251 return iter(sorted(self._state))
252 252
253 253 def files(self):
254 254 return self._state.keys()
255 255
256 256 def mark(self, dfile, state):
257 257 self._state[dfile][0] = state
258 258 self._dirty = True
259 259
260 260 def unresolved(self):
261 261 """Obtain the paths of unresolved files."""
262 262
263 263 for f, entry in self._state.items():
264 264 if entry[0] == 'u':
265 265 yield f
266 266
267 267 def resolve(self, dfile, wctx):
268 268 """rerun merge process for file path `dfile`"""
269 269 if self[dfile] == 'r':
270 270 return 0
271 271 stateentry = self._state[dfile]
272 272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
273 273 octx = self._repo[self._other]
274 274 fcd = wctx[dfile]
275 275 fco = octx[ofile]
276 276 fca = self._repo.filectx(afile, fileid=anode)
277 277 # "premerge" x flags
278 278 flo = fco.flags()
279 279 fla = fca.flags()
280 280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
281 281 if fca.node() == nullid:
282 282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
283 283 afile)
284 284 elif flags == fla:
285 285 flags = flo
286 286 # restore local
287 287 f = self._repo.opener("merge/" + hash)
288 288 self._repo.wwrite(dfile, f.read(), flags)
289 289 f.close()
290 290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
291 291 if r is None:
292 292 # no real conflict
293 293 del self._state[dfile]
294 294 self._dirty = True
295 295 elif not r:
296 296 self.mark(dfile, 'r')
297 297 return r
298 298
299 299 def _checkunknownfile(repo, wctx, mctx, f):
300 300 return (not repo.dirstate._ignore(f)
301 301 and os.path.isfile(repo.wjoin(f))
302 302 and repo.wopener.audit.check(f)
303 303 and repo.dirstate.normalize(f) not in repo.dirstate
304 304 and mctx[f].cmp(wctx[f]))
305 305
306 306 def _checkunknown(repo, wctx, mctx):
307 307 "check for collisions between unknown files and files in mctx"
308 308
309 309 error = False
310 310 for f in mctx:
311 311 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
312 312 error = True
313 313 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
314 314 if error:
315 315 raise util.Abort(_("untracked files in working directory differ "
316 316 "from files in requested revision"))
317 317
318 318 def _forgetremoved(wctx, mctx, branchmerge):
319 319 """
320 320 Forget removed files
321 321
322 322 If we're jumping between revisions (as opposed to merging), and if
323 323 neither the working directory nor the target rev has the file,
324 324 then we need to remove it from the dirstate, to prevent the
325 325 dirstate from listing the file when it is no longer in the
326 326 manifest.
327 327
328 328 If we're merging, and the other revision has removed a file
329 329 that is not present in the working directory, we need to mark it
330 330 as removed.
331 331 """
332 332
333 333 actions = []
334 334 state = branchmerge and 'r' or 'f'
335 335 for f in wctx.deleted():
336 336 if f not in mctx:
337 337 actions.append((f, state, None, "forget deleted"))
338 338
339 339 if not branchmerge:
340 340 for f in wctx.removed():
341 341 if f not in mctx:
342 342 actions.append((f, "f", None, "forget removed"))
343 343
344 344 return actions
345 345
346 346 def _checkcollision(repo, wmf, actions):
347 347 # build provisional merged manifest up
348 348 pmmf = set(wmf)
349 349
350 350 def addop(f, args):
351 351 pmmf.add(f)
352 352 def removeop(f, args):
353 353 pmmf.discard(f)
354 354 def nop(f, args):
355 355 pass
356 356
357 357 def renamemoveop(f, args):
358 358 f2, flags = args
359 359 pmmf.discard(f2)
360 360 pmmf.add(f)
361 361 def renamegetop(f, args):
362 362 f2, flags = args
363 363 pmmf.add(f)
364 364 def mergeop(f, args):
365 365 f1, f2, fa, move, anc = args
366 366 if move:
367 367 pmmf.discard(f1)
368 368 pmmf.add(f)
369 369
370 370 opmap = {
371 371 "a": addop,
372 372 "dm": renamemoveop,
373 373 "dg": renamegetop,
374 374 "dr": nop,
375 375 "e": nop,
376 376 "k": nop,
377 377 "f": addop, # untracked file should be kept in working directory
378 378 "g": addop,
379 379 "m": mergeop,
380 380 "r": removeop,
381 381 "rd": nop,
382 382 "cd": addop,
383 383 "dc": addop,
384 384 }
385 385 for f, m, args, msg in actions:
386 386 op = opmap.get(m)
387 387 assert op, m
388 388 op(f, args)
389 389
390 390 # check case-folding collision in provisional merged manifest
391 391 foldmap = {}
392 392 for f in sorted(pmmf):
393 393 fold = util.normcase(f)
394 394 if fold in foldmap:
395 395 raise util.Abort(_("case-folding collision between %s and %s")
396 396 % (f, foldmap[fold]))
397 397 foldmap[fold] = f
398 398
399 399 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
400 400 acceptremote, followcopies):
401 401 """
402 402 Merge p1 and p2 with ancestor pa and generate merge action list
403 403
404 404 branchmerge and force are as passed in to update
405 405 partial = function to filter file lists
406 406 acceptremote = accept the incoming changes without prompting
407 407 """
408 408
409 409 actions, copy, movewithdir = [], {}, {}
410 410
411 411 # manifests fetched in order are going to be faster, so prime the caches
412 412 [x.manifest() for x in
413 413 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
414 414
415 415 if followcopies:
416 416 ret = copies.mergecopies(repo, wctx, p2, pa)
417 417 copy, movewithdir, diverge, renamedelete = ret
418 418 for of, fl in diverge.iteritems():
419 419 actions.append((of, "dr", (fl,), "divergent renames"))
420 420 for of, fl in renamedelete.iteritems():
421 421 actions.append((of, "rd", (fl,), "rename and delete"))
422 422
423 423 repo.ui.note(_("resolving manifests\n"))
424 424 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
425 425 % (bool(branchmerge), bool(force), bool(partial)))
426 426 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
427 427
428 428 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
429 429 copied = set(copy.values())
430 430 copied.update(movewithdir.values())
431 431
432 432 if '.hgsubstate' in m1:
433 433 # check whether sub state is modified
434 434 for s in sorted(wctx.substate):
435 435 if wctx.sub(s).dirty():
436 436 m1['.hgsubstate'] += "+"
437 437 break
438 438
439 439 aborts = []
440 440 # Compare manifests
441 441 fdiff = dicthelpers.diff(m1, m2)
442 442 flagsdiff = m1.flagsdiff(m2)
443 443 diff12 = dicthelpers.join(fdiff, flagsdiff)
444 444
445 445 for f, (n12, fl12) in diff12.iteritems():
446 446 if n12:
447 447 n1, n2 = n12
448 448 else: # file contents didn't change, but flags did
449 449 n1 = n2 = m1.get(f, None)
450 450 if n1 is None:
451 451 # Since n1 == n2, the file isn't present in m2 either. This
452 452 # means that the file was removed or deleted locally and
453 453 # removed remotely, but that residual entries remain in flags.
454 454 # This can happen in manifests generated by workingctx.
455 455 continue
456 456 if fl12:
457 457 fl1, fl2 = fl12
458 458 else: # flags didn't change, file contents did
459 459 fl1 = fl2 = m1.flags(f)
460 460
461 461 if partial and not partial(f):
462 462 continue
463 463 if n1 and n2:
464 464 fa = f
465 465 a = ma.get(f, nullid)
466 466 if a == nullid:
467 467 fa = copy.get(f, f)
468 468 # Note: f as default is wrong - we can't really make a 3-way
469 469 # merge without an ancestor file.
470 470 fla = ma.flags(fa)
471 471 nol = 'l' not in fl1 + fl2 + fla
472 472 if n2 == a and fl2 == fla:
473 473 actions.append((f, "k", (), "keep")) # remote unchanged
474 474 elif n1 == a and fl1 == fla: # local unchanged - use remote
475 475 if n1 == n2: # optimization: keep local content
476 476 actions.append((f, "e", (fl2,), "update permissions"))
477 477 else:
478 478 actions.append((f, "g", (fl2,), "remote is newer"))
479 479 elif nol and n2 == a: # remote only changed 'x'
480 480 actions.append((f, "e", (fl2,), "update permissions"))
481 481 elif nol and n1 == a: # local only changed 'x'
482 482 actions.append((f, "g", (fl1,), "remote is newer"))
483 483 else: # both changed something
484 484 actions.append((f, "m", (f, f, fa, False, pa.node()),
485 485 "versions differ"))
486 486 elif f in copied: # files we'll deal with on m2 side
487 487 pass
488 488 elif n1 and f in movewithdir: # directory rename, move local
489 489 f2 = movewithdir[f]
490 490 actions.append((f2, "dm", (f, fl1),
491 491 "remote directory rename - move from " + f))
492 492 elif n1 and f in copy:
493 493 f2 = copy[f]
494 494 actions.append((f, "m", (f, f2, f2, False, pa.node()),
495 495 "local copied/moved from " + f2))
496 496 elif n1 and f in ma: # clean, a different, no remote
497 497 if n1 != ma[f]:
498 498 if acceptremote:
499 499 actions.append((f, "r", None, "remote delete"))
500 500 else:
501 501 actions.append((f, "cd", None, "prompt changed/deleted"))
502 502 elif n1[20:] == "a": # added, no remote
503 503 actions.append((f, "f", None, "remote deleted"))
504 504 else:
505 505 actions.append((f, "r", None, "other deleted"))
506 506 elif n2 and f in movewithdir:
507 507 f2 = movewithdir[f]
508 508 actions.append((f2, "dg", (f, fl2),
509 509 "local directory rename - get from " + f))
510 510 elif n2 and f in copy:
511 511 f2 = copy[f]
512 512 if f2 in m2:
513 513 actions.append((f, "m", (f2, f, f2, False, pa.node()),
514 514 "remote copied from " + f2))
515 515 else:
516 516 actions.append((f, "m", (f2, f, f2, True, pa.node()),
517 517 "remote moved from " + f2))
518 518 elif n2 and f not in ma:
519 519 # local unknown, remote created: the logic is described by the
520 520 # following table:
521 521 #
522 522 # force branchmerge different | action
523 523 # n * n | get
524 524 # n * y | abort
525 525 # y n * | get
526 526 # y y n | get
527 527 # y y y | merge
528 528 #
529 529 # Checking whether the files are different is expensive, so we
530 530 # don't do that when we can avoid it.
531 531 if force and not branchmerge:
532 532 actions.append((f, "g", (fl2,), "remote created"))
533 533 else:
534 534 different = _checkunknownfile(repo, wctx, p2, f)
535 535 if force and branchmerge and different:
536 536 # FIXME: This is wrong - f is not in ma ...
537 537 actions.append((f, "m", (f, f, f, False, pa.node()),
538 538 "remote differs from untracked local"))
539 539 elif not force and different:
540 540 aborts.append((f, "ud"))
541 541 else:
542 542 actions.append((f, "g", (fl2,), "remote created"))
543 543 elif n2 and n2 != ma[f]:
544 544 different = _checkunknownfile(repo, wctx, p2, f)
545 545 if not force and different:
546 546 aborts.append((f, "ud"))
547 547 else:
548 548 # if different: old untracked f may be overwritten and lost
549 549 if acceptremote:
550 550 actions.append((f, "g", (m2.flags(f),),
551 551 "remote recreating"))
552 552 else:
553 553 actions.append((f, "dc", (m2.flags(f),),
554 554 "prompt deleted/changed"))
555 555
556 556 for f, m in sorted(aborts):
557 557 if m == "ud":
558 558 repo.ui.warn(_("%s: untracked file differs\n") % f)
559 559 else: assert False, m
560 560 if aborts:
561 561 raise util.Abort(_("untracked files in working directory differ "
562 562 "from files in requested revision"))
563 563
564 564 if not util.checkcase(repo.path):
565 565 # check collision between files only in p2 for clean update
566 566 if (not branchmerge and
567 567 (force or not wctx.dirty(missing=True, branch=False))):
568 568 _checkcollision(repo, m2, [])
569 569 else:
570 570 _checkcollision(repo, m1, actions)
571 571
572 572 return actions
573 573
574 574 actionpriority = dict((m, p) for p, m in enumerate(
575 575 ['r', 'f', 'g', 'a', 'k', 'm', 'dm', 'dg', 'dr', 'cd', 'dc', 'rd', 'e']))
576 576
577 577 def actionkey(a):
578 578 return actionpriority[a[1]], a
579 579
580 def getremove(repo, mctx, overwrite, args):
581 """apply usually-non-interactive updates to the working directory
582
583 mctx is the context to be merged into the working copy
580 def batchremove(repo, actions):
581 """apply removes to the working directory
584 582
585 583 yields tuples for progress updates
586 584 """
587 585 verbose = repo.ui.verbose
588 586 unlink = util.unlinkpath
589 587 wjoin = repo.wjoin
590 fctx = mctx.filectx
591 wwrite = repo.wwrite
592 588 audit = repo.wopener.audit
593 589 i = 0
594 for f, m, args, msg in args:
595 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
596 if m == 'r':
590 for f, m, args, msg in actions:
591 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
592 if True:
597 593 if verbose:
598 594 repo.ui.note(_("removing %s\n") % f)
599 595 audit(f)
600 596 try:
601 597 unlink(wjoin(f), ignoremissing=True)
602 598 except OSError, inst:
603 599 repo.ui.warn(_("update failed to remove %s: %s!\n") %
604 600 (f, inst.strerror))
605 else:
601 if i == 100:
602 yield i, f
603 i = 0
604 i += 1
605 if i > 0:
606 yield i, f
607
608 def batchget(repo, mctx, actions):
609 """apply gets to the working directory
610
611 mctx is the context to get from
612
613 yields tuples for progress updates
614 """
615 verbose = repo.ui.verbose
616 fctx = mctx.filectx
617 wwrite = repo.wwrite
618 i = 0
619 for f, m, args, msg in actions:
620 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
621 if True:
606 622 if verbose:
607 623 repo.ui.note(_("getting %s\n") % f)
608 624 wwrite(f, fctx(f).data(), args[0])
609 625 if i == 100:
610 626 yield i, f
611 627 i = 0
612 628 i += 1
613 629 if i > 0:
614 630 yield i, f
615 631
616 632 def applyupdates(repo, actions, wctx, mctx, overwrite):
617 633 """apply the merge action list to the working directory
618 634
619 635 wctx is the working copy context
620 636 mctx is the context to be merged into the working copy
621 637
622 638 Return a tuple of counts (updated, merged, removed, unresolved) that
623 639 describes how many files were affected by the update.
624 640 """
625 641
626 642 updated, merged, removed, unresolved = 0, 0, 0, 0
627 643 ms = mergestate(repo)
628 644 ms.reset(wctx.p1().node(), mctx.node())
629 645 moves = []
630 646 actions.sort(key=actionkey)
631 647
632 648 # prescan for merges
633 649 for a in actions:
634 650 f, m, args, msg = a
635 651 if m == "m": # merge
636 652 f1, f2, fa, move, anc = args
637 653 if f == '.hgsubstate': # merged internally
638 654 continue
639 655 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
640 656 fcl = wctx[f1]
641 657 fco = mctx[f2]
642 658 actx = repo[anc]
643 659 if fa in actx:
644 660 fca = actx[fa]
645 661 else:
646 662 fca = repo.filectx(f1, fileid=nullrev)
647 663 ms.add(fcl, fco, fca, f)
648 664 if f1 != f and move:
649 665 moves.append(f1)
650 666
651 667 audit = repo.wopener.audit
652 668 _updating = _('updating')
653 669 _files = _('files')
654 670 progress = repo.ui.progress
655 671
656 672 # remove renamed files after safely stored
657 673 for f in moves:
658 674 if os.path.lexists(repo.wjoin(f)):
659 675 repo.ui.debug("removing %s\n" % f)
660 676 audit(f)
661 677 util.unlinkpath(repo.wjoin(f))
662 678
663 679 numupdates = len([a for a in actions if a[1] != 'k'])
664 680 workeractions = [a for a in actions if a[1] in 'gr']
665 681 updateactions = [a for a in workeractions if a[1] == 'g']
666 682 updated = len(updateactions)
667 683 removeactions = [a for a in workeractions if a[1] == 'r']
668 684 removed = len(removeactions)
669 685 actions = [a for a in actions if a[1] not in 'gr']
670 686
671 687 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
672 688 if hgsub and hgsub[0] == 'r':
673 689 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
674 690
675 691 # remove in parallel (must come first)
676 692 z = 0
677 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
678 removeactions)
693 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), removeactions)
679 694 for i, item in prog:
680 695 z += i
681 696 progress(_updating, z, item=item, total=numupdates, unit=_files)
682 697
683 698 # get in parallel
684 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
685 updateactions)
699 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), updateactions)
686 700 for i, item in prog:
687 701 z += i
688 702 progress(_updating, z, item=item, total=numupdates, unit=_files)
689 703
690 704 if hgsub and hgsub[0] == 'g':
691 705 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
692 706
693 707 for f, m, args, msg in actions:
694 708
695 709 # forget (manifest only, just log it) (must come first)
696 710 if m == "f":
697 711 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
698 712 z += 1
699 713 progress(_updating, z, item=f, total=numupdates, unit=_files)
700 714
701 715 # re-add (manifest only, just log it)
702 716 elif m == "a":
703 717 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
704 718 z += 1
705 719 progress(_updating, z, item=f, total=numupdates, unit=_files)
706 720
707 721 # keep (noop, just log it)
708 722 elif m == "k":
709 723 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
710 724 # no progress
711 725
712 726 # merge
713 727 elif m == "m":
714 728 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
715 729 z += 1
716 730 progress(_updating, z, item=f, total=numupdates, unit=_files)
717 731 f1, f2, fa, move, anc = args
718 732 if f == '.hgsubstate': # subrepo states need updating
719 733 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
720 734 overwrite)
721 735 continue
722 736 audit(f)
723 737 r = ms.resolve(f, wctx)
724 738 if r is not None and r > 0:
725 739 unresolved += 1
726 740 else:
727 741 if r is None:
728 742 updated += 1
729 743 else:
730 744 merged += 1
731 745
732 746 # directory rename, move local
733 747 elif m == "dm":
734 748 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
735 749 z += 1
736 750 progress(_updating, z, item=f, total=numupdates, unit=_files)
737 751 f0, flags = args
738 752 repo.ui.note(_("moving %s to %s\n") % (f0, f))
739 753 audit(f)
740 754 repo.wwrite(f, wctx.filectx(f0).data(), flags)
741 755 util.unlinkpath(repo.wjoin(f0))
742 756 updated += 1
743 757
744 758 # local directory rename, get
745 759 elif m == "dg":
746 760 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
747 761 z += 1
748 762 progress(_updating, z, item=f, total=numupdates, unit=_files)
749 763 f0, flags = args
750 764 repo.ui.note(_("getting %s to %s\n") % (f0, f))
751 765 repo.wwrite(f, mctx.filectx(f0).data(), flags)
752 766 updated += 1
753 767
754 768 # divergent renames
755 769 elif m == "dr":
756 770 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
757 771 z += 1
758 772 progress(_updating, z, item=f, total=numupdates, unit=_files)
759 773 fl, = args
760 774 repo.ui.warn(_("note: possible conflict - %s was renamed "
761 775 "multiple times to:\n") % f)
762 776 for nf in fl:
763 777 repo.ui.warn(" %s\n" % nf)
764 778
765 779 # rename and delete
766 780 elif m == "rd":
767 781 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
768 782 z += 1
769 783 progress(_updating, z, item=f, total=numupdates, unit=_files)
770 784 fl, = args
771 785 repo.ui.warn(_("note: possible conflict - %s was deleted "
772 786 "and renamed to:\n") % f)
773 787 for nf in fl:
774 788 repo.ui.warn(" %s\n" % nf)
775 789
776 790 # exec
777 791 elif m == "e":
778 792 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
779 793 z += 1
780 794 progress(_updating, z, item=f, total=numupdates, unit=_files)
781 795 flags, = args
782 796 audit(f)
783 797 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
784 798 updated += 1
785 799
786 800 ms.commit()
787 801 progress(_updating, None, total=numupdates, unit=_files)
788 802
789 803 return updated, merged, removed, unresolved
790 804
791 805 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
792 806 acceptremote, followcopies):
793 807 "Calculate the actions needed to merge mctx into wctx using ancestors"
794 808
795 809 if len(ancestors) == 1: # default
796 810 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
797 811 branchmerge, force,
798 812 partial, acceptremote, followcopies)
799 813
800 814 else: # only when merge.preferancestor=* - experimentalish code
801 815 repo.ui.status(
802 816 _("note: merging %s and %s using bids from ancestors %s\n") %
803 817 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
804 818
805 819 # Call for bids
806 820 fbids = {} # mapping filename to list af action bids
807 821 for ancestor in ancestors:
808 822 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
809 823 actions = manifestmerge(repo, wctx, mctx, ancestor,
810 824 branchmerge, force,
811 825 partial, acceptremote, followcopies)
812 826 for a in sorted(actions, key=lambda a: (a[1], a)):
813 827 f, m, args, msg = a
814 828 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
815 829 if f in fbids:
816 830 fbids[f].append(a)
817 831 else:
818 832 fbids[f] = [a]
819 833
820 834 # Pick the best bid for each file
821 835 repo.ui.note(_('\nauction for merging merge bids\n'))
822 836 actions = []
823 837 for f, bidsl in sorted(fbids.items()):
824 838 # Consensus?
825 839 a0 = bidsl[0]
826 840 if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1
827 841 repo.ui.note(" %s: consensus for %s\n" % (f, a0[1]))
828 842 actions.append(a0)
829 843 continue
830 844 # Group bids by kind of action
831 845 bids = {}
832 846 for a in bidsl:
833 847 m = a[1]
834 848 if m in bids:
835 849 bids[m].append(a)
836 850 else:
837 851 bids[m] = [a]
838 852 # If keep is an option, just do it.
839 853 if "k" in bids:
840 854 repo.ui.note(" %s: picking 'keep' action\n" % f)
841 855 actions.append(bids["k"][0])
842 856 continue
843 857 # If all gets agree [how could they not?], just do it.
844 858 if "g" in bids:
845 859 ga0 = bids["g"][0]
846 860 if util.all(a == ga0 for a in bids["g"][1:]):
847 861 repo.ui.note(" %s: picking 'get' action\n" % f)
848 862 actions.append(ga0)
849 863 continue
850 864 # TODO: Consider other simple actions such as mode changes
851 865 # Handle inefficient democrazy.
852 866 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
853 867 for _f, m, args, msg in bidsl:
854 868 repo.ui.note(' %s -> %s\n' % (msg, m))
855 869 # Pick random action. TODO: Instead, prompt user when resolving
856 870 a0 = bidsl[0]
857 871 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
858 872 (f, a0[1]))
859 873 actions.append(a0)
860 874 continue
861 875 repo.ui.note(_('end of auction\n\n'))
862 876
863 877 # Filter out prompts.
864 878 newactions, prompts = [], []
865 879 for a in actions:
866 880 if a[1] in ("cd", "dc"):
867 881 prompts.append(a)
868 882 else:
869 883 newactions.append(a)
870 884 # Prompt and create actions. TODO: Move this towards resolve phase.
871 885 for f, m, args, msg in sorted(prompts):
872 886 if m == "cd":
873 887 if repo.ui.promptchoice(
874 888 _("local changed %s which remote deleted\n"
875 889 "use (c)hanged version or (d)elete?"
876 890 "$$ &Changed $$ &Delete") % f, 0):
877 891 newactions.append((f, "r", None, "prompt delete"))
878 892 else:
879 893 newactions.append((f, "a", None, "prompt keep"))
880 894 elif m == "dc":
881 895 flags, = args
882 896 if repo.ui.promptchoice(
883 897 _("remote changed %s which local deleted\n"
884 898 "use (c)hanged version or leave (d)eleted?"
885 899 "$$ &Changed $$ &Deleted") % f, 0) == 0:
886 900 newactions.append((f, "g", (flags,), "prompt recreating"))
887 901 else: assert False, m
888 902
889 903 if wctx.rev() is None:
890 904 newactions += _forgetremoved(wctx, mctx, branchmerge)
891 905
892 906 return newactions
893 907
894 908 def recordupdates(repo, actions, branchmerge):
895 909 "record merge actions to the dirstate"
896 910
897 911 for f, m, args, msg in actions:
898 912
899 913 # remove (must come first)
900 914 if m == "r": # remove
901 915 if branchmerge:
902 916 repo.dirstate.remove(f)
903 917 else:
904 918 repo.dirstate.drop(f)
905 919
906 920 # forget (must come first)
907 921 elif m == "f":
908 922 repo.dirstate.drop(f)
909 923
910 924 # re-add
911 925 elif m == "a":
912 926 if not branchmerge:
913 927 repo.dirstate.add(f)
914 928
915 929 # exec change
916 930 elif m == "e":
917 931 repo.dirstate.normallookup(f)
918 932
919 933 # keep
920 934 elif m == "k":
921 935 pass
922 936
923 937 # get
924 938 elif m == "g":
925 939 if branchmerge:
926 940 repo.dirstate.otherparent(f)
927 941 else:
928 942 repo.dirstate.normal(f)
929 943
930 944 # merge
931 945 elif m == "m":
932 946 f1, f2, fa, move, anc = args
933 947 if branchmerge:
934 948 # We've done a branch merge, mark this file as merged
935 949 # so that we properly record the merger later
936 950 repo.dirstate.merge(f)
937 951 if f1 != f2: # copy/rename
938 952 if move:
939 953 repo.dirstate.remove(f1)
940 954 if f1 != f:
941 955 repo.dirstate.copy(f1, f)
942 956 else:
943 957 repo.dirstate.copy(f2, f)
944 958 else:
945 959 # We've update-merged a locally modified file, so
946 960 # we set the dirstate to emulate a normal checkout
947 961 # of that file some time in the past. Thus our
948 962 # merge will appear as a normal local file
949 963 # modification.
950 964 if f2 == f: # file not locally copied/moved
951 965 repo.dirstate.normallookup(f)
952 966 if move:
953 967 repo.dirstate.drop(f1)
954 968
955 969 # directory rename, move local
956 970 elif m == "dm":
957 971 f0, flag = args
958 972 if f0 not in repo.dirstate:
959 973 # untracked file moved
960 974 continue
961 975 if branchmerge:
962 976 repo.dirstate.add(f)
963 977 repo.dirstate.remove(f0)
964 978 repo.dirstate.copy(f0, f)
965 979 else:
966 980 repo.dirstate.normal(f)
967 981 repo.dirstate.drop(f0)
968 982
969 983 # directory rename, get
970 984 elif m == "dg":
971 985 f0, flag = args
972 986 if branchmerge:
973 987 repo.dirstate.add(f)
974 988 repo.dirstate.copy(f0, f)
975 989 else:
976 990 repo.dirstate.normal(f)
977 991
978 992 def update(repo, node, branchmerge, force, partial, ancestor=None,
979 993 mergeancestor=False):
980 994 """
981 995 Perform a merge between the working directory and the given node
982 996
983 997 node = the node to update to, or None if unspecified
984 998 branchmerge = whether to merge between branches
985 999 force = whether to force branch merging or file overwriting
986 1000 partial = a function to filter file lists (dirstate not updated)
987 1001 mergeancestor = whether it is merging with an ancestor. If true,
988 1002 we should accept the incoming changes for any prompts that occur.
989 1003 If false, merging with an ancestor (fast-forward) is only allowed
990 1004 between different named branches. This flag is used by rebase extension
991 1005 as a temporary fix and should be avoided in general.
992 1006
993 1007 The table below shows all the behaviors of the update command
994 1008 given the -c and -C or no options, whether the working directory
995 1009 is dirty, whether a revision is specified, and the relationship of
996 1010 the parent rev to the target rev (linear, on the same named
997 1011 branch, or on another named branch).
998 1012
999 1013 This logic is tested by test-update-branches.t.
1000 1014
1001 1015 -c -C dirty rev | linear same cross
1002 1016 n n n n | ok (1) x
1003 1017 n n n y | ok ok ok
1004 1018 n n y n | merge (2) (2)
1005 1019 n n y y | merge (3) (3)
1006 1020 n y * * | --- discard ---
1007 1021 y n y * | --- (4) ---
1008 1022 y n n * | --- ok ---
1009 1023 y y * * | --- (5) ---
1010 1024
1011 1025 x = can't happen
1012 1026 * = don't-care
1013 1027 1 = abort: not a linear update (merge or update --check to force update)
1014 1028 2 = abort: uncommitted changes (commit and merge, or update --clean to
1015 1029 discard changes)
1016 1030 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1017 1031 4 = abort: uncommitted changes (checked in commands.py)
1018 1032 5 = incompatible options (checked in commands.py)
1019 1033
1020 1034 Return the same tuple as applyupdates().
1021 1035 """
1022 1036
1023 1037 onode = node
1024 1038 wlock = repo.wlock()
1025 1039 try:
1026 1040 wc = repo[None]
1027 1041 pl = wc.parents()
1028 1042 p1 = pl[0]
1029 1043 pas = [None]
1030 1044 if ancestor:
1031 1045 pas = [repo[ancestor]]
1032 1046
1033 1047 if node is None:
1034 1048 # Here is where we should consider bookmarks, divergent bookmarks,
1035 1049 # foreground changesets (successors), and tip of current branch;
1036 1050 # but currently we are only checking the branch tips.
1037 1051 try:
1038 1052 node = repo.branchtip(wc.branch())
1039 1053 except error.RepoLookupError:
1040 1054 if wc.branch() == "default": # no default branch!
1041 1055 node = repo.lookup("tip") # update to tip
1042 1056 else:
1043 1057 raise util.Abort(_("branch %s not found") % wc.branch())
1044 1058
1045 1059 if p1.obsolete() and not p1.children():
1046 1060 # allow updating to successors
1047 1061 successors = obsolete.successorssets(repo, p1.node())
1048 1062
1049 1063 # behavior of certain cases is as follows,
1050 1064 #
1051 1065 # divergent changesets: update to highest rev, similar to what
1052 1066 # is currently done when there are more than one head
1053 1067 # (i.e. 'tip')
1054 1068 #
1055 1069 # replaced changesets: same as divergent except we know there
1056 1070 # is no conflict
1057 1071 #
1058 1072 # pruned changeset: no update is done; though, we could
1059 1073 # consider updating to the first non-obsolete parent,
1060 1074 # similar to what is current done for 'hg prune'
1061 1075
1062 1076 if successors:
1063 1077 # flatten the list here handles both divergent (len > 1)
1064 1078 # and the usual case (len = 1)
1065 1079 successors = [n for sub in successors for n in sub]
1066 1080
1067 1081 # get the max revision for the given successors set,
1068 1082 # i.e. the 'tip' of a set
1069 1083 node = repo.revs("max(%ln)", successors)[0]
1070 1084 pas = [p1]
1071 1085
1072 1086 overwrite = force and not branchmerge
1073 1087
1074 1088 p2 = repo[node]
1075 1089 if pas[0] is None:
1076 1090 if repo.ui.config("merge", "preferancestor") == '*':
1077 1091 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1078 1092 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1079 1093 else:
1080 1094 pas = [p1.ancestor(p2, warn=True)]
1081 1095
1082 1096 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1083 1097
1084 1098 ### check phase
1085 1099 if not overwrite and len(pl) > 1:
1086 1100 raise util.Abort(_("outstanding uncommitted merges"))
1087 1101 if branchmerge:
1088 1102 if pas == [p2]:
1089 1103 raise util.Abort(_("merging with a working directory ancestor"
1090 1104 " has no effect"))
1091 1105 elif pas == [p1]:
1092 1106 if not mergeancestor and p1.branch() == p2.branch():
1093 1107 raise util.Abort(_("nothing to merge"),
1094 1108 hint=_("use 'hg update' "
1095 1109 "or check 'hg heads'"))
1096 1110 if not force and (wc.files() or wc.deleted()):
1097 1111 raise util.Abort(_("uncommitted changes"),
1098 1112 hint=_("use 'hg status' to list changes"))
1099 1113 for s in sorted(wc.substate):
1100 1114 if wc.sub(s).dirty():
1101 1115 raise util.Abort(_("uncommitted changes in "
1102 1116 "subrepository '%s'") % s)
1103 1117
1104 1118 elif not overwrite:
1105 1119 if p1 == p2: # no-op update
1106 1120 # call the hooks and exit early
1107 1121 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1108 1122 repo.hook('update', parent1=xp2, parent2='', error=0)
1109 1123 return 0, 0, 0, 0
1110 1124
1111 1125 if pas not in ([p1], [p2]): # nonlinear
1112 1126 dirty = wc.dirty(missing=True)
1113 1127 if dirty or onode is None:
1114 1128 # Branching is a bit strange to ensure we do the minimal
1115 1129 # amount of call to obsolete.background.
1116 1130 foreground = obsolete.foreground(repo, [p1.node()])
1117 1131 # note: the <node> variable contains a random identifier
1118 1132 if repo[node].node() in foreground:
1119 1133 pas = [p1] # allow updating to successors
1120 1134 elif dirty:
1121 1135 msg = _("uncommitted changes")
1122 1136 if onode is None:
1123 1137 hint = _("commit and merge, or update --clean to"
1124 1138 " discard changes")
1125 1139 else:
1126 1140 hint = _("commit or update --clean to discard"
1127 1141 " changes")
1128 1142 raise util.Abort(msg, hint=hint)
1129 1143 else: # node is none
1130 1144 msg = _("not a linear update")
1131 1145 hint = _("merge or update --check to force update")
1132 1146 raise util.Abort(msg, hint=hint)
1133 1147 else:
1134 1148 # Allow jumping branches if clean and specific rev given
1135 1149 pas = [p1]
1136 1150
1137 1151 followcopies = False
1138 1152 if overwrite:
1139 1153 pas = [wc]
1140 1154 elif pas == [p2]: # backwards
1141 1155 pas = [wc.p1()]
1142 1156 elif not branchmerge and not wc.dirty(missing=True):
1143 1157 pass
1144 1158 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1145 1159 followcopies = True
1146 1160
1147 1161 ### calculate phase
1148 1162 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1149 1163 partial, mergeancestor, followcopies)
1150 1164
1151 1165 ### apply phase
1152 1166 if not branchmerge: # just jump to the new rev
1153 1167 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1154 1168 if not partial:
1155 1169 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1156 1170 # note that we're in the middle of an update
1157 1171 repo.vfs.write('updatestate', p2.hex())
1158 1172
1159 1173 stats = applyupdates(repo, actions, wc, p2, overwrite)
1160 1174
1161 1175 if not partial:
1162 1176 repo.setparents(fp1, fp2)
1163 1177 recordupdates(repo, actions, branchmerge)
1164 1178 # update completed, clear state
1165 1179 util.unlink(repo.join('updatestate'))
1166 1180
1167 1181 if not branchmerge:
1168 1182 repo.dirstate.setbranch(p2.branch())
1169 1183 finally:
1170 1184 wlock.release()
1171 1185
1172 1186 if not partial:
1173 1187 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1174 1188 return stats
General Comments 0
You need to be logged in to leave comments. Login now