##// END OF EJS Templates
merge: move calculateupdates() before applyupdated()...
Martin von Zweigbergk -
r23385:91c24457 default
parent child Browse files
Show More
@@ -1,1144 +1,1144 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 as errormod, util, filemerge, copies, subrepo, worker
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, labels=None):
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 labels=labels)
292 292 if r is None:
293 293 # no real conflict
294 294 del self._state[dfile]
295 295 self._dirty = True
296 296 elif not r:
297 297 self.mark(dfile, 'r')
298 298 return r
299 299
300 300 def _checkunknownfile(repo, wctx, mctx, f):
301 301 return (not repo.dirstate._ignore(f)
302 302 and os.path.isfile(repo.wjoin(f))
303 303 and repo.wopener.audit.check(f)
304 304 and repo.dirstate.normalize(f) not in repo.dirstate
305 305 and mctx[f].cmp(wctx[f]))
306 306
307 307 def _forgetremoved(wctx, mctx, branchmerge):
308 308 """
309 309 Forget removed files
310 310
311 311 If we're jumping between revisions (as opposed to merging), and if
312 312 neither the working directory nor the target rev has the file,
313 313 then we need to remove it from the dirstate, to prevent the
314 314 dirstate from listing the file when it is no longer in the
315 315 manifest.
316 316
317 317 If we're merging, and the other revision has removed a file
318 318 that is not present in the working directory, we need to mark it
319 319 as removed.
320 320 """
321 321
322 322 ractions = []
323 323 factions = xactions = []
324 324 if branchmerge:
325 325 xactions = ractions
326 326 for f in wctx.deleted():
327 327 if f not in mctx:
328 328 xactions.append((f, None, "forget deleted"))
329 329
330 330 if not branchmerge:
331 331 for f in wctx.removed():
332 332 if f not in mctx:
333 333 factions.append((f, None, "forget removed"))
334 334
335 335 return ractions, factions
336 336
337 337 def _checkcollision(repo, wmf, actions):
338 338 # build provisional merged manifest up
339 339 pmmf = set(wmf)
340 340
341 341 if actions:
342 342 # k, dr, e and rd are no-op
343 343 for m in 'a', 'f', 'g', 'cd', 'dc':
344 344 for f, args, msg in actions[m]:
345 345 pmmf.add(f)
346 346 for f, args, msg in actions['r']:
347 347 pmmf.discard(f)
348 348 for f, args, msg in actions['dm']:
349 349 f2, flags = args
350 350 pmmf.discard(f2)
351 351 pmmf.add(f)
352 352 for f, args, msg in actions['dg']:
353 353 pmmf.add(f)
354 354 for f, args, msg in actions['m']:
355 355 f1, f2, fa, move, anc = args
356 356 if move:
357 357 pmmf.discard(f1)
358 358 pmmf.add(f)
359 359
360 360 # check case-folding collision in provisional merged manifest
361 361 foldmap = {}
362 362 for f in sorted(pmmf):
363 363 fold = util.normcase(f)
364 364 if fold in foldmap:
365 365 raise util.Abort(_("case-folding collision between %s and %s")
366 366 % (f, foldmap[fold]))
367 367 foldmap[fold] = f
368 368
369 369 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
370 370 acceptremote, followcopies):
371 371 """
372 372 Merge p1 and p2 with ancestor pa and generate merge action list
373 373
374 374 branchmerge and force are as passed in to update
375 375 partial = function to filter file lists
376 376 acceptremote = accept the incoming changes without prompting
377 377 """
378 378
379 379 actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
380 380 copy, movewithdir = {}, {}
381 381
382 382 # manifests fetched in order are going to be faster, so prime the caches
383 383 [x.manifest() for x in
384 384 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
385 385
386 386 if followcopies:
387 387 ret = copies.mergecopies(repo, wctx, p2, pa)
388 388 copy, movewithdir, diverge, renamedelete = ret
389 389 for of, fl in diverge.iteritems():
390 390 actions['dr'].append((of, (fl,), "divergent renames"))
391 391 for of, fl in renamedelete.iteritems():
392 392 actions['rd'].append((of, (fl,), "rename and delete"))
393 393
394 394 repo.ui.note(_("resolving manifests\n"))
395 395 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
396 396 % (bool(branchmerge), bool(force), bool(partial)))
397 397 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
398 398
399 399 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
400 400 copied = set(copy.values())
401 401 copied.update(movewithdir.values())
402 402
403 403 if '.hgsubstate' in m1:
404 404 # check whether sub state is modified
405 405 for s in sorted(wctx.substate):
406 406 if wctx.sub(s).dirty():
407 407 m1['.hgsubstate'] += '+'
408 408 break
409 409
410 410 aborts = []
411 411 # Compare manifests
412 412 diff = m1.diff(m2)
413 413
414 414 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
415 415 if partial and not partial(f):
416 416 continue
417 417 if n1 and n2:
418 418 fa = f
419 419 a = ma.get(f, nullid)
420 420 if a == nullid:
421 421 fa = copy.get(f, f)
422 422 # Note: f as default is wrong - we can't really make a 3-way
423 423 # merge without an ancestor file.
424 424 fla = ma.flags(fa)
425 425 nol = 'l' not in fl1 + fl2 + fla
426 426 if n2 == a and fl2 == fla:
427 427 actions['k'].append((f, (), "keep")) # remote unchanged
428 428 elif n1 == a and fl1 == fla: # local unchanged - use remote
429 429 if n1 == n2: # optimization: keep local content
430 430 actions['e'].append((f, (fl2,), "update permissions"))
431 431 else:
432 432 actions['g'].append((f, (fl2,), "remote is newer"))
433 433 elif nol and n2 == a: # remote only changed 'x'
434 434 actions['e'].append((f, (fl2,), "update permissions"))
435 435 elif nol and n1 == a: # local only changed 'x'
436 436 actions['g'].append((f, (fl1,), "remote is newer"))
437 437 else: # both changed something
438 438 actions['m'].append((f, (f, f, fa, False, pa.node()),
439 439 "versions differ"))
440 440 elif f in copied: # files we'll deal with on m2 side
441 441 pass
442 442 elif n1 and f in movewithdir: # directory rename, move local
443 443 f2 = movewithdir[f]
444 444 actions['dm'].append((f2, (f, fl1),
445 445 "remote directory rename - move from " + f))
446 446 elif n1 and f in copy:
447 447 f2 = copy[f]
448 448 actions['m'].append((f, (f, f2, f2, False, pa.node()),
449 449 "local copied/moved from " + f2))
450 450 elif n1 and f in ma: # clean, a different, no remote
451 451 if n1 != ma[f]:
452 452 if acceptremote:
453 453 actions['r'].append((f, None, "remote delete"))
454 454 else:
455 455 actions['cd'].append((f, None, "prompt changed/deleted"))
456 456 elif n1[20:] == 'a': # added, no remote
457 457 actions['f'].append((f, None, "remote deleted"))
458 458 else:
459 459 actions['r'].append((f, None, "other deleted"))
460 460 elif n2 and f in movewithdir:
461 461 f2 = movewithdir[f]
462 462 actions['dg'].append((f2, (f, fl2),
463 463 "local directory rename - get from " + f))
464 464 elif n2 and f in copy:
465 465 f2 = copy[f]
466 466 if f2 in m2:
467 467 actions['m'].append((f, (f2, f, f2, False, pa.node()),
468 468 "remote copied from " + f2))
469 469 else:
470 470 actions['m'].append((f, (f2, f, f2, True, pa.node()),
471 471 "remote moved from " + f2))
472 472 elif n2 and f not in ma:
473 473 # local unknown, remote created: the logic is described by the
474 474 # following table:
475 475 #
476 476 # force branchmerge different | action
477 477 # n * n | get
478 478 # n * y | abort
479 479 # y n * | get
480 480 # y y n | get
481 481 # y y y | merge
482 482 #
483 483 # Checking whether the files are different is expensive, so we
484 484 # don't do that when we can avoid it.
485 485 if force and not branchmerge:
486 486 actions['g'].append((f, (fl2,), "remote created"))
487 487 else:
488 488 different = _checkunknownfile(repo, wctx, p2, f)
489 489 if force and branchmerge and different:
490 490 # FIXME: This is wrong - f is not in ma ...
491 491 actions['m'].append((f, (f, f, f, False, pa.node()),
492 492 "remote differs from untracked local"))
493 493 elif not force and different:
494 494 aborts.append((f, 'ud'))
495 495 else:
496 496 actions['g'].append((f, (fl2,), "remote created"))
497 497 elif n2 and n2 != ma[f]:
498 498 different = _checkunknownfile(repo, wctx, p2, f)
499 499 if not force and different:
500 500 aborts.append((f, 'ud'))
501 501 else:
502 502 if acceptremote:
503 503 actions['g'].append((f, (fl2,), "remote recreating"))
504 504 else:
505 505 actions['dc'].append((f, (fl2,), "prompt deleted/changed"))
506 506
507 507 for f, m in sorted(aborts):
508 508 if m == 'ud':
509 509 repo.ui.warn(_("%s: untracked file differs\n") % f)
510 510 else: assert False, m
511 511 if aborts:
512 512 raise util.Abort(_("untracked files in working directory differ "
513 513 "from files in requested revision"))
514 514
515 515 if not util.checkcase(repo.path):
516 516 # check collision between files only in p2 for clean update
517 517 if (not branchmerge and
518 518 (force or not wctx.dirty(missing=True, branch=False))):
519 519 _checkcollision(repo, m2, None)
520 520 else:
521 521 _checkcollision(repo, m1, actions)
522 522
523 523 return actions
524 524
525 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
526 acceptremote, followcopies):
527 "Calculate the actions needed to merge mctx into wctx using ancestors"
528
529 if len(ancestors) == 1: # default
530 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
531 branchmerge, force,
532 partial, acceptremote, followcopies)
533
534 else: # only when merge.preferancestor=* - the default
535 repo.ui.note(
536 _("note: merging %s and %s using bids from ancestors %s\n") %
537 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
538
539 # Call for bids
540 fbids = {} # mapping filename to bids (action method to list af actions)
541 for ancestor in ancestors:
542 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
543 actions = manifestmerge(repo, wctx, mctx, ancestor,
544 branchmerge, force,
545 partial, acceptremote, followcopies)
546 for m, l in sorted(actions.items()):
547 for a in l:
548 f, args, msg = a
549 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
550 if f in fbids:
551 d = fbids[f]
552 if m in d:
553 d[m].append(a)
554 else:
555 d[m] = [a]
556 else:
557 fbids[f] = {m: [a]}
558
559 # Pick the best bid for each file
560 repo.ui.note(_('\nauction for merging merge bids\n'))
561 actions = dict((m, []) for m in actions.keys())
562 for f, bids in sorted(fbids.items()):
563 # bids is a mapping from action method to list af actions
564 # Consensus?
565 if len(bids) == 1: # all bids are the same kind of method
566 m, l = bids.items()[0]
567 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
568 repo.ui.note(" %s: consensus for %s\n" % (f, m))
569 actions[m].append(l[0])
570 continue
571 # If keep is an option, just do it.
572 if 'k' in bids:
573 repo.ui.note(" %s: picking 'keep' action\n" % f)
574 actions['k'].append(bids['k'][0])
575 continue
576 # If there are gets and they all agree [how could they not?], do it.
577 if 'g' in bids:
578 ga0 = bids['g'][0]
579 if util.all(a == ga0 for a in bids['g'][1:]):
580 repo.ui.note(" %s: picking 'get' action\n" % f)
581 actions['g'].append(ga0)
582 continue
583 # TODO: Consider other simple actions such as mode changes
584 # Handle inefficient democrazy.
585 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
586 for m, l in sorted(bids.items()):
587 for _f, args, msg in l:
588 repo.ui.note(' %s -> %s\n' % (msg, m))
589 # Pick random action. TODO: Instead, prompt user when resolving
590 m, l = bids.items()[0]
591 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
592 (f, m))
593 actions[m].append(l[0])
594 continue
595 repo.ui.note(_('end of auction\n\n'))
596
597 # Prompt and create actions. TODO: Move this towards resolve phase.
598 for f, args, msg in actions['cd']:
599 if repo.ui.promptchoice(
600 _("local changed %s which remote deleted\n"
601 "use (c)hanged version or (d)elete?"
602 "$$ &Changed $$ &Delete") % f, 0):
603 actions['r'].append((f, None, "prompt delete"))
604 else:
605 actions['a'].append((f, None, "prompt keep"))
606 del actions['cd'][:]
607
608 for f, args, msg in actions['dc']:
609 flags, = args
610 if repo.ui.promptchoice(
611 _("remote changed %s which local deleted\n"
612 "use (c)hanged version or leave (d)eleted?"
613 "$$ &Changed $$ &Deleted") % f, 0) == 0:
614 actions['g'].append((f, (flags,), "prompt recreating"))
615 del actions['dc'][:]
616
617 if wctx.rev() is None:
618 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
619 actions['r'].extend(ractions)
620 actions['f'].extend(factions)
621
622 return actions
623
525 624 def batchremove(repo, actions):
526 625 """apply removes to the working directory
527 626
528 627 yields tuples for progress updates
529 628 """
530 629 verbose = repo.ui.verbose
531 630 unlink = util.unlinkpath
532 631 wjoin = repo.wjoin
533 632 audit = repo.wopener.audit
534 633 i = 0
535 634 for f, args, msg in actions:
536 635 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
537 636 if verbose:
538 637 repo.ui.note(_("removing %s\n") % f)
539 638 audit(f)
540 639 try:
541 640 unlink(wjoin(f), ignoremissing=True)
542 641 except OSError, inst:
543 642 repo.ui.warn(_("update failed to remove %s: %s!\n") %
544 643 (f, inst.strerror))
545 644 if i == 100:
546 645 yield i, f
547 646 i = 0
548 647 i += 1
549 648 if i > 0:
550 649 yield i, f
551 650
552 651 def batchget(repo, mctx, actions):
553 652 """apply gets to the working directory
554 653
555 654 mctx is the context to get from
556 655
557 656 yields tuples for progress updates
558 657 """
559 658 verbose = repo.ui.verbose
560 659 fctx = mctx.filectx
561 660 wwrite = repo.wwrite
562 661 i = 0
563 662 for f, args, msg in actions:
564 663 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
565 664 if verbose:
566 665 repo.ui.note(_("getting %s\n") % f)
567 666 wwrite(f, fctx(f).data(), args[0])
568 667 if i == 100:
569 668 yield i, f
570 669 i = 0
571 670 i += 1
572 671 if i > 0:
573 672 yield i, f
574 673
575 674 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
576 675 """apply the merge action list to the working directory
577 676
578 677 wctx is the working copy context
579 678 mctx is the context to be merged into the working copy
580 679
581 680 Return a tuple of counts (updated, merged, removed, unresolved) that
582 681 describes how many files were affected by the update.
583 682 """
584 683
585 684 updated, merged, removed, unresolved = 0, 0, 0, 0
586 685 ms = mergestate(repo)
587 686 ms.reset(wctx.p1().node(), mctx.node())
588 687 moves = []
589 688 for m, l in actions.items():
590 689 l.sort()
591 690
592 691 # prescan for merges
593 692 for f, args, msg in actions['m']:
594 693 f1, f2, fa, move, anc = args
595 694 if f == '.hgsubstate': # merged internally
596 695 continue
597 696 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
598 697 fcl = wctx[f1]
599 698 fco = mctx[f2]
600 699 actx = repo[anc]
601 700 if fa in actx:
602 701 fca = actx[fa]
603 702 else:
604 703 fca = repo.filectx(f1, fileid=nullrev)
605 704 ms.add(fcl, fco, fca, f)
606 705 if f1 != f and move:
607 706 moves.append(f1)
608 707
609 708 audit = repo.wopener.audit
610 709 _updating = _('updating')
611 710 _files = _('files')
612 711 progress = repo.ui.progress
613 712
614 713 # remove renamed files after safely stored
615 714 for f in moves:
616 715 if os.path.lexists(repo.wjoin(f)):
617 716 repo.ui.debug("removing %s\n" % f)
618 717 audit(f)
619 718 util.unlinkpath(repo.wjoin(f))
620 719
621 720 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
622 721
623 722 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
624 723 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
625 724
626 725 # remove in parallel (must come first)
627 726 z = 0
628 727 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
629 728 for i, item in prog:
630 729 z += i
631 730 progress(_updating, z, item=item, total=numupdates, unit=_files)
632 731 removed = len(actions['r'])
633 732
634 733 # get in parallel
635 734 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
636 735 for i, item in prog:
637 736 z += i
638 737 progress(_updating, z, item=item, total=numupdates, unit=_files)
639 738 updated = len(actions['g'])
640 739
641 740 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
642 741 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
643 742
644 743 # forget (manifest only, just log it) (must come first)
645 744 for f, args, msg in actions['f']:
646 745 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
647 746 z += 1
648 747 progress(_updating, z, item=f, total=numupdates, unit=_files)
649 748
650 749 # re-add (manifest only, just log it)
651 750 for f, args, msg in actions['a']:
652 751 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
653 752 z += 1
654 753 progress(_updating, z, item=f, total=numupdates, unit=_files)
655 754
656 755 # keep (noop, just log it)
657 756 for f, args, msg in actions['k']:
658 757 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
659 758 # no progress
660 759
661 760 # merge
662 761 for f, args, msg in actions['m']:
663 762 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
664 763 z += 1
665 764 progress(_updating, z, item=f, total=numupdates, unit=_files)
666 765 f1, f2, fa, move, anc = args
667 766 if f == '.hgsubstate': # subrepo states need updating
668 767 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
669 768 overwrite)
670 769 continue
671 770 audit(f)
672 771 r = ms.resolve(f, wctx, labels=labels)
673 772 if r is not None and r > 0:
674 773 unresolved += 1
675 774 else:
676 775 if r is None:
677 776 updated += 1
678 777 else:
679 778 merged += 1
680 779
681 780 # directory rename, move local
682 781 for f, args, msg in actions['dm']:
683 782 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
684 783 z += 1
685 784 progress(_updating, z, item=f, total=numupdates, unit=_files)
686 785 f0, flags = args
687 786 repo.ui.note(_("moving %s to %s\n") % (f0, f))
688 787 audit(f)
689 788 repo.wwrite(f, wctx.filectx(f0).data(), flags)
690 789 util.unlinkpath(repo.wjoin(f0))
691 790 updated += 1
692 791
693 792 # local directory rename, get
694 793 for f, args, msg in actions['dg']:
695 794 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
696 795 z += 1
697 796 progress(_updating, z, item=f, total=numupdates, unit=_files)
698 797 f0, flags = args
699 798 repo.ui.note(_("getting %s to %s\n") % (f0, f))
700 799 repo.wwrite(f, mctx.filectx(f0).data(), flags)
701 800 updated += 1
702 801
703 802 # divergent renames
704 803 for f, args, msg in actions['dr']:
705 804 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
706 805 z += 1
707 806 progress(_updating, z, item=f, total=numupdates, unit=_files)
708 807 fl, = args
709 808 repo.ui.warn(_("note: possible conflict - %s was renamed "
710 809 "multiple times to:\n") % f)
711 810 for nf in fl:
712 811 repo.ui.warn(" %s\n" % nf)
713 812
714 813 # rename and delete
715 814 for f, args, msg in actions['rd']:
716 815 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
717 816 z += 1
718 817 progress(_updating, z, item=f, total=numupdates, unit=_files)
719 818 fl, = args
720 819 repo.ui.warn(_("note: possible conflict - %s was deleted "
721 820 "and renamed to:\n") % f)
722 821 for nf in fl:
723 822 repo.ui.warn(" %s\n" % nf)
724 823
725 824 # exec
726 825 for f, args, msg in actions['e']:
727 826 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
728 827 z += 1
729 828 progress(_updating, z, item=f, total=numupdates, unit=_files)
730 829 flags, = args
731 830 audit(f)
732 831 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
733 832 updated += 1
734 833
735 834 ms.commit()
736 835 progress(_updating, None, total=numupdates, unit=_files)
737 836
738 837 return updated, merged, removed, unresolved
739 838
740 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
741 acceptremote, followcopies):
742 "Calculate the actions needed to merge mctx into wctx using ancestors"
743
744 if len(ancestors) == 1: # default
745 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
746 branchmerge, force,
747 partial, acceptremote, followcopies)
748
749 else: # only when merge.preferancestor=* - the default
750 repo.ui.note(
751 _("note: merging %s and %s using bids from ancestors %s\n") %
752 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
753
754 # Call for bids
755 fbids = {} # mapping filename to bids (action method to list af actions)
756 for ancestor in ancestors:
757 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
758 actions = manifestmerge(repo, wctx, mctx, ancestor,
759 branchmerge, force,
760 partial, acceptremote, followcopies)
761 for m, l in sorted(actions.items()):
762 for a in l:
763 f, args, msg = a
764 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
765 if f in fbids:
766 d = fbids[f]
767 if m in d:
768 d[m].append(a)
769 else:
770 d[m] = [a]
771 else:
772 fbids[f] = {m: [a]}
773
774 # Pick the best bid for each file
775 repo.ui.note(_('\nauction for merging merge bids\n'))
776 actions = dict((m, []) for m in actions.keys())
777 for f, bids in sorted(fbids.items()):
778 # bids is a mapping from action method to list af actions
779 # Consensus?
780 if len(bids) == 1: # all bids are the same kind of method
781 m, l = bids.items()[0]
782 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
783 repo.ui.note(" %s: consensus for %s\n" % (f, m))
784 actions[m].append(l[0])
785 continue
786 # If keep is an option, just do it.
787 if 'k' in bids:
788 repo.ui.note(" %s: picking 'keep' action\n" % f)
789 actions['k'].append(bids['k'][0])
790 continue
791 # If there are gets and they all agree [how could they not?], do it.
792 if 'g' in bids:
793 ga0 = bids['g'][0]
794 if util.all(a == ga0 for a in bids['g'][1:]):
795 repo.ui.note(" %s: picking 'get' action\n" % f)
796 actions['g'].append(ga0)
797 continue
798 # TODO: Consider other simple actions such as mode changes
799 # Handle inefficient democrazy.
800 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
801 for m, l in sorted(bids.items()):
802 for _f, args, msg in l:
803 repo.ui.note(' %s -> %s\n' % (msg, m))
804 # Pick random action. TODO: Instead, prompt user when resolving
805 m, l = bids.items()[0]
806 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
807 (f, m))
808 actions[m].append(l[0])
809 continue
810 repo.ui.note(_('end of auction\n\n'))
811
812 # Prompt and create actions. TODO: Move this towards resolve phase.
813 for f, args, msg in actions['cd']:
814 if repo.ui.promptchoice(
815 _("local changed %s which remote deleted\n"
816 "use (c)hanged version or (d)elete?"
817 "$$ &Changed $$ &Delete") % f, 0):
818 actions['r'].append((f, None, "prompt delete"))
819 else:
820 actions['a'].append((f, None, "prompt keep"))
821 del actions['cd'][:]
822
823 for f, args, msg in actions['dc']:
824 flags, = args
825 if repo.ui.promptchoice(
826 _("remote changed %s which local deleted\n"
827 "use (c)hanged version or leave (d)eleted?"
828 "$$ &Changed $$ &Deleted") % f, 0) == 0:
829 actions['g'].append((f, (flags,), "prompt recreating"))
830 del actions['dc'][:]
831
832 if wctx.rev() is None:
833 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
834 actions['r'].extend(ractions)
835 actions['f'].extend(factions)
836
837 return actions
838
839 839 def recordupdates(repo, actions, branchmerge):
840 840 "record merge actions to the dirstate"
841 841 # remove (must come first)
842 842 for f, args, msg in actions['r']:
843 843 if branchmerge:
844 844 repo.dirstate.remove(f)
845 845 else:
846 846 repo.dirstate.drop(f)
847 847
848 848 # forget (must come first)
849 849 for f, args, msg in actions['f']:
850 850 repo.dirstate.drop(f)
851 851
852 852 # re-add
853 853 for f, args, msg in actions['a']:
854 854 if not branchmerge:
855 855 repo.dirstate.add(f)
856 856
857 857 # exec change
858 858 for f, args, msg in actions['e']:
859 859 repo.dirstate.normallookup(f)
860 860
861 861 # keep
862 862 for f, args, msg in actions['k']:
863 863 pass
864 864
865 865 # get
866 866 for f, args, msg in actions['g']:
867 867 if branchmerge:
868 868 repo.dirstate.otherparent(f)
869 869 else:
870 870 repo.dirstate.normal(f)
871 871
872 872 # merge
873 873 for f, args, msg in actions['m']:
874 874 f1, f2, fa, move, anc = args
875 875 if branchmerge:
876 876 # We've done a branch merge, mark this file as merged
877 877 # so that we properly record the merger later
878 878 repo.dirstate.merge(f)
879 879 if f1 != f2: # copy/rename
880 880 if move:
881 881 repo.dirstate.remove(f1)
882 882 if f1 != f:
883 883 repo.dirstate.copy(f1, f)
884 884 else:
885 885 repo.dirstate.copy(f2, f)
886 886 else:
887 887 # We've update-merged a locally modified file, so
888 888 # we set the dirstate to emulate a normal checkout
889 889 # of that file some time in the past. Thus our
890 890 # merge will appear as a normal local file
891 891 # modification.
892 892 if f2 == f: # file not locally copied/moved
893 893 repo.dirstate.normallookup(f)
894 894 if move:
895 895 repo.dirstate.drop(f1)
896 896
897 897 # directory rename, move local
898 898 for f, args, msg in actions['dm']:
899 899 f0, flag = args
900 900 if f0 not in repo.dirstate:
901 901 # untracked file moved
902 902 continue
903 903 if branchmerge:
904 904 repo.dirstate.add(f)
905 905 repo.dirstate.remove(f0)
906 906 repo.dirstate.copy(f0, f)
907 907 else:
908 908 repo.dirstate.normal(f)
909 909 repo.dirstate.drop(f0)
910 910
911 911 # directory rename, get
912 912 for f, args, msg in actions['dg']:
913 913 f0, flag = args
914 914 if branchmerge:
915 915 repo.dirstate.add(f)
916 916 repo.dirstate.copy(f0, f)
917 917 else:
918 918 repo.dirstate.normal(f)
919 919
920 920 def update(repo, node, branchmerge, force, partial, ancestor=None,
921 921 mergeancestor=False, labels=None):
922 922 """
923 923 Perform a merge between the working directory and the given node
924 924
925 925 node = the node to update to, or None if unspecified
926 926 branchmerge = whether to merge between branches
927 927 force = whether to force branch merging or file overwriting
928 928 partial = a function to filter file lists (dirstate not updated)
929 929 mergeancestor = whether it is merging with an ancestor. If true,
930 930 we should accept the incoming changes for any prompts that occur.
931 931 If false, merging with an ancestor (fast-forward) is only allowed
932 932 between different named branches. This flag is used by rebase extension
933 933 as a temporary fix and should be avoided in general.
934 934
935 935 The table below shows all the behaviors of the update command
936 936 given the -c and -C or no options, whether the working directory
937 937 is dirty, whether a revision is specified, and the relationship of
938 938 the parent rev to the target rev (linear, on the same named
939 939 branch, or on another named branch).
940 940
941 941 This logic is tested by test-update-branches.t.
942 942
943 943 -c -C dirty rev | linear same cross
944 944 n n n n | ok (1) x
945 945 n n n y | ok ok ok
946 946 n n y n | merge (2) (2)
947 947 n n y y | merge (3) (3)
948 948 n y * * | --- discard ---
949 949 y n y * | --- (4) ---
950 950 y n n * | --- ok ---
951 951 y y * * | --- (5) ---
952 952
953 953 x = can't happen
954 954 * = don't-care
955 955 1 = abort: not a linear update (merge or update --check to force update)
956 956 2 = abort: uncommitted changes (commit and merge, or update --clean to
957 957 discard changes)
958 958 3 = abort: uncommitted changes (commit or update --clean to discard changes)
959 959 4 = abort: uncommitted changes (checked in commands.py)
960 960 5 = incompatible options (checked in commands.py)
961 961
962 962 Return the same tuple as applyupdates().
963 963 """
964 964
965 965 onode = node
966 966 wlock = repo.wlock()
967 967 try:
968 968 wc = repo[None]
969 969 pl = wc.parents()
970 970 p1 = pl[0]
971 971 pas = [None]
972 972 if ancestor:
973 973 pas = [repo[ancestor]]
974 974
975 975 if node is None:
976 976 # Here is where we should consider bookmarks, divergent bookmarks,
977 977 # foreground changesets (successors), and tip of current branch;
978 978 # but currently we are only checking the branch tips.
979 979 try:
980 980 node = repo.branchtip(wc.branch())
981 981 except errormod.RepoLookupError:
982 982 if wc.branch() == 'default': # no default branch!
983 983 node = repo.lookup('tip') # update to tip
984 984 else:
985 985 raise util.Abort(_("branch %s not found") % wc.branch())
986 986
987 987 if p1.obsolete() and not p1.children():
988 988 # allow updating to successors
989 989 successors = obsolete.successorssets(repo, p1.node())
990 990
991 991 # behavior of certain cases is as follows,
992 992 #
993 993 # divergent changesets: update to highest rev, similar to what
994 994 # is currently done when there are more than one head
995 995 # (i.e. 'tip')
996 996 #
997 997 # replaced changesets: same as divergent except we know there
998 998 # is no conflict
999 999 #
1000 1000 # pruned changeset: no update is done; though, we could
1001 1001 # consider updating to the first non-obsolete parent,
1002 1002 # similar to what is current done for 'hg prune'
1003 1003
1004 1004 if successors:
1005 1005 # flatten the list here handles both divergent (len > 1)
1006 1006 # and the usual case (len = 1)
1007 1007 successors = [n for sub in successors for n in sub]
1008 1008
1009 1009 # get the max revision for the given successors set,
1010 1010 # i.e. the 'tip' of a set
1011 1011 node = repo.revs('max(%ln)', successors).first()
1012 1012 pas = [p1]
1013 1013
1014 1014 overwrite = force and not branchmerge
1015 1015
1016 1016 p2 = repo[node]
1017 1017 if pas[0] is None:
1018 1018 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1019 1019 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1020 1020 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1021 1021 else:
1022 1022 pas = [p1.ancestor(p2, warn=branchmerge)]
1023 1023
1024 1024 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1025 1025
1026 1026 ### check phase
1027 1027 if not overwrite and len(pl) > 1:
1028 1028 raise util.Abort(_("outstanding uncommitted merge"))
1029 1029 if branchmerge:
1030 1030 if pas == [p2]:
1031 1031 raise util.Abort(_("merging with a working directory ancestor"
1032 1032 " has no effect"))
1033 1033 elif pas == [p1]:
1034 1034 if not mergeancestor and p1.branch() == p2.branch():
1035 1035 raise util.Abort(_("nothing to merge"),
1036 1036 hint=_("use 'hg update' "
1037 1037 "or check 'hg heads'"))
1038 1038 if not force and (wc.files() or wc.deleted()):
1039 1039 raise util.Abort(_("uncommitted changes"),
1040 1040 hint=_("use 'hg status' to list changes"))
1041 1041 for s in sorted(wc.substate):
1042 1042 if wc.sub(s).dirty():
1043 1043 raise util.Abort(_("uncommitted changes in "
1044 1044 "subrepository '%s'") % s)
1045 1045
1046 1046 elif not overwrite:
1047 1047 if p1 == p2: # no-op update
1048 1048 # call the hooks and exit early
1049 1049 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1050 1050 repo.hook('update', parent1=xp2, parent2='', error=0)
1051 1051 return 0, 0, 0, 0
1052 1052
1053 1053 if pas not in ([p1], [p2]): # nonlinear
1054 1054 dirty = wc.dirty(missing=True)
1055 1055 if dirty or onode is None:
1056 1056 # Branching is a bit strange to ensure we do the minimal
1057 1057 # amount of call to obsolete.background.
1058 1058 foreground = obsolete.foreground(repo, [p1.node()])
1059 1059 # note: the <node> variable contains a random identifier
1060 1060 if repo[node].node() in foreground:
1061 1061 pas = [p1] # allow updating to successors
1062 1062 elif dirty:
1063 1063 msg = _("uncommitted changes")
1064 1064 if onode is None:
1065 1065 hint = _("commit and merge, or update --clean to"
1066 1066 " discard changes")
1067 1067 else:
1068 1068 hint = _("commit or update --clean to discard"
1069 1069 " changes")
1070 1070 raise util.Abort(msg, hint=hint)
1071 1071 else: # node is none
1072 1072 msg = _("not a linear update")
1073 1073 hint = _("merge or update --check to force update")
1074 1074 raise util.Abort(msg, hint=hint)
1075 1075 else:
1076 1076 # Allow jumping branches if clean and specific rev given
1077 1077 pas = [p1]
1078 1078
1079 1079 followcopies = False
1080 1080 if overwrite:
1081 1081 pas = [wc]
1082 1082 elif pas == [p2]: # backwards
1083 1083 pas = [wc.p1()]
1084 1084 elif not branchmerge and not wc.dirty(missing=True):
1085 1085 pass
1086 1086 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1087 1087 followcopies = True
1088 1088
1089 1089 ### calculate phase
1090 1090 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1091 1091 partial, mergeancestor, followcopies)
1092 1092
1093 1093 ### apply phase
1094 1094 if not branchmerge: # just jump to the new rev
1095 1095 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1096 1096 if not partial:
1097 1097 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1098 1098 # note that we're in the middle of an update
1099 1099 repo.vfs.write('updatestate', p2.hex())
1100 1100
1101 1101 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1102 1102
1103 1103 if not partial:
1104 1104 repo.dirstate.beginparentchange()
1105 1105 repo.setparents(fp1, fp2)
1106 1106 recordupdates(repo, actions, branchmerge)
1107 1107 # update completed, clear state
1108 1108 util.unlink(repo.join('updatestate'))
1109 1109
1110 1110 if not branchmerge:
1111 1111 repo.dirstate.setbranch(p2.branch())
1112 1112 repo.dirstate.endparentchange()
1113 1113 finally:
1114 1114 wlock.release()
1115 1115
1116 1116 if not partial:
1117 1117 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1118 1118 return stats
1119 1119
1120 1120 def graft(repo, ctx, pctx, labels):
1121 1121 """Do a graft-like merge.
1122 1122
1123 1123 This is a merge where the merge ancestor is chosen such that one
1124 1124 or more changesets are grafted onto the current changeset. In
1125 1125 addition to the merge, this fixes up the dirstate to include only
1126 1126 a single parent and tries to duplicate any renames/copies
1127 1127 appropriately.
1128 1128
1129 1129 ctx - changeset to rebase
1130 1130 pctx - merge base, usually ctx.p1()
1131 1131 labels - merge labels eg ['local', 'graft']
1132 1132
1133 1133 """
1134 1134
1135 1135 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1136 1136 labels=labels)
1137 1137 # drop the second merge parent
1138 1138 repo.dirstate.beginparentchange()
1139 1139 repo.setparents(repo['.'].node(), nullid)
1140 1140 repo.dirstate.write()
1141 1141 # fix up dirstate for copies and renames
1142 1142 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1143 1143 repo.dirstate.endparentchange()
1144 1144 return stats
General Comments 0
You need to be logged in to leave comments. Login now