##// END OF EJS Templates
merge: clarify warning for (not) merging flags without ancestor...
Mads Kiilerich -
r30162:5cb83080 default
parent child Browse files
Show More
@@ -1,1674 +1,1676 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 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import os
13 13 import shutil
14 14 import struct
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 bin,
19 19 hex,
20 20 nullhex,
21 21 nullid,
22 22 nullrev,
23 23 )
24 24 from . import (
25 25 copies,
26 26 destutil,
27 27 error,
28 28 filemerge,
29 29 obsolete,
30 30 scmutil,
31 31 subrepo,
32 32 util,
33 33 worker,
34 34 )
35 35
36 36 _pack = struct.pack
37 37 _unpack = struct.unpack
38 38
39 39 def _droponode(data):
40 40 # used for compatibility for v1
41 41 bits = data.split('\0')
42 42 bits = bits[:-2] + bits[-1:]
43 43 return '\0'.join(bits)
44 44
45 45 class mergestate(object):
46 46 '''track 3-way merge state of individual files
47 47
48 48 The merge state is stored on disk when needed. Two files are used: one with
49 49 an old format (version 1), and one with a new format (version 2). Version 2
50 50 stores a superset of the data in version 1, including new kinds of records
51 51 in the future. For more about the new format, see the documentation for
52 52 `_readrecordsv2`.
53 53
54 54 Each record can contain arbitrary content, and has an associated type. This
55 55 `type` should be a letter. If `type` is uppercase, the record is mandatory:
56 56 versions of Mercurial that don't support it should abort. If `type` is
57 57 lowercase, the record can be safely ignored.
58 58
59 59 Currently known records:
60 60
61 61 L: the node of the "local" part of the merge (hexified version)
62 62 O: the node of the "other" part of the merge (hexified version)
63 63 F: a file to be merged entry
64 64 C: a change/delete or delete/change conflict
65 65 D: a file that the external merge driver will merge internally
66 66 (experimental)
67 67 m: the external merge driver defined for this merge plus its run state
68 68 (experimental)
69 69 f: a (filename, dictonary) tuple of optional values for a given file
70 70 X: unsupported mandatory record type (used in tests)
71 71 x: unsupported advisory record type (used in tests)
72 72 l: the labels for the parts of the merge.
73 73
74 74 Merge driver run states (experimental):
75 75 u: driver-resolved files unmarked -- needs to be run next time we're about
76 76 to resolve or commit
77 77 m: driver-resolved files marked -- only needs to be run before commit
78 78 s: success/skipped -- does not need to be run any more
79 79
80 80 '''
81 81 statepathv1 = 'merge/state'
82 82 statepathv2 = 'merge/state2'
83 83
84 84 @staticmethod
85 85 def clean(repo, node=None, other=None, labels=None):
86 86 """Initialize a brand new merge state, removing any existing state on
87 87 disk."""
88 88 ms = mergestate(repo)
89 89 ms.reset(node, other, labels)
90 90 return ms
91 91
92 92 @staticmethod
93 93 def read(repo):
94 94 """Initialize the merge state, reading it from disk."""
95 95 ms = mergestate(repo)
96 96 ms._read()
97 97 return ms
98 98
99 99 def __init__(self, repo):
100 100 """Initialize the merge state.
101 101
102 102 Do not use this directly! Instead call read() or clean()."""
103 103 self._repo = repo
104 104 self._dirty = False
105 105 self._labels = None
106 106
107 107 def reset(self, node=None, other=None, labels=None):
108 108 self._state = {}
109 109 self._stateextras = {}
110 110 self._local = None
111 111 self._other = None
112 112 self._labels = labels
113 113 for var in ('localctx', 'otherctx'):
114 114 if var in vars(self):
115 115 delattr(self, var)
116 116 if node:
117 117 self._local = node
118 118 self._other = other
119 119 self._readmergedriver = None
120 120 if self.mergedriver:
121 121 self._mdstate = 's'
122 122 else:
123 123 self._mdstate = 'u'
124 124 shutil.rmtree(self._repo.join('merge'), True)
125 125 self._results = {}
126 126 self._dirty = False
127 127
128 128 def _read(self):
129 129 """Analyse each record content to restore a serialized state from disk
130 130
131 131 This function process "record" entry produced by the de-serialization
132 132 of on disk file.
133 133 """
134 134 self._state = {}
135 135 self._stateextras = {}
136 136 self._local = None
137 137 self._other = None
138 138 for var in ('localctx', 'otherctx'):
139 139 if var in vars(self):
140 140 delattr(self, var)
141 141 self._readmergedriver = None
142 142 self._mdstate = 's'
143 143 unsupported = set()
144 144 records = self._readrecords()
145 145 for rtype, record in records:
146 146 if rtype == 'L':
147 147 self._local = bin(record)
148 148 elif rtype == 'O':
149 149 self._other = bin(record)
150 150 elif rtype == 'm':
151 151 bits = record.split('\0', 1)
152 152 mdstate = bits[1]
153 153 if len(mdstate) != 1 or mdstate not in 'ums':
154 154 # the merge driver should be idempotent, so just rerun it
155 155 mdstate = 'u'
156 156
157 157 self._readmergedriver = bits[0]
158 158 self._mdstate = mdstate
159 159 elif rtype in 'FDC':
160 160 bits = record.split('\0')
161 161 self._state[bits[0]] = bits[1:]
162 162 elif rtype == 'f':
163 163 filename, rawextras = record.split('\0', 1)
164 164 extraparts = rawextras.split('\0')
165 165 extras = {}
166 166 i = 0
167 167 while i < len(extraparts):
168 168 extras[extraparts[i]] = extraparts[i + 1]
169 169 i += 2
170 170
171 171 self._stateextras[filename] = extras
172 172 elif rtype == 'l':
173 173 labels = record.split('\0', 2)
174 174 self._labels = [l for l in labels if len(l) > 0]
175 175 elif not rtype.islower():
176 176 unsupported.add(rtype)
177 177 self._results = {}
178 178 self._dirty = False
179 179
180 180 if unsupported:
181 181 raise error.UnsupportedMergeRecords(unsupported)
182 182
183 183 def _readrecords(self):
184 184 """Read merge state from disk and return a list of record (TYPE, data)
185 185
186 186 We read data from both v1 and v2 files and decide which one to use.
187 187
188 188 V1 has been used by version prior to 2.9.1 and contains less data than
189 189 v2. We read both versions and check if no data in v2 contradicts
190 190 v1. If there is not contradiction we can safely assume that both v1
191 191 and v2 were written at the same time and use the extract data in v2. If
192 192 there is contradiction we ignore v2 content as we assume an old version
193 193 of Mercurial has overwritten the mergestate file and left an old v2
194 194 file around.
195 195
196 196 returns list of record [(TYPE, data), ...]"""
197 197 v1records = self._readrecordsv1()
198 198 v2records = self._readrecordsv2()
199 199 if self._v1v2match(v1records, v2records):
200 200 return v2records
201 201 else:
202 202 # v1 file is newer than v2 file, use it
203 203 # we have to infer the "other" changeset of the merge
204 204 # we cannot do better than that with v1 of the format
205 205 mctx = self._repo[None].parents()[-1]
206 206 v1records.append(('O', mctx.hex()))
207 207 # add place holder "other" file node information
208 208 # nobody is using it yet so we do no need to fetch the data
209 209 # if mctx was wrong `mctx[bits[-2]]` may fails.
210 210 for idx, r in enumerate(v1records):
211 211 if r[0] == 'F':
212 212 bits = r[1].split('\0')
213 213 bits.insert(-2, '')
214 214 v1records[idx] = (r[0], '\0'.join(bits))
215 215 return v1records
216 216
217 217 def _v1v2match(self, v1records, v2records):
218 218 oldv2 = set() # old format version of v2 record
219 219 for rec in v2records:
220 220 if rec[0] == 'L':
221 221 oldv2.add(rec)
222 222 elif rec[0] == 'F':
223 223 # drop the onode data (not contained in v1)
224 224 oldv2.add(('F', _droponode(rec[1])))
225 225 for rec in v1records:
226 226 if rec not in oldv2:
227 227 return False
228 228 else:
229 229 return True
230 230
231 231 def _readrecordsv1(self):
232 232 """read on disk merge state for version 1 file
233 233
234 234 returns list of record [(TYPE, data), ...]
235 235
236 236 Note: the "F" data from this file are one entry short
237 237 (no "other file node" entry)
238 238 """
239 239 records = []
240 240 try:
241 241 f = self._repo.vfs(self.statepathv1)
242 242 for i, l in enumerate(f):
243 243 if i == 0:
244 244 records.append(('L', l[:-1]))
245 245 else:
246 246 records.append(('F', l[:-1]))
247 247 f.close()
248 248 except IOError as err:
249 249 if err.errno != errno.ENOENT:
250 250 raise
251 251 return records
252 252
253 253 def _readrecordsv2(self):
254 254 """read on disk merge state for version 2 file
255 255
256 256 This format is a list of arbitrary records of the form:
257 257
258 258 [type][length][content]
259 259
260 260 `type` is a single character, `length` is a 4 byte integer, and
261 261 `content` is an arbitrary byte sequence of length `length`.
262 262
263 263 Mercurial versions prior to 3.7 have a bug where if there are
264 264 unsupported mandatory merge records, attempting to clear out the merge
265 265 state with hg update --clean or similar aborts. The 't' record type
266 266 works around that by writing out what those versions treat as an
267 267 advisory record, but later versions interpret as special: the first
268 268 character is the 'real' record type and everything onwards is the data.
269 269
270 270 Returns list of records [(TYPE, data), ...]."""
271 271 records = []
272 272 try:
273 273 f = self._repo.vfs(self.statepathv2)
274 274 data = f.read()
275 275 off = 0
276 276 end = len(data)
277 277 while off < end:
278 278 rtype = data[off]
279 279 off += 1
280 280 length = _unpack('>I', data[off:(off + 4)])[0]
281 281 off += 4
282 282 record = data[off:(off + length)]
283 283 off += length
284 284 if rtype == 't':
285 285 rtype, record = record[0], record[1:]
286 286 records.append((rtype, record))
287 287 f.close()
288 288 except IOError as err:
289 289 if err.errno != errno.ENOENT:
290 290 raise
291 291 return records
292 292
293 293 @util.propertycache
294 294 def mergedriver(self):
295 295 # protect against the following:
296 296 # - A configures a malicious merge driver in their hgrc, then
297 297 # pauses the merge
298 298 # - A edits their hgrc to remove references to the merge driver
299 299 # - A gives a copy of their entire repo, including .hg, to B
300 300 # - B inspects .hgrc and finds it to be clean
301 301 # - B then continues the merge and the malicious merge driver
302 302 # gets invoked
303 303 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
304 304 if (self._readmergedriver is not None
305 305 and self._readmergedriver != configmergedriver):
306 306 raise error.ConfigError(
307 307 _("merge driver changed since merge started"),
308 308 hint=_("revert merge driver change or abort merge"))
309 309
310 310 return configmergedriver
311 311
312 312 @util.propertycache
313 313 def localctx(self):
314 314 if self._local is None:
315 315 raise RuntimeError("localctx accessed but self._local isn't set")
316 316 return self._repo[self._local]
317 317
318 318 @util.propertycache
319 319 def otherctx(self):
320 320 if self._other is None:
321 321 raise RuntimeError("otherctx accessed but self._other isn't set")
322 322 return self._repo[self._other]
323 323
324 324 def active(self):
325 325 """Whether mergestate is active.
326 326
327 327 Returns True if there appears to be mergestate. This is a rough proxy
328 328 for "is a merge in progress."
329 329 """
330 330 # Check local variables before looking at filesystem for performance
331 331 # reasons.
332 332 return bool(self._local) or bool(self._state) or \
333 333 self._repo.vfs.exists(self.statepathv1) or \
334 334 self._repo.vfs.exists(self.statepathv2)
335 335
336 336 def commit(self):
337 337 """Write current state on disk (if necessary)"""
338 338 if self._dirty:
339 339 records = self._makerecords()
340 340 self._writerecords(records)
341 341 self._dirty = False
342 342
343 343 def _makerecords(self):
344 344 records = []
345 345 records.append(('L', hex(self._local)))
346 346 records.append(('O', hex(self._other)))
347 347 if self.mergedriver:
348 348 records.append(('m', '\0'.join([
349 349 self.mergedriver, self._mdstate])))
350 350 for d, v in self._state.iteritems():
351 351 if v[0] == 'd':
352 352 records.append(('D', '\0'.join([d] + v)))
353 353 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
354 354 # older versions of Mercurial
355 355 elif v[1] == nullhex or v[6] == nullhex:
356 356 records.append(('C', '\0'.join([d] + v)))
357 357 else:
358 358 records.append(('F', '\0'.join([d] + v)))
359 359 for filename, extras in sorted(self._stateextras.iteritems()):
360 360 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
361 361 extras.iteritems())
362 362 records.append(('f', '%s\0%s' % (filename, rawextras)))
363 363 if self._labels is not None:
364 364 labels = '\0'.join(self._labels)
365 365 records.append(('l', labels))
366 366 return records
367 367
368 368 def _writerecords(self, records):
369 369 """Write current state on disk (both v1 and v2)"""
370 370 self._writerecordsv1(records)
371 371 self._writerecordsv2(records)
372 372
373 373 def _writerecordsv1(self, records):
374 374 """Write current state on disk in a version 1 file"""
375 375 f = self._repo.vfs(self.statepathv1, 'w')
376 376 irecords = iter(records)
377 377 lrecords = next(irecords)
378 378 assert lrecords[0] == 'L'
379 379 f.write(hex(self._local) + '\n')
380 380 for rtype, data in irecords:
381 381 if rtype == 'F':
382 382 f.write('%s\n' % _droponode(data))
383 383 f.close()
384 384
385 385 def _writerecordsv2(self, records):
386 386 """Write current state on disk in a version 2 file
387 387
388 388 See the docstring for _readrecordsv2 for why we use 't'."""
389 389 # these are the records that all version 2 clients can read
390 390 whitelist = 'LOF'
391 391 f = self._repo.vfs(self.statepathv2, 'w')
392 392 for key, data in records:
393 393 assert len(key) == 1
394 394 if key not in whitelist:
395 395 key, data = 't', '%s%s' % (key, data)
396 396 format = '>sI%is' % len(data)
397 397 f.write(_pack(format, key, len(data), data))
398 398 f.close()
399 399
400 400 def add(self, fcl, fco, fca, fd):
401 401 """add a new (potentially?) conflicting file the merge state
402 402 fcl: file context for local,
403 403 fco: file context for remote,
404 404 fca: file context for ancestors,
405 405 fd: file path of the resulting merge.
406 406
407 407 note: also write the local version to the `.hg/merge` directory.
408 408 """
409 409 if fcl.isabsent():
410 410 hash = nullhex
411 411 else:
412 412 hash = hashlib.sha1(fcl.path()).hexdigest()
413 413 self._repo.vfs.write('merge/' + hash, fcl.data())
414 414 self._state[fd] = ['u', hash, fcl.path(),
415 415 fca.path(), hex(fca.filenode()),
416 416 fco.path(), hex(fco.filenode()),
417 417 fcl.flags()]
418 418 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
419 419 self._dirty = True
420 420
421 421 def __contains__(self, dfile):
422 422 return dfile in self._state
423 423
424 424 def __getitem__(self, dfile):
425 425 return self._state[dfile][0]
426 426
427 427 def __iter__(self):
428 428 return iter(sorted(self._state))
429 429
430 430 def files(self):
431 431 return self._state.keys()
432 432
433 433 def mark(self, dfile, state):
434 434 self._state[dfile][0] = state
435 435 self._dirty = True
436 436
437 437 def mdstate(self):
438 438 return self._mdstate
439 439
440 440 def unresolved(self):
441 441 """Obtain the paths of unresolved files."""
442 442
443 443 for f, entry in self._state.items():
444 444 if entry[0] == 'u':
445 445 yield f
446 446
447 447 def driverresolved(self):
448 448 """Obtain the paths of driver-resolved files."""
449 449
450 450 for f, entry in self._state.items():
451 451 if entry[0] == 'd':
452 452 yield f
453 453
454 454 def extras(self, filename):
455 455 return self._stateextras.setdefault(filename, {})
456 456
457 457 def _resolve(self, preresolve, dfile, wctx):
458 458 """rerun merge process for file path `dfile`"""
459 459 if self[dfile] in 'rd':
460 460 return True, 0
461 461 stateentry = self._state[dfile]
462 462 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
463 463 octx = self._repo[self._other]
464 464 extras = self.extras(dfile)
465 465 anccommitnode = extras.get('ancestorlinknode')
466 466 if anccommitnode:
467 467 actx = self._repo[anccommitnode]
468 468 else:
469 469 actx = None
470 470 fcd = self._filectxorabsent(hash, wctx, dfile)
471 471 fco = self._filectxorabsent(onode, octx, ofile)
472 472 # TODO: move this to filectxorabsent
473 473 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
474 474 # "premerge" x flags
475 475 flo = fco.flags()
476 476 fla = fca.flags()
477 477 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
478 478 if fca.node() == nullid and flags != flo:
479 479 if preresolve:
480 480 self._repo.ui.warn(
481 _('warning: cannot merge flags for %s\n') % afile)
481 _('warning: cannot merge flags for %s '
482 'without common ancestor - keeping local flags\n')
483 % afile)
482 484 elif flags == fla:
483 485 flags = flo
484 486 if preresolve:
485 487 # restore local
486 488 if hash != nullhex:
487 489 f = self._repo.vfs('merge/' + hash)
488 490 self._repo.wwrite(dfile, f.read(), flags)
489 491 f.close()
490 492 else:
491 493 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
492 494 complete, r, deleted = filemerge.premerge(self._repo, self._local,
493 495 lfile, fcd, fco, fca,
494 496 labels=self._labels)
495 497 else:
496 498 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
497 499 lfile, fcd, fco, fca,
498 500 labels=self._labels)
499 501 if r is None:
500 502 # no real conflict
501 503 del self._state[dfile]
502 504 self._stateextras.pop(dfile, None)
503 505 self._dirty = True
504 506 elif not r:
505 507 self.mark(dfile, 'r')
506 508
507 509 if complete:
508 510 action = None
509 511 if deleted:
510 512 if fcd.isabsent():
511 513 # dc: local picked. Need to drop if present, which may
512 514 # happen on re-resolves.
513 515 action = 'f'
514 516 else:
515 517 # cd: remote picked (or otherwise deleted)
516 518 action = 'r'
517 519 else:
518 520 if fcd.isabsent(): # dc: remote picked
519 521 action = 'g'
520 522 elif fco.isabsent(): # cd: local picked
521 523 if dfile in self.localctx:
522 524 action = 'am'
523 525 else:
524 526 action = 'a'
525 527 # else: regular merges (no action necessary)
526 528 self._results[dfile] = r, action
527 529
528 530 return complete, r
529 531
530 532 def _filectxorabsent(self, hexnode, ctx, f):
531 533 if hexnode == nullhex:
532 534 return filemerge.absentfilectx(ctx, f)
533 535 else:
534 536 return ctx[f]
535 537
536 538 def preresolve(self, dfile, wctx):
537 539 """run premerge process for dfile
538 540
539 541 Returns whether the merge is complete, and the exit code."""
540 542 return self._resolve(True, dfile, wctx)
541 543
542 544 def resolve(self, dfile, wctx):
543 545 """run merge process (assuming premerge was run) for dfile
544 546
545 547 Returns the exit code of the merge."""
546 548 return self._resolve(False, dfile, wctx)[1]
547 549
548 550 def counts(self):
549 551 """return counts for updated, merged and removed files in this
550 552 session"""
551 553 updated, merged, removed = 0, 0, 0
552 554 for r, action in self._results.itervalues():
553 555 if r is None:
554 556 updated += 1
555 557 elif r == 0:
556 558 if action == 'r':
557 559 removed += 1
558 560 else:
559 561 merged += 1
560 562 return updated, merged, removed
561 563
562 564 def unresolvedcount(self):
563 565 """get unresolved count for this merge (persistent)"""
564 566 return len([True for f, entry in self._state.iteritems()
565 567 if entry[0] == 'u'])
566 568
567 569 def actions(self):
568 570 """return lists of actions to perform on the dirstate"""
569 571 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
570 572 for f, (r, action) in self._results.iteritems():
571 573 if action is not None:
572 574 actions[action].append((f, None, "merge result"))
573 575 return actions
574 576
575 577 def recordactions(self):
576 578 """record remove/add/get actions in the dirstate"""
577 579 branchmerge = self._repo.dirstate.p2() != nullid
578 580 recordupdates(self._repo, self.actions(), branchmerge)
579 581
580 582 def queueremove(self, f):
581 583 """queues a file to be removed from the dirstate
582 584
583 585 Meant for use by custom merge drivers."""
584 586 self._results[f] = 0, 'r'
585 587
586 588 def queueadd(self, f):
587 589 """queues a file to be added to the dirstate
588 590
589 591 Meant for use by custom merge drivers."""
590 592 self._results[f] = 0, 'a'
591 593
592 594 def queueget(self, f):
593 595 """queues a file to be marked modified in the dirstate
594 596
595 597 Meant for use by custom merge drivers."""
596 598 self._results[f] = 0, 'g'
597 599
598 600 def _getcheckunknownconfig(repo, section, name):
599 601 config = repo.ui.config(section, name, default='abort')
600 602 valid = ['abort', 'ignore', 'warn']
601 603 if config not in valid:
602 604 validstr = ', '.join(["'" + v + "'" for v in valid])
603 605 raise error.ConfigError(_("%s.%s not valid "
604 606 "('%s' is none of %s)")
605 607 % (section, name, config, validstr))
606 608 return config
607 609
608 610 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
609 611 if f2 is None:
610 612 f2 = f
611 613 return (repo.wvfs.audit.check(f)
612 614 and repo.wvfs.isfileorlink(f)
613 615 and repo.dirstate.normalize(f) not in repo.dirstate
614 616 and mctx[f2].cmp(wctx[f]))
615 617
616 618 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
617 619 """
618 620 Considers any actions that care about the presence of conflicting unknown
619 621 files. For some actions, the result is to abort; for others, it is to
620 622 choose a different action.
621 623 """
622 624 conflicts = set()
623 625 warnconflicts = set()
624 626 abortconflicts = set()
625 627 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
626 628 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
627 629 if not force:
628 630 def collectconflicts(conflicts, config):
629 631 if config == 'abort':
630 632 abortconflicts.update(conflicts)
631 633 elif config == 'warn':
632 634 warnconflicts.update(conflicts)
633 635
634 636 for f, (m, args, msg) in actions.iteritems():
635 637 if m in ('c', 'dc'):
636 638 if _checkunknownfile(repo, wctx, mctx, f):
637 639 conflicts.add(f)
638 640 elif m == 'dg':
639 641 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
640 642 conflicts.add(f)
641 643
642 644 ignoredconflicts = set([c for c in conflicts
643 645 if repo.dirstate._ignore(c)])
644 646 unknownconflicts = conflicts - ignoredconflicts
645 647 collectconflicts(ignoredconflicts, ignoredconfig)
646 648 collectconflicts(unknownconflicts, unknownconfig)
647 649 else:
648 650 for f, (m, args, msg) in actions.iteritems():
649 651 if m == 'cm':
650 652 fl2, anc = args
651 653 different = _checkunknownfile(repo, wctx, mctx, f)
652 654 if repo.dirstate._ignore(f):
653 655 config = ignoredconfig
654 656 else:
655 657 config = unknownconfig
656 658
657 659 # The behavior when force is True is described by this table:
658 660 # config different mergeforce | action backup
659 661 # * n * | get n
660 662 # * y y | merge -
661 663 # abort y n | merge - (1)
662 664 # warn y n | warn + get y
663 665 # ignore y n | get y
664 666 #
665 667 # (1) this is probably the wrong behavior here -- we should
666 668 # probably abort, but some actions like rebases currently
667 669 # don't like an abort happening in the middle of
668 670 # merge.update.
669 671 if not different:
670 672 actions[f] = ('g', (fl2, False), "remote created")
671 673 elif mergeforce or config == 'abort':
672 674 actions[f] = ('m', (f, f, None, False, anc),
673 675 "remote differs from untracked local")
674 676 elif config == 'abort':
675 677 abortconflicts.add(f)
676 678 else:
677 679 if config == 'warn':
678 680 warnconflicts.add(f)
679 681 actions[f] = ('g', (fl2, True), "remote created")
680 682
681 683 for f in sorted(abortconflicts):
682 684 repo.ui.warn(_("%s: untracked file differs\n") % f)
683 685 if abortconflicts:
684 686 raise error.Abort(_("untracked files in working directory "
685 687 "differ from files in requested revision"))
686 688
687 689 for f in sorted(warnconflicts):
688 690 repo.ui.warn(_("%s: replacing untracked file\n") % f)
689 691
690 692 for f, (m, args, msg) in actions.iteritems():
691 693 backup = f in conflicts
692 694 if m == 'c':
693 695 flags, = args
694 696 actions[f] = ('g', (flags, backup), msg)
695 697
696 698 def _forgetremoved(wctx, mctx, branchmerge):
697 699 """
698 700 Forget removed files
699 701
700 702 If we're jumping between revisions (as opposed to merging), and if
701 703 neither the working directory nor the target rev has the file,
702 704 then we need to remove it from the dirstate, to prevent the
703 705 dirstate from listing the file when it is no longer in the
704 706 manifest.
705 707
706 708 If we're merging, and the other revision has removed a file
707 709 that is not present in the working directory, we need to mark it
708 710 as removed.
709 711 """
710 712
711 713 actions = {}
712 714 m = 'f'
713 715 if branchmerge:
714 716 m = 'r'
715 717 for f in wctx.deleted():
716 718 if f not in mctx:
717 719 actions[f] = m, None, "forget deleted"
718 720
719 721 if not branchmerge:
720 722 for f in wctx.removed():
721 723 if f not in mctx:
722 724 actions[f] = 'f', None, "forget removed"
723 725
724 726 return actions
725 727
726 728 def _checkcollision(repo, wmf, actions):
727 729 # build provisional merged manifest up
728 730 pmmf = set(wmf)
729 731
730 732 if actions:
731 733 # k, dr, e and rd are no-op
732 734 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
733 735 for f, args, msg in actions[m]:
734 736 pmmf.add(f)
735 737 for f, args, msg in actions['r']:
736 738 pmmf.discard(f)
737 739 for f, args, msg in actions['dm']:
738 740 f2, flags = args
739 741 pmmf.discard(f2)
740 742 pmmf.add(f)
741 743 for f, args, msg in actions['dg']:
742 744 pmmf.add(f)
743 745 for f, args, msg in actions['m']:
744 746 f1, f2, fa, move, anc = args
745 747 if move:
746 748 pmmf.discard(f1)
747 749 pmmf.add(f)
748 750
749 751 # check case-folding collision in provisional merged manifest
750 752 foldmap = {}
751 753 for f in sorted(pmmf):
752 754 fold = util.normcase(f)
753 755 if fold in foldmap:
754 756 raise error.Abort(_("case-folding collision between %s and %s")
755 757 % (f, foldmap[fold]))
756 758 foldmap[fold] = f
757 759
758 760 # check case-folding of directories
759 761 foldprefix = unfoldprefix = lastfull = ''
760 762 for fold, f in sorted(foldmap.items()):
761 763 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
762 764 # the folded prefix matches but actual casing is different
763 765 raise error.Abort(_("case-folding collision between "
764 766 "%s and directory of %s") % (lastfull, f))
765 767 foldprefix = fold + '/'
766 768 unfoldprefix = f + '/'
767 769 lastfull = f
768 770
769 771 def driverpreprocess(repo, ms, wctx, labels=None):
770 772 """run the preprocess step of the merge driver, if any
771 773
772 774 This is currently not implemented -- it's an extension point."""
773 775 return True
774 776
775 777 def driverconclude(repo, ms, wctx, labels=None):
776 778 """run the conclude step of the merge driver, if any
777 779
778 780 This is currently not implemented -- it's an extension point."""
779 781 return True
780 782
781 783 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
782 784 acceptremote, followcopies):
783 785 """
784 786 Merge wctx and p2 with ancestor pa and generate merge action list
785 787
786 788 branchmerge and force are as passed in to update
787 789 matcher = matcher to filter file lists
788 790 acceptremote = accept the incoming changes without prompting
789 791 """
790 792 if matcher is not None and matcher.always():
791 793 matcher = None
792 794
793 795 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
794 796
795 797 # manifests fetched in order are going to be faster, so prime the caches
796 798 [x.manifest() for x in
797 799 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
798 800
799 801 if followcopies:
800 802 ret = copies.mergecopies(repo, wctx, p2, pa)
801 803 copy, movewithdir, diverge, renamedelete = ret
802 804
803 805 repo.ui.note(_("resolving manifests\n"))
804 806 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
805 807 % (bool(branchmerge), bool(force), bool(matcher)))
806 808 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
807 809
808 810 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
809 811 copied = set(copy.values())
810 812 copied.update(movewithdir.values())
811 813
812 814 if '.hgsubstate' in m1:
813 815 # check whether sub state is modified
814 816 if any(wctx.sub(s).dirty() for s in wctx.substate):
815 817 m1['.hgsubstate'] += '+'
816 818
817 819 # Compare manifests
818 820 if matcher is not None:
819 821 m1 = m1.matches(matcher)
820 822 m2 = m2.matches(matcher)
821 823 diff = m1.diff(m2)
822 824
823 825 actions = {}
824 826 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
825 827 if n1 and n2: # file exists on both local and remote side
826 828 if f not in ma:
827 829 fa = copy.get(f, None)
828 830 if fa is not None:
829 831 actions[f] = ('m', (f, f, fa, False, pa.node()),
830 832 "both renamed from " + fa)
831 833 else:
832 834 actions[f] = ('m', (f, f, None, False, pa.node()),
833 835 "both created")
834 836 else:
835 837 a = ma[f]
836 838 fla = ma.flags(f)
837 839 nol = 'l' not in fl1 + fl2 + fla
838 840 if n2 == a and fl2 == fla:
839 841 actions[f] = ('k' , (), "remote unchanged")
840 842 elif n1 == a and fl1 == fla: # local unchanged - use remote
841 843 if n1 == n2: # optimization: keep local content
842 844 actions[f] = ('e', (fl2,), "update permissions")
843 845 else:
844 846 actions[f] = ('g', (fl2, False), "remote is newer")
845 847 elif nol and n2 == a: # remote only changed 'x'
846 848 actions[f] = ('e', (fl2,), "update permissions")
847 849 elif nol and n1 == a: # local only changed 'x'
848 850 actions[f] = ('g', (fl1, False), "remote is newer")
849 851 else: # both changed something
850 852 actions[f] = ('m', (f, f, f, False, pa.node()),
851 853 "versions differ")
852 854 elif n1: # file exists only on local side
853 855 if f in copied:
854 856 pass # we'll deal with it on m2 side
855 857 elif f in movewithdir: # directory rename, move local
856 858 f2 = movewithdir[f]
857 859 if f2 in m2:
858 860 actions[f2] = ('m', (f, f2, None, True, pa.node()),
859 861 "remote directory rename, both created")
860 862 else:
861 863 actions[f2] = ('dm', (f, fl1),
862 864 "remote directory rename - move from " + f)
863 865 elif f in copy:
864 866 f2 = copy[f]
865 867 actions[f] = ('m', (f, f2, f2, False, pa.node()),
866 868 "local copied/moved from " + f2)
867 869 elif f in ma: # clean, a different, no remote
868 870 if n1 != ma[f]:
869 871 if acceptremote:
870 872 actions[f] = ('r', None, "remote delete")
871 873 else:
872 874 actions[f] = ('cd', (f, None, f, False, pa.node()),
873 875 "prompt changed/deleted")
874 876 elif n1[20:] == 'a':
875 877 # This extra 'a' is added by working copy manifest to mark
876 878 # the file as locally added. We should forget it instead of
877 879 # deleting it.
878 880 actions[f] = ('f', None, "remote deleted")
879 881 else:
880 882 actions[f] = ('r', None, "other deleted")
881 883 elif n2: # file exists only on remote side
882 884 if f in copied:
883 885 pass # we'll deal with it on m1 side
884 886 elif f in movewithdir:
885 887 f2 = movewithdir[f]
886 888 if f2 in m1:
887 889 actions[f2] = ('m', (f2, f, None, False, pa.node()),
888 890 "local directory rename, both created")
889 891 else:
890 892 actions[f2] = ('dg', (f, fl2),
891 893 "local directory rename - get from " + f)
892 894 elif f in copy:
893 895 f2 = copy[f]
894 896 if f2 in m2:
895 897 actions[f] = ('m', (f2, f, f2, False, pa.node()),
896 898 "remote copied from " + f2)
897 899 else:
898 900 actions[f] = ('m', (f2, f, f2, True, pa.node()),
899 901 "remote moved from " + f2)
900 902 elif f not in ma:
901 903 # local unknown, remote created: the logic is described by the
902 904 # following table:
903 905 #
904 906 # force branchmerge different | action
905 907 # n * * | create
906 908 # y n * | create
907 909 # y y n | create
908 910 # y y y | merge
909 911 #
910 912 # Checking whether the files are different is expensive, so we
911 913 # don't do that when we can avoid it.
912 914 if not force:
913 915 actions[f] = ('c', (fl2,), "remote created")
914 916 elif not branchmerge:
915 917 actions[f] = ('c', (fl2,), "remote created")
916 918 else:
917 919 actions[f] = ('cm', (fl2, pa.node()),
918 920 "remote created, get or merge")
919 921 elif n2 != ma[f]:
920 922 if acceptremote:
921 923 actions[f] = ('c', (fl2,), "remote recreating")
922 924 else:
923 925 actions[f] = ('dc', (None, f, f, False, pa.node()),
924 926 "prompt deleted/changed")
925 927
926 928 return actions, diverge, renamedelete
927 929
928 930 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
929 931 """Resolves false conflicts where the nodeid changed but the content
930 932 remained the same."""
931 933
932 934 for f, (m, args, msg) in actions.items():
933 935 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
934 936 # local did change but ended up with same content
935 937 actions[f] = 'r', None, "prompt same"
936 938 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
937 939 # remote did change but ended up with same content
938 940 del actions[f] # don't get = keep local deleted
939 941
940 942 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
941 943 acceptremote, followcopies, matcher=None,
942 944 mergeforce=False):
943 945 "Calculate the actions needed to merge mctx into wctx using ancestors"
944 946 if len(ancestors) == 1: # default
945 947 actions, diverge, renamedelete = manifestmerge(
946 948 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
947 949 acceptremote, followcopies)
948 950 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
949 951
950 952 else: # only when merge.preferancestor=* - the default
951 953 repo.ui.note(
952 954 _("note: merging %s and %s using bids from ancestors %s\n") %
953 955 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
954 956
955 957 # Call for bids
956 958 fbids = {} # mapping filename to bids (action method to list af actions)
957 959 diverge, renamedelete = None, None
958 960 for ancestor in ancestors:
959 961 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
960 962 actions, diverge1, renamedelete1 = manifestmerge(
961 963 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
962 964 acceptremote, followcopies)
963 965 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
964 966
965 967 # Track the shortest set of warning on the theory that bid
966 968 # merge will correctly incorporate more information
967 969 if diverge is None or len(diverge1) < len(diverge):
968 970 diverge = diverge1
969 971 if renamedelete is None or len(renamedelete) < len(renamedelete1):
970 972 renamedelete = renamedelete1
971 973
972 974 for f, a in sorted(actions.iteritems()):
973 975 m, args, msg = a
974 976 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
975 977 if f in fbids:
976 978 d = fbids[f]
977 979 if m in d:
978 980 d[m].append(a)
979 981 else:
980 982 d[m] = [a]
981 983 else:
982 984 fbids[f] = {m: [a]}
983 985
984 986 # Pick the best bid for each file
985 987 repo.ui.note(_('\nauction for merging merge bids\n'))
986 988 actions = {}
987 989 for f, bids in sorted(fbids.items()):
988 990 # bids is a mapping from action method to list af actions
989 991 # Consensus?
990 992 if len(bids) == 1: # all bids are the same kind of method
991 993 m, l = bids.items()[0]
992 994 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
993 995 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
994 996 actions[f] = l[0]
995 997 continue
996 998 # If keep is an option, just do it.
997 999 if 'k' in bids:
998 1000 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
999 1001 actions[f] = bids['k'][0]
1000 1002 continue
1001 1003 # If there are gets and they all agree [how could they not?], do it.
1002 1004 if 'g' in bids:
1003 1005 ga0 = bids['g'][0]
1004 1006 if all(a == ga0 for a in bids['g'][1:]):
1005 1007 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1006 1008 actions[f] = ga0
1007 1009 continue
1008 1010 # TODO: Consider other simple actions such as mode changes
1009 1011 # Handle inefficient democrazy.
1010 1012 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1011 1013 for m, l in sorted(bids.items()):
1012 1014 for _f, args, msg in l:
1013 1015 repo.ui.note(' %s -> %s\n' % (msg, m))
1014 1016 # Pick random action. TODO: Instead, prompt user when resolving
1015 1017 m, l = bids.items()[0]
1016 1018 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1017 1019 (f, m))
1018 1020 actions[f] = l[0]
1019 1021 continue
1020 1022 repo.ui.note(_('end of auction\n\n'))
1021 1023
1022 1024 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1023 1025
1024 1026 if wctx.rev() is None:
1025 1027 fractions = _forgetremoved(wctx, mctx, branchmerge)
1026 1028 actions.update(fractions)
1027 1029
1028 1030 return actions, diverge, renamedelete
1029 1031
1030 1032 def batchremove(repo, actions):
1031 1033 """apply removes to the working directory
1032 1034
1033 1035 yields tuples for progress updates
1034 1036 """
1035 1037 verbose = repo.ui.verbose
1036 1038 unlink = util.unlinkpath
1037 1039 wjoin = repo.wjoin
1038 1040 audit = repo.wvfs.audit
1039 1041 i = 0
1040 1042 for f, args, msg in actions:
1041 1043 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1042 1044 if verbose:
1043 1045 repo.ui.note(_("removing %s\n") % f)
1044 1046 audit(f)
1045 1047 try:
1046 1048 unlink(wjoin(f), ignoremissing=True)
1047 1049 except OSError as inst:
1048 1050 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1049 1051 (f, inst.strerror))
1050 1052 if i == 100:
1051 1053 yield i, f
1052 1054 i = 0
1053 1055 i += 1
1054 1056 if i > 0:
1055 1057 yield i, f
1056 1058
1057 1059 def batchget(repo, mctx, actions):
1058 1060 """apply gets to the working directory
1059 1061
1060 1062 mctx is the context to get from
1061 1063
1062 1064 yields tuples for progress updates
1063 1065 """
1064 1066 verbose = repo.ui.verbose
1065 1067 fctx = mctx.filectx
1066 1068 wwrite = repo.wwrite
1067 1069 ui = repo.ui
1068 1070 i = 0
1069 1071 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1070 1072 for f, (flags, backup), msg in actions:
1071 1073 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1072 1074 if verbose:
1073 1075 repo.ui.note(_("getting %s\n") % f)
1074 1076
1075 1077 if backup:
1076 1078 absf = repo.wjoin(f)
1077 1079 orig = scmutil.origpath(ui, repo, absf)
1078 1080 try:
1079 1081 if repo.wvfs.isfileorlink(f):
1080 1082 util.rename(absf, orig)
1081 1083 except OSError as e:
1082 1084 if e.errno != errno.ENOENT:
1083 1085 raise
1084 1086
1085 1087 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1086 1088 repo.wvfs.removedirs(f)
1087 1089 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1088 1090 if i == 100:
1089 1091 yield i, f
1090 1092 i = 0
1091 1093 i += 1
1092 1094 if i > 0:
1093 1095 yield i, f
1094 1096
1095 1097 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1096 1098 """apply the merge action list to the working directory
1097 1099
1098 1100 wctx is the working copy context
1099 1101 mctx is the context to be merged into the working copy
1100 1102
1101 1103 Return a tuple of counts (updated, merged, removed, unresolved) that
1102 1104 describes how many files were affected by the update.
1103 1105 """
1104 1106
1105 1107 updated, merged, removed = 0, 0, 0
1106 1108 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1107 1109 moves = []
1108 1110 for m, l in actions.items():
1109 1111 l.sort()
1110 1112
1111 1113 # 'cd' and 'dc' actions are treated like other merge conflicts
1112 1114 mergeactions = sorted(actions['cd'])
1113 1115 mergeactions.extend(sorted(actions['dc']))
1114 1116 mergeactions.extend(actions['m'])
1115 1117 for f, args, msg in mergeactions:
1116 1118 f1, f2, fa, move, anc = args
1117 1119 if f == '.hgsubstate': # merged internally
1118 1120 continue
1119 1121 if f1 is None:
1120 1122 fcl = filemerge.absentfilectx(wctx, fa)
1121 1123 else:
1122 1124 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1123 1125 fcl = wctx[f1]
1124 1126 if f2 is None:
1125 1127 fco = filemerge.absentfilectx(mctx, fa)
1126 1128 else:
1127 1129 fco = mctx[f2]
1128 1130 actx = repo[anc]
1129 1131 if fa in actx:
1130 1132 fca = actx[fa]
1131 1133 else:
1132 1134 # TODO: move to absentfilectx
1133 1135 fca = repo.filectx(f1, fileid=nullrev)
1134 1136 ms.add(fcl, fco, fca, f)
1135 1137 if f1 != f and move:
1136 1138 moves.append(f1)
1137 1139
1138 1140 audit = repo.wvfs.audit
1139 1141 _updating = _('updating')
1140 1142 _files = _('files')
1141 1143 progress = repo.ui.progress
1142 1144
1143 1145 # remove renamed files after safely stored
1144 1146 for f in moves:
1145 1147 if os.path.lexists(repo.wjoin(f)):
1146 1148 repo.ui.debug("removing %s\n" % f)
1147 1149 audit(f)
1148 1150 util.unlinkpath(repo.wjoin(f))
1149 1151
1150 1152 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1151 1153
1152 1154 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1153 1155 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1154 1156
1155 1157 # remove in parallel (must come first)
1156 1158 z = 0
1157 1159 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1158 1160 for i, item in prog:
1159 1161 z += i
1160 1162 progress(_updating, z, item=item, total=numupdates, unit=_files)
1161 1163 removed = len(actions['r'])
1162 1164
1163 1165 # get in parallel
1164 1166 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1165 1167 for i, item in prog:
1166 1168 z += i
1167 1169 progress(_updating, z, item=item, total=numupdates, unit=_files)
1168 1170 updated = len(actions['g'])
1169 1171
1170 1172 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1171 1173 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1172 1174
1173 1175 # forget (manifest only, just log it) (must come first)
1174 1176 for f, args, msg in actions['f']:
1175 1177 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1176 1178 z += 1
1177 1179 progress(_updating, z, item=f, total=numupdates, unit=_files)
1178 1180
1179 1181 # re-add (manifest only, just log it)
1180 1182 for f, args, msg in actions['a']:
1181 1183 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1182 1184 z += 1
1183 1185 progress(_updating, z, item=f, total=numupdates, unit=_files)
1184 1186
1185 1187 # re-add/mark as modified (manifest only, just log it)
1186 1188 for f, args, msg in actions['am']:
1187 1189 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1188 1190 z += 1
1189 1191 progress(_updating, z, item=f, total=numupdates, unit=_files)
1190 1192
1191 1193 # keep (noop, just log it)
1192 1194 for f, args, msg in actions['k']:
1193 1195 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1194 1196 # no progress
1195 1197
1196 1198 # directory rename, move local
1197 1199 for f, args, msg in actions['dm']:
1198 1200 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1199 1201 z += 1
1200 1202 progress(_updating, z, item=f, total=numupdates, unit=_files)
1201 1203 f0, flags = args
1202 1204 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1203 1205 audit(f)
1204 1206 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1205 1207 util.unlinkpath(repo.wjoin(f0))
1206 1208 updated += 1
1207 1209
1208 1210 # local directory rename, get
1209 1211 for f, args, msg in actions['dg']:
1210 1212 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1211 1213 z += 1
1212 1214 progress(_updating, z, item=f, total=numupdates, unit=_files)
1213 1215 f0, flags = args
1214 1216 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1215 1217 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1216 1218 updated += 1
1217 1219
1218 1220 # exec
1219 1221 for f, args, msg in actions['e']:
1220 1222 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1221 1223 z += 1
1222 1224 progress(_updating, z, item=f, total=numupdates, unit=_files)
1223 1225 flags, = args
1224 1226 audit(f)
1225 1227 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
1226 1228 updated += 1
1227 1229
1228 1230 # the ordering is important here -- ms.mergedriver will raise if the merge
1229 1231 # driver has changed, and we want to be able to bypass it when overwrite is
1230 1232 # True
1231 1233 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1232 1234
1233 1235 if usemergedriver:
1234 1236 ms.commit()
1235 1237 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1236 1238 # the driver might leave some files unresolved
1237 1239 unresolvedf = set(ms.unresolved())
1238 1240 if not proceed:
1239 1241 # XXX setting unresolved to at least 1 is a hack to make sure we
1240 1242 # error out
1241 1243 return updated, merged, removed, max(len(unresolvedf), 1)
1242 1244 newactions = []
1243 1245 for f, args, msg in mergeactions:
1244 1246 if f in unresolvedf:
1245 1247 newactions.append((f, args, msg))
1246 1248 mergeactions = newactions
1247 1249
1248 1250 # premerge
1249 1251 tocomplete = []
1250 1252 for f, args, msg in mergeactions:
1251 1253 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1252 1254 z += 1
1253 1255 progress(_updating, z, item=f, total=numupdates, unit=_files)
1254 1256 if f == '.hgsubstate': # subrepo states need updating
1255 1257 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1256 1258 overwrite, labels)
1257 1259 continue
1258 1260 audit(f)
1259 1261 complete, r = ms.preresolve(f, wctx)
1260 1262 if not complete:
1261 1263 numupdates += 1
1262 1264 tocomplete.append((f, args, msg))
1263 1265
1264 1266 # merge
1265 1267 for f, args, msg in tocomplete:
1266 1268 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1267 1269 z += 1
1268 1270 progress(_updating, z, item=f, total=numupdates, unit=_files)
1269 1271 ms.resolve(f, wctx)
1270 1272
1271 1273 ms.commit()
1272 1274
1273 1275 unresolved = ms.unresolvedcount()
1274 1276
1275 1277 if usemergedriver and not unresolved and ms.mdstate() != 's':
1276 1278 if not driverconclude(repo, ms, wctx, labels=labels):
1277 1279 # XXX setting unresolved to at least 1 is a hack to make sure we
1278 1280 # error out
1279 1281 unresolved = max(unresolved, 1)
1280 1282
1281 1283 ms.commit()
1282 1284
1283 1285 msupdated, msmerged, msremoved = ms.counts()
1284 1286 updated += msupdated
1285 1287 merged += msmerged
1286 1288 removed += msremoved
1287 1289
1288 1290 extraactions = ms.actions()
1289 1291 if extraactions:
1290 1292 mfiles = set(a[0] for a in actions['m'])
1291 1293 for k, acts in extraactions.iteritems():
1292 1294 actions[k].extend(acts)
1293 1295 # Remove these files from actions['m'] as well. This is important
1294 1296 # because in recordupdates, files in actions['m'] are processed
1295 1297 # after files in other actions, and the merge driver might add
1296 1298 # files to those actions via extraactions above. This can lead to a
1297 1299 # file being recorded twice, with poor results. This is especially
1298 1300 # problematic for actions['r'] (currently only possible with the
1299 1301 # merge driver in the initial merge process; interrupted merges
1300 1302 # don't go through this flow).
1301 1303 #
1302 1304 # The real fix here is to have indexes by both file and action so
1303 1305 # that when the action for a file is changed it is automatically
1304 1306 # reflected in the other action lists. But that involves a more
1305 1307 # complex data structure, so this will do for now.
1306 1308 #
1307 1309 # We don't need to do the same operation for 'dc' and 'cd' because
1308 1310 # those lists aren't consulted again.
1309 1311 mfiles.difference_update(a[0] for a in acts)
1310 1312
1311 1313 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1312 1314
1313 1315 progress(_updating, None, total=numupdates, unit=_files)
1314 1316
1315 1317 return updated, merged, removed, unresolved
1316 1318
1317 1319 def recordupdates(repo, actions, branchmerge):
1318 1320 "record merge actions to the dirstate"
1319 1321 # remove (must come first)
1320 1322 for f, args, msg in actions.get('r', []):
1321 1323 if branchmerge:
1322 1324 repo.dirstate.remove(f)
1323 1325 else:
1324 1326 repo.dirstate.drop(f)
1325 1327
1326 1328 # forget (must come first)
1327 1329 for f, args, msg in actions.get('f', []):
1328 1330 repo.dirstate.drop(f)
1329 1331
1330 1332 # re-add
1331 1333 for f, args, msg in actions.get('a', []):
1332 1334 repo.dirstate.add(f)
1333 1335
1334 1336 # re-add/mark as modified
1335 1337 for f, args, msg in actions.get('am', []):
1336 1338 if branchmerge:
1337 1339 repo.dirstate.normallookup(f)
1338 1340 else:
1339 1341 repo.dirstate.add(f)
1340 1342
1341 1343 # exec change
1342 1344 for f, args, msg in actions.get('e', []):
1343 1345 repo.dirstate.normallookup(f)
1344 1346
1345 1347 # keep
1346 1348 for f, args, msg in actions.get('k', []):
1347 1349 pass
1348 1350
1349 1351 # get
1350 1352 for f, args, msg in actions.get('g', []):
1351 1353 if branchmerge:
1352 1354 repo.dirstate.otherparent(f)
1353 1355 else:
1354 1356 repo.dirstate.normal(f)
1355 1357
1356 1358 # merge
1357 1359 for f, args, msg in actions.get('m', []):
1358 1360 f1, f2, fa, move, anc = args
1359 1361 if branchmerge:
1360 1362 # We've done a branch merge, mark this file as merged
1361 1363 # so that we properly record the merger later
1362 1364 repo.dirstate.merge(f)
1363 1365 if f1 != f2: # copy/rename
1364 1366 if move:
1365 1367 repo.dirstate.remove(f1)
1366 1368 if f1 != f:
1367 1369 repo.dirstate.copy(f1, f)
1368 1370 else:
1369 1371 repo.dirstate.copy(f2, f)
1370 1372 else:
1371 1373 # We've update-merged a locally modified file, so
1372 1374 # we set the dirstate to emulate a normal checkout
1373 1375 # of that file some time in the past. Thus our
1374 1376 # merge will appear as a normal local file
1375 1377 # modification.
1376 1378 if f2 == f: # file not locally copied/moved
1377 1379 repo.dirstate.normallookup(f)
1378 1380 if move:
1379 1381 repo.dirstate.drop(f1)
1380 1382
1381 1383 # directory rename, move local
1382 1384 for f, args, msg in actions.get('dm', []):
1383 1385 f0, flag = args
1384 1386 if branchmerge:
1385 1387 repo.dirstate.add(f)
1386 1388 repo.dirstate.remove(f0)
1387 1389 repo.dirstate.copy(f0, f)
1388 1390 else:
1389 1391 repo.dirstate.normal(f)
1390 1392 repo.dirstate.drop(f0)
1391 1393
1392 1394 # directory rename, get
1393 1395 for f, args, msg in actions.get('dg', []):
1394 1396 f0, flag = args
1395 1397 if branchmerge:
1396 1398 repo.dirstate.add(f)
1397 1399 repo.dirstate.copy(f0, f)
1398 1400 else:
1399 1401 repo.dirstate.normal(f)
1400 1402
1401 1403 def update(repo, node, branchmerge, force, ancestor=None,
1402 1404 mergeancestor=False, labels=None, matcher=None, mergeforce=False):
1403 1405 """
1404 1406 Perform a merge between the working directory and the given node
1405 1407
1406 1408 node = the node to update to, or None if unspecified
1407 1409 branchmerge = whether to merge between branches
1408 1410 force = whether to force branch merging or file overwriting
1409 1411 matcher = a matcher to filter file lists (dirstate not updated)
1410 1412 mergeancestor = whether it is merging with an ancestor. If true,
1411 1413 we should accept the incoming changes for any prompts that occur.
1412 1414 If false, merging with an ancestor (fast-forward) is only allowed
1413 1415 between different named branches. This flag is used by rebase extension
1414 1416 as a temporary fix and should be avoided in general.
1415 1417 labels = labels to use for base, local and other
1416 1418 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1417 1419 this is True, then 'force' should be True as well.
1418 1420
1419 1421 The table below shows all the behaviors of the update command
1420 1422 given the -c and -C or no options, whether the working directory
1421 1423 is dirty, whether a revision is specified, and the relationship of
1422 1424 the parent rev to the target rev (linear, on the same named
1423 1425 branch, or on another named branch).
1424 1426
1425 1427 This logic is tested by test-update-branches.t.
1426 1428
1427 1429 -c -C dirty rev | linear same cross
1428 1430 n n n n | ok (1) x
1429 1431 n n n y | ok ok ok
1430 1432 n n y n | merge (2) (2)
1431 1433 n n y y | merge (3) (3)
1432 1434 n y * * | discard discard discard
1433 1435 y n y * | (4) (4) (4)
1434 1436 y n n * | ok ok ok
1435 1437 y y * * | (5) (5) (5)
1436 1438
1437 1439 x = can't happen
1438 1440 * = don't-care
1439 1441 1 = abort: not a linear update (merge or update --check to force update)
1440 1442 2 = abort: uncommitted changes (commit and merge, or update --clean to
1441 1443 discard changes)
1442 1444 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1443 1445 4 = abort: uncommitted changes (checked in commands.py)
1444 1446 5 = incompatible options (checked in commands.py)
1445 1447
1446 1448 Return the same tuple as applyupdates().
1447 1449 """
1448 1450
1449 1451 onode = node
1450 1452 # If we're doing a partial update, we need to skip updating
1451 1453 # the dirstate, so make a note of any partial-ness to the
1452 1454 # update here.
1453 1455 if matcher is None or matcher.always():
1454 1456 partial = False
1455 1457 else:
1456 1458 partial = True
1457 1459 with repo.wlock():
1458 1460 wc = repo[None]
1459 1461 pl = wc.parents()
1460 1462 p1 = pl[0]
1461 1463 pas = [None]
1462 1464 if ancestor is not None:
1463 1465 pas = [repo[ancestor]]
1464 1466
1465 1467 if node is None:
1466 1468 repo.ui.deprecwarn('update with no target', '3.9')
1467 1469 rev, _mark, _act = destutil.destupdate(repo)
1468 1470 node = repo[rev].node()
1469 1471
1470 1472 overwrite = force and not branchmerge
1471 1473
1472 1474 p2 = repo[node]
1473 1475 if pas[0] is None:
1474 1476 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1475 1477 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1476 1478 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1477 1479 else:
1478 1480 pas = [p1.ancestor(p2, warn=branchmerge)]
1479 1481
1480 1482 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1481 1483
1482 1484 ### check phase
1483 1485 if not overwrite:
1484 1486 if len(pl) > 1:
1485 1487 raise error.Abort(_("outstanding uncommitted merge"))
1486 1488 ms = mergestate.read(repo)
1487 1489 if list(ms.unresolved()):
1488 1490 raise error.Abort(_("outstanding merge conflicts"))
1489 1491 if branchmerge:
1490 1492 if pas == [p2]:
1491 1493 raise error.Abort(_("merging with a working directory ancestor"
1492 1494 " has no effect"))
1493 1495 elif pas == [p1]:
1494 1496 if not mergeancestor and p1.branch() == p2.branch():
1495 1497 raise error.Abort(_("nothing to merge"),
1496 1498 hint=_("use 'hg update' "
1497 1499 "or check 'hg heads'"))
1498 1500 if not force and (wc.files() or wc.deleted()):
1499 1501 raise error.Abort(_("uncommitted changes"),
1500 1502 hint=_("use 'hg status' to list changes"))
1501 1503 for s in sorted(wc.substate):
1502 1504 wc.sub(s).bailifchanged()
1503 1505
1504 1506 elif not overwrite:
1505 1507 if p1 == p2: # no-op update
1506 1508 # call the hooks and exit early
1507 1509 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1508 1510 repo.hook('update', parent1=xp2, parent2='', error=0)
1509 1511 return 0, 0, 0, 0
1510 1512
1511 1513 if pas not in ([p1], [p2]): # nonlinear
1512 1514 dirty = wc.dirty(missing=True)
1513 1515 if dirty or onode is None:
1514 1516 # Branching is a bit strange to ensure we do the minimal
1515 1517 # amount of call to obsolete.background.
1516 1518 foreground = obsolete.foreground(repo, [p1.node()])
1517 1519 # note: the <node> variable contains a random identifier
1518 1520 if repo[node].node() in foreground:
1519 1521 pas = [p1] # allow updating to successors
1520 1522 elif dirty:
1521 1523 msg = _("uncommitted changes")
1522 1524 if onode is None:
1523 1525 hint = _("commit and merge, or update --clean to"
1524 1526 " discard changes")
1525 1527 else:
1526 1528 hint = _("commit or update --clean to discard"
1527 1529 " changes")
1528 1530 raise error.Abort(msg, hint=hint)
1529 1531 else: # node is none
1530 1532 msg = _("not a linear update")
1531 1533 hint = _("merge or update --check to force update")
1532 1534 raise error.Abort(msg, hint=hint)
1533 1535 else:
1534 1536 # Allow jumping branches if clean and specific rev given
1535 1537 pas = [p1]
1536 1538
1537 1539 # deprecated config: merge.followcopies
1538 1540 followcopies = False
1539 1541 if overwrite:
1540 1542 pas = [wc]
1541 1543 elif pas == [p2]: # backwards
1542 1544 pas = [wc.p1()]
1543 1545 elif not branchmerge and not wc.dirty(missing=True):
1544 1546 pass
1545 1547 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1546 1548 followcopies = True
1547 1549
1548 1550 ### calculate phase
1549 1551 actionbyfile, diverge, renamedelete = calculateupdates(
1550 1552 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1551 1553 followcopies, matcher=matcher, mergeforce=mergeforce)
1552 1554
1553 1555 # Prompt and create actions. Most of this is in the resolve phase
1554 1556 # already, but we can't handle .hgsubstate in filemerge or
1555 1557 # subrepo.submerge yet so we have to keep prompting for it.
1556 1558 if '.hgsubstate' in actionbyfile:
1557 1559 f = '.hgsubstate'
1558 1560 m, args, msg = actionbyfile[f]
1559 1561 prompts = filemerge.partextras(labels)
1560 1562 prompts['f'] = f
1561 1563 if m == 'cd':
1562 1564 if repo.ui.promptchoice(
1563 1565 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1564 1566 "use (c)hanged version or (d)elete?"
1565 1567 "$$ &Changed $$ &Delete") % prompts, 0):
1566 1568 actionbyfile[f] = ('r', None, "prompt delete")
1567 1569 elif f in p1:
1568 1570 actionbyfile[f] = ('am', None, "prompt keep")
1569 1571 else:
1570 1572 actionbyfile[f] = ('a', None, "prompt keep")
1571 1573 elif m == 'dc':
1572 1574 f1, f2, fa, move, anc = args
1573 1575 flags = p2[f2].flags()
1574 1576 if repo.ui.promptchoice(
1575 1577 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1576 1578 "use (c)hanged version or leave (d)eleted?"
1577 1579 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1578 1580 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1579 1581 else:
1580 1582 del actionbyfile[f]
1581 1583
1582 1584 # Convert to dictionary-of-lists format
1583 1585 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1584 1586 for f, (m, args, msg) in actionbyfile.iteritems():
1585 1587 if m not in actions:
1586 1588 actions[m] = []
1587 1589 actions[m].append((f, args, msg))
1588 1590
1589 1591 if not util.fscasesensitive(repo.path):
1590 1592 # check collision between files only in p2 for clean update
1591 1593 if (not branchmerge and
1592 1594 (force or not wc.dirty(missing=True, branch=False))):
1593 1595 _checkcollision(repo, p2.manifest(), None)
1594 1596 else:
1595 1597 _checkcollision(repo, wc.manifest(), actions)
1596 1598
1597 1599 # divergent renames
1598 1600 for f, fl in sorted(diverge.iteritems()):
1599 1601 repo.ui.warn(_("note: possible conflict - %s was renamed "
1600 1602 "multiple times to:\n") % f)
1601 1603 for nf in fl:
1602 1604 repo.ui.warn(" %s\n" % nf)
1603 1605
1604 1606 # rename and delete
1605 1607 for f, fl in sorted(renamedelete.iteritems()):
1606 1608 repo.ui.warn(_("note: possible conflict - %s was deleted "
1607 1609 "and renamed to:\n") % f)
1608 1610 for nf in fl:
1609 1611 repo.ui.warn(" %s\n" % nf)
1610 1612
1611 1613 ### apply phase
1612 1614 if not branchmerge: # just jump to the new rev
1613 1615 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1614 1616 if not partial:
1615 1617 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1616 1618 # note that we're in the middle of an update
1617 1619 repo.vfs.write('updatestate', p2.hex())
1618 1620
1619 1621 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1620 1622
1621 1623 if not partial:
1622 1624 repo.dirstate.beginparentchange()
1623 1625 repo.setparents(fp1, fp2)
1624 1626 recordupdates(repo, actions, branchmerge)
1625 1627 # update completed, clear state
1626 1628 util.unlink(repo.join('updatestate'))
1627 1629
1628 1630 if not branchmerge:
1629 1631 repo.dirstate.setbranch(p2.branch())
1630 1632 repo.dirstate.endparentchange()
1631 1633
1632 1634 if not partial:
1633 1635 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1634 1636 return stats
1635 1637
1636 1638 def graft(repo, ctx, pctx, labels, keepparent=False):
1637 1639 """Do a graft-like merge.
1638 1640
1639 1641 This is a merge where the merge ancestor is chosen such that one
1640 1642 or more changesets are grafted onto the current changeset. In
1641 1643 addition to the merge, this fixes up the dirstate to include only
1642 1644 a single parent (if keepparent is False) and tries to duplicate any
1643 1645 renames/copies appropriately.
1644 1646
1645 1647 ctx - changeset to rebase
1646 1648 pctx - merge base, usually ctx.p1()
1647 1649 labels - merge labels eg ['local', 'graft']
1648 1650 keepparent - keep second parent if any
1649 1651
1650 1652 """
1651 1653 # If we're grafting a descendant onto an ancestor, be sure to pass
1652 1654 # mergeancestor=True to update. This does two things: 1) allows the merge if
1653 1655 # the destination is the same as the parent of the ctx (so we can use graft
1654 1656 # to copy commits), and 2) informs update that the incoming changes are
1655 1657 # newer than the destination so it doesn't prompt about "remote changed foo
1656 1658 # which local deleted".
1657 1659 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1658 1660
1659 1661 stats = update(repo, ctx.node(), True, True, pctx.node(),
1660 1662 mergeancestor=mergeancestor, labels=labels)
1661 1663
1662 1664 pother = nullid
1663 1665 parents = ctx.parents()
1664 1666 if keepparent and len(parents) == 2 and pctx in parents:
1665 1667 parents.remove(pctx)
1666 1668 pother = parents[0].node()
1667 1669
1668 1670 repo.dirstate.beginparentchange()
1669 1671 repo.setparents(repo['.'].node(), pother)
1670 1672 repo.dirstate.write(repo.currenttransaction())
1671 1673 # fix up dirstate for copies and renames
1672 1674 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1673 1675 repo.dirstate.endparentchange()
1674 1676 return stats
@@ -1,452 +1,452 b''
1 1 #require symlink execbit
2 2
3 3 $ tellmeabout() {
4 4 > if [ -h $1 ]; then
5 5 > echo $1 is a symlink:
6 6 > $TESTDIR/readlink.py $1
7 7 > elif [ -x $1 ]; then
8 8 > echo $1 is an executable file with content:
9 9 > cat $1
10 10 > else
11 11 > echo $1 is a plain file with content:
12 12 > cat $1
13 13 > fi
14 14 > }
15 15
16 16 $ hg init test1
17 17 $ cd test1
18 18
19 19 $ echo a > a
20 20 $ hg ci -Aqmadd
21 21 $ chmod +x a
22 22 $ hg ci -mexecutable
23 23
24 24 $ hg up -q 0
25 25 $ rm a
26 26 $ ln -s symlink a
27 27 $ hg ci -msymlink
28 28 created new head
29 29
30 30 Symlink is local parent, executable is other:
31 31
32 32 $ hg merge --debug
33 33 searching for copies back to rev 1
34 34 resolving manifests
35 35 branchmerge: True, force: False, partial: False
36 36 ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c
37 37 preserving a for resolve of a
38 38 a: versions differ -> m (premerge)
39 39 picked tool ':merge' for a (binary False symlink True changedelete False)
40 40 merging a
41 41 my a@521a1e40188f+ other a@3574f3e69b1c ancestor a@c334dc3be0da
42 42 warning: internal :merge cannot merge symlinks for a
43 43 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
44 44 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
45 45 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
46 46 [1]
47 47
48 48 $ tellmeabout a
49 49 a is a symlink:
50 50 a -> symlink
51 51 $ hg resolve a --tool internal:other
52 52 (no more unresolved files)
53 53 $ tellmeabout a
54 54 a is an executable file with content:
55 55 a
56 56 $ hg st
57 57 M a
58 58 ? a.orig
59 59
60 60 Symlink is other parent, executable is local:
61 61
62 62 $ hg update -C 1
63 63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 64
65 65 $ hg merge --debug --tool :union
66 66 searching for copies back to rev 1
67 67 resolving manifests
68 68 branchmerge: True, force: False, partial: False
69 69 ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
70 70 preserving a for resolve of a
71 71 a: versions differ -> m (premerge)
72 72 picked tool ':union' for a (binary False symlink True changedelete False)
73 73 merging a
74 74 my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
75 75 warning: internal :union cannot merge symlinks for a
76 76 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
77 77 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
78 78 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
79 79 [1]
80 80
81 81 $ tellmeabout a
82 82 a is an executable file with content:
83 83 a
84 84
85 85 $ hg update -C 1
86 86 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
87 87
88 88 $ hg merge --debug --tool :merge3
89 89 searching for copies back to rev 1
90 90 resolving manifests
91 91 branchmerge: True, force: False, partial: False
92 92 ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
93 93 preserving a for resolve of a
94 94 a: versions differ -> m (premerge)
95 95 picked tool ':merge3' for a (binary False symlink True changedelete False)
96 96 merging a
97 97 my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
98 98 warning: internal :merge3 cannot merge symlinks for a
99 99 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
100 100 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
101 101 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
102 102 [1]
103 103
104 104 $ tellmeabout a
105 105 a is an executable file with content:
106 106 a
107 107
108 108 $ hg update -C 1
109 109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 110
111 111 $ hg merge --debug --tool :merge-local
112 112 searching for copies back to rev 1
113 113 resolving manifests
114 114 branchmerge: True, force: False, partial: False
115 115 ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
116 116 preserving a for resolve of a
117 117 a: versions differ -> m (premerge)
118 118 picked tool ':merge-local' for a (binary False symlink True changedelete False)
119 119 merging a
120 120 my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
121 121 warning: internal :merge-local cannot merge symlinks for a
122 122 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
123 123 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
124 124 [1]
125 125
126 126 $ tellmeabout a
127 127 a is an executable file with content:
128 128 a
129 129
130 130 $ hg update -C 1
131 131 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
132 132
133 133 $ hg merge --debug --tool :merge-other
134 134 searching for copies back to rev 1
135 135 resolving manifests
136 136 branchmerge: True, force: False, partial: False
137 137 ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
138 138 preserving a for resolve of a
139 139 a: versions differ -> m (premerge)
140 140 picked tool ':merge-other' for a (binary False symlink True changedelete False)
141 141 merging a
142 142 my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
143 143 warning: internal :merge-other cannot merge symlinks for a
144 144 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
145 145 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
146 146 [1]
147 147
148 148 $ tellmeabout a
149 149 a is an executable file with content:
150 150 a
151 151
152 152 Update to link without local change should get us a symlink (issue3316):
153 153
154 154 $ hg up -C 0
155 155 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 156 $ hg up
157 157 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
158 158 1 other heads for branch "default"
159 159 $ hg st
160 160 ? a.orig
161 161
162 162 Update to link with local change should cause a merge prompt (issue3200):
163 163
164 164 $ hg up -Cq 0
165 165 $ echo data > a
166 166 $ HGMERGE= hg up -y --debug
167 167 searching for copies back to rev 2
168 168 resolving manifests
169 169 branchmerge: False, force: False, partial: False
170 170 ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f
171 171 preserving a for resolve of a
172 172 a: versions differ -> m (premerge)
173 173 (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
174 174 picked tool ':prompt' for a (binary False symlink True changedelete False)
175 175 no tool found to merge a
176 176 keep (l)ocal [working copy], take (o)ther [destination], or leave (u)nresolved? u
177 177 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
178 178 use 'hg resolve' to retry unresolved file merges
179 179 1 other heads for branch "default"
180 180 [1]
181 181 $ hg diff --git
182 182 diff --git a/a b/a
183 183 old mode 120000
184 184 new mode 100644
185 185 --- a/a
186 186 +++ b/a
187 187 @@ -1,1 +1,1 @@
188 188 -symlink
189 189 \ No newline at end of file
190 190 +data
191 191
192 192
193 193 Test only 'l' change - happens rarely, except when recovering from situations
194 194 where that was what happened.
195 195
196 196 $ hg init test2
197 197 $ cd test2
198 198 $ printf base > f
199 199 $ hg ci -Aqm0
200 200 $ echo file > f
201 201 $ echo content >> f
202 202 $ hg ci -qm1
203 203 $ hg up -qr0
204 204 $ rm f
205 205 $ ln -s base f
206 206 $ hg ci -qm2
207 207 $ hg merge
208 208 merging f
209 209 warning: internal :merge cannot merge symlinks for f
210 210 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
211 211 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
212 212 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
213 213 [1]
214 214 $ tellmeabout f
215 215 f is a symlink:
216 216 f -> base
217 217
218 218 $ hg up -Cqr1
219 219 $ hg merge
220 220 merging f
221 221 warning: internal :merge cannot merge symlinks for f
222 222 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
223 223 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
224 224 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
225 225 [1]
226 226 $ tellmeabout f
227 227 f is a plain file with content:
228 228 file
229 229 content
230 230
231 231 $ cd ..
232 232
233 233 Test removed 'x' flag merged with change to symlink
234 234
235 235 $ hg init test3
236 236 $ cd test3
237 237 $ echo f > f
238 238 $ chmod +x f
239 239 $ hg ci -Aqm0
240 240 $ chmod -x f
241 241 $ hg ci -qm1
242 242 $ hg up -qr0
243 243 $ rm f
244 244 $ ln -s dangling f
245 245 $ hg ci -qm2
246 246 $ hg merge
247 247 merging f
248 248 warning: internal :merge cannot merge symlinks for f
249 249 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
250 250 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
251 251 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
252 252 [1]
253 253 $ tellmeabout f
254 254 f is a symlink:
255 255 f -> dangling
256 256
257 257 $ hg up -Cqr1
258 258 $ hg merge
259 259 merging f
260 260 warning: internal :merge cannot merge symlinks for f
261 261 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
262 262 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
263 263 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
264 264 [1]
265 265 $ tellmeabout f
266 266 f is a plain file with content:
267 267 f
268 268
269 269 Test removed 'x' flag merged with content change - both ways
270 270
271 271 $ hg up -Cqr0
272 272 $ echo change > f
273 273 $ hg ci -qm3
274 274 $ hg merge -r1
275 275 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 276 (branch merge, don't forget to commit)
277 277 $ tellmeabout f
278 278 f is a plain file with content:
279 279 change
280 280
281 281 $ hg up -qCr1
282 282 $ hg merge -r3
283 283 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 284 (branch merge, don't forget to commit)
285 285 $ tellmeabout f
286 286 f is a plain file with content:
287 287 change
288 288
289 289 $ cd ..
290 290
291 291 Test merge with no common ancestor:
292 292 a: just different
293 293 b: x vs -, different (cannot calculate x, cannot ask merge tool)
294 294 c: x vs -, same (cannot calculate x, merge tool is no good)
295 295 d: x vs l, different
296 296 e: x vs l, same
297 297 f: - vs l, different
298 298 g: - vs l, same
299 299 h: l vs l, different
300 300 (where same means the filelog entry is shared and there thus is an ancestor!)
301 301
302 302 $ hg init test4
303 303 $ cd test4
304 304 $ echo 0 > 0
305 305 $ hg ci -Aqm0
306 306
307 307 $ echo 1 > a
308 308 $ echo 1 > b
309 309 $ chmod +x b
310 310 $ echo 1 > bx
311 311 $ chmod +x bx
312 312 $ echo x > c
313 313 $ chmod +x c
314 314 $ echo 1 > d
315 315 $ chmod +x d
316 316 $ printf x > e
317 317 $ chmod +x e
318 318 $ echo 1 > f
319 319 $ printf x > g
320 320 $ ln -s 1 h
321 321 $ hg ci -qAm1
322 322
323 323 $ hg up -qr0
324 324 $ echo 2 > a
325 325 $ echo 2 > b
326 326 $ echo 2 > bx
327 327 $ chmod +x bx
328 328 $ echo x > c
329 329 $ ln -s 2 d
330 330 $ ln -s x e
331 331 $ ln -s 2 f
332 332 $ ln -s x g
333 333 $ ln -s 2 h
334 334 $ hg ci -Aqm2
335 335
336 336 $ hg merge
337 337 merging a
338 warning: cannot merge flags for b
338 warning: cannot merge flags for b without common ancestor - keeping local flags
339 339 merging b
340 340 merging bx
341 warning: cannot merge flags for c
341 warning: cannot merge flags for c without common ancestor - keeping local flags
342 342 merging d
343 343 warning: internal :merge cannot merge symlinks for d
344 344 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
345 345 merging f
346 346 warning: internal :merge cannot merge symlinks for f
347 347 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
348 348 merging h
349 349 warning: internal :merge cannot merge symlinks for h
350 350 warning: conflicts while merging h! (edit, then use 'hg resolve --mark')
351 351 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
352 352 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
353 353 warning: conflicts while merging bx! (edit, then use 'hg resolve --mark')
354 354 3 files updated, 0 files merged, 0 files removed, 6 files unresolved
355 355 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
356 356 [1]
357 357 $ hg resolve -l
358 358 U a
359 359 U b
360 360 U bx
361 361 U d
362 362 U f
363 363 U h
364 364 $ tellmeabout a
365 365 a is a plain file with content:
366 366 <<<<<<< working copy: 0c617753b41b - test: 2
367 367 2
368 368 =======
369 369 1
370 370 >>>>>>> merge rev: 2e60aa20b912 - test: 1
371 371 $ tellmeabout b
372 372 b is a plain file with content:
373 373 <<<<<<< working copy: 0c617753b41b - test: 2
374 374 2
375 375 =======
376 376 1
377 377 >>>>>>> merge rev: 2e60aa20b912 - test: 1
378 378 $ tellmeabout c
379 379 c is a plain file with content:
380 380 x
381 381 $ tellmeabout d
382 382 d is a symlink:
383 383 d -> 2
384 384 $ tellmeabout e
385 385 e is a symlink:
386 386 e -> x
387 387 $ tellmeabout f
388 388 f is a symlink:
389 389 f -> 2
390 390 $ tellmeabout g
391 391 g is a symlink:
392 392 g -> x
393 393 $ tellmeabout h
394 394 h is a symlink:
395 395 h -> 2
396 396
397 397 $ hg up -Cqr1
398 398 $ hg merge
399 399 merging a
400 warning: cannot merge flags for b
400 warning: cannot merge flags for b without common ancestor - keeping local flags
401 401 merging b
402 402 merging bx
403 warning: cannot merge flags for c
403 warning: cannot merge flags for c without common ancestor - keeping local flags
404 404 merging d
405 405 warning: internal :merge cannot merge symlinks for d
406 406 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
407 407 merging f
408 408 warning: internal :merge cannot merge symlinks for f
409 409 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
410 410 merging h
411 411 warning: internal :merge cannot merge symlinks for h
412 412 warning: conflicts while merging h! (edit, then use 'hg resolve --mark')
413 413 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
414 414 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
415 415 warning: conflicts while merging bx! (edit, then use 'hg resolve --mark')
416 416 3 files updated, 0 files merged, 0 files removed, 6 files unresolved
417 417 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
418 418 [1]
419 419 $ tellmeabout a
420 420 a is a plain file with content:
421 421 <<<<<<< working copy: 2e60aa20b912 - test: 1
422 422 1
423 423 =======
424 424 2
425 425 >>>>>>> merge rev: 0c617753b41b - test: 2
426 426 $ tellmeabout b
427 427 b is an executable file with content:
428 428 <<<<<<< working copy: 2e60aa20b912 - test: 1
429 429 1
430 430 =======
431 431 2
432 432 >>>>>>> merge rev: 0c617753b41b - test: 2
433 433 $ tellmeabout c
434 434 c is an executable file with content:
435 435 x
436 436 $ tellmeabout d
437 437 d is an executable file with content:
438 438 1
439 439 $ tellmeabout e
440 440 e is an executable file with content:
441 441 x (no-eol)
442 442 $ tellmeabout f
443 443 f is a plain file with content:
444 444 1
445 445 $ tellmeabout g
446 446 g is a plain file with content:
447 447 x (no-eol)
448 448 $ tellmeabout h
449 449 h is a symlink:
450 450 h -> 1
451 451
452 452 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now