##// END OF EJS Templates
merge: hint about using `hg resolve` for resolving conflicts...
Martin von Zweigbergk -
r43072:7b80ad5a default
parent child Browse files
Show More
@@ -1,2334 +1,2335 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 shutil
13 13 import stat
14 14 import struct
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 addednodeid,
19 19 bin,
20 20 hex,
21 21 modifiednodeid,
22 22 nullhex,
23 23 nullid,
24 24 nullrev,
25 25 )
26 26 from .thirdparty import (
27 27 attr,
28 28 )
29 29 from . import (
30 30 copies,
31 31 encoding,
32 32 error,
33 33 filemerge,
34 34 match as matchmod,
35 35 obsutil,
36 36 pycompat,
37 37 scmutil,
38 38 subrepoutil,
39 39 util,
40 40 worker,
41 41 )
42 42
43 43 _pack = struct.pack
44 44 _unpack = struct.unpack
45 45
46 46 def _droponode(data):
47 47 # used for compatibility for v1
48 48 bits = data.split('\0')
49 49 bits = bits[:-2] + bits[-1:]
50 50 return '\0'.join(bits)
51 51
52 52 # Merge state record types. See ``mergestate`` docs for more.
53 53 RECORD_LOCAL = b'L'
54 54 RECORD_OTHER = b'O'
55 55 RECORD_MERGED = b'F'
56 56 RECORD_CHANGEDELETE_CONFLICT = b'C'
57 57 RECORD_MERGE_DRIVER_MERGE = b'D'
58 58 RECORD_PATH_CONFLICT = b'P'
59 59 RECORD_MERGE_DRIVER_STATE = b'm'
60 60 RECORD_FILE_VALUES = b'f'
61 61 RECORD_LABELS = b'l'
62 62 RECORD_OVERRIDE = b't'
63 63 RECORD_UNSUPPORTED_MANDATORY = b'X'
64 64 RECORD_UNSUPPORTED_ADVISORY = b'x'
65 65
66 66 MERGE_DRIVER_STATE_UNMARKED = b'u'
67 67 MERGE_DRIVER_STATE_MARKED = b'm'
68 68 MERGE_DRIVER_STATE_SUCCESS = b's'
69 69
70 70 MERGE_RECORD_UNRESOLVED = b'u'
71 71 MERGE_RECORD_RESOLVED = b'r'
72 72 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
73 73 MERGE_RECORD_RESOLVED_PATH = b'pr'
74 74 MERGE_RECORD_DRIVER_RESOLVED = b'd'
75 75
76 76 ACTION_FORGET = b'f'
77 77 ACTION_REMOVE = b'r'
78 78 ACTION_ADD = b'a'
79 79 ACTION_GET = b'g'
80 80 ACTION_PATH_CONFLICT = b'p'
81 81 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
82 82 ACTION_ADD_MODIFIED = b'am'
83 83 ACTION_CREATED = b'c'
84 84 ACTION_DELETED_CHANGED = b'dc'
85 85 ACTION_CHANGED_DELETED = b'cd'
86 86 ACTION_MERGE = b'm'
87 87 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
88 88 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
89 89 ACTION_KEEP = b'k'
90 90 ACTION_EXEC = b'e'
91 91 ACTION_CREATED_MERGE = b'cm'
92 92
93 93 class mergestate(object):
94 94 '''track 3-way merge state of individual files
95 95
96 96 The merge state is stored on disk when needed. Two files are used: one with
97 97 an old format (version 1), and one with a new format (version 2). Version 2
98 98 stores a superset of the data in version 1, including new kinds of records
99 99 in the future. For more about the new format, see the documentation for
100 100 `_readrecordsv2`.
101 101
102 102 Each record can contain arbitrary content, and has an associated type. This
103 103 `type` should be a letter. If `type` is uppercase, the record is mandatory:
104 104 versions of Mercurial that don't support it should abort. If `type` is
105 105 lowercase, the record can be safely ignored.
106 106
107 107 Currently known records:
108 108
109 109 L: the node of the "local" part of the merge (hexified version)
110 110 O: the node of the "other" part of the merge (hexified version)
111 111 F: a file to be merged entry
112 112 C: a change/delete or delete/change conflict
113 113 D: a file that the external merge driver will merge internally
114 114 (experimental)
115 115 P: a path conflict (file vs directory)
116 116 m: the external merge driver defined for this merge plus its run state
117 117 (experimental)
118 118 f: a (filename, dictionary) tuple of optional values for a given file
119 119 X: unsupported mandatory record type (used in tests)
120 120 x: unsupported advisory record type (used in tests)
121 121 l: the labels for the parts of the merge.
122 122
123 123 Merge driver run states (experimental):
124 124 u: driver-resolved files unmarked -- needs to be run next time we're about
125 125 to resolve or commit
126 126 m: driver-resolved files marked -- only needs to be run before commit
127 127 s: success/skipped -- does not need to be run any more
128 128
129 129 Merge record states (stored in self._state, indexed by filename):
130 130 u: unresolved conflict
131 131 r: resolved conflict
132 132 pu: unresolved path conflict (file conflicts with directory)
133 133 pr: resolved path conflict
134 134 d: driver-resolved conflict
135 135
136 136 The resolve command transitions between 'u' and 'r' for conflicts and
137 137 'pu' and 'pr' for path conflicts.
138 138 '''
139 139 statepathv1 = 'merge/state'
140 140 statepathv2 = 'merge/state2'
141 141
142 142 @staticmethod
143 143 def clean(repo, node=None, other=None, labels=None):
144 144 """Initialize a brand new merge state, removing any existing state on
145 145 disk."""
146 146 ms = mergestate(repo)
147 147 ms.reset(node, other, labels)
148 148 return ms
149 149
150 150 @staticmethod
151 151 def read(repo):
152 152 """Initialize the merge state, reading it from disk."""
153 153 ms = mergestate(repo)
154 154 ms._read()
155 155 return ms
156 156
157 157 def __init__(self, repo):
158 158 """Initialize the merge state.
159 159
160 160 Do not use this directly! Instead call read() or clean()."""
161 161 self._repo = repo
162 162 self._dirty = False
163 163 self._labels = None
164 164
165 165 def reset(self, node=None, other=None, labels=None):
166 166 self._state = {}
167 167 self._stateextras = {}
168 168 self._local = None
169 169 self._other = None
170 170 self._labels = labels
171 171 for var in ('localctx', 'otherctx'):
172 172 if var in vars(self):
173 173 delattr(self, var)
174 174 if node:
175 175 self._local = node
176 176 self._other = other
177 177 self._readmergedriver = None
178 178 if self.mergedriver:
179 179 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
180 180 else:
181 181 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
182 182 shutil.rmtree(self._repo.vfs.join('merge'), True)
183 183 self._results = {}
184 184 self._dirty = False
185 185
186 186 def _read(self):
187 187 """Analyse each record content to restore a serialized state from disk
188 188
189 189 This function process "record" entry produced by the de-serialization
190 190 of on disk file.
191 191 """
192 192 self._state = {}
193 193 self._stateextras = {}
194 194 self._local = None
195 195 self._other = None
196 196 for var in ('localctx', 'otherctx'):
197 197 if var in vars(self):
198 198 delattr(self, var)
199 199 self._readmergedriver = None
200 200 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
201 201 unsupported = set()
202 202 records = self._readrecords()
203 203 for rtype, record in records:
204 204 if rtype == RECORD_LOCAL:
205 205 self._local = bin(record)
206 206 elif rtype == RECORD_OTHER:
207 207 self._other = bin(record)
208 208 elif rtype == RECORD_MERGE_DRIVER_STATE:
209 209 bits = record.split('\0', 1)
210 210 mdstate = bits[1]
211 211 if len(mdstate) != 1 or mdstate not in (
212 212 MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
213 213 MERGE_DRIVER_STATE_SUCCESS):
214 214 # the merge driver should be idempotent, so just rerun it
215 215 mdstate = MERGE_DRIVER_STATE_UNMARKED
216 216
217 217 self._readmergedriver = bits[0]
218 218 self._mdstate = mdstate
219 219 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
220 220 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
221 221 bits = record.split('\0')
222 222 self._state[bits[0]] = bits[1:]
223 223 elif rtype == RECORD_FILE_VALUES:
224 224 filename, rawextras = record.split('\0', 1)
225 225 extraparts = rawextras.split('\0')
226 226 extras = {}
227 227 i = 0
228 228 while i < len(extraparts):
229 229 extras[extraparts[i]] = extraparts[i + 1]
230 230 i += 2
231 231
232 232 self._stateextras[filename] = extras
233 233 elif rtype == RECORD_LABELS:
234 234 labels = record.split('\0', 2)
235 235 self._labels = [l for l in labels if len(l) > 0]
236 236 elif not rtype.islower():
237 237 unsupported.add(rtype)
238 238 self._results = {}
239 239 self._dirty = False
240 240
241 241 if unsupported:
242 242 raise error.UnsupportedMergeRecords(unsupported)
243 243
244 244 def _readrecords(self):
245 245 """Read merge state from disk and return a list of record (TYPE, data)
246 246
247 247 We read data from both v1 and v2 files and decide which one to use.
248 248
249 249 V1 has been used by version prior to 2.9.1 and contains less data than
250 250 v2. We read both versions and check if no data in v2 contradicts
251 251 v1. If there is not contradiction we can safely assume that both v1
252 252 and v2 were written at the same time and use the extract data in v2. If
253 253 there is contradiction we ignore v2 content as we assume an old version
254 254 of Mercurial has overwritten the mergestate file and left an old v2
255 255 file around.
256 256
257 257 returns list of record [(TYPE, data), ...]"""
258 258 v1records = self._readrecordsv1()
259 259 v2records = self._readrecordsv2()
260 260 if self._v1v2match(v1records, v2records):
261 261 return v2records
262 262 else:
263 263 # v1 file is newer than v2 file, use it
264 264 # we have to infer the "other" changeset of the merge
265 265 # we cannot do better than that with v1 of the format
266 266 mctx = self._repo[None].parents()[-1]
267 267 v1records.append((RECORD_OTHER, mctx.hex()))
268 268 # add place holder "other" file node information
269 269 # nobody is using it yet so we do no need to fetch the data
270 270 # if mctx was wrong `mctx[bits[-2]]` may fails.
271 271 for idx, r in enumerate(v1records):
272 272 if r[0] == RECORD_MERGED:
273 273 bits = r[1].split('\0')
274 274 bits.insert(-2, '')
275 275 v1records[idx] = (r[0], '\0'.join(bits))
276 276 return v1records
277 277
278 278 def _v1v2match(self, v1records, v2records):
279 279 oldv2 = set() # old format version of v2 record
280 280 for rec in v2records:
281 281 if rec[0] == RECORD_LOCAL:
282 282 oldv2.add(rec)
283 283 elif rec[0] == RECORD_MERGED:
284 284 # drop the onode data (not contained in v1)
285 285 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
286 286 for rec in v1records:
287 287 if rec not in oldv2:
288 288 return False
289 289 else:
290 290 return True
291 291
292 292 def _readrecordsv1(self):
293 293 """read on disk merge state for version 1 file
294 294
295 295 returns list of record [(TYPE, data), ...]
296 296
297 297 Note: the "F" data from this file are one entry short
298 298 (no "other file node" entry)
299 299 """
300 300 records = []
301 301 try:
302 302 f = self._repo.vfs(self.statepathv1)
303 303 for i, l in enumerate(f):
304 304 if i == 0:
305 305 records.append((RECORD_LOCAL, l[:-1]))
306 306 else:
307 307 records.append((RECORD_MERGED, l[:-1]))
308 308 f.close()
309 309 except IOError as err:
310 310 if err.errno != errno.ENOENT:
311 311 raise
312 312 return records
313 313
314 314 def _readrecordsv2(self):
315 315 """read on disk merge state for version 2 file
316 316
317 317 This format is a list of arbitrary records of the form:
318 318
319 319 [type][length][content]
320 320
321 321 `type` is a single character, `length` is a 4 byte integer, and
322 322 `content` is an arbitrary byte sequence of length `length`.
323 323
324 324 Mercurial versions prior to 3.7 have a bug where if there are
325 325 unsupported mandatory merge records, attempting to clear out the merge
326 326 state with hg update --clean or similar aborts. The 't' record type
327 327 works around that by writing out what those versions treat as an
328 328 advisory record, but later versions interpret as special: the first
329 329 character is the 'real' record type and everything onwards is the data.
330 330
331 331 Returns list of records [(TYPE, data), ...]."""
332 332 records = []
333 333 try:
334 334 f = self._repo.vfs(self.statepathv2)
335 335 data = f.read()
336 336 off = 0
337 337 end = len(data)
338 338 while off < end:
339 339 rtype = data[off:off + 1]
340 340 off += 1
341 341 length = _unpack('>I', data[off:(off + 4)])[0]
342 342 off += 4
343 343 record = data[off:(off + length)]
344 344 off += length
345 345 if rtype == RECORD_OVERRIDE:
346 346 rtype, record = record[0:1], record[1:]
347 347 records.append((rtype, record))
348 348 f.close()
349 349 except IOError as err:
350 350 if err.errno != errno.ENOENT:
351 351 raise
352 352 return records
353 353
354 354 @util.propertycache
355 355 def mergedriver(self):
356 356 # protect against the following:
357 357 # - A configures a malicious merge driver in their hgrc, then
358 358 # pauses the merge
359 359 # - A edits their hgrc to remove references to the merge driver
360 360 # - A gives a copy of their entire repo, including .hg, to B
361 361 # - B inspects .hgrc and finds it to be clean
362 362 # - B then continues the merge and the malicious merge driver
363 363 # gets invoked
364 364 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
365 365 if (self._readmergedriver is not None
366 366 and self._readmergedriver != configmergedriver):
367 367 raise error.ConfigError(
368 368 _("merge driver changed since merge started"),
369 369 hint=_("revert merge driver change or abort merge"))
370 370
371 371 return configmergedriver
372 372
373 373 @util.propertycache
374 374 def localctx(self):
375 375 if self._local is None:
376 376 msg = "localctx accessed but self._local isn't set"
377 377 raise error.ProgrammingError(msg)
378 378 return self._repo[self._local]
379 379
380 380 @util.propertycache
381 381 def otherctx(self):
382 382 if self._other is None:
383 383 msg = "otherctx accessed but self._other isn't set"
384 384 raise error.ProgrammingError(msg)
385 385 return self._repo[self._other]
386 386
387 387 def active(self):
388 388 """Whether mergestate is active.
389 389
390 390 Returns True if there appears to be mergestate. This is a rough proxy
391 391 for "is a merge in progress."
392 392 """
393 393 # Check local variables before looking at filesystem for performance
394 394 # reasons.
395 395 return (bool(self._local) or bool(self._state) or
396 396 self._repo.vfs.exists(self.statepathv1) or
397 397 self._repo.vfs.exists(self.statepathv2))
398 398
399 399 def commit(self):
400 400 """Write current state on disk (if necessary)"""
401 401 if self._dirty:
402 402 records = self._makerecords()
403 403 self._writerecords(records)
404 404 self._dirty = False
405 405
406 406 def _makerecords(self):
407 407 records = []
408 408 records.append((RECORD_LOCAL, hex(self._local)))
409 409 records.append((RECORD_OTHER, hex(self._other)))
410 410 if self.mergedriver:
411 411 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
412 412 self.mergedriver, self._mdstate])))
413 413 # Write out state items. In all cases, the value of the state map entry
414 414 # is written as the contents of the record. The record type depends on
415 415 # the type of state that is stored, and capital-letter records are used
416 416 # to prevent older versions of Mercurial that do not support the feature
417 417 # from loading them.
418 418 for filename, v in self._state.iteritems():
419 419 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
420 420 # Driver-resolved merge. These are stored in 'D' records.
421 421 records.append((RECORD_MERGE_DRIVER_MERGE,
422 422 '\0'.join([filename] + v)))
423 423 elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
424 424 MERGE_RECORD_RESOLVED_PATH):
425 425 # Path conflicts. These are stored in 'P' records. The current
426 426 # resolution state ('pu' or 'pr') is stored within the record.
427 427 records.append((RECORD_PATH_CONFLICT,
428 428 '\0'.join([filename] + v)))
429 429 elif v[1] == nullhex or v[6] == nullhex:
430 430 # Change/Delete or Delete/Change conflicts. These are stored in
431 431 # 'C' records. v[1] is the local file, and is nullhex when the
432 432 # file is deleted locally ('dc'). v[6] is the remote file, and
433 433 # is nullhex when the file is deleted remotely ('cd').
434 434 records.append((RECORD_CHANGEDELETE_CONFLICT,
435 435 '\0'.join([filename] + v)))
436 436 else:
437 437 # Normal files. These are stored in 'F' records.
438 438 records.append((RECORD_MERGED,
439 439 '\0'.join([filename] + v)))
440 440 for filename, extras in sorted(self._stateextras.iteritems()):
441 441 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
442 442 extras.iteritems())
443 443 records.append((RECORD_FILE_VALUES,
444 444 '%s\0%s' % (filename, rawextras)))
445 445 if self._labels is not None:
446 446 labels = '\0'.join(self._labels)
447 447 records.append((RECORD_LABELS, labels))
448 448 return records
449 449
450 450 def _writerecords(self, records):
451 451 """Write current state on disk (both v1 and v2)"""
452 452 self._writerecordsv1(records)
453 453 self._writerecordsv2(records)
454 454
455 455 def _writerecordsv1(self, records):
456 456 """Write current state on disk in a version 1 file"""
457 457 f = self._repo.vfs(self.statepathv1, 'wb')
458 458 irecords = iter(records)
459 459 lrecords = next(irecords)
460 460 assert lrecords[0] == RECORD_LOCAL
461 461 f.write(hex(self._local) + '\n')
462 462 for rtype, data in irecords:
463 463 if rtype == RECORD_MERGED:
464 464 f.write('%s\n' % _droponode(data))
465 465 f.close()
466 466
467 467 def _writerecordsv2(self, records):
468 468 """Write current state on disk in a version 2 file
469 469
470 470 See the docstring for _readrecordsv2 for why we use 't'."""
471 471 # these are the records that all version 2 clients can read
472 472 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
473 473 f = self._repo.vfs(self.statepathv2, 'wb')
474 474 for key, data in records:
475 475 assert len(key) == 1
476 476 if key not in allowlist:
477 477 key, data = RECORD_OVERRIDE, '%s%s' % (key, data)
478 478 format = '>sI%is' % len(data)
479 479 f.write(_pack(format, key, len(data), data))
480 480 f.close()
481 481
482 482 @staticmethod
483 483 def getlocalkey(path):
484 484 """hash the path of a local file context for storage in the .hg/merge
485 485 directory."""
486 486
487 487 return hex(hashlib.sha1(path).digest())
488 488
489 489 def add(self, fcl, fco, fca, fd):
490 490 """add a new (potentially?) conflicting file the merge state
491 491 fcl: file context for local,
492 492 fco: file context for remote,
493 493 fca: file context for ancestors,
494 494 fd: file path of the resulting merge.
495 495
496 496 note: also write the local version to the `.hg/merge` directory.
497 497 """
498 498 if fcl.isabsent():
499 499 localkey = nullhex
500 500 else:
501 501 localkey = mergestate.getlocalkey(fcl.path())
502 502 self._repo.vfs.write('merge/' + localkey, fcl.data())
503 503 self._state[fd] = [MERGE_RECORD_UNRESOLVED, localkey, fcl.path(),
504 504 fca.path(), hex(fca.filenode()),
505 505 fco.path(), hex(fco.filenode()),
506 506 fcl.flags()]
507 507 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
508 508 self._dirty = True
509 509
510 510 def addpath(self, path, frename, forigin):
511 511 """add a new conflicting path to the merge state
512 512 path: the path that conflicts
513 513 frename: the filename the conflicting file was renamed to
514 514 forigin: origin of the file ('l' or 'r' for local/remote)
515 515 """
516 516 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
517 517 self._dirty = True
518 518
519 519 def __contains__(self, dfile):
520 520 return dfile in self._state
521 521
522 522 def __getitem__(self, dfile):
523 523 return self._state[dfile][0]
524 524
525 525 def __iter__(self):
526 526 return iter(sorted(self._state))
527 527
528 528 def files(self):
529 529 return self._state.keys()
530 530
531 531 def mark(self, dfile, state):
532 532 self._state[dfile][0] = state
533 533 self._dirty = True
534 534
535 535 def mdstate(self):
536 536 return self._mdstate
537 537
538 538 def unresolved(self):
539 539 """Obtain the paths of unresolved files."""
540 540
541 541 for f, entry in self._state.iteritems():
542 542 if entry[0] in (MERGE_RECORD_UNRESOLVED,
543 543 MERGE_RECORD_UNRESOLVED_PATH):
544 544 yield f
545 545
546 546 def driverresolved(self):
547 547 """Obtain the paths of driver-resolved files."""
548 548
549 549 for f, entry in self._state.items():
550 550 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
551 551 yield f
552 552
553 553 def extras(self, filename):
554 554 return self._stateextras.setdefault(filename, {})
555 555
556 556 def _resolve(self, preresolve, dfile, wctx):
557 557 """rerun merge process for file path `dfile`"""
558 558 if self[dfile] in (MERGE_RECORD_RESOLVED,
559 559 MERGE_RECORD_DRIVER_RESOLVED):
560 560 return True, 0
561 561 stateentry = self._state[dfile]
562 562 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
563 563 octx = self._repo[self._other]
564 564 extras = self.extras(dfile)
565 565 anccommitnode = extras.get('ancestorlinknode')
566 566 if anccommitnode:
567 567 actx = self._repo[anccommitnode]
568 568 else:
569 569 actx = None
570 570 fcd = self._filectxorabsent(localkey, wctx, dfile)
571 571 fco = self._filectxorabsent(onode, octx, ofile)
572 572 # TODO: move this to filectxorabsent
573 573 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
574 574 # "premerge" x flags
575 575 flo = fco.flags()
576 576 fla = fca.flags()
577 577 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
578 578 if fca.node() == nullid and flags != flo:
579 579 if preresolve:
580 580 self._repo.ui.warn(
581 581 _('warning: cannot merge flags for %s '
582 582 'without common ancestor - keeping local flags\n')
583 583 % afile)
584 584 elif flags == fla:
585 585 flags = flo
586 586 if preresolve:
587 587 # restore local
588 588 if localkey != nullhex:
589 589 f = self._repo.vfs('merge/' + localkey)
590 590 wctx[dfile].write(f.read(), flags)
591 591 f.close()
592 592 else:
593 593 wctx[dfile].remove(ignoremissing=True)
594 594 complete, r, deleted = filemerge.premerge(self._repo, wctx,
595 595 self._local, lfile, fcd,
596 596 fco, fca,
597 597 labels=self._labels)
598 598 else:
599 599 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
600 600 self._local, lfile, fcd,
601 601 fco, fca,
602 602 labels=self._labels)
603 603 if r is None:
604 604 # no real conflict
605 605 del self._state[dfile]
606 606 self._stateextras.pop(dfile, None)
607 607 self._dirty = True
608 608 elif not r:
609 609 self.mark(dfile, MERGE_RECORD_RESOLVED)
610 610
611 611 if complete:
612 612 action = None
613 613 if deleted:
614 614 if fcd.isabsent():
615 615 # dc: local picked. Need to drop if present, which may
616 616 # happen on re-resolves.
617 617 action = ACTION_FORGET
618 618 else:
619 619 # cd: remote picked (or otherwise deleted)
620 620 action = ACTION_REMOVE
621 621 else:
622 622 if fcd.isabsent(): # dc: remote picked
623 623 action = ACTION_GET
624 624 elif fco.isabsent(): # cd: local picked
625 625 if dfile in self.localctx:
626 626 action = ACTION_ADD_MODIFIED
627 627 else:
628 628 action = ACTION_ADD
629 629 # else: regular merges (no action necessary)
630 630 self._results[dfile] = r, action
631 631
632 632 return complete, r
633 633
634 634 def _filectxorabsent(self, hexnode, ctx, f):
635 635 if hexnode == nullhex:
636 636 return filemerge.absentfilectx(ctx, f)
637 637 else:
638 638 return ctx[f]
639 639
640 640 def preresolve(self, dfile, wctx):
641 641 """run premerge process for dfile
642 642
643 643 Returns whether the merge is complete, and the exit code."""
644 644 return self._resolve(True, dfile, wctx)
645 645
646 646 def resolve(self, dfile, wctx):
647 647 """run merge process (assuming premerge was run) for dfile
648 648
649 649 Returns the exit code of the merge."""
650 650 return self._resolve(False, dfile, wctx)[1]
651 651
652 652 def counts(self):
653 653 """return counts for updated, merged and removed files in this
654 654 session"""
655 655 updated, merged, removed = 0, 0, 0
656 656 for r, action in self._results.itervalues():
657 657 if r is None:
658 658 updated += 1
659 659 elif r == 0:
660 660 if action == ACTION_REMOVE:
661 661 removed += 1
662 662 else:
663 663 merged += 1
664 664 return updated, merged, removed
665 665
666 666 def unresolvedcount(self):
667 667 """get unresolved count for this merge (persistent)"""
668 668 return len(list(self.unresolved()))
669 669
670 670 def actions(self):
671 671 """return lists of actions to perform on the dirstate"""
672 672 actions = {
673 673 ACTION_REMOVE: [],
674 674 ACTION_FORGET: [],
675 675 ACTION_ADD: [],
676 676 ACTION_ADD_MODIFIED: [],
677 677 ACTION_GET: [],
678 678 }
679 679 for f, (r, action) in self._results.iteritems():
680 680 if action is not None:
681 681 actions[action].append((f, None, "merge result"))
682 682 return actions
683 683
684 684 def recordactions(self):
685 685 """record remove/add/get actions in the dirstate"""
686 686 branchmerge = self._repo.dirstate.p2() != nullid
687 687 recordupdates(self._repo, self.actions(), branchmerge, None)
688 688
689 689 def queueremove(self, f):
690 690 """queues a file to be removed from the dirstate
691 691
692 692 Meant for use by custom merge drivers."""
693 693 self._results[f] = 0, ACTION_REMOVE
694 694
695 695 def queueadd(self, f):
696 696 """queues a file to be added to the dirstate
697 697
698 698 Meant for use by custom merge drivers."""
699 699 self._results[f] = 0, ACTION_ADD
700 700
701 701 def queueget(self, f):
702 702 """queues a file to be marked modified in the dirstate
703 703
704 704 Meant for use by custom merge drivers."""
705 705 self._results[f] = 0, ACTION_GET
706 706
707 707 def _getcheckunknownconfig(repo, section, name):
708 708 config = repo.ui.config(section, name)
709 709 valid = ['abort', 'ignore', 'warn']
710 710 if config not in valid:
711 711 validstr = ', '.join(["'" + v + "'" for v in valid])
712 712 raise error.ConfigError(_("%s.%s not valid "
713 713 "('%s' is none of %s)")
714 714 % (section, name, config, validstr))
715 715 return config
716 716
717 717 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
718 718 if wctx.isinmemory():
719 719 # Nothing to do in IMM because nothing in the "working copy" can be an
720 720 # unknown file.
721 721 #
722 722 # Note that we should bail out here, not in ``_checkunknownfiles()``,
723 723 # because that function does other useful work.
724 724 return False
725 725
726 726 if f2 is None:
727 727 f2 = f
728 728 return (repo.wvfs.audit.check(f)
729 729 and repo.wvfs.isfileorlink(f)
730 730 and repo.dirstate.normalize(f) not in repo.dirstate
731 731 and mctx[f2].cmp(wctx[f]))
732 732
733 733 class _unknowndirschecker(object):
734 734 """
735 735 Look for any unknown files or directories that may have a path conflict
736 736 with a file. If any path prefix of the file exists as a file or link,
737 737 then it conflicts. If the file itself is a directory that contains any
738 738 file that is not tracked, then it conflicts.
739 739
740 740 Returns the shortest path at which a conflict occurs, or None if there is
741 741 no conflict.
742 742 """
743 743 def __init__(self):
744 744 # A set of paths known to be good. This prevents repeated checking of
745 745 # dirs. It will be updated with any new dirs that are checked and found
746 746 # to be safe.
747 747 self._unknowndircache = set()
748 748
749 749 # A set of paths that are known to be absent. This prevents repeated
750 750 # checking of subdirectories that are known not to exist. It will be
751 751 # updated with any new dirs that are checked and found to be absent.
752 752 self._missingdircache = set()
753 753
754 754 def __call__(self, repo, wctx, f):
755 755 if wctx.isinmemory():
756 756 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
757 757 return False
758 758
759 759 # Check for path prefixes that exist as unknown files.
760 760 for p in reversed(list(util.finddirs(f))):
761 761 if p in self._missingdircache:
762 762 return
763 763 if p in self._unknowndircache:
764 764 continue
765 765 if repo.wvfs.audit.check(p):
766 766 if (repo.wvfs.isfileorlink(p)
767 767 and repo.dirstate.normalize(p) not in repo.dirstate):
768 768 return p
769 769 if not repo.wvfs.lexists(p):
770 770 self._missingdircache.add(p)
771 771 return
772 772 self._unknowndircache.add(p)
773 773
774 774 # Check if the file conflicts with a directory containing unknown files.
775 775 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
776 776 # Does the directory contain any files that are not in the dirstate?
777 777 for p, dirs, files in repo.wvfs.walk(f):
778 778 for fn in files:
779 779 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
780 780 relf = repo.dirstate.normalize(relf, isknown=True)
781 781 if relf not in repo.dirstate:
782 782 return f
783 783 return None
784 784
785 785 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
786 786 """
787 787 Considers any actions that care about the presence of conflicting unknown
788 788 files. For some actions, the result is to abort; for others, it is to
789 789 choose a different action.
790 790 """
791 791 fileconflicts = set()
792 792 pathconflicts = set()
793 793 warnconflicts = set()
794 794 abortconflicts = set()
795 795 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
796 796 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
797 797 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
798 798 if not force:
799 799 def collectconflicts(conflicts, config):
800 800 if config == 'abort':
801 801 abortconflicts.update(conflicts)
802 802 elif config == 'warn':
803 803 warnconflicts.update(conflicts)
804 804
805 805 checkunknowndirs = _unknowndirschecker()
806 806 for f, (m, args, msg) in actions.iteritems():
807 807 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
808 808 if _checkunknownfile(repo, wctx, mctx, f):
809 809 fileconflicts.add(f)
810 810 elif pathconfig and f not in wctx:
811 811 path = checkunknowndirs(repo, wctx, f)
812 812 if path is not None:
813 813 pathconflicts.add(path)
814 814 elif m == ACTION_LOCAL_DIR_RENAME_GET:
815 815 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
816 816 fileconflicts.add(f)
817 817
818 818 allconflicts = fileconflicts | pathconflicts
819 819 ignoredconflicts = {c for c in allconflicts
820 820 if repo.dirstate._ignore(c)}
821 821 unknownconflicts = allconflicts - ignoredconflicts
822 822 collectconflicts(ignoredconflicts, ignoredconfig)
823 823 collectconflicts(unknownconflicts, unknownconfig)
824 824 else:
825 825 for f, (m, args, msg) in actions.iteritems():
826 826 if m == ACTION_CREATED_MERGE:
827 827 fl2, anc = args
828 828 different = _checkunknownfile(repo, wctx, mctx, f)
829 829 if repo.dirstate._ignore(f):
830 830 config = ignoredconfig
831 831 else:
832 832 config = unknownconfig
833 833
834 834 # The behavior when force is True is described by this table:
835 835 # config different mergeforce | action backup
836 836 # * n * | get n
837 837 # * y y | merge -
838 838 # abort y n | merge - (1)
839 839 # warn y n | warn + get y
840 840 # ignore y n | get y
841 841 #
842 842 # (1) this is probably the wrong behavior here -- we should
843 843 # probably abort, but some actions like rebases currently
844 844 # don't like an abort happening in the middle of
845 845 # merge.update.
846 846 if not different:
847 847 actions[f] = (ACTION_GET, (fl2, False), 'remote created')
848 848 elif mergeforce or config == 'abort':
849 849 actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
850 850 'remote differs from untracked local')
851 851 elif config == 'abort':
852 852 abortconflicts.add(f)
853 853 else:
854 854 if config == 'warn':
855 855 warnconflicts.add(f)
856 856 actions[f] = (ACTION_GET, (fl2, True), 'remote created')
857 857
858 858 for f in sorted(abortconflicts):
859 859 warn = repo.ui.warn
860 860 if f in pathconflicts:
861 861 if repo.wvfs.isfileorlink(f):
862 862 warn(_("%s: untracked file conflicts with directory\n") % f)
863 863 else:
864 864 warn(_("%s: untracked directory conflicts with file\n") % f)
865 865 else:
866 866 warn(_("%s: untracked file differs\n") % f)
867 867 if abortconflicts:
868 868 raise error.Abort(_("untracked files in working directory "
869 869 "differ from files in requested revision"))
870 870
871 871 for f in sorted(warnconflicts):
872 872 if repo.wvfs.isfileorlink(f):
873 873 repo.ui.warn(_("%s: replacing untracked file\n") % f)
874 874 else:
875 875 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
876 876
877 877 for f, (m, args, msg) in actions.iteritems():
878 878 if m == ACTION_CREATED:
879 879 backup = (f in fileconflicts or f in pathconflicts or
880 880 any(p in pathconflicts for p in util.finddirs(f)))
881 881 flags, = args
882 882 actions[f] = (ACTION_GET, (flags, backup), msg)
883 883
884 884 def _forgetremoved(wctx, mctx, branchmerge):
885 885 """
886 886 Forget removed files
887 887
888 888 If we're jumping between revisions (as opposed to merging), and if
889 889 neither the working directory nor the target rev has the file,
890 890 then we need to remove it from the dirstate, to prevent the
891 891 dirstate from listing the file when it is no longer in the
892 892 manifest.
893 893
894 894 If we're merging, and the other revision has removed a file
895 895 that is not present in the working directory, we need to mark it
896 896 as removed.
897 897 """
898 898
899 899 actions = {}
900 900 m = ACTION_FORGET
901 901 if branchmerge:
902 902 m = ACTION_REMOVE
903 903 for f in wctx.deleted():
904 904 if f not in mctx:
905 905 actions[f] = m, None, "forget deleted"
906 906
907 907 if not branchmerge:
908 908 for f in wctx.removed():
909 909 if f not in mctx:
910 910 actions[f] = ACTION_FORGET, None, "forget removed"
911 911
912 912 return actions
913 913
914 914 def _checkcollision(repo, wmf, actions):
915 915 """
916 916 Check for case-folding collisions.
917 917 """
918 918
919 919 # If the repo is narrowed, filter out files outside the narrowspec.
920 920 narrowmatch = repo.narrowmatch()
921 921 if not narrowmatch.always():
922 922 wmf = wmf.matches(narrowmatch)
923 923 if actions:
924 924 narrowactions = {}
925 925 for m, actionsfortype in actions.iteritems():
926 926 narrowactions[m] = []
927 927 for (f, args, msg) in actionsfortype:
928 928 if narrowmatch(f):
929 929 narrowactions[m].append((f, args, msg))
930 930 actions = narrowactions
931 931
932 932 # build provisional merged manifest up
933 933 pmmf = set(wmf)
934 934
935 935 if actions:
936 936 # KEEP and EXEC are no-op
937 937 for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
938 938 ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
939 939 for f, args, msg in actions[m]:
940 940 pmmf.add(f)
941 941 for f, args, msg in actions[ACTION_REMOVE]:
942 942 pmmf.discard(f)
943 943 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
944 944 f2, flags = args
945 945 pmmf.discard(f2)
946 946 pmmf.add(f)
947 947 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
948 948 pmmf.add(f)
949 949 for f, args, msg in actions[ACTION_MERGE]:
950 950 f1, f2, fa, move, anc = args
951 951 if move:
952 952 pmmf.discard(f1)
953 953 pmmf.add(f)
954 954
955 955 # check case-folding collision in provisional merged manifest
956 956 foldmap = {}
957 957 for f in pmmf:
958 958 fold = util.normcase(f)
959 959 if fold in foldmap:
960 960 raise error.Abort(_("case-folding collision between %s and %s")
961 961 % (f, foldmap[fold]))
962 962 foldmap[fold] = f
963 963
964 964 # check case-folding of directories
965 965 foldprefix = unfoldprefix = lastfull = ''
966 966 for fold, f in sorted(foldmap.items()):
967 967 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
968 968 # the folded prefix matches but actual casing is different
969 969 raise error.Abort(_("case-folding collision between "
970 970 "%s and directory of %s") % (lastfull, f))
971 971 foldprefix = fold + '/'
972 972 unfoldprefix = f + '/'
973 973 lastfull = f
974 974
975 975 def driverpreprocess(repo, ms, wctx, labels=None):
976 976 """run the preprocess step of the merge driver, if any
977 977
978 978 This is currently not implemented -- it's an extension point."""
979 979 return True
980 980
981 981 def driverconclude(repo, ms, wctx, labels=None):
982 982 """run the conclude step of the merge driver, if any
983 983
984 984 This is currently not implemented -- it's an extension point."""
985 985 return True
986 986
987 987 def _filesindirs(repo, manifest, dirs):
988 988 """
989 989 Generator that yields pairs of all the files in the manifest that are found
990 990 inside the directories listed in dirs, and which directory they are found
991 991 in.
992 992 """
993 993 for f in manifest:
994 994 for p in util.finddirs(f):
995 995 if p in dirs:
996 996 yield f, p
997 997 break
998 998
999 999 def checkpathconflicts(repo, wctx, mctx, actions):
1000 1000 """
1001 1001 Check if any actions introduce path conflicts in the repository, updating
1002 1002 actions to record or handle the path conflict accordingly.
1003 1003 """
1004 1004 mf = wctx.manifest()
1005 1005
1006 1006 # The set of local files that conflict with a remote directory.
1007 1007 localconflicts = set()
1008 1008
1009 1009 # The set of directories that conflict with a remote file, and so may cause
1010 1010 # conflicts if they still contain any files after the merge.
1011 1011 remoteconflicts = set()
1012 1012
1013 1013 # The set of directories that appear as both a file and a directory in the
1014 1014 # remote manifest. These indicate an invalid remote manifest, which
1015 1015 # can't be updated to cleanly.
1016 1016 invalidconflicts = set()
1017 1017
1018 1018 # The set of directories that contain files that are being created.
1019 1019 createdfiledirs = set()
1020 1020
1021 1021 # The set of files deleted by all the actions.
1022 1022 deletedfiles = set()
1023 1023
1024 1024 for f, (m, args, msg) in actions.items():
1025 1025 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
1026 1026 ACTION_CREATED_MERGE):
1027 1027 # This action may create a new local file.
1028 1028 createdfiledirs.update(util.finddirs(f))
1029 1029 if mf.hasdir(f):
1030 1030 # The file aliases a local directory. This might be ok if all
1031 1031 # the files in the local directory are being deleted. This
1032 1032 # will be checked once we know what all the deleted files are.
1033 1033 remoteconflicts.add(f)
1034 1034 # Track the names of all deleted files.
1035 1035 if m == ACTION_REMOVE:
1036 1036 deletedfiles.add(f)
1037 1037 if m == ACTION_MERGE:
1038 1038 f1, f2, fa, move, anc = args
1039 1039 if move:
1040 1040 deletedfiles.add(f1)
1041 1041 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1042 1042 f2, flags = args
1043 1043 deletedfiles.add(f2)
1044 1044
1045 1045 # Check all directories that contain created files for path conflicts.
1046 1046 for p in createdfiledirs:
1047 1047 if p in mf:
1048 1048 if p in mctx:
1049 1049 # A file is in a directory which aliases both a local
1050 1050 # and a remote file. This is an internal inconsistency
1051 1051 # within the remote manifest.
1052 1052 invalidconflicts.add(p)
1053 1053 else:
1054 1054 # A file is in a directory which aliases a local file.
1055 1055 # We will need to rename the local file.
1056 1056 localconflicts.add(p)
1057 1057 if p in actions and actions[p][0] in (ACTION_CREATED,
1058 1058 ACTION_DELETED_CHANGED,
1059 1059 ACTION_MERGE,
1060 1060 ACTION_CREATED_MERGE):
1061 1061 # The file is in a directory which aliases a remote file.
1062 1062 # This is an internal inconsistency within the remote
1063 1063 # manifest.
1064 1064 invalidconflicts.add(p)
1065 1065
1066 1066 # Rename all local conflicting files that have not been deleted.
1067 1067 for p in localconflicts:
1068 1068 if p not in deletedfiles:
1069 1069 ctxname = bytes(wctx).rstrip('+')
1070 1070 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1071 1071 actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
1072 1072 'local path conflict')
1073 1073 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
1074 1074 'path conflict')
1075 1075
1076 1076 if remoteconflicts:
1077 1077 # Check if all files in the conflicting directories have been removed.
1078 1078 ctxname = bytes(mctx).rstrip('+')
1079 1079 for f, p in _filesindirs(repo, mf, remoteconflicts):
1080 1080 if f not in deletedfiles:
1081 1081 m, args, msg = actions[p]
1082 1082 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1083 1083 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1084 1084 # Action was merge, just update target.
1085 1085 actions[pnew] = (m, args, msg)
1086 1086 else:
1087 1087 # Action was create, change to renamed get action.
1088 1088 fl = args[0]
1089 1089 actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
1090 1090 'remote path conflict')
1091 1091 actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
1092 1092 'path conflict')
1093 1093 remoteconflicts.remove(p)
1094 1094 break
1095 1095
1096 1096 if invalidconflicts:
1097 1097 for p in invalidconflicts:
1098 1098 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
1099 1099 raise error.Abort(_("destination manifest contains path conflicts"))
1100 1100
1101 1101 def _filternarrowactions(narrowmatch, branchmerge, actions):
1102 1102 """
1103 1103 Filters out actions that can ignored because the repo is narrowed.
1104 1104
1105 1105 Raise an exception if the merge cannot be completed because the repo is
1106 1106 narrowed.
1107 1107 """
1108 1108 nooptypes = {'k'} # TODO: handle with nonconflicttypes
1109 1109 nonconflicttypes = set('a am c cm f g r e'.split())
1110 1110 # We mutate the items in the dict during iteration, so iterate
1111 1111 # over a copy.
1112 1112 for f, action in list(actions.items()):
1113 1113 if narrowmatch(f):
1114 1114 pass
1115 1115 elif not branchmerge:
1116 1116 del actions[f] # just updating, ignore changes outside clone
1117 1117 elif action[0] in nooptypes:
1118 1118 del actions[f] # merge does not affect file
1119 1119 elif action[0] in nonconflicttypes:
1120 1120 raise error.Abort(_('merge affects file \'%s\' outside narrow, '
1121 1121 'which is not yet supported') % f,
1122 1122 hint=_('merging in the other direction '
1123 1123 'may work'))
1124 1124 else:
1125 1125 raise error.Abort(_('conflict in file \'%s\' is outside '
1126 1126 'narrow clone') % f)
1127 1127
1128 1128 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
1129 1129 acceptremote, followcopies, forcefulldiff=False):
1130 1130 """
1131 1131 Merge wctx and p2 with ancestor pa and generate merge action list
1132 1132
1133 1133 branchmerge and force are as passed in to update
1134 1134 matcher = matcher to filter file lists
1135 1135 acceptremote = accept the incoming changes without prompting
1136 1136 """
1137 1137 if matcher is not None and matcher.always():
1138 1138 matcher = None
1139 1139
1140 1140 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1141 1141
1142 1142 # manifests fetched in order are going to be faster, so prime the caches
1143 1143 [x.manifest() for x in
1144 1144 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
1145 1145
1146 1146 if followcopies:
1147 1147 ret = copies.mergecopies(repo, wctx, p2, pa)
1148 1148 copy, movewithdir, diverge, renamedelete, dirmove = ret
1149 1149
1150 1150 boolbm = pycompat.bytestr(bool(branchmerge))
1151 1151 boolf = pycompat.bytestr(bool(force))
1152 1152 boolm = pycompat.bytestr(bool(matcher))
1153 1153 repo.ui.note(_("resolving manifests\n"))
1154 1154 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
1155 1155 % (boolbm, boolf, boolm))
1156 1156 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1157 1157
1158 1158 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1159 1159 copied = set(copy.values())
1160 1160 copied.update(movewithdir.values())
1161 1161
1162 1162 if '.hgsubstate' in m1 and wctx.rev() is None:
1163 1163 # Check whether sub state is modified, and overwrite the manifest
1164 1164 # to flag the change. If wctx is a committed revision, we shouldn't
1165 1165 # care for the dirty state of the working directory.
1166 1166 if any(wctx.sub(s).dirty() for s in wctx.substate):
1167 1167 m1['.hgsubstate'] = modifiednodeid
1168 1168
1169 1169 # Don't use m2-vs-ma optimization if:
1170 1170 # - ma is the same as m1 or m2, which we're just going to diff again later
1171 1171 # - The caller specifically asks for a full diff, which is useful during bid
1172 1172 # merge.
1173 1173 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
1174 1174 # Identify which files are relevant to the merge, so we can limit the
1175 1175 # total m1-vs-m2 diff to just those files. This has significant
1176 1176 # performance benefits in large repositories.
1177 1177 relevantfiles = set(ma.diff(m2).keys())
1178 1178
1179 1179 # For copied and moved files, we need to add the source file too.
1180 1180 for copykey, copyvalue in copy.iteritems():
1181 1181 if copyvalue in relevantfiles:
1182 1182 relevantfiles.add(copykey)
1183 1183 for movedirkey in movewithdir:
1184 1184 relevantfiles.add(movedirkey)
1185 1185 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1186 1186 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1187 1187
1188 1188 diff = m1.diff(m2, match=matcher)
1189 1189
1190 1190 actions = {}
1191 1191 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1192 1192 if n1 and n2: # file exists on both local and remote side
1193 1193 if f not in ma:
1194 1194 fa = copy.get(f, None)
1195 1195 if fa is not None:
1196 1196 actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
1197 1197 'both renamed from %s' % fa)
1198 1198 else:
1199 1199 actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
1200 1200 'both created')
1201 1201 else:
1202 1202 a = ma[f]
1203 1203 fla = ma.flags(f)
1204 1204 nol = 'l' not in fl1 + fl2 + fla
1205 1205 if n2 == a and fl2 == fla:
1206 1206 actions[f] = (ACTION_KEEP, (), 'remote unchanged')
1207 1207 elif n1 == a and fl1 == fla: # local unchanged - use remote
1208 1208 if n1 == n2: # optimization: keep local content
1209 1209 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1210 1210 else:
1211 1211 actions[f] = (ACTION_GET, (fl2, False),
1212 1212 'remote is newer')
1213 1213 elif nol and n2 == a: # remote only changed 'x'
1214 1214 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1215 1215 elif nol and n1 == a: # local only changed 'x'
1216 1216 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
1217 1217 else: # both changed something
1218 1218 actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
1219 1219 'versions differ')
1220 1220 elif n1: # file exists only on local side
1221 1221 if f in copied:
1222 1222 pass # we'll deal with it on m2 side
1223 1223 elif f in movewithdir: # directory rename, move local
1224 1224 f2 = movewithdir[f]
1225 1225 if f2 in m2:
1226 1226 actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
1227 1227 'remote directory rename, both created')
1228 1228 else:
1229 1229 actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
1230 1230 'remote directory rename - move from %s' % f)
1231 1231 elif f in copy:
1232 1232 f2 = copy[f]
1233 1233 actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
1234 1234 'local copied/moved from %s' % f2)
1235 1235 elif f in ma: # clean, a different, no remote
1236 1236 if n1 != ma[f]:
1237 1237 if acceptremote:
1238 1238 actions[f] = (ACTION_REMOVE, None, 'remote delete')
1239 1239 else:
1240 1240 actions[f] = (ACTION_CHANGED_DELETED,
1241 1241 (f, None, f, False, pa.node()),
1242 1242 'prompt changed/deleted')
1243 1243 elif n1 == addednodeid:
1244 1244 # This extra 'a' is added by working copy manifest to mark
1245 1245 # the file as locally added. We should forget it instead of
1246 1246 # deleting it.
1247 1247 actions[f] = (ACTION_FORGET, None, 'remote deleted')
1248 1248 else:
1249 1249 actions[f] = (ACTION_REMOVE, None, 'other deleted')
1250 1250 elif n2: # file exists only on remote side
1251 1251 if f in copied:
1252 1252 pass # we'll deal with it on m1 side
1253 1253 elif f in movewithdir:
1254 1254 f2 = movewithdir[f]
1255 1255 if f2 in m1:
1256 1256 actions[f2] = (ACTION_MERGE,
1257 1257 (f2, f, None, False, pa.node()),
1258 1258 'local directory rename, both created')
1259 1259 else:
1260 1260 actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
1261 1261 'local directory rename - get from %s' % f)
1262 1262 elif f in copy:
1263 1263 f2 = copy[f]
1264 1264 if f2 in m2:
1265 1265 actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
1266 1266 'remote copied from %s' % f2)
1267 1267 else:
1268 1268 actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
1269 1269 'remote moved from %s' % f2)
1270 1270 elif f not in ma:
1271 1271 # local unknown, remote created: the logic is described by the
1272 1272 # following table:
1273 1273 #
1274 1274 # force branchmerge different | action
1275 1275 # n * * | create
1276 1276 # y n * | create
1277 1277 # y y n | create
1278 1278 # y y y | merge
1279 1279 #
1280 1280 # Checking whether the files are different is expensive, so we
1281 1281 # don't do that when we can avoid it.
1282 1282 if not force:
1283 1283 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1284 1284 elif not branchmerge:
1285 1285 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1286 1286 else:
1287 1287 actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
1288 1288 'remote created, get or merge')
1289 1289 elif n2 != ma[f]:
1290 1290 df = None
1291 1291 for d in dirmove:
1292 1292 if f.startswith(d):
1293 1293 # new file added in a directory that was moved
1294 1294 df = dirmove[d] + f[len(d):]
1295 1295 break
1296 1296 if df is not None and df in m1:
1297 1297 actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
1298 1298 'local directory rename - respect move '
1299 1299 'from %s' % f)
1300 1300 elif acceptremote:
1301 1301 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
1302 1302 else:
1303 1303 actions[f] = (ACTION_DELETED_CHANGED,
1304 1304 (None, f, f, False, pa.node()),
1305 1305 'prompt deleted/changed')
1306 1306
1307 1307 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1308 1308 # If we are merging, look for path conflicts.
1309 1309 checkpathconflicts(repo, wctx, p2, actions)
1310 1310
1311 1311 narrowmatch = repo.narrowmatch()
1312 1312 if not narrowmatch.always():
1313 1313 # Updates "actions" in place
1314 1314 _filternarrowactions(narrowmatch, branchmerge, actions)
1315 1315
1316 1316 return actions, diverge, renamedelete
1317 1317
1318 1318 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1319 1319 """Resolves false conflicts where the nodeid changed but the content
1320 1320 remained the same."""
1321 1321 # We force a copy of actions.items() because we're going to mutate
1322 1322 # actions as we resolve trivial conflicts.
1323 1323 for f, (m, args, msg) in list(actions.items()):
1324 1324 if (m == ACTION_CHANGED_DELETED and f in ancestor
1325 1325 and not wctx[f].cmp(ancestor[f])):
1326 1326 # local did change but ended up with same content
1327 1327 actions[f] = ACTION_REMOVE, None, 'prompt same'
1328 1328 elif (m == ACTION_DELETED_CHANGED and f in ancestor
1329 1329 and not mctx[f].cmp(ancestor[f])):
1330 1330 # remote did change but ended up with same content
1331 1331 del actions[f] # don't get = keep local deleted
1332 1332
1333 1333 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1334 1334 acceptremote, followcopies, matcher=None,
1335 1335 mergeforce=False):
1336 1336 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1337 1337 # Avoid cycle.
1338 1338 from . import sparse
1339 1339
1340 1340 if len(ancestors) == 1: # default
1341 1341 actions, diverge, renamedelete = manifestmerge(
1342 1342 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1343 1343 acceptremote, followcopies)
1344 1344 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1345 1345
1346 1346 else: # only when merge.preferancestor=* - the default
1347 1347 repo.ui.note(
1348 1348 _("note: merging %s and %s using bids from ancestors %s\n") %
1349 1349 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1350 1350 for anc in ancestors)))
1351 1351
1352 1352 # Call for bids
1353 1353 fbids = {} # mapping filename to bids (action method to list af actions)
1354 1354 diverge, renamedelete = None, None
1355 1355 for ancestor in ancestors:
1356 1356 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1357 1357 actions, diverge1, renamedelete1 = manifestmerge(
1358 1358 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1359 1359 acceptremote, followcopies, forcefulldiff=True)
1360 1360 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1361 1361
1362 1362 # Track the shortest set of warning on the theory that bid
1363 1363 # merge will correctly incorporate more information
1364 1364 if diverge is None or len(diverge1) < len(diverge):
1365 1365 diverge = diverge1
1366 1366 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1367 1367 renamedelete = renamedelete1
1368 1368
1369 1369 for f, a in sorted(actions.iteritems()):
1370 1370 m, args, msg = a
1371 1371 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1372 1372 if f in fbids:
1373 1373 d = fbids[f]
1374 1374 if m in d:
1375 1375 d[m].append(a)
1376 1376 else:
1377 1377 d[m] = [a]
1378 1378 else:
1379 1379 fbids[f] = {m: [a]}
1380 1380
1381 1381 # Pick the best bid for each file
1382 1382 repo.ui.note(_('\nauction for merging merge bids\n'))
1383 1383 actions = {}
1384 1384 for f, bids in sorted(fbids.items()):
1385 1385 # bids is a mapping from action method to list af actions
1386 1386 # Consensus?
1387 1387 if len(bids) == 1: # all bids are the same kind of method
1388 1388 m, l = list(bids.items())[0]
1389 1389 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1390 1390 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1391 1391 actions[f] = l[0]
1392 1392 continue
1393 1393 # If keep is an option, just do it.
1394 1394 if ACTION_KEEP in bids:
1395 1395 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1396 1396 actions[f] = bids[ACTION_KEEP][0]
1397 1397 continue
1398 1398 # If there are gets and they all agree [how could they not?], do it.
1399 1399 if ACTION_GET in bids:
1400 1400 ga0 = bids[ACTION_GET][0]
1401 1401 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1402 1402 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1403 1403 actions[f] = ga0
1404 1404 continue
1405 1405 # TODO: Consider other simple actions such as mode changes
1406 1406 # Handle inefficient democrazy.
1407 1407 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1408 1408 for m, l in sorted(bids.items()):
1409 1409 for _f, args, msg in l:
1410 1410 repo.ui.note(' %s -> %s\n' % (msg, m))
1411 1411 # Pick random action. TODO: Instead, prompt user when resolving
1412 1412 m, l = list(bids.items())[0]
1413 1413 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1414 1414 (f, m))
1415 1415 actions[f] = l[0]
1416 1416 continue
1417 1417 repo.ui.note(_('end of auction\n\n'))
1418 1418
1419 1419 if wctx.rev() is None:
1420 1420 fractions = _forgetremoved(wctx, mctx, branchmerge)
1421 1421 actions.update(fractions)
1422 1422
1423 1423 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1424 1424 actions)
1425 1425 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1426 1426
1427 1427 return prunedactions, diverge, renamedelete
1428 1428
1429 1429 def _getcwd():
1430 1430 try:
1431 1431 return encoding.getcwd()
1432 1432 except OSError as err:
1433 1433 if err.errno == errno.ENOENT:
1434 1434 return None
1435 1435 raise
1436 1436
1437 1437 def batchremove(repo, wctx, actions):
1438 1438 """apply removes to the working directory
1439 1439
1440 1440 yields tuples for progress updates
1441 1441 """
1442 1442 verbose = repo.ui.verbose
1443 1443 cwd = _getcwd()
1444 1444 i = 0
1445 1445 for f, args, msg in actions:
1446 1446 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1447 1447 if verbose:
1448 1448 repo.ui.note(_("removing %s\n") % f)
1449 1449 wctx[f].audit()
1450 1450 try:
1451 1451 wctx[f].remove(ignoremissing=True)
1452 1452 except OSError as inst:
1453 1453 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1454 1454 (f, inst.strerror))
1455 1455 if i == 100:
1456 1456 yield i, f
1457 1457 i = 0
1458 1458 i += 1
1459 1459 if i > 0:
1460 1460 yield i, f
1461 1461
1462 1462 if cwd and not _getcwd():
1463 1463 # cwd was removed in the course of removing files; print a helpful
1464 1464 # warning.
1465 1465 repo.ui.warn(_("current directory was removed\n"
1466 1466 "(consider changing to repo root: %s)\n") % repo.root)
1467 1467
1468 1468 def batchget(repo, mctx, wctx, wantfiledata, actions):
1469 1469 """apply gets to the working directory
1470 1470
1471 1471 mctx is the context to get from
1472 1472
1473 1473 Yields arbitrarily many (False, tuple) for progress updates, followed by
1474 1474 exactly one (True, filedata). When wantfiledata is false, filedata is an
1475 1475 empty dict. When wantfiledata is true, filedata[f] is a triple (mode, size,
1476 1476 mtime) of the file f written for each action.
1477 1477 """
1478 1478 filedata = {}
1479 1479 verbose = repo.ui.verbose
1480 1480 fctx = mctx.filectx
1481 1481 ui = repo.ui
1482 1482 i = 0
1483 1483 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1484 1484 for f, (flags, backup), msg in actions:
1485 1485 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1486 1486 if verbose:
1487 1487 repo.ui.note(_("getting %s\n") % f)
1488 1488
1489 1489 if backup:
1490 1490 # If a file or directory exists with the same name, back that
1491 1491 # up. Otherwise, look to see if there is a file that conflicts
1492 1492 # with a directory this file is in, and if so, back that up.
1493 1493 conflicting = f
1494 1494 if not repo.wvfs.lexists(f):
1495 1495 for p in util.finddirs(f):
1496 1496 if repo.wvfs.isfileorlink(p):
1497 1497 conflicting = p
1498 1498 break
1499 1499 if repo.wvfs.lexists(conflicting):
1500 1500 orig = scmutil.backuppath(ui, repo, conflicting)
1501 1501 util.rename(repo.wjoin(conflicting), orig)
1502 1502 wfctx = wctx[f]
1503 1503 wfctx.clearunknown()
1504 1504 atomictemp = ui.configbool("experimental", "update.atomic-file")
1505 1505 size = wfctx.write(fctx(f).data(), flags,
1506 1506 backgroundclose=True,
1507 1507 atomictemp=atomictemp)
1508 1508 if wantfiledata:
1509 1509 s = wfctx.lstat()
1510 1510 mode = s.st_mode
1511 1511 mtime = s[stat.ST_MTIME]
1512 1512 filedata[f] = ((mode, size, mtime)) # for dirstate.normal
1513 1513 if i == 100:
1514 1514 yield False, (i, f)
1515 1515 i = 0
1516 1516 i += 1
1517 1517 if i > 0:
1518 1518 yield False, (i, f)
1519 1519 yield True, filedata
1520 1520
1521 1521 def _prefetchfiles(repo, ctx, actions):
1522 1522 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1523 1523 of merge actions. ``ctx`` is the context being merged in."""
1524 1524
1525 1525 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1526 1526 # don't touch the context to be merged in. 'cd' is skipped, because
1527 1527 # changed/deleted never resolves to something from the remote side.
1528 1528 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
1529 1529 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
1530 1530 prefetch = scmutil.prefetchfiles
1531 1531 matchfiles = scmutil.matchfiles
1532 1532 prefetch(repo, [ctx.rev()],
1533 1533 matchfiles(repo,
1534 1534 [f for sublist in oplist for f, args, msg in sublist]))
1535 1535
1536 1536 @attr.s(frozen=True)
1537 1537 class updateresult(object):
1538 1538 updatedcount = attr.ib()
1539 1539 mergedcount = attr.ib()
1540 1540 removedcount = attr.ib()
1541 1541 unresolvedcount = attr.ib()
1542 1542
1543 1543 def isempty(self):
1544 1544 return not (self.updatedcount or self.mergedcount
1545 1545 or self.removedcount or self.unresolvedcount)
1546 1546
1547 1547 def emptyactions():
1548 1548 """create an actions dict, to be populated and passed to applyupdates()"""
1549 1549 return dict((m, [])
1550 1550 for m in (
1551 1551 ACTION_ADD,
1552 1552 ACTION_ADD_MODIFIED,
1553 1553 ACTION_FORGET,
1554 1554 ACTION_GET,
1555 1555 ACTION_CHANGED_DELETED,
1556 1556 ACTION_DELETED_CHANGED,
1557 1557 ACTION_REMOVE,
1558 1558 ACTION_DIR_RENAME_MOVE_LOCAL,
1559 1559 ACTION_LOCAL_DIR_RENAME_GET,
1560 1560 ACTION_MERGE,
1561 1561 ACTION_EXEC,
1562 1562 ACTION_KEEP,
1563 1563 ACTION_PATH_CONFLICT,
1564 1564 ACTION_PATH_CONFLICT_RESOLVE))
1565 1565
1566 1566 def applyupdates(repo, actions, wctx, mctx, overwrite, wantfiledata,
1567 1567 labels=None):
1568 1568 """apply the merge action list to the working directory
1569 1569
1570 1570 wctx is the working copy context
1571 1571 mctx is the context to be merged into the working copy
1572 1572
1573 1573 Return a tuple of (counts, filedata), where counts is a tuple
1574 1574 (updated, merged, removed, unresolved) that describes how many
1575 1575 files were affected by the update, and filedata is as described in
1576 1576 batchget.
1577 1577 """
1578 1578
1579 1579 _prefetchfiles(repo, mctx, actions)
1580 1580
1581 1581 updated, merged, removed = 0, 0, 0
1582 1582 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1583 1583 moves = []
1584 1584 for m, l in actions.items():
1585 1585 l.sort()
1586 1586
1587 1587 # 'cd' and 'dc' actions are treated like other merge conflicts
1588 1588 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1589 1589 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1590 1590 mergeactions.extend(actions[ACTION_MERGE])
1591 1591 for f, args, msg in mergeactions:
1592 1592 f1, f2, fa, move, anc = args
1593 1593 if f == '.hgsubstate': # merged internally
1594 1594 continue
1595 1595 if f1 is None:
1596 1596 fcl = filemerge.absentfilectx(wctx, fa)
1597 1597 else:
1598 1598 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1599 1599 fcl = wctx[f1]
1600 1600 if f2 is None:
1601 1601 fco = filemerge.absentfilectx(mctx, fa)
1602 1602 else:
1603 1603 fco = mctx[f2]
1604 1604 actx = repo[anc]
1605 1605 if fa in actx:
1606 1606 fca = actx[fa]
1607 1607 else:
1608 1608 # TODO: move to absentfilectx
1609 1609 fca = repo.filectx(f1, fileid=nullrev)
1610 1610 ms.add(fcl, fco, fca, f)
1611 1611 if f1 != f and move:
1612 1612 moves.append(f1)
1613 1613
1614 1614 # remove renamed files after safely stored
1615 1615 for f in moves:
1616 1616 if wctx[f].lexists():
1617 1617 repo.ui.debug("removing %s\n" % f)
1618 1618 wctx[f].audit()
1619 1619 wctx[f].remove()
1620 1620
1621 1621 numupdates = sum(len(l) for m, l in actions.items()
1622 1622 if m != ACTION_KEEP)
1623 1623 progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
1624 1624 total=numupdates)
1625 1625
1626 1626 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
1627 1627 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1628 1628
1629 1629 # record path conflicts
1630 1630 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1631 1631 f1, fo = args
1632 1632 s = repo.ui.status
1633 1633 s(_("%s: path conflict - a file or link has the same name as a "
1634 1634 "directory\n") % f)
1635 1635 if fo == 'l':
1636 1636 s(_("the local file has been renamed to %s\n") % f1)
1637 1637 else:
1638 1638 s(_("the remote file has been renamed to %s\n") % f1)
1639 1639 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1640 1640 ms.addpath(f, f1, fo)
1641 1641 progress.increment(item=f)
1642 1642
1643 1643 # When merging in-memory, we can't support worker processes, so set the
1644 1644 # per-item cost at 0 in that case.
1645 1645 cost = 0 if wctx.isinmemory() else 0.001
1646 1646
1647 1647 # remove in parallel (must come before resolving path conflicts and getting)
1648 1648 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1649 1649 actions[ACTION_REMOVE])
1650 1650 for i, item in prog:
1651 1651 progress.increment(step=i, item=item)
1652 1652 removed = len(actions[ACTION_REMOVE])
1653 1653
1654 1654 # resolve path conflicts (must come before getting)
1655 1655 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1656 1656 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1657 1657 f0, = args
1658 1658 if wctx[f0].lexists():
1659 1659 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1660 1660 wctx[f].audit()
1661 1661 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1662 1662 wctx[f0].remove()
1663 1663 progress.increment(item=f)
1664 1664
1665 1665 # get in parallel.
1666 1666 threadsafe = repo.ui.configbool('experimental',
1667 1667 'worker.wdir-get-thread-safe')
1668 1668 prog = worker.worker(repo.ui, cost, batchget,
1669 1669 (repo, mctx, wctx, wantfiledata),
1670 1670 actions[ACTION_GET],
1671 1671 threadsafe=threadsafe,
1672 1672 hasretval=True)
1673 1673 getfiledata = {}
1674 1674 for final, res in prog:
1675 1675 if final:
1676 1676 getfiledata = res
1677 1677 else:
1678 1678 i, item = res
1679 1679 progress.increment(step=i, item=item)
1680 1680 updated = len(actions[ACTION_GET])
1681 1681
1682 1682 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
1683 1683 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1684 1684
1685 1685 # forget (manifest only, just log it) (must come first)
1686 1686 for f, args, msg in actions[ACTION_FORGET]:
1687 1687 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1688 1688 progress.increment(item=f)
1689 1689
1690 1690 # re-add (manifest only, just log it)
1691 1691 for f, args, msg in actions[ACTION_ADD]:
1692 1692 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1693 1693 progress.increment(item=f)
1694 1694
1695 1695 # re-add/mark as modified (manifest only, just log it)
1696 1696 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1697 1697 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1698 1698 progress.increment(item=f)
1699 1699
1700 1700 # keep (noop, just log it)
1701 1701 for f, args, msg in actions[ACTION_KEEP]:
1702 1702 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1703 1703 # no progress
1704 1704
1705 1705 # directory rename, move local
1706 1706 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1707 1707 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1708 1708 progress.increment(item=f)
1709 1709 f0, flags = args
1710 1710 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1711 1711 wctx[f].audit()
1712 1712 wctx[f].write(wctx.filectx(f0).data(), flags)
1713 1713 wctx[f0].remove()
1714 1714 updated += 1
1715 1715
1716 1716 # local directory rename, get
1717 1717 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1718 1718 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1719 1719 progress.increment(item=f)
1720 1720 f0, flags = args
1721 1721 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1722 1722 wctx[f].write(mctx.filectx(f0).data(), flags)
1723 1723 updated += 1
1724 1724
1725 1725 # exec
1726 1726 for f, args, msg in actions[ACTION_EXEC]:
1727 1727 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1728 1728 progress.increment(item=f)
1729 1729 flags, = args
1730 1730 wctx[f].audit()
1731 1731 wctx[f].setflags('l' in flags, 'x' in flags)
1732 1732 updated += 1
1733 1733
1734 1734 # the ordering is important here -- ms.mergedriver will raise if the merge
1735 1735 # driver has changed, and we want to be able to bypass it when overwrite is
1736 1736 # True
1737 1737 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1738 1738
1739 1739 if usemergedriver:
1740 1740 if wctx.isinmemory():
1741 1741 raise error.InMemoryMergeConflictsError("in-memory merge does not "
1742 1742 "support mergedriver")
1743 1743 ms.commit()
1744 1744 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1745 1745 # the driver might leave some files unresolved
1746 1746 unresolvedf = set(ms.unresolved())
1747 1747 if not proceed:
1748 1748 # XXX setting unresolved to at least 1 is a hack to make sure we
1749 1749 # error out
1750 1750 return updateresult(updated, merged, removed,
1751 1751 max(len(unresolvedf), 1))
1752 1752 newactions = []
1753 1753 for f, args, msg in mergeactions:
1754 1754 if f in unresolvedf:
1755 1755 newactions.append((f, args, msg))
1756 1756 mergeactions = newactions
1757 1757
1758 1758 try:
1759 1759 # premerge
1760 1760 tocomplete = []
1761 1761 for f, args, msg in mergeactions:
1762 1762 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1763 1763 progress.increment(item=f)
1764 1764 if f == '.hgsubstate': # subrepo states need updating
1765 1765 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1766 1766 overwrite, labels)
1767 1767 continue
1768 1768 wctx[f].audit()
1769 1769 complete, r = ms.preresolve(f, wctx)
1770 1770 if not complete:
1771 1771 numupdates += 1
1772 1772 tocomplete.append((f, args, msg))
1773 1773
1774 1774 # merge
1775 1775 for f, args, msg in tocomplete:
1776 1776 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1777 1777 progress.increment(item=f, total=numupdates)
1778 1778 ms.resolve(f, wctx)
1779 1779
1780 1780 finally:
1781 1781 ms.commit()
1782 1782
1783 1783 unresolved = ms.unresolvedcount()
1784 1784
1785 1785 if (usemergedriver and not unresolved
1786 1786 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
1787 1787 if not driverconclude(repo, ms, wctx, labels=labels):
1788 1788 # XXX setting unresolved to at least 1 is a hack to make sure we
1789 1789 # error out
1790 1790 unresolved = max(unresolved, 1)
1791 1791
1792 1792 ms.commit()
1793 1793
1794 1794 msupdated, msmerged, msremoved = ms.counts()
1795 1795 updated += msupdated
1796 1796 merged += msmerged
1797 1797 removed += msremoved
1798 1798
1799 1799 extraactions = ms.actions()
1800 1800 if extraactions:
1801 1801 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1802 1802 for k, acts in extraactions.iteritems():
1803 1803 actions[k].extend(acts)
1804 1804 if k == ACTION_GET and wantfiledata:
1805 1805 # no filedata until mergestate is updated to provide it
1806 1806 for a in acts:
1807 1807 getfiledata[a[0]] = None
1808 1808 # Remove these files from actions[ACTION_MERGE] as well. This is
1809 1809 # important because in recordupdates, files in actions[ACTION_MERGE]
1810 1810 # are processed after files in other actions, and the merge driver
1811 1811 # might add files to those actions via extraactions above. This can
1812 1812 # lead to a file being recorded twice, with poor results. This is
1813 1813 # especially problematic for actions[ACTION_REMOVE] (currently only
1814 1814 # possible with the merge driver in the initial merge process;
1815 1815 # interrupted merges don't go through this flow).
1816 1816 #
1817 1817 # The real fix here is to have indexes by both file and action so
1818 1818 # that when the action for a file is changed it is automatically
1819 1819 # reflected in the other action lists. But that involves a more
1820 1820 # complex data structure, so this will do for now.
1821 1821 #
1822 1822 # We don't need to do the same operation for 'dc' and 'cd' because
1823 1823 # those lists aren't consulted again.
1824 1824 mfiles.difference_update(a[0] for a in acts)
1825 1825
1826 1826 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
1827 1827 if a[0] in mfiles]
1828 1828
1829 1829 progress.complete()
1830 1830 assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
1831 1831 return updateresult(updated, merged, removed, unresolved), getfiledata
1832 1832
1833 1833 def recordupdates(repo, actions, branchmerge, getfiledata):
1834 1834 "record merge actions to the dirstate"
1835 1835 # remove (must come first)
1836 1836 for f, args, msg in actions.get(ACTION_REMOVE, []):
1837 1837 if branchmerge:
1838 1838 repo.dirstate.remove(f)
1839 1839 else:
1840 1840 repo.dirstate.drop(f)
1841 1841
1842 1842 # forget (must come first)
1843 1843 for f, args, msg in actions.get(ACTION_FORGET, []):
1844 1844 repo.dirstate.drop(f)
1845 1845
1846 1846 # resolve path conflicts
1847 1847 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
1848 1848 f0, = args
1849 1849 origf0 = repo.dirstate.copied(f0) or f0
1850 1850 repo.dirstate.add(f)
1851 1851 repo.dirstate.copy(origf0, f)
1852 1852 if f0 == origf0:
1853 1853 repo.dirstate.remove(f0)
1854 1854 else:
1855 1855 repo.dirstate.drop(f0)
1856 1856
1857 1857 # re-add
1858 1858 for f, args, msg in actions.get(ACTION_ADD, []):
1859 1859 repo.dirstate.add(f)
1860 1860
1861 1861 # re-add/mark as modified
1862 1862 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
1863 1863 if branchmerge:
1864 1864 repo.dirstate.normallookup(f)
1865 1865 else:
1866 1866 repo.dirstate.add(f)
1867 1867
1868 1868 # exec change
1869 1869 for f, args, msg in actions.get(ACTION_EXEC, []):
1870 1870 repo.dirstate.normallookup(f)
1871 1871
1872 1872 # keep
1873 1873 for f, args, msg in actions.get(ACTION_KEEP, []):
1874 1874 pass
1875 1875
1876 1876 # get
1877 1877 for f, args, msg in actions.get(ACTION_GET, []):
1878 1878 if branchmerge:
1879 1879 repo.dirstate.otherparent(f)
1880 1880 else:
1881 1881 parentfiledata = getfiledata[f] if getfiledata else None
1882 1882 repo.dirstate.normal(f, parentfiledata=parentfiledata)
1883 1883
1884 1884 # merge
1885 1885 for f, args, msg in actions.get(ACTION_MERGE, []):
1886 1886 f1, f2, fa, move, anc = args
1887 1887 if branchmerge:
1888 1888 # We've done a branch merge, mark this file as merged
1889 1889 # so that we properly record the merger later
1890 1890 repo.dirstate.merge(f)
1891 1891 if f1 != f2: # copy/rename
1892 1892 if move:
1893 1893 repo.dirstate.remove(f1)
1894 1894 if f1 != f:
1895 1895 repo.dirstate.copy(f1, f)
1896 1896 else:
1897 1897 repo.dirstate.copy(f2, f)
1898 1898 else:
1899 1899 # We've update-merged a locally modified file, so
1900 1900 # we set the dirstate to emulate a normal checkout
1901 1901 # of that file some time in the past. Thus our
1902 1902 # merge will appear as a normal local file
1903 1903 # modification.
1904 1904 if f2 == f: # file not locally copied/moved
1905 1905 repo.dirstate.normallookup(f)
1906 1906 if move:
1907 1907 repo.dirstate.drop(f1)
1908 1908
1909 1909 # directory rename, move local
1910 1910 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
1911 1911 f0, flag = args
1912 1912 if branchmerge:
1913 1913 repo.dirstate.add(f)
1914 1914 repo.dirstate.remove(f0)
1915 1915 repo.dirstate.copy(f0, f)
1916 1916 else:
1917 1917 repo.dirstate.normal(f)
1918 1918 repo.dirstate.drop(f0)
1919 1919
1920 1920 # directory rename, get
1921 1921 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
1922 1922 f0, flag = args
1923 1923 if branchmerge:
1924 1924 repo.dirstate.add(f)
1925 1925 repo.dirstate.copy(f0, f)
1926 1926 else:
1927 1927 repo.dirstate.normal(f)
1928 1928
1929 1929 def update(repo, node, branchmerge, force, ancestor=None,
1930 1930 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1931 1931 updatecheck=None, wc=None):
1932 1932 """
1933 1933 Perform a merge between the working directory and the given node
1934 1934
1935 1935 node = the node to update to
1936 1936 branchmerge = whether to merge between branches
1937 1937 force = whether to force branch merging or file overwriting
1938 1938 matcher = a matcher to filter file lists (dirstate not updated)
1939 1939 mergeancestor = whether it is merging with an ancestor. If true,
1940 1940 we should accept the incoming changes for any prompts that occur.
1941 1941 If false, merging with an ancestor (fast-forward) is only allowed
1942 1942 between different named branches. This flag is used by rebase extension
1943 1943 as a temporary fix and should be avoided in general.
1944 1944 labels = labels to use for base, local and other
1945 1945 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1946 1946 this is True, then 'force' should be True as well.
1947 1947
1948 1948 The table below shows all the behaviors of the update command given the
1949 1949 -c/--check and -C/--clean or no options, whether the working directory is
1950 1950 dirty, whether a revision is specified, and the relationship of the parent
1951 1951 rev to the target rev (linear or not). Match from top first. The -n
1952 1952 option doesn't exist on the command line, but represents the
1953 1953 experimental.updatecheck=noconflict option.
1954 1954
1955 1955 This logic is tested by test-update-branches.t.
1956 1956
1957 1957 -c -C -n -m dirty rev linear | result
1958 1958 y y * * * * * | (1)
1959 1959 y * y * * * * | (1)
1960 1960 y * * y * * * | (1)
1961 1961 * y y * * * * | (1)
1962 1962 * y * y * * * | (1)
1963 1963 * * y y * * * | (1)
1964 1964 * * * * * n n | x
1965 1965 * * * * n * * | ok
1966 1966 n n n n y * y | merge
1967 1967 n n n n y y n | (2)
1968 1968 n n n y y * * | merge
1969 1969 n n y n y * * | merge if no conflict
1970 1970 n y n n y * * | discard
1971 1971 y n n n y * * | (3)
1972 1972
1973 1973 x = can't happen
1974 1974 * = don't-care
1975 1975 1 = incompatible options (checked in commands.py)
1976 1976 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1977 1977 3 = abort: uncommitted changes (checked in commands.py)
1978 1978
1979 1979 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1980 1980 to repo[None] if None is passed.
1981 1981
1982 1982 Return the same tuple as applyupdates().
1983 1983 """
1984 1984 # Avoid cycle.
1985 1985 from . import sparse
1986 1986
1987 1987 # This function used to find the default destination if node was None, but
1988 1988 # that's now in destutil.py.
1989 1989 assert node is not None
1990 1990 if not branchmerge and not force:
1991 1991 # TODO: remove the default once all callers that pass branchmerge=False
1992 1992 # and force=False pass a value for updatecheck. We may want to allow
1993 1993 # updatecheck='abort' to better suppport some of these callers.
1994 1994 if updatecheck is None:
1995 1995 updatecheck = 'linear'
1996 1996 assert updatecheck in ('none', 'linear', 'noconflict')
1997 1997 # If we're doing a partial update, we need to skip updating
1998 1998 # the dirstate, so make a note of any partial-ness to the
1999 1999 # update here.
2000 2000 if matcher is None or matcher.always():
2001 2001 partial = False
2002 2002 else:
2003 2003 partial = True
2004 2004 with repo.wlock():
2005 2005 if wc is None:
2006 2006 wc = repo[None]
2007 2007 pl = wc.parents()
2008 2008 p1 = pl[0]
2009 2009 p2 = repo[node]
2010 2010 if ancestor is not None:
2011 2011 pas = [repo[ancestor]]
2012 2012 else:
2013 2013 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
2014 2014 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2015 2015 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2016 2016 else:
2017 2017 pas = [p1.ancestor(p2, warn=branchmerge)]
2018 2018
2019 2019 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2020 2020
2021 2021 overwrite = force and not branchmerge
2022 2022 ### check phase
2023 2023 if not overwrite:
2024 2024 if len(pl) > 1:
2025 2025 raise error.Abort(_("outstanding uncommitted merge"))
2026 2026 ms = mergestate.read(repo)
2027 2027 if list(ms.unresolved()):
2028 raise error.Abort(_("outstanding merge conflicts"))
2028 raise error.Abort(_("outstanding merge conflicts"),
2029 hint=_("use 'hg resolve' to resolve"))
2029 2030 if branchmerge:
2030 2031 if pas == [p2]:
2031 2032 raise error.Abort(_("merging with a working directory ancestor"
2032 2033 " has no effect"))
2033 2034 elif pas == [p1]:
2034 2035 if not mergeancestor and wc.branch() == p2.branch():
2035 2036 raise error.Abort(_("nothing to merge"),
2036 2037 hint=_("use 'hg update' "
2037 2038 "or check 'hg heads'"))
2038 2039 if not force and (wc.files() or wc.deleted()):
2039 2040 raise error.Abort(_("uncommitted changes"),
2040 2041 hint=_("use 'hg status' to list changes"))
2041 2042 if not wc.isinmemory():
2042 2043 for s in sorted(wc.substate):
2043 2044 wc.sub(s).bailifchanged()
2044 2045
2045 2046 elif not overwrite:
2046 2047 if p1 == p2: # no-op update
2047 2048 # call the hooks and exit early
2048 2049 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
2049 2050 repo.hook('update', parent1=xp2, parent2='', error=0)
2050 2051 return updateresult(0, 0, 0, 0)
2051 2052
2052 2053 if (updatecheck == 'linear' and
2053 2054 pas not in ([p1], [p2])): # nonlinear
2054 2055 dirty = wc.dirty(missing=True)
2055 2056 if dirty:
2056 2057 # Branching is a bit strange to ensure we do the minimal
2057 2058 # amount of call to obsutil.foreground.
2058 2059 foreground = obsutil.foreground(repo, [p1.node()])
2059 2060 # note: the <node> variable contains a random identifier
2060 2061 if repo[node].node() in foreground:
2061 2062 pass # allow updating to successors
2062 2063 else:
2063 2064 msg = _("uncommitted changes")
2064 2065 hint = _("commit or update --clean to discard changes")
2065 2066 raise error.UpdateAbort(msg, hint=hint)
2066 2067 else:
2067 2068 # Allow jumping branches if clean and specific rev given
2068 2069 pass
2069 2070
2070 2071 if overwrite:
2071 2072 pas = [wc]
2072 2073 elif not branchmerge:
2073 2074 pas = [p1]
2074 2075
2075 2076 # deprecated config: merge.followcopies
2076 2077 followcopies = repo.ui.configbool('merge', 'followcopies')
2077 2078 if overwrite:
2078 2079 followcopies = False
2079 2080 elif not pas[0]:
2080 2081 followcopies = False
2081 2082 if not branchmerge and not wc.dirty(missing=True):
2082 2083 followcopies = False
2083 2084
2084 2085 ### calculate phase
2085 2086 actionbyfile, diverge, renamedelete = calculateupdates(
2086 2087 repo, wc, p2, pas, branchmerge, force, mergeancestor,
2087 2088 followcopies, matcher=matcher, mergeforce=mergeforce)
2088 2089
2089 2090 if updatecheck == 'noconflict':
2090 2091 for f, (m, args, msg) in actionbyfile.iteritems():
2091 2092 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
2092 2093 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
2093 2094 msg = _("conflicting changes")
2094 2095 hint = _("commit or update --clean to discard changes")
2095 2096 raise error.Abort(msg, hint=hint)
2096 2097
2097 2098 # Prompt and create actions. Most of this is in the resolve phase
2098 2099 # already, but we can't handle .hgsubstate in filemerge or
2099 2100 # subrepoutil.submerge yet so we have to keep prompting for it.
2100 2101 if '.hgsubstate' in actionbyfile:
2101 2102 f = '.hgsubstate'
2102 2103 m, args, msg = actionbyfile[f]
2103 2104 prompts = filemerge.partextras(labels)
2104 2105 prompts['f'] = f
2105 2106 if m == ACTION_CHANGED_DELETED:
2106 2107 if repo.ui.promptchoice(
2107 2108 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
2108 2109 "use (c)hanged version or (d)elete?"
2109 2110 "$$ &Changed $$ &Delete") % prompts, 0):
2110 2111 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
2111 2112 elif f in p1:
2112 2113 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
2113 2114 else:
2114 2115 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep')
2115 2116 elif m == ACTION_DELETED_CHANGED:
2116 2117 f1, f2, fa, move, anc = args
2117 2118 flags = p2[f2].flags()
2118 2119 if repo.ui.promptchoice(
2119 2120 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
2120 2121 "use (c)hanged version or leave (d)eleted?"
2121 2122 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
2122 2123 actionbyfile[f] = (ACTION_GET, (flags, False),
2123 2124 'prompt recreating')
2124 2125 else:
2125 2126 del actionbyfile[f]
2126 2127
2127 2128 # Convert to dictionary-of-lists format
2128 2129 actions = emptyactions()
2129 2130 for f, (m, args, msg) in actionbyfile.iteritems():
2130 2131 if m not in actions:
2131 2132 actions[m] = []
2132 2133 actions[m].append((f, args, msg))
2133 2134
2134 2135 if not util.fscasesensitive(repo.path):
2135 2136 # check collision between files only in p2 for clean update
2136 2137 if (not branchmerge and
2137 2138 (force or not wc.dirty(missing=True, branch=False))):
2138 2139 _checkcollision(repo, p2.manifest(), None)
2139 2140 else:
2140 2141 _checkcollision(repo, wc.manifest(), actions)
2141 2142
2142 2143 # divergent renames
2143 2144 for f, fl in sorted(diverge.iteritems()):
2144 2145 repo.ui.warn(_("note: possible conflict - %s was renamed "
2145 2146 "multiple times to:\n") % f)
2146 2147 for nf in sorted(fl):
2147 2148 repo.ui.warn(" %s\n" % nf)
2148 2149
2149 2150 # rename and delete
2150 2151 for f, fl in sorted(renamedelete.iteritems()):
2151 2152 repo.ui.warn(_("note: possible conflict - %s was deleted "
2152 2153 "and renamed to:\n") % f)
2153 2154 for nf in sorted(fl):
2154 2155 repo.ui.warn(" %s\n" % nf)
2155 2156
2156 2157 ### apply phase
2157 2158 if not branchmerge: # just jump to the new rev
2158 2159 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
2159 2160 if not partial and not wc.isinmemory():
2160 2161 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
2161 2162 # note that we're in the middle of an update
2162 2163 repo.vfs.write('updatestate', p2.hex())
2163 2164
2164 2165 # Advertise fsmonitor when its presence could be useful.
2165 2166 #
2166 2167 # We only advertise when performing an update from an empty working
2167 2168 # directory. This typically only occurs during initial clone.
2168 2169 #
2169 2170 # We give users a mechanism to disable the warning in case it is
2170 2171 # annoying.
2171 2172 #
2172 2173 # We only allow on Linux and MacOS because that's where fsmonitor is
2173 2174 # considered stable.
2174 2175 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
2175 2176 fsmonitorthreshold = repo.ui.configint('fsmonitor',
2176 2177 'warn_update_file_count')
2177 2178 try:
2178 2179 # avoid cycle: extensions -> cmdutil -> merge
2179 2180 from . import extensions
2180 2181 extensions.find('fsmonitor')
2181 2182 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
2182 2183 # We intentionally don't look at whether fsmonitor has disabled
2183 2184 # itself because a) fsmonitor may have already printed a warning
2184 2185 # b) we only care about the config state here.
2185 2186 except KeyError:
2186 2187 fsmonitorenabled = False
2187 2188
2188 2189 if (fsmonitorwarning
2189 2190 and not fsmonitorenabled
2190 2191 and p1.node() == nullid
2191 2192 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2192 2193 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
2193 2194 repo.ui.warn(
2194 2195 _('(warning: large working directory being used without '
2195 2196 'fsmonitor enabled; enable fsmonitor to improve performance; '
2196 2197 'see "hg help -e fsmonitor")\n'))
2197 2198
2198 2199 updatedirstate = not partial and not wc.isinmemory()
2199 2200 wantfiledata = updatedirstate and not branchmerge
2200 2201 stats, getfiledata = applyupdates(repo, actions, wc, p2, overwrite,
2201 2202 wantfiledata, labels=labels)
2202 2203
2203 2204 if updatedirstate:
2204 2205 with repo.dirstate.parentchange():
2205 2206 repo.setparents(fp1, fp2)
2206 2207 recordupdates(repo, actions, branchmerge, getfiledata)
2207 2208 # update completed, clear state
2208 2209 util.unlink(repo.vfs.join('updatestate'))
2209 2210
2210 2211 if not branchmerge:
2211 2212 repo.dirstate.setbranch(p2.branch())
2212 2213
2213 2214 # If we're updating to a location, clean up any stale temporary includes
2214 2215 # (ex: this happens during hg rebase --abort).
2215 2216 if not branchmerge:
2216 2217 sparse.prunetemporaryincludes(repo)
2217 2218
2218 2219 if not partial:
2219 2220 repo.hook('update', parent1=xp1, parent2=xp2,
2220 2221 error=stats.unresolvedcount)
2221 2222 return stats
2222 2223
2223 2224 def graft(repo, ctx, pctx, labels=None, keepparent=False,
2224 2225 keepconflictparent=False):
2225 2226 """Do a graft-like merge.
2226 2227
2227 2228 This is a merge where the merge ancestor is chosen such that one
2228 2229 or more changesets are grafted onto the current changeset. In
2229 2230 addition to the merge, this fixes up the dirstate to include only
2230 2231 a single parent (if keepparent is False) and tries to duplicate any
2231 2232 renames/copies appropriately.
2232 2233
2233 2234 ctx - changeset to rebase
2234 2235 pctx - merge base, usually ctx.p1()
2235 2236 labels - merge labels eg ['local', 'graft']
2236 2237 keepparent - keep second parent if any
2237 2238 keepconflictparent - if unresolved, keep parent used for the merge
2238 2239
2239 2240 """
2240 2241 # If we're grafting a descendant onto an ancestor, be sure to pass
2241 2242 # mergeancestor=True to update. This does two things: 1) allows the merge if
2242 2243 # the destination is the same as the parent of the ctx (so we can use graft
2243 2244 # to copy commits), and 2) informs update that the incoming changes are
2244 2245 # newer than the destination so it doesn't prompt about "remote changed foo
2245 2246 # which local deleted".
2246 2247 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2247 2248
2248 2249 stats = update(repo, ctx.node(), True, True, pctx.node(),
2249 2250 mergeancestor=mergeancestor, labels=labels)
2250 2251
2251 2252
2252 2253 if keepconflictparent and stats.unresolvedcount:
2253 2254 pother = ctx.node()
2254 2255 else:
2255 2256 pother = nullid
2256 2257 parents = ctx.parents()
2257 2258 if keepparent and len(parents) == 2 and pctx in parents:
2258 2259 parents.remove(pctx)
2259 2260 pother = parents[0].node()
2260 2261
2261 2262 with repo.dirstate.parentchange():
2262 2263 repo.setparents(repo['.'].node(), pother)
2263 2264 repo.dirstate.write(repo.currenttransaction())
2264 2265 # fix up dirstate for copies and renames
2265 2266 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2266 2267 return stats
2267 2268
2268 2269 def purge(repo, matcher, ignored=False, removeemptydirs=True,
2269 2270 removefiles=True, abortonerror=False, noop=False):
2270 2271 """Purge the working directory of untracked files.
2271 2272
2272 2273 ``matcher`` is a matcher configured to scan the working directory -
2273 2274 potentially a subset.
2274 2275
2275 2276 ``ignored`` controls whether ignored files should also be purged.
2276 2277
2277 2278 ``removeemptydirs`` controls whether empty directories should be removed.
2278 2279
2279 2280 ``removefiles`` controls whether files are removed.
2280 2281
2281 2282 ``abortonerror`` causes an exception to be raised if an error occurs
2282 2283 deleting a file or directory.
2283 2284
2284 2285 ``noop`` controls whether to actually remove files. If not defined, actions
2285 2286 will be taken.
2286 2287
2287 2288 Returns an iterable of relative paths in the working directory that were
2288 2289 or would be removed.
2289 2290 """
2290 2291
2291 2292 def remove(removefn, path):
2292 2293 try:
2293 2294 removefn(path)
2294 2295 except OSError:
2295 2296 m = _('%s cannot be removed') % path
2296 2297 if abortonerror:
2297 2298 raise error.Abort(m)
2298 2299 else:
2299 2300 repo.ui.warn(_('warning: %s\n') % m)
2300 2301
2301 2302 # There's no API to copy a matcher. So mutate the passed matcher and
2302 2303 # restore it when we're done.
2303 2304 oldexplicitdir = matcher.explicitdir
2304 2305 oldtraversedir = matcher.traversedir
2305 2306
2306 2307 res = []
2307 2308
2308 2309 try:
2309 2310 if removeemptydirs:
2310 2311 directories = []
2311 2312 matcher.explicitdir = matcher.traversedir = directories.append
2312 2313
2313 2314 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2314 2315
2315 2316 if removefiles:
2316 2317 for f in sorted(status.unknown + status.ignored):
2317 2318 if not noop:
2318 2319 repo.ui.note(_('removing file %s\n') % f)
2319 2320 remove(repo.wvfs.unlink, f)
2320 2321 res.append(f)
2321 2322
2322 2323 if removeemptydirs:
2323 2324 for f in sorted(directories, reverse=True):
2324 2325 if matcher(f) and not repo.wvfs.listdir(f):
2325 2326 if not noop:
2326 2327 repo.ui.note(_('removing directory %s\n') % f)
2327 2328 remove(repo.wvfs.rmdir, f)
2328 2329 res.append(f)
2329 2330
2330 2331 return res
2331 2332
2332 2333 finally:
2333 2334 matcher.explicitdir = oldexplicitdir
2334 2335 matcher.traversedir = oldtraversedir
@@ -1,863 +1,864 b''
1 1 #require symlink execbit
2 2 $ cat << EOF >> $HGRCPATH
3 3 > [phases]
4 4 > publish=False
5 5 > [extensions]
6 6 > amend=
7 7 > rebase=
8 8 > debugdrawdag=$TESTDIR/drawdag.py
9 9 > strip=
10 10 > [rebase]
11 11 > experimental.inmemory=1
12 12 > [diff]
13 13 > git=1
14 14 > [alias]
15 15 > tglog = log -G --template "{rev}: {node|short} '{desc}'\n"
16 16 > EOF
17 17
18 18 Rebase a simple DAG:
19 19 $ hg init repo1
20 20 $ cd repo1
21 21 $ hg debugdrawdag <<'EOS'
22 22 > c b
23 23 > |/
24 24 > d
25 25 > |
26 26 > a
27 27 > EOS
28 28 $ hg up -C a
29 29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 $ hg tglog
31 31 o 3: 814f6bd05178 'c'
32 32 |
33 33 | o 2: db0e82a16a62 'b'
34 34 |/
35 35 o 1: 02952614a83d 'd'
36 36 |
37 37 @ 0: b173517d0057 'a'
38 38
39 39 $ hg cat -r 3 c
40 40 c (no-eol)
41 41 $ hg cat -r 2 b
42 42 b (no-eol)
43 43 $ hg rebase --debug -r b -d c | grep rebasing
44 44 rebasing in-memory
45 45 rebasing 2:db0e82a16a62 "b" (b)
46 46 $ hg tglog
47 47 o 3: ca58782ad1e4 'b'
48 48 |
49 49 o 2: 814f6bd05178 'c'
50 50 |
51 51 o 1: 02952614a83d 'd'
52 52 |
53 53 @ 0: b173517d0057 'a'
54 54
55 55 $ hg cat -r 3 b
56 56 b (no-eol)
57 57 $ hg cat -r 2 c
58 58 c (no-eol)
59 59 $ cd ..
60 60
61 61 Case 2:
62 62 $ hg init repo2
63 63 $ cd repo2
64 64 $ hg debugdrawdag <<'EOS'
65 65 > c b
66 66 > |/
67 67 > d
68 68 > |
69 69 > a
70 70 > EOS
71 71
72 72 Add a symlink and executable file:
73 73 $ hg up -C c
74 74 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 75 $ ln -s somefile e
76 76 $ echo f > f
77 77 $ chmod +x f
78 78 $ hg add e f
79 79 $ hg amend -q
80 80 $ hg up -Cq a
81 81
82 82 Write files to the working copy, and ensure they're still there after the rebase
83 83 $ echo "abc" > a
84 84 $ ln -s def b
85 85 $ echo "ghi" > c
86 86 $ echo "jkl" > d
87 87 $ echo "mno" > e
88 88 $ hg tglog
89 89 o 3: f56b71190a8f 'c'
90 90 |
91 91 | o 2: db0e82a16a62 'b'
92 92 |/
93 93 o 1: 02952614a83d 'd'
94 94 |
95 95 @ 0: b173517d0057 'a'
96 96
97 97 $ hg cat -r 3 c
98 98 c (no-eol)
99 99 $ hg cat -r 2 b
100 100 b (no-eol)
101 101 $ hg cat -r 3 e
102 102 somefile (no-eol)
103 103 $ hg rebase --debug -s b -d a | grep rebasing
104 104 rebasing in-memory
105 105 rebasing 2:db0e82a16a62 "b" (b)
106 106 $ hg tglog
107 107 o 3: fc055c3b4d33 'b'
108 108 |
109 109 | o 2: f56b71190a8f 'c'
110 110 | |
111 111 | o 1: 02952614a83d 'd'
112 112 |/
113 113 @ 0: b173517d0057 'a'
114 114
115 115 $ hg cat -r 2 c
116 116 c (no-eol)
117 117 $ hg cat -r 3 b
118 118 b (no-eol)
119 119 $ hg rebase --debug -s 1 -d 3 | grep rebasing
120 120 rebasing in-memory
121 121 rebasing 1:02952614a83d "d" (d)
122 122 rebasing 2:f56b71190a8f "c"
123 123 $ hg tglog
124 124 o 3: 753feb6fd12a 'c'
125 125 |
126 126 o 2: 09c044d2cb43 'd'
127 127 |
128 128 o 1: fc055c3b4d33 'b'
129 129 |
130 130 @ 0: b173517d0057 'a'
131 131
132 132 Ensure working copy files are still there:
133 133 $ cat a
134 134 abc
135 135 $ readlink.py b
136 136 b -> def
137 137 $ cat e
138 138 mno
139 139
140 140 Ensure symlink and executable files were rebased properly:
141 141 $ hg up -Cq 3
142 142 $ readlink.py e
143 143 e -> somefile
144 144 $ ls -l f | cut -c -10
145 145 -rwxr-xr-x
146 146
147 147 Rebase the working copy parent
148 148 $ hg up -C 3
149 149 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 150 $ hg rebase -r 3 -d 0 --debug | grep rebasing
151 151 rebasing in-memory
152 152 rebasing 3:753feb6fd12a "c" (tip)
153 153 $ hg tglog
154 154 @ 3: 844a7de3e617 'c'
155 155 |
156 156 | o 2: 09c044d2cb43 'd'
157 157 | |
158 158 | o 1: fc055c3b4d33 'b'
159 159 |/
160 160 o 0: b173517d0057 'a'
161 161
162 162
163 163 Test reporting of path conflicts
164 164
165 165 $ hg rm a
166 166 $ mkdir a
167 167 $ touch a/a
168 168 $ hg ci -Am "a/a"
169 169 adding a/a
170 170 $ hg tglog
171 171 @ 4: daf7dfc139cb 'a/a'
172 172 |
173 173 o 3: 844a7de3e617 'c'
174 174 |
175 175 | o 2: 09c044d2cb43 'd'
176 176 | |
177 177 | o 1: fc055c3b4d33 'b'
178 178 |/
179 179 o 0: b173517d0057 'a'
180 180
181 181 $ hg rebase -r . -d 2
182 182 rebasing 4:daf7dfc139cb "a/a" (tip)
183 183 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/daf7dfc139cb-fdbfcf4f-rebase.hg
184 184
185 185 $ hg tglog
186 186 @ 4: c6ad37a4f250 'a/a'
187 187 |
188 188 | o 3: 844a7de3e617 'c'
189 189 | |
190 190 o | 2: 09c044d2cb43 'd'
191 191 | |
192 192 o | 1: fc055c3b4d33 'b'
193 193 |/
194 194 o 0: b173517d0057 'a'
195 195
196 196 $ echo foo > foo
197 197 $ hg ci -Aqm "added foo"
198 198 $ hg up '.^'
199 199 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
200 200 $ echo bar > bar
201 201 $ hg ci -Aqm "added bar"
202 202 $ hg rm a/a
203 203 $ echo a > a
204 204 $ hg ci -Aqm "added a back!"
205 205 $ hg tglog
206 206 @ 7: 855e9797387e 'added a back!'
207 207 |
208 208 o 6: d14530e5e3e6 'added bar'
209 209 |
210 210 | o 5: 9b94b9373deb 'added foo'
211 211 |/
212 212 o 4: c6ad37a4f250 'a/a'
213 213 |
214 214 | o 3: 844a7de3e617 'c'
215 215 | |
216 216 o | 2: 09c044d2cb43 'd'
217 217 | |
218 218 o | 1: fc055c3b4d33 'b'
219 219 |/
220 220 o 0: b173517d0057 'a'
221 221
222 222 $ hg rebase -r . -d 5
223 223 rebasing 7:855e9797387e "added a back!" (tip)
224 224 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/855e9797387e-81ee4c5d-rebase.hg
225 225
226 226 $ hg tglog
227 227 @ 7: bb3f02be2688 'added a back!'
228 228 |
229 229 | o 6: d14530e5e3e6 'added bar'
230 230 | |
231 231 o | 5: 9b94b9373deb 'added foo'
232 232 |/
233 233 o 4: c6ad37a4f250 'a/a'
234 234 |
235 235 | o 3: 844a7de3e617 'c'
236 236 | |
237 237 o | 2: 09c044d2cb43 'd'
238 238 | |
239 239 o | 1: fc055c3b4d33 'b'
240 240 |/
241 241 o 0: b173517d0057 'a'
242 242
243 243 $ mkdir -p c/subdir
244 244 $ echo c > c/subdir/file.txt
245 245 $ hg add c/subdir/file.txt
246 246 $ hg ci -m 'c/subdir/file.txt'
247 247 $ hg rebase -r . -d 3 -n
248 248 starting dry-run rebase; repository will not be changed
249 249 rebasing 8:e147e6e3c490 "c/subdir/file.txt" (tip)
250 250 abort: error: 'c/subdir/file.txt' conflicts with file 'c' in 3.
251 251 [255]
252 252 $ hg rebase -r 3 -d . -n
253 253 starting dry-run rebase; repository will not be changed
254 254 rebasing 3:844a7de3e617 "c"
255 255 abort: error: file 'c' cannot be written because 'c/' is a directory in e147e6e3c490 (containing 1 entries: c/subdir/file.txt)
256 256 [255]
257 257
258 258 $ cd ..
259 259
260 260 Test path auditing (issue5818)
261 261
262 262 $ mkdir lib_
263 263 $ ln -s lib_ lib
264 264 $ hg init repo
265 265 $ cd repo
266 266 $ mkdir -p ".$TESTTMP/lib"
267 267 $ touch ".$TESTTMP/lib/a"
268 268 $ hg add ".$TESTTMP/lib/a"
269 269 $ hg ci -m 'a'
270 270
271 271 $ touch ".$TESTTMP/lib/b"
272 272 $ hg add ".$TESTTMP/lib/b"
273 273 $ hg ci -m 'b'
274 274
275 275 $ hg up -q '.^'
276 276 $ touch ".$TESTTMP/lib/c"
277 277 $ hg add ".$TESTTMP/lib/c"
278 278 $ hg ci -m 'c'
279 279 created new head
280 280 $ hg rebase -s 1 -d .
281 281 rebasing 1:* "b" (glob)
282 282 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-rebase.hg (glob)
283 283 $ cd ..
284 284
285 285 Test dry-run rebasing
286 286
287 287 $ hg init repo3
288 288 $ cd repo3
289 289 $ echo a>a
290 290 $ hg ci -Aqma
291 291 $ echo b>b
292 292 $ hg ci -Aqmb
293 293 $ echo c>c
294 294 $ hg ci -Aqmc
295 295 $ echo d>d
296 296 $ hg ci -Aqmd
297 297 $ echo e>e
298 298 $ hg ci -Aqme
299 299
300 300 $ hg up 1 -q
301 301 $ echo f>f
302 302 $ hg ci -Amf
303 303 adding f
304 304 created new head
305 305 $ echo g>g
306 306 $ hg ci -Aqmg
307 307 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
308 308 @ 6:baf10c5166d4 test
309 309 | g
310 310 |
311 311 o 5:6343ca3eff20 test
312 312 | f
313 313 |
314 314 | o 4:e860deea161a test
315 315 | | e
316 316 | |
317 317 | o 3:055a42cdd887 test
318 318 | | d
319 319 | |
320 320 | o 2:177f92b77385 test
321 321 |/ c
322 322 |
323 323 o 1:d2ae7f538514 test
324 324 | b
325 325 |
326 326 o 0:cb9a9f314b8b test
327 327 a
328 328
329 329 Make sure it throws error while passing --continue or --abort with --dry-run
330 330 $ hg rebase -s 2 -d 6 -n --continue
331 331 abort: cannot specify both --dry-run and --continue
332 332 [255]
333 333 $ hg rebase -s 2 -d 6 -n --abort
334 334 abort: cannot specify both --dry-run and --abort
335 335 [255]
336 336
337 337 Check dryrun gives correct results when there is no conflict in rebasing
338 338 $ hg rebase -s 2 -d 6 -n
339 339 starting dry-run rebase; repository will not be changed
340 340 rebasing 2:177f92b77385 "c"
341 341 rebasing 3:055a42cdd887 "d"
342 342 rebasing 4:e860deea161a "e"
343 343 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
344 344
345 345 $ hg diff
346 346 $ hg status
347 347
348 348 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
349 349 @ 6:baf10c5166d4 test
350 350 | g
351 351 |
352 352 o 5:6343ca3eff20 test
353 353 | f
354 354 |
355 355 | o 4:e860deea161a test
356 356 | | e
357 357 | |
358 358 | o 3:055a42cdd887 test
359 359 | | d
360 360 | |
361 361 | o 2:177f92b77385 test
362 362 |/ c
363 363 |
364 364 o 1:d2ae7f538514 test
365 365 | b
366 366 |
367 367 o 0:cb9a9f314b8b test
368 368 a
369 369
370 370 Check dryrun working with --collapse when there is no conflict
371 371 $ hg rebase -s 2 -d 6 -n --collapse
372 372 starting dry-run rebase; repository will not be changed
373 373 rebasing 2:177f92b77385 "c"
374 374 rebasing 3:055a42cdd887 "d"
375 375 rebasing 4:e860deea161a "e"
376 376 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
377 377
378 378 Check dryrun gives correct results when there is conflict in rebasing
379 379 Make a conflict:
380 380 $ hg up 6 -q
381 381 $ echo conflict>e
382 382 $ hg ci -Aqm "conflict with e"
383 383 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
384 384 @ 7:d2c195b28050 test
385 385 | conflict with e
386 386 |
387 387 o 6:baf10c5166d4 test
388 388 | g
389 389 |
390 390 o 5:6343ca3eff20 test
391 391 | f
392 392 |
393 393 | o 4:e860deea161a test
394 394 | | e
395 395 | |
396 396 | o 3:055a42cdd887 test
397 397 | | d
398 398 | |
399 399 | o 2:177f92b77385 test
400 400 |/ c
401 401 |
402 402 o 1:d2ae7f538514 test
403 403 | b
404 404 |
405 405 o 0:cb9a9f314b8b test
406 406 a
407 407
408 408 $ hg rebase -s 2 -d 7 -n
409 409 starting dry-run rebase; repository will not be changed
410 410 rebasing 2:177f92b77385 "c"
411 411 rebasing 3:055a42cdd887 "d"
412 412 rebasing 4:e860deea161a "e"
413 413 merging e
414 414 transaction abort!
415 415 rollback completed
416 416 hit a merge conflict
417 417 [1]
418 418 $ hg diff
419 419 $ hg status
420 420 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
421 421 @ 7:d2c195b28050 test
422 422 | conflict with e
423 423 |
424 424 o 6:baf10c5166d4 test
425 425 | g
426 426 |
427 427 o 5:6343ca3eff20 test
428 428 | f
429 429 |
430 430 | o 4:e860deea161a test
431 431 | | e
432 432 | |
433 433 | o 3:055a42cdd887 test
434 434 | | d
435 435 | |
436 436 | o 2:177f92b77385 test
437 437 |/ c
438 438 |
439 439 o 1:d2ae7f538514 test
440 440 | b
441 441 |
442 442 o 0:cb9a9f314b8b test
443 443 a
444 444
445 445 Check dryrun working with --collapse when there is conflicts
446 446 $ hg rebase -s 2 -d 7 -n --collapse
447 447 starting dry-run rebase; repository will not be changed
448 448 rebasing 2:177f92b77385 "c"
449 449 rebasing 3:055a42cdd887 "d"
450 450 rebasing 4:e860deea161a "e"
451 451 merging e
452 452 hit a merge conflict
453 453 [1]
454 454
455 455 In-memory rebase that fails due to merge conflicts
456 456
457 457 $ hg rebase -s 2 -d 7
458 458 rebasing 2:177f92b77385 "c"
459 459 rebasing 3:055a42cdd887 "d"
460 460 rebasing 4:e860deea161a "e"
461 461 merging e
462 462 transaction abort!
463 463 rollback completed
464 464 hit merge conflicts; re-running rebase without in-memory merge
465 465 rebasing 2:177f92b77385 "c"
466 466 rebasing 3:055a42cdd887 "d"
467 467 rebasing 4:e860deea161a "e"
468 468 merging e
469 469 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
470 470 unresolved conflicts (see hg resolve, then hg rebase --continue)
471 471 [1]
472 472 $ hg rebase --abort
473 473 saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/c1e524d4287c-f91f82e1-backup.hg
474 474 rebase aborted
475 475
476 476 Retrying without in-memory merge won't lose working copy changes
477 477 $ cd ..
478 478 $ hg clone repo3 repo3-dirty -q
479 479 $ cd repo3-dirty
480 480 $ echo dirty > a
481 481 $ hg rebase -s 2 -d 7
482 482 rebasing 2:177f92b77385 "c"
483 483 rebasing 3:055a42cdd887 "d"
484 484 rebasing 4:e860deea161a "e"
485 485 merging e
486 486 transaction abort!
487 487 rollback completed
488 488 hit merge conflicts; re-running rebase without in-memory merge
489 489 abort: uncommitted changes
490 490 [255]
491 491 $ cat a
492 492 dirty
493 493
494 494 Retrying without in-memory merge won't lose merge state
495 495 $ cd ..
496 496 $ hg clone repo3 repo3-merge-state -q
497 497 $ cd repo3-merge-state
498 498 $ hg merge 4
499 499 merging e
500 500 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
501 501 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
502 502 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
503 503 [1]
504 504 $ hg resolve -l
505 505 U e
506 506 $ hg rebase -s 2 -d 7
507 507 rebasing 2:177f92b77385 "c"
508 508 abort: outstanding merge conflicts
509 (use 'hg resolve' to resolve)
509 510 [255]
510 511 $ hg resolve -l
511 512 U e
512 513
513 514 ==========================
514 515 Test for --confirm option|
515 516 ==========================
516 517 $ cd ..
517 518 $ hg clone repo3 repo4 -q
518 519 $ cd repo4
519 520 $ hg strip 7 -q
520 521 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
521 522 @ 6:baf10c5166d4 test
522 523 | g
523 524 |
524 525 o 5:6343ca3eff20 test
525 526 | f
526 527 |
527 528 | o 4:e860deea161a test
528 529 | | e
529 530 | |
530 531 | o 3:055a42cdd887 test
531 532 | | d
532 533 | |
533 534 | o 2:177f92b77385 test
534 535 |/ c
535 536 |
536 537 o 1:d2ae7f538514 test
537 538 | b
538 539 |
539 540 o 0:cb9a9f314b8b test
540 541 a
541 542
542 543 Check it gives error when both --dryrun and --confirm is used:
543 544 $ hg rebase -s 2 -d . --confirm --dry-run
544 545 abort: cannot specify both --confirm and --dry-run
545 546 [255]
546 547 $ hg rebase -s 2 -d . --confirm --abort
547 548 abort: cannot specify both --confirm and --abort
548 549 [255]
549 550 $ hg rebase -s 2 -d . --confirm --continue
550 551 abort: cannot specify both --confirm and --continue
551 552 [255]
552 553
553 554 Test --confirm option when there are no conflicts:
554 555 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
555 556 > n
556 557 > EOF
557 558 starting in-memory rebase
558 559 rebasing 2:177f92b77385 "c"
559 560 rebasing 3:055a42cdd887 "d"
560 561 rebasing 4:e860deea161a "e"
561 562 rebase completed successfully
562 563 apply changes (yn)? n
563 564 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
564 565 @ 6:baf10c5166d4 test
565 566 | g
566 567 |
567 568 o 5:6343ca3eff20 test
568 569 | f
569 570 |
570 571 | o 4:e860deea161a test
571 572 | | e
572 573 | |
573 574 | o 3:055a42cdd887 test
574 575 | | d
575 576 | |
576 577 | o 2:177f92b77385 test
577 578 |/ c
578 579 |
579 580 o 1:d2ae7f538514 test
580 581 | b
581 582 |
582 583 o 0:cb9a9f314b8b test
583 584 a
584 585
585 586 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
586 587 > y
587 588 > EOF
588 589 starting in-memory rebase
589 590 rebasing 2:177f92b77385 "c"
590 591 rebasing 3:055a42cdd887 "d"
591 592 rebasing 4:e860deea161a "e"
592 593 rebase completed successfully
593 594 apply changes (yn)? y
594 595 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
595 596 o 9:9fd28f55f6dc test
596 597 | e
597 598 |
598 599 o 8:12cbf031f469 test
599 600 | d
600 601 |
601 602 o 7:c83b1da5b1ae test
602 603 | c
603 604 |
604 605 @ 6:baf10c5166d4 test
605 606 | g
606 607 |
607 608 o 5:6343ca3eff20 test
608 609 | f
609 610 |
610 611 | o 4:e860deea161a test
611 612 | | e
612 613 | |
613 614 | o 3:055a42cdd887 test
614 615 | | d
615 616 | |
616 617 | o 2:177f92b77385 test
617 618 |/ c
618 619 |
619 620 o 1:d2ae7f538514 test
620 621 | b
621 622 |
622 623 o 0:cb9a9f314b8b test
623 624 a
624 625
625 626 Test --confirm option when there is a conflict
626 627 $ hg up tip -q
627 628 $ echo ee>e
628 629 $ hg ci --amend -m "conflict with e" -q
629 630 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
630 631 @ 9:906d72f66a59 test
631 632 | conflict with e
632 633 |
633 634 o 8:12cbf031f469 test
634 635 | d
635 636 |
636 637 o 7:c83b1da5b1ae test
637 638 | c
638 639 |
639 640 o 6:baf10c5166d4 test
640 641 | g
641 642 |
642 643 o 5:6343ca3eff20 test
643 644 | f
644 645 |
645 646 | o 4:e860deea161a test
646 647 | | e
647 648 | |
648 649 | o 3:055a42cdd887 test
649 650 | | d
650 651 | |
651 652 | o 2:177f92b77385 test
652 653 |/ c
653 654 |
654 655 o 1:d2ae7f538514 test
655 656 | b
656 657 |
657 658 o 0:cb9a9f314b8b test
658 659 a
659 660
660 661 $ hg rebase -s 4 -d . --keep --confirm
661 662 starting in-memory rebase
662 663 rebasing 4:e860deea161a "e"
663 664 merging e
664 665 hit a merge conflict
665 666 [1]
666 667 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
667 668 @ 9:906d72f66a59 test
668 669 | conflict with e
669 670 |
670 671 o 8:12cbf031f469 test
671 672 | d
672 673 |
673 674 o 7:c83b1da5b1ae test
674 675 | c
675 676 |
676 677 o 6:baf10c5166d4 test
677 678 | g
678 679 |
679 680 o 5:6343ca3eff20 test
680 681 | f
681 682 |
682 683 | o 4:e860deea161a test
683 684 | | e
684 685 | |
685 686 | o 3:055a42cdd887 test
686 687 | | d
687 688 | |
688 689 | o 2:177f92b77385 test
689 690 |/ c
690 691 |
691 692 o 1:d2ae7f538514 test
692 693 | b
693 694 |
694 695 o 0:cb9a9f314b8b test
695 696 a
696 697
697 698 Test a metadata-only in-memory merge
698 699 $ cd $TESTTMP
699 700 $ hg init no_exception
700 701 $ cd no_exception
701 702 # Produce the following graph:
702 703 # o 'add +x to foo.txt'
703 704 # | o r1 (adds bar.txt, just for something to rebase to)
704 705 # |/
705 706 # o r0 (adds foo.txt, no +x)
706 707 $ echo hi > foo.txt
707 708 $ hg ci -qAm r0
708 709 $ echo hi > bar.txt
709 710 $ hg ci -qAm r1
710 711 $ hg co -qr ".^"
711 712 $ chmod +x foo.txt
712 713 $ hg ci -qAm 'add +x to foo.txt'
713 714 issue5960: this was raising an AttributeError exception
714 715 $ hg rebase -r . -d 1
715 716 rebasing 2:539b93e77479 "add +x to foo.txt" (tip)
716 717 saved backup bundle to $TESTTMP/no_exception/.hg/strip-backup/*.hg (glob)
717 718 $ hg diff -c tip
718 719 diff --git a/foo.txt b/foo.txt
719 720 old mode 100644
720 721 new mode 100755
721 722
722 723 Test rebasing a commit with copy information, but no content changes
723 724
724 725 $ cd ..
725 726 $ hg clone -q repo1 merge-and-rename
726 727 $ cd merge-and-rename
727 728 $ cat << EOF >> .hg/hgrc
728 729 > [experimental]
729 730 > evolution.createmarkers=True
730 731 > evolution.allowunstable=True
731 732 > EOF
732 733 $ hg co -q 1
733 734 $ hg mv d e
734 735 $ hg ci -qm 'rename d to e'
735 736 $ hg co -q 3
736 737 $ hg merge -q 4
737 738 $ hg ci -m 'merge'
738 739 $ hg co -q 2
739 740 $ mv d e
740 741 $ hg addremove -qs 0
741 742 $ hg ci -qm 'untracked rename of d to e'
742 743 $ hg debugobsolete -q `hg log -T '{node}' -r 4` `hg log -T '{node}' -r .`
743 744 1 new orphan changesets
744 745 $ hg tglog
745 746 @ 6: 676538af172d 'untracked rename of d to e'
746 747 |
747 748 | * 5: 574d92ad16fc 'merge'
748 749 | |\
749 750 | | x 4: 2c8b5dad7956 'rename d to e'
750 751 | | |
751 752 | o | 3: ca58782ad1e4 'b'
752 753 |/ /
753 754 o / 2: 814f6bd05178 'c'
754 755 |/
755 756 o 1: 02952614a83d 'd'
756 757 |
757 758 o 0: b173517d0057 'a'
758 759
759 760 $ hg rebase -b 5 -d tip
760 761 rebasing 3:ca58782ad1e4 "b"
761 762 rebasing 5:574d92ad16fc "merge"
762 763 note: not rebasing 5:574d92ad16fc "merge", its destination already has all its changes
763 764
764 765 $ cd ..
765 766
766 767 Test rebasing a commit with copy information
767 768
768 769 $ hg init rebase-rename
769 770 $ cd rebase-rename
770 771 $ echo a > a
771 772 $ hg ci -Aqm 'add a'
772 773 $ echo a2 > a
773 774 $ hg ci -m 'modify a'
774 775 $ hg co -q 0
775 776 $ hg mv a b
776 777 $ hg ci -qm 'rename a to b'
777 778 $ hg rebase -d 1
778 779 rebasing 2:b977edf6f839 "rename a to b" (tip)
779 780 merging a and b to b
780 781 saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/b977edf6f839-0864f570-rebase.hg
781 782 $ hg st --copies --change .
782 783 A b
783 784 a
784 785 R a
785 786 $ cd ..
786 787
787 788 Test rebasing a commit with copy information, where the target is empty
788 789
789 790 $ hg init rebase-rename-empty
790 791 $ cd rebase-rename-empty
791 792 $ echo a > a
792 793 $ hg ci -Aqm 'add a'
793 794 $ cat > a
794 795 $ hg ci -m 'make a empty'
795 796 $ hg co -q 0
796 797 $ hg mv a b
797 798 $ hg ci -qm 'rename a to b'
798 799 $ hg rebase -d 1
799 800 rebasing 2:b977edf6f839 "rename a to b" (tip)
800 801 merging a and b to b
801 802 saved backup bundle to $TESTTMP/rebase-rename-empty/.hg/strip-backup/b977edf6f839-0864f570-rebase.hg
802 803 $ hg st --copies --change .
803 804 A b
804 805 a
805 806 R a
806 807 $ cd ..
807 808 Rebase across a copy with --collapse
808 809
809 810 $ hg init rebase-rename-collapse
810 811 $ cd rebase-rename-collapse
811 812 $ echo a > a
812 813 $ hg ci -Aqm 'add a'
813 814 $ hg mv a b
814 815 $ hg ci -m 'rename a to b'
815 816 $ hg co -q 0
816 817 $ echo a2 > a
817 818 $ hg ci -qm 'modify a'
818 819 $ hg rebase -r . -d 1 --collapse
819 820 rebasing 2:41c4ea50d4cf "modify a" (tip)
820 821 merging b and a to b
821 822 saved backup bundle to $TESTTMP/rebase-rename-collapse/.hg/strip-backup/41c4ea50d4cf-b90b7994-rebase.hg
822 823 $ cd ..
823 824
824 825 Test rebasing when the file we are merging in destination is empty
825 826
826 827 $ hg init test
827 828 $ cd test
828 829 $ echo a > foo
829 830 $ hg ci -Aqm 'added a to foo'
830 831
831 832 $ rm foo
832 833 $ touch foo
833 834 $ hg di
834 835 diff --git a/foo b/foo
835 836 --- a/foo
836 837 +++ b/foo
837 838 @@ -1,1 +0,0 @@
838 839 -a
839 840
840 841 $ hg ci -m "make foo an empty file"
841 842
842 843 $ hg up '.^'
843 844 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
844 845 $ echo b > foo
845 846 $ hg di
846 847 diff --git a/foo b/foo
847 848 --- a/foo
848 849 +++ b/foo
849 850 @@ -1,1 +1,1 @@
850 851 -a
851 852 +b
852 853 $ hg ci -m "add b to foo"
853 854 created new head
854 855
855 856 $ hg rebase -r . -d 1 --config ui.merge=internal:merge3
856 857 rebasing 2:fb62b706688e "add b to foo" (tip)
857 858 merging foo
858 859 hit merge conflicts; re-running rebase without in-memory merge
859 860 rebasing 2:fb62b706688e "add b to foo" (tip)
860 861 merging foo
861 862 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
862 863 unresolved conflicts (see hg resolve, then hg rebase --continue)
863 864 [1]
@@ -1,800 +1,803 b''
1 1 test that a commit clears the merge state.
2 2
3 3 $ hg init repo
4 4 $ cd repo
5 5
6 6 $ echo foo > file1
7 7 $ echo foo > file2
8 8 $ hg commit -Am 'add files'
9 9 adding file1
10 10 adding file2
11 11
12 12 $ echo bar >> file1
13 13 $ echo bar >> file2
14 14 $ hg commit -Am 'append bar to files'
15 15
16 16 create a second head with conflicting edits
17 17
18 18 $ hg up -C 0
19 19 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 20 $ echo baz >> file1
21 21 $ echo baz >> file2
22 22 $ hg commit -Am 'append baz to files'
23 23 created new head
24 24
25 25 create a third head with no conflicting edits
26 26 $ hg up -qC 0
27 27 $ echo foo > file3
28 28 $ hg commit -Am 'add non-conflicting file'
29 29 adding file3
30 30 created new head
31 31
32 32 failing merge
33 33
34 34 $ hg up -qC 2
35 35 $ hg merge --tool=internal:fail 1
36 36 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
37 37 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
38 38 [1]
39 39
40 40 resolve -l should contain unresolved entries
41 41
42 42 $ hg resolve -l
43 43 U file1
44 44 U file2
45 45
46 46 $ hg resolve -l --no-status
47 47 file1
48 48 file2
49 49
50 50 resolving an unknown path should emit a warning, but not for -l
51 51
52 52 $ hg resolve -m does-not-exist
53 53 arguments do not match paths that need resolving
54 54 $ hg resolve -l does-not-exist
55 55
56 56 tell users how they could have used resolve
57 57
58 58 $ mkdir nested
59 59 $ cd nested
60 60 $ hg resolve -m file1
61 61 arguments do not match paths that need resolving
62 62 (try: hg resolve -m path:file1)
63 63 $ hg resolve -m file1 filez
64 64 arguments do not match paths that need resolving
65 65 (try: hg resolve -m path:file1 path:filez)
66 66 $ hg resolve -m path:file1 path:filez
67 67 $ hg resolve -l
68 68 R file1
69 69 U file2
70 70 $ hg resolve -l --config ui.relative-paths=yes
71 71 R ../file1
72 72 U ../file2
73 73 $ hg resolve --re-merge filez file2
74 74 arguments do not match paths that need resolving
75 75 (try: hg resolve --re-merge path:filez path:file2)
76 76 $ hg resolve -m filez file2
77 77 arguments do not match paths that need resolving
78 78 (try: hg resolve -m path:filez path:file2)
79 79 $ hg resolve -m path:filez path:file2
80 80 (no more unresolved files)
81 81 $ hg resolve -l
82 82 R file1
83 83 R file2
84 84
85 85 cleanup
86 86 $ hg resolve -u
87 87 $ cd ..
88 88 $ rmdir nested
89 89
90 90 don't allow marking or unmarking driver-resolved files
91 91
92 92 $ cat > $TESTTMP/markdriver.py << EOF
93 93 > '''mark and unmark files as driver-resolved'''
94 94 > from mercurial import (
95 95 > merge,
96 96 > pycompat,
97 97 > registrar,
98 98 > scmutil,
99 99 > )
100 100 > cmdtable = {}
101 101 > command = registrar.command(cmdtable)
102 102 > @command(b'markdriver',
103 103 > [(b'u', b'unmark', None, b'')],
104 104 > b'FILE...')
105 105 > def markdriver(ui, repo, *pats, **opts):
106 106 > wlock = repo.wlock()
107 107 > opts = pycompat.byteskwargs(opts)
108 108 > try:
109 109 > ms = merge.mergestate.read(repo)
110 110 > m = scmutil.match(repo[None], pats, opts)
111 111 > for f in ms:
112 112 > if not m(f):
113 113 > continue
114 114 > if not opts[b'unmark']:
115 115 > ms.mark(f, b'd')
116 116 > else:
117 117 > ms.mark(f, b'u')
118 118 > ms.commit()
119 119 > finally:
120 120 > wlock.release()
121 121 > EOF
122 122 $ hg --config extensions.markdriver=$TESTTMP/markdriver.py markdriver file1
123 123 $ hg resolve --list
124 124 D file1
125 125 U file2
126 126 $ hg resolve --mark file1
127 127 not marking file1 as it is driver-resolved
128 128 this should not print out file1
129 129 $ hg resolve --mark --all
130 130 (no more unresolved files -- run "hg resolve --all" to conclude)
131 131 $ hg resolve --mark 'glob:file*'
132 132 (no more unresolved files -- run "hg resolve --all" to conclude)
133 133 $ hg resolve --list
134 134 D file1
135 135 R file2
136 136 $ hg resolve --unmark file1
137 137 not unmarking file1 as it is driver-resolved
138 138 (no more unresolved files -- run "hg resolve --all" to conclude)
139 139 $ hg resolve --unmark --all
140 140 $ hg resolve --list
141 141 D file1
142 142 U file2
143 143 $ hg --config extensions.markdriver=$TESTTMP/markdriver.py markdriver --unmark file1
144 144 $ hg resolve --list
145 145 U file1
146 146 U file2
147 147
148 148 resolve the failure
149 149
150 150 $ echo resolved > file1
151 151 $ hg resolve -m file1
152 152
153 153 resolve -l should show resolved file as resolved
154 154
155 155 $ hg resolve -l
156 156 R file1
157 157 U file2
158 158
159 159 $ hg resolve -l -Tjson
160 160 [
161 161 {
162 162 "mergestatus": "R",
163 163 "path": "file1"
164 164 },
165 165 {
166 166 "mergestatus": "U",
167 167 "path": "file2"
168 168 }
169 169 ]
170 170
171 171 $ hg resolve -l -T '{path} {mergestatus} {status} {p1rev} {p2rev}\n'
172 172 file1 R M 2 1
173 173 file2 U M 2 1
174 174
175 175 resolve -m without paths should mark all resolved
176 176
177 177 $ hg resolve -m
178 178 (no more unresolved files)
179 179 $ hg commit -m 'resolved'
180 180
181 181 resolve -l should be empty after commit
182 182
183 183 $ hg resolve -l
184 184
185 185 $ hg resolve -l -Tjson
186 186 [
187 187 ]
188 188
189 189 resolve --all should abort when no merge in progress
190 190
191 191 $ hg resolve --all
192 192 abort: resolve command not applicable when not merging
193 193 [255]
194 194
195 195 resolve -m should abort when no merge in progress
196 196
197 197 $ hg resolve -m
198 198 abort: resolve command not applicable when not merging
199 199 [255]
200 200
201 201 can not update or merge when there are unresolved conflicts
202 202
203 203 $ hg up -qC 0
204 204 $ echo quux >> file1
205 205 $ hg up 1
206 206 merging file1
207 207 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
208 208 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
209 209 use 'hg resolve' to retry unresolved file merges
210 210 [1]
211 211 $ hg up 0
212 212 abort: outstanding merge conflicts
213 (use 'hg resolve' to resolve)
213 214 [255]
214 215 $ hg merge 2
215 216 abort: outstanding merge conflicts
217 (use 'hg resolve' to resolve)
216 218 [255]
217 219 $ hg merge --force 2
218 220 abort: outstanding merge conflicts
221 (use 'hg resolve' to resolve)
219 222 [255]
220 223
221 224 set up conflict-free merge
222 225
223 226 $ hg up -qC 3
224 227 $ hg merge 1
225 228 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 229 (branch merge, don't forget to commit)
227 230
228 231 resolve --all should do nothing in merge without conflicts
229 232 $ hg resolve --all
230 233 (no more unresolved files)
231 234
232 235 resolve -m should do nothing in merge without conflicts
233 236
234 237 $ hg resolve -m
235 238 (no more unresolved files)
236 239
237 240 get back to conflicting state
238 241
239 242 $ hg up -qC 2
240 243 $ hg merge --tool=internal:fail 1
241 244 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
242 245 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
243 246 [1]
244 247
245 248 resolve without arguments should suggest --all
246 249 $ hg resolve
247 250 abort: no files or directories specified
248 251 (use --all to re-merge all unresolved files)
249 252 [255]
250 253
251 254 resolve --all should re-merge all unresolved files
252 255 $ hg resolve --all
253 256 merging file1
254 257 merging file2
255 258 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
256 259 warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
257 260 [1]
258 261 $ cat file1.orig
259 262 foo
260 263 baz
261 264 $ cat file2.orig
262 265 foo
263 266 baz
264 267
265 268 .orig files should exists where specified
266 269 $ hg resolve --all --verbose --config 'ui.origbackuppath=.hg/origbackups'
267 270 merging file1
268 271 creating directory: $TESTTMP/repo/.hg/origbackups
269 272 merging file2
270 273 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
271 274 warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
272 275 [1]
273 276 $ ls .hg/origbackups
274 277 file1
275 278 file2
276 279 $ grep '<<<' file1 > /dev/null
277 280 $ grep '<<<' file2 > /dev/null
278 281
279 282 resolve <file> should re-merge file
280 283 $ echo resolved > file1
281 284 $ hg resolve -q file1
282 285 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
283 286 [1]
284 287 $ grep '<<<' file1 > /dev/null
285 288
286 289 test .orig behavior with resolve
287 290
288 291 $ hg resolve -q file1 --tool "sh -c 'f --dump \"$TESTTMP/repo/file1.orig\"'"
289 292 $TESTTMP/repo/file1.orig:
290 293 >>>
291 294 foo
292 295 baz
293 296 <<<
294 297
295 298 resolve <file> should do nothing if 'file' was marked resolved
296 299 $ echo resolved > file1
297 300 $ hg resolve -m file1
298 301 $ hg resolve -q file1
299 302 $ cat file1
300 303 resolved
301 304
302 305 insert unsupported advisory merge record
303 306
304 307 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
305 308 $ hg debugmergestate
306 309 * version 2 records
307 310 local: 57653b9f834a4493f7240b0681efcb9ae7cab745
308 311 other: dc77451844e37f03f5c559e3b8529b2b48d381d1
309 312 labels:
310 313 local: working copy
311 314 other: merge rev
312 315 unrecognized entry: x advisory record
313 316 file extras: file1 (ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac)
314 317 file: file1 (record type "F", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390)
315 318 local path: file1 (flags "")
316 319 ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
317 320 other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
318 321 file extras: file2 (ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac)
319 322 file: file2 (record type "F", state "u", hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523)
320 323 local path: file2 (flags "")
321 324 ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
322 325 other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
323 326 $ hg resolve -l
324 327 R file1
325 328 U file2
326 329
327 330 insert unsupported mandatory merge record
328 331
329 332 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
330 333 $ hg debugmergestate
331 334 * version 2 records
332 335 local: 57653b9f834a4493f7240b0681efcb9ae7cab745
333 336 other: dc77451844e37f03f5c559e3b8529b2b48d381d1
334 337 labels:
335 338 local: working copy
336 339 other: merge rev
337 340 file extras: file1 (ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac)
338 341 file: file1 (record type "F", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390)
339 342 local path: file1 (flags "")
340 343 ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
341 344 other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
342 345 file extras: file2 (ancestorlinknode = 99726c03216e233810a2564cbc0adfe395007eac)
343 346 file: file2 (record type "F", state "u", hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523)
344 347 local path: file2 (flags "")
345 348 ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
346 349 other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
347 350 unrecognized entry: X mandatory record
348 351 $ hg resolve -l
349 352 abort: unsupported merge state records: X
350 353 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
351 354 [255]
352 355 $ hg resolve -ma
353 356 abort: unsupported merge state records: X
354 357 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
355 358 [255]
356 359 $ hg summary
357 360 warning: merge state has unsupported record types: X
358 361 parent: 2:57653b9f834a
359 362 append baz to files
360 363 parent: 1:dc77451844e3
361 364 append bar to files
362 365 branch: default
363 366 commit: 2 modified, 2 unknown (merge)
364 367 update: 2 new changesets (update)
365 368 phases: 5 draft
366 369
367 370 update --clean shouldn't abort on unsupported records
368 371
369 372 $ hg up -qC 1
370 373 $ hg debugmergestate
371 374 no merge state found
372 375
373 376 test crashed merge with empty mergestate
374 377
375 378 $ mkdir .hg/merge
376 379 $ touch .hg/merge/state
377 380
378 381 resolve -l should be empty
379 382
380 383 $ hg resolve -l
381 384
382 385 resolve -m can be configured to look for remaining conflict markers
383 386 $ hg up -qC 2
384 387 $ hg merge -q --tool=internal:merge 1
385 388 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
386 389 warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
387 390 [1]
388 391 $ hg resolve -l
389 392 U file1
390 393 U file2
391 394 $ echo 'remove markers' > file1
392 395 $ hg --config commands.resolve.mark-check=abort resolve -m
393 396 warning: the following files still have conflict markers:
394 397 file2
395 398 abort: conflict markers detected
396 399 (use --all to mark anyway)
397 400 [255]
398 401 $ hg resolve -l
399 402 U file1
400 403 U file2
401 404 Try with --all from the hint
402 405 $ hg --config commands.resolve.mark-check=abort resolve -m --all
403 406 warning: the following files still have conflict markers:
404 407 file2
405 408 (no more unresolved files)
406 409 $ hg resolve -l
407 410 R file1
408 411 R file2
409 412 Test option value 'warn'
410 413 $ hg resolve --unmark
411 414 $ hg resolve -l
412 415 U file1
413 416 U file2
414 417 $ hg --config commands.resolve.mark-check=warn resolve -m
415 418 warning: the following files still have conflict markers:
416 419 file2
417 420 (no more unresolved files)
418 421 $ hg resolve -l
419 422 R file1
420 423 R file2
421 424 If the file is already marked as resolved, we don't warn about it
422 425 $ hg resolve --unmark file1
423 426 $ hg resolve -l
424 427 U file1
425 428 R file2
426 429 $ hg --config commands.resolve.mark-check=warn resolve -m
427 430 (no more unresolved files)
428 431 $ hg resolve -l
429 432 R file1
430 433 R file2
431 434 If the user passes an invalid value, we treat it as 'none'.
432 435 $ hg resolve --unmark
433 436 $ hg resolve -l
434 437 U file1
435 438 U file2
436 439 $ hg --config commands.resolve.mark-check=nope resolve -m
437 440 (no more unresolved files)
438 441 $ hg resolve -l
439 442 R file1
440 443 R file2
441 444 Test explicitly setting the option to 'none'
442 445 $ hg resolve --unmark
443 446 $ hg resolve -l
444 447 U file1
445 448 U file2
446 449 $ hg --config commands.resolve.mark-check=none resolve -m
447 450 (no more unresolved files)
448 451 $ hg resolve -l
449 452 R file1
450 453 R file2
451 454 Test with marking an explicit file as resolved, this should not abort (since
452 455 there's no --force flag, we have no way of combining --all with a filename)
453 456 $ hg resolve --unmark
454 457 $ hg resolve -l
455 458 U file1
456 459 U file2
457 460 (This downgrades to a warning since an explicit file was specified).
458 461 $ hg --config commands.resolve.mark-check=abort resolve -m file2
459 462 warning: the following files still have conflict markers:
460 463 file2
461 464 $ hg resolve -l
462 465 U file1
463 466 R file2
464 467 Testing the --re-merge flag
465 468 $ hg resolve --unmark file1
466 469 $ hg resolve -l
467 470 U file1
468 471 R file2
469 472 $ hg resolve --mark --re-merge
470 473 abort: too many actions specified
471 474 [255]
472 475 $ hg resolve --re-merge --all
473 476 merging file1
474 477 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
475 478 [1]
476 479 Explicit re-merge
477 480 $ hg resolve --unmark file1
478 481 $ hg resolve --config commands.resolve.explicit-re-merge=1 --all
479 482 abort: no action specified
480 483 (use --mark, --unmark, --list or --re-merge)
481 484 [255]
482 485 $ hg resolve --config commands.resolve.explicit-re-merge=1 --re-merge --all
483 486 merging file1
484 487 warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
485 488 [1]
486 489
487 490 $ cd ..
488 491
489 492 ======================================================
490 493 Test 'hg resolve' confirm config option functionality |
491 494 ======================================================
492 495 $ cat >> $HGRCPATH << EOF
493 496 > [extensions]
494 497 > rebase=
495 498 > EOF
496 499
497 500 $ hg init repo2
498 501 $ cd repo2
499 502
500 503 $ echo boss > boss
501 504 $ hg ci -Am "add boss"
502 505 adding boss
503 506
504 507 $ for emp in emp1 emp2 emp3; do echo work > $emp; done;
505 508 $ hg ci -Aqm "added emp1 emp2 emp3"
506 509
507 510 $ hg up 0
508 511 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
509 512
510 513 $ for emp in emp1 emp2 emp3; do echo nowork > $emp; done;
511 514 $ hg ci -Aqm "added lazy emp1 emp2 emp3"
512 515
513 516 $ hg log -GT "{rev} {node|short} {firstline(desc)}\n"
514 517 @ 2 0acfd4a49af0 added lazy emp1 emp2 emp3
515 518 |
516 519 | o 1 f30f98a8181f added emp1 emp2 emp3
517 520 |/
518 521 o 0 88660038d466 add boss
519 522
520 523 $ hg rebase -s 1 -d 2
521 524 rebasing 1:f30f98a8181f "added emp1 emp2 emp3"
522 525 merging emp1
523 526 merging emp2
524 527 merging emp3
525 528 warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
526 529 warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
527 530 warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
528 531 unresolved conflicts (see hg resolve, then hg rebase --continue)
529 532 [1]
530 533
531 534 Test when commands.resolve.confirm config option is not set:
532 535 ===========================================================
533 536 $ hg resolve --all
534 537 merging emp1
535 538 merging emp2
536 539 merging emp3
537 540 warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
538 541 warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
539 542 warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
540 543 [1]
541 544
542 545 Test when config option is set:
543 546 ==============================
544 547 $ cat >> .hg/hgrc << EOF
545 548 > [ui]
546 549 > interactive = True
547 550 > [commands]
548 551 > resolve.confirm = True
549 552 > EOF
550 553
551 554 $ hg resolve
552 555 abort: no files or directories specified
553 556 (use --all to re-merge all unresolved files)
554 557 [255]
555 558 $ hg resolve --all << EOF
556 559 > n
557 560 > EOF
558 561 re-merge all unresolved files (yn)? n
559 562 abort: user quit
560 563 [255]
561 564
562 565 $ hg resolve --all << EOF
563 566 > y
564 567 > EOF
565 568 re-merge all unresolved files (yn)? y
566 569 merging emp1
567 570 merging emp2
568 571 merging emp3
569 572 warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
570 573 warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
571 574 warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
572 575 [1]
573 576
574 577 Test that commands.resolve.confirm respect --mark option (only when no patterns args are given):
575 578 ===============================================================================================
576 579
577 580 $ hg resolve -m emp1
578 581 $ hg resolve -l
579 582 R emp1
580 583 U emp2
581 584 U emp3
582 585
583 586 $ hg resolve -m << EOF
584 587 > n
585 588 > EOF
586 589 mark all unresolved files as resolved (yn)? n
587 590 abort: user quit
588 591 [255]
589 592
590 593 $ hg resolve -m << EOF
591 594 > y
592 595 > EOF
593 596 mark all unresolved files as resolved (yn)? y
594 597 (no more unresolved files)
595 598 continue: hg rebase --continue
596 599 $ hg resolve -l
597 600 R emp1
598 601 R emp2
599 602 R emp3
600 603
601 604 Test that commands.resolve.confirm respect --unmark option (only when no patterns args are given):
602 605 =================================================================================================
603 606
604 607 $ hg resolve -u emp1
605 608
606 609 $ hg resolve -l
607 610 U emp1
608 611 R emp2
609 612 R emp3
610 613
611 614 $ hg resolve -u << EOF
612 615 > n
613 616 > EOF
614 617 mark all resolved files as unresolved (yn)? n
615 618 abort: user quit
616 619 [255]
617 620
618 621 $ hg resolve -m << EOF
619 622 > y
620 623 > EOF
621 624 mark all unresolved files as resolved (yn)? y
622 625 (no more unresolved files)
623 626 continue: hg rebase --continue
624 627
625 628 $ hg resolve -l
626 629 R emp1
627 630 R emp2
628 631 R emp3
629 632
630 633 $ hg rebase --abort
631 634 rebase aborted
632 635
633 636 Done with commands.resolve.confirm tests:
634 637 $ cd ..
635 638
636 639 Test that commands.resolve.mark-check works even if there are deleted files:
637 640 $ hg init resolve-deleted
638 641 $ cd resolve-deleted
639 642 $ echo r0 > file1
640 643 $ hg ci -qAm r0
641 644 $ echo r1 > file1
642 645 $ hg ci -qm r1
643 646 $ hg co -qr 0
644 647 $ hg rm file1
645 648 $ hg ci -qm "r2 (delete file1)"
646 649
647 650 (At this point we have r0 creating file1, and sibling commits r1 and r2, which
648 651 modify and delete file1, respectively)
649 652
650 653 $ hg merge -r 1
651 654 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
652 655 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
653 656 What do you want to do? u
654 657 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
655 658 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
656 659 [1]
657 660 $ hg resolve --list
658 661 U file1
659 662 Because we left it as 'unresolved' the file should still exist.
660 663 $ [ -f file1 ] || echo "File does not exist?"
661 664 BC behavior: `hg resolve --mark` accepts that the file is still there, and
662 665 doesn't have a problem with this situation.
663 666 $ hg resolve --mark --config commands.resolve.mark-check=abort
664 667 (no more unresolved files)
665 668 $ hg resolve --list
666 669 R file1
667 670 The file is still there:
668 671 $ [ -f file1 ] || echo "File does not exist?"
669 672 Let's check mark-check=warn:
670 673 $ hg resolve --unmark file1
671 674 $ hg resolve --mark --config commands.resolve.mark-check=warn
672 675 (no more unresolved files)
673 676 $ hg resolve --list
674 677 R file1
675 678 The file is still there:
676 679 $ [ -f file1 ] || echo "File does not exist?"
677 680 Let's resolve the issue by deleting the file via `hg resolve`
678 681 $ hg resolve --unmark file1
679 682 $ echo 'd' | hg resolve file1 --config ui.interactive=1
680 683 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
681 684 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
682 685 What do you want to do? d
683 686 (no more unresolved files)
684 687 $ hg resolve --list
685 688 R file1
686 689 The file is deleted:
687 690 $ [ -f file1 ] && echo "File still exists?" || true
688 691 Doing `hg resolve --mark` doesn't break now that the file is missing:
689 692 $ hg resolve --mark --config commands.resolve.mark-check=abort
690 693 (no more unresolved files)
691 694 $ hg resolve --mark --config commands.resolve.mark-check=warn
692 695 (no more unresolved files)
693 696 Resurrect the file, and delete it outside of hg:
694 697 $ hg resolve --unmark file1
695 698 $ hg resolve file1
696 699 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
697 700 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
698 701 What do you want to do? u
699 702 [1]
700 703 $ [ -f file1 ] || echo "File does not exist?"
701 704 $ hg resolve --list
702 705 U file1
703 706 $ rm file1
704 707 $ hg resolve --mark --config commands.resolve.mark-check=abort
705 708 (no more unresolved files)
706 709 $ hg resolve --list
707 710 R file1
708 711 $ hg resolve --unmark file1
709 712 $ hg resolve file1
710 713 file 'file1' was deleted in local [working copy] but was modified in other [merge rev].
711 714 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
712 715 What do you want to do? u
713 716 [1]
714 717 $ [ -f file1 ] || echo "File does not exist?"
715 718 $ hg resolve --list
716 719 U file1
717 720 $ rm file1
718 721 $ hg resolve --mark --config commands.resolve.mark-check=warn
719 722 (no more unresolved files)
720 723 $ hg resolve --list
721 724 R file1
722 725
723 726
724 727 For completeness, let's try that in the opposite direction (merging r2 into r1,
725 728 instead of r1 into r2):
726 729 $ hg update -qCr 1
727 730 $ hg merge -r 2
728 731 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
729 732 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
730 733 What do you want to do? u
731 734 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
732 735 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
733 736 [1]
734 737 $ hg resolve --list
735 738 U file1
736 739 Because we left it as 'unresolved' the file should still exist.
737 740 $ [ -f file1 ] || echo "File does not exist?"
738 741 BC behavior: `hg resolve --mark` accepts that the file is still there, and
739 742 doesn't have a problem with this situation.
740 743 $ hg resolve --mark --config commands.resolve.mark-check=abort
741 744 (no more unresolved files)
742 745 $ hg resolve --list
743 746 R file1
744 747 The file is still there:
745 748 $ [ -f file1 ] || echo "File does not exist?"
746 749 Let's check mark-check=warn:
747 750 $ hg resolve --unmark file1
748 751 $ hg resolve --mark --config commands.resolve.mark-check=warn
749 752 (no more unresolved files)
750 753 $ hg resolve --list
751 754 R file1
752 755 The file is still there:
753 756 $ [ -f file1 ] || echo "File does not exist?"
754 757 Let's resolve the issue by deleting the file via `hg resolve`
755 758 $ hg resolve --unmark file1
756 759 $ echo 'd' | hg resolve file1 --config ui.interactive=1
757 760 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
758 761 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
759 762 What do you want to do? d
760 763 (no more unresolved files)
761 764 $ hg resolve --list
762 765 R file1
763 766 The file is deleted:
764 767 $ [ -f file1 ] && echo "File still exists?" || true
765 768 Doing `hg resolve --mark` doesn't break now that the file is missing:
766 769 $ hg resolve --mark --config commands.resolve.mark-check=abort
767 770 (no more unresolved files)
768 771 $ hg resolve --mark --config commands.resolve.mark-check=warn
769 772 (no more unresolved files)
770 773 Resurrect the file, and delete it outside of hg:
771 774 $ hg resolve --unmark file1
772 775 $ hg resolve file1
773 776 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
774 777 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
775 778 What do you want to do? u
776 779 [1]
777 780 $ [ -f file1 ] || echo "File does not exist?"
778 781 $ hg resolve --list
779 782 U file1
780 783 $ rm file1
781 784 $ hg resolve --mark --config commands.resolve.mark-check=abort
782 785 (no more unresolved files)
783 786 $ hg resolve --list
784 787 R file1
785 788 $ hg resolve --unmark file1
786 789 $ hg resolve file1
787 790 file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
788 791 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
789 792 What do you want to do? u
790 793 [1]
791 794 $ [ -f file1 ] || echo "File does not exist?"
792 795 $ hg resolve --list
793 796 U file1
794 797 $ rm file1
795 798 $ hg resolve --mark --config commands.resolve.mark-check=warn
796 799 (no more unresolved files)
797 800 $ hg resolve --list
798 801 R file1
799 802
800 803 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now