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