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