##// END OF EJS Templates
merge: check for path conflicts when merging (issue5628)...
Mark Thomas -
r34556:989e884d default
parent child Browse files
Show More
@@ -1,1884 +1,1988 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 679 pathconflicts = set()
680 680 warnconflicts = set()
681 681 abortconflicts = set()
682 682 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
683 683 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
684 684 if not force:
685 685 def collectconflicts(conflicts, config):
686 686 if config == 'abort':
687 687 abortconflicts.update(conflicts)
688 688 elif config == 'warn':
689 689 warnconflicts.update(conflicts)
690 690
691 691 for f, (m, args, msg) in actions.iteritems():
692 692 if m in ('c', 'dc'):
693 693 if _checkunknownfile(repo, wctx, mctx, f):
694 694 fileconflicts.add(f)
695 695 elif f not in wctx:
696 696 path = _checkunknowndirs(repo, f)
697 697 if path is not None:
698 698 pathconflicts.add(path)
699 699 elif m == 'dg':
700 700 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
701 701 fileconflicts.add(f)
702 702
703 703 allconflicts = fileconflicts | pathconflicts
704 704 ignoredconflicts = set([c for c in allconflicts
705 705 if repo.dirstate._ignore(c)])
706 706 unknownconflicts = allconflicts - ignoredconflicts
707 707 collectconflicts(ignoredconflicts, ignoredconfig)
708 708 collectconflicts(unknownconflicts, unknownconfig)
709 709 else:
710 710 for f, (m, args, msg) in actions.iteritems():
711 711 if m == 'cm':
712 712 fl2, anc = args
713 713 different = _checkunknownfile(repo, wctx, mctx, f)
714 714 if repo.dirstate._ignore(f):
715 715 config = ignoredconfig
716 716 else:
717 717 config = unknownconfig
718 718
719 719 # The behavior when force is True is described by this table:
720 720 # config different mergeforce | action backup
721 721 # * n * | get n
722 722 # * y y | merge -
723 723 # abort y n | merge - (1)
724 724 # warn y n | warn + get y
725 725 # ignore y n | get y
726 726 #
727 727 # (1) this is probably the wrong behavior here -- we should
728 728 # probably abort, but some actions like rebases currently
729 729 # don't like an abort happening in the middle of
730 730 # merge.update.
731 731 if not different:
732 732 actions[f] = ('g', (fl2, False), "remote created")
733 733 elif mergeforce or config == 'abort':
734 734 actions[f] = ('m', (f, f, None, False, anc),
735 735 "remote differs from untracked local")
736 736 elif config == 'abort':
737 737 abortconflicts.add(f)
738 738 else:
739 739 if config == 'warn':
740 740 warnconflicts.add(f)
741 741 actions[f] = ('g', (fl2, True), "remote created")
742 742
743 743 for f in sorted(abortconflicts):
744 744 warn = repo.ui.warn
745 745 if f in pathconflicts:
746 746 if repo.wvfs.isfileorlink(f):
747 747 warn(_("%s: untracked file conflicts with directory\n") % f)
748 748 else:
749 749 warn(_("%s: untracked directory conflicts with file\n") % f)
750 750 else:
751 751 warn(_("%s: untracked file differs\n") % f)
752 752 if abortconflicts:
753 753 raise error.Abort(_("untracked files in working directory "
754 754 "differ from files in requested revision"))
755 755
756 756 for f in sorted(warnconflicts):
757 757 if repo.wvfs.isfileorlink(f):
758 758 repo.ui.warn(_("%s: replacing untracked file\n") % f)
759 759 else:
760 760 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
761 761
762 762 for f, (m, args, msg) in actions.iteritems():
763 763 if m == 'c':
764 764 backup = (f in fileconflicts or f in pathconflicts or
765 765 any(p in pathconflicts for p in util.finddirs(f)))
766 766 flags, = args
767 767 actions[f] = ('g', (flags, backup), msg)
768 768
769 769 def _forgetremoved(wctx, mctx, branchmerge):
770 770 """
771 771 Forget removed files
772 772
773 773 If we're jumping between revisions (as opposed to merging), and if
774 774 neither the working directory nor the target rev has the file,
775 775 then we need to remove it from the dirstate, to prevent the
776 776 dirstate from listing the file when it is no longer in the
777 777 manifest.
778 778
779 779 If we're merging, and the other revision has removed a file
780 780 that is not present in the working directory, we need to mark it
781 781 as removed.
782 782 """
783 783
784 784 actions = {}
785 785 m = 'f'
786 786 if branchmerge:
787 787 m = 'r'
788 788 for f in wctx.deleted():
789 789 if f not in mctx:
790 790 actions[f] = m, None, "forget deleted"
791 791
792 792 if not branchmerge:
793 793 for f in wctx.removed():
794 794 if f not in mctx:
795 795 actions[f] = 'f', None, "forget removed"
796 796
797 797 return actions
798 798
799 799 def _checkcollision(repo, wmf, actions):
800 800 # build provisional merged manifest up
801 801 pmmf = set(wmf)
802 802
803 803 if actions:
804 804 # k, dr, e and rd are no-op
805 805 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
806 806 for f, args, msg in actions[m]:
807 807 pmmf.add(f)
808 808 for f, args, msg in actions['r']:
809 809 pmmf.discard(f)
810 810 for f, args, msg in actions['dm']:
811 811 f2, flags = args
812 812 pmmf.discard(f2)
813 813 pmmf.add(f)
814 814 for f, args, msg in actions['dg']:
815 815 pmmf.add(f)
816 816 for f, args, msg in actions['m']:
817 817 f1, f2, fa, move, anc = args
818 818 if move:
819 819 pmmf.discard(f1)
820 820 pmmf.add(f)
821 821
822 822 # check case-folding collision in provisional merged manifest
823 823 foldmap = {}
824 824 for f in pmmf:
825 825 fold = util.normcase(f)
826 826 if fold in foldmap:
827 827 raise error.Abort(_("case-folding collision between %s and %s")
828 828 % (f, foldmap[fold]))
829 829 foldmap[fold] = f
830 830
831 831 # check case-folding of directories
832 832 foldprefix = unfoldprefix = lastfull = ''
833 833 for fold, f in sorted(foldmap.items()):
834 834 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
835 835 # the folded prefix matches but actual casing is different
836 836 raise error.Abort(_("case-folding collision between "
837 837 "%s and directory of %s") % (lastfull, f))
838 838 foldprefix = fold + '/'
839 839 unfoldprefix = f + '/'
840 840 lastfull = f
841 841
842 842 def driverpreprocess(repo, ms, wctx, labels=None):
843 843 """run the preprocess step of the merge driver, if any
844 844
845 845 This is currently not implemented -- it's an extension point."""
846 846 return True
847 847
848 848 def driverconclude(repo, ms, wctx, labels=None):
849 849 """run the conclude step of the merge driver, if any
850 850
851 851 This is currently not implemented -- it's an extension point."""
852 852 return True
853 853
854 def _filesindirs(repo, manifest, dirs):
855 """
856 Generator that yields pairs of all the files in the manifest that are found
857 inside the directories listed in dirs, and which directory they are found
858 in.
859 """
860 for f in manifest:
861 for p in util.finddirs(f):
862 if p in dirs:
863 yield f, p
864 break
865
866 def checkpathconflicts(repo, wctx, mctx, actions):
867 """
868 Check if any actions introduce path conflicts in the repository, updating
869 actions to record or handle the path conflict accordingly.
870 """
871 mf = wctx.manifest()
872
873 # The set of local files that conflict with a remote directory.
874 localconflicts = set()
875
876 # The set of directories that conflict with a remote file, and so may cause
877 # conflicts if they still contain any files after the merge.
878 remoteconflicts = set()
879
880 # The set of directories that appear as both a file and a directory in the
881 # remote manifest. These indicate an invalid remote manifest, which
882 # can't be updated to cleanly.
883 invalidconflicts = set()
884
885 # The set of files deleted by all the actions.
886 deletedfiles = set()
887
888 for f, (m, args, msg) in actions.items():
889 if m in ('c', 'dc', 'm', 'cm'):
890 # This action may create a new local file.
891 if mf.hasdir(f):
892 # The file aliases a local directory. This might be ok if all
893 # the files in the local directory are being deleted. This
894 # will be checked once we know what all the deleted files are.
895 remoteconflicts.add(f)
896 for p in util.finddirs(f):
897 if p in mf:
898 if p in mctx:
899 # The file is in a directory which aliases both a local
900 # and a remote file. This is an internal inconsistency
901 # within the remote manifest.
902 invalidconflicts.add(p)
903 else:
904 # The file is in a directory which aliases a local file.
905 # We will need to rename the local file.
906 localconflicts.add(p)
907 if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
908 # The file is in a directory which aliases a remote file.
909 # This is an internal inconsistency within the remote
910 # manifest.
911 invalidconflicts.add(p)
912
913 # Track the names of all deleted files.
914 if m == 'r':
915 deletedfiles.add(f)
916 if m == 'm':
917 f1, f2, fa, move, anc = args
918 if move:
919 deletedfiles.add(f1)
920 if m == 'dm':
921 f2, flags = args
922 deletedfiles.add(f2)
923
924 # Rename all local conflicting files that have not been deleted.
925 for p in localconflicts:
926 if p not in deletedfiles:
927 ctxname = str(wctx).rstrip('+')
928 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
929 actions[pnew] = ('pr', (p,), "local path conflict")
930 actions[p] = ('p', (pnew, 'l'), "path conflict")
931
932 if remoteconflicts:
933 # Check if all files in the conflicting directories have been removed.
934 ctxname = str(mctx).rstrip('+')
935 for f, p in _filesindirs(repo, mf, remoteconflicts):
936 if f not in deletedfiles:
937 m, args, msg = actions[p]
938 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
939 if m in ('dc', 'm'):
940 # Action was merge, just update target.
941 actions[pnew] = (m, args, msg)
942 else:
943 # Action was create, change to renamed get action.
944 fl = args[0]
945 actions[pnew] = ('dg', (p, fl), "remote path conflict")
946 actions[p] = ('p', (pnew, 'r'), "path conflict")
947 remoteconflicts.remove(p)
948 break
949
950 if invalidconflicts:
951 for p in invalidconflicts:
952 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
953 raise error.Abort(_("destination manifest contains path conflicts"))
954
854 955 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
855 956 acceptremote, followcopies, forcefulldiff=False):
856 957 """
857 958 Merge wctx and p2 with ancestor pa and generate merge action list
858 959
859 960 branchmerge and force are as passed in to update
860 961 matcher = matcher to filter file lists
861 962 acceptremote = accept the incoming changes without prompting
862 963 """
863 964 if matcher is not None and matcher.always():
864 965 matcher = None
865 966
866 967 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
867 968
868 969 # manifests fetched in order are going to be faster, so prime the caches
869 970 [x.manifest() for x in
870 971 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
871 972
872 973 if followcopies:
873 974 ret = copies.mergecopies(repo, wctx, p2, pa)
874 975 copy, movewithdir, diverge, renamedelete, dirmove = ret
875 976
876 977 boolbm = pycompat.bytestr(bool(branchmerge))
877 978 boolf = pycompat.bytestr(bool(force))
878 979 boolm = pycompat.bytestr(bool(matcher))
879 980 repo.ui.note(_("resolving manifests\n"))
880 981 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
881 982 % (boolbm, boolf, boolm))
882 983 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
883 984
884 985 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
885 986 copied = set(copy.values())
886 987 copied.update(movewithdir.values())
887 988
888 989 if '.hgsubstate' in m1:
889 990 # check whether sub state is modified
890 991 if any(wctx.sub(s).dirty() for s in wctx.substate):
891 992 m1['.hgsubstate'] = modifiednodeid
892 993
893 994 # Don't use m2-vs-ma optimization if:
894 995 # - ma is the same as m1 or m2, which we're just going to diff again later
895 996 # - The caller specifically asks for a full diff, which is useful during bid
896 997 # merge.
897 998 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
898 999 # Identify which files are relevant to the merge, so we can limit the
899 1000 # total m1-vs-m2 diff to just those files. This has significant
900 1001 # performance benefits in large repositories.
901 1002 relevantfiles = set(ma.diff(m2).keys())
902 1003
903 1004 # For copied and moved files, we need to add the source file too.
904 1005 for copykey, copyvalue in copy.iteritems():
905 1006 if copyvalue in relevantfiles:
906 1007 relevantfiles.add(copykey)
907 1008 for movedirkey in movewithdir:
908 1009 relevantfiles.add(movedirkey)
909 1010 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
910 1011 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
911 1012
912 1013 diff = m1.diff(m2, match=matcher)
913 1014
914 1015 if matcher is None:
915 1016 matcher = matchmod.always('', '')
916 1017
917 1018 actions = {}
918 1019 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
919 1020 if n1 and n2: # file exists on both local and remote side
920 1021 if f not in ma:
921 1022 fa = copy.get(f, None)
922 1023 if fa is not None:
923 1024 actions[f] = ('m', (f, f, fa, False, pa.node()),
924 1025 "both renamed from " + fa)
925 1026 else:
926 1027 actions[f] = ('m', (f, f, None, False, pa.node()),
927 1028 "both created")
928 1029 else:
929 1030 a = ma[f]
930 1031 fla = ma.flags(f)
931 1032 nol = 'l' not in fl1 + fl2 + fla
932 1033 if n2 == a and fl2 == fla:
933 1034 actions[f] = ('k', (), "remote unchanged")
934 1035 elif n1 == a and fl1 == fla: # local unchanged - use remote
935 1036 if n1 == n2: # optimization: keep local content
936 1037 actions[f] = ('e', (fl2,), "update permissions")
937 1038 else:
938 1039 actions[f] = ('g', (fl2, False), "remote is newer")
939 1040 elif nol and n2 == a: # remote only changed 'x'
940 1041 actions[f] = ('e', (fl2,), "update permissions")
941 1042 elif nol and n1 == a: # local only changed 'x'
942 1043 actions[f] = ('g', (fl1, False), "remote is newer")
943 1044 else: # both changed something
944 1045 actions[f] = ('m', (f, f, f, False, pa.node()),
945 1046 "versions differ")
946 1047 elif n1: # file exists only on local side
947 1048 if f in copied:
948 1049 pass # we'll deal with it on m2 side
949 1050 elif f in movewithdir: # directory rename, move local
950 1051 f2 = movewithdir[f]
951 1052 if f2 in m2:
952 1053 actions[f2] = ('m', (f, f2, None, True, pa.node()),
953 1054 "remote directory rename, both created")
954 1055 else:
955 1056 actions[f2] = ('dm', (f, fl1),
956 1057 "remote directory rename - move from " + f)
957 1058 elif f in copy:
958 1059 f2 = copy[f]
959 1060 actions[f] = ('m', (f, f2, f2, False, pa.node()),
960 1061 "local copied/moved from " + f2)
961 1062 elif f in ma: # clean, a different, no remote
962 1063 if n1 != ma[f]:
963 1064 if acceptremote:
964 1065 actions[f] = ('r', None, "remote delete")
965 1066 else:
966 1067 actions[f] = ('cd', (f, None, f, False, pa.node()),
967 1068 "prompt changed/deleted")
968 1069 elif n1 == addednodeid:
969 1070 # This extra 'a' is added by working copy manifest to mark
970 1071 # the file as locally added. We should forget it instead of
971 1072 # deleting it.
972 1073 actions[f] = ('f', None, "remote deleted")
973 1074 else:
974 1075 actions[f] = ('r', None, "other deleted")
975 1076 elif n2: # file exists only on remote side
976 1077 if f in copied:
977 1078 pass # we'll deal with it on m1 side
978 1079 elif f in movewithdir:
979 1080 f2 = movewithdir[f]
980 1081 if f2 in m1:
981 1082 actions[f2] = ('m', (f2, f, None, False, pa.node()),
982 1083 "local directory rename, both created")
983 1084 else:
984 1085 actions[f2] = ('dg', (f, fl2),
985 1086 "local directory rename - get from " + f)
986 1087 elif f in copy:
987 1088 f2 = copy[f]
988 1089 if f2 in m2:
989 1090 actions[f] = ('m', (f2, f, f2, False, pa.node()),
990 1091 "remote copied from " + f2)
991 1092 else:
992 1093 actions[f] = ('m', (f2, f, f2, True, pa.node()),
993 1094 "remote moved from " + f2)
994 1095 elif f not in ma:
995 1096 # local unknown, remote created: the logic is described by the
996 1097 # following table:
997 1098 #
998 1099 # force branchmerge different | action
999 1100 # n * * | create
1000 1101 # y n * | create
1001 1102 # y y n | create
1002 1103 # y y y | merge
1003 1104 #
1004 1105 # Checking whether the files are different is expensive, so we
1005 1106 # don't do that when we can avoid it.
1006 1107 if not force:
1007 1108 actions[f] = ('c', (fl2,), "remote created")
1008 1109 elif not branchmerge:
1009 1110 actions[f] = ('c', (fl2,), "remote created")
1010 1111 else:
1011 1112 actions[f] = ('cm', (fl2, pa.node()),
1012 1113 "remote created, get or merge")
1013 1114 elif n2 != ma[f]:
1014 1115 df = None
1015 1116 for d in dirmove:
1016 1117 if f.startswith(d):
1017 1118 # new file added in a directory that was moved
1018 1119 df = dirmove[d] + f[len(d):]
1019 1120 break
1020 1121 if df is not None and df in m1:
1021 1122 actions[df] = ('m', (df, f, f, False, pa.node()),
1022 1123 "local directory rename - respect move from " + f)
1023 1124 elif acceptremote:
1024 1125 actions[f] = ('c', (fl2,), "remote recreating")
1025 1126 else:
1026 1127 actions[f] = ('dc', (None, f, f, False, pa.node()),
1027 1128 "prompt deleted/changed")
1028 1129
1130 # If we are merging, look for path conflicts.
1131 checkpathconflicts(repo, wctx, p2, actions)
1132
1029 1133 return actions, diverge, renamedelete
1030 1134
1031 1135 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1032 1136 """Resolves false conflicts where the nodeid changed but the content
1033 1137 remained the same."""
1034 1138
1035 1139 for f, (m, args, msg) in actions.items():
1036 1140 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
1037 1141 # local did change but ended up with same content
1038 1142 actions[f] = 'r', None, "prompt same"
1039 1143 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
1040 1144 # remote did change but ended up with same content
1041 1145 del actions[f] # don't get = keep local deleted
1042 1146
1043 1147 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1044 1148 acceptremote, followcopies, matcher=None,
1045 1149 mergeforce=False):
1046 1150 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1047 1151 # Avoid cycle.
1048 1152 from . import sparse
1049 1153
1050 1154 if len(ancestors) == 1: # default
1051 1155 actions, diverge, renamedelete = manifestmerge(
1052 1156 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1053 1157 acceptremote, followcopies)
1054 1158 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1055 1159
1056 1160 else: # only when merge.preferancestor=* - the default
1057 1161 repo.ui.note(
1058 1162 _("note: merging %s and %s using bids from ancestors %s\n") %
1059 1163 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1060 1164 for anc in ancestors)))
1061 1165
1062 1166 # Call for bids
1063 1167 fbids = {} # mapping filename to bids (action method to list af actions)
1064 1168 diverge, renamedelete = None, None
1065 1169 for ancestor in ancestors:
1066 1170 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1067 1171 actions, diverge1, renamedelete1 = manifestmerge(
1068 1172 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1069 1173 acceptremote, followcopies, forcefulldiff=True)
1070 1174 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1071 1175
1072 1176 # Track the shortest set of warning on the theory that bid
1073 1177 # merge will correctly incorporate more information
1074 1178 if diverge is None or len(diverge1) < len(diverge):
1075 1179 diverge = diverge1
1076 1180 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1077 1181 renamedelete = renamedelete1
1078 1182
1079 1183 for f, a in sorted(actions.iteritems()):
1080 1184 m, args, msg = a
1081 1185 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1082 1186 if f in fbids:
1083 1187 d = fbids[f]
1084 1188 if m in d:
1085 1189 d[m].append(a)
1086 1190 else:
1087 1191 d[m] = [a]
1088 1192 else:
1089 1193 fbids[f] = {m: [a]}
1090 1194
1091 1195 # Pick the best bid for each file
1092 1196 repo.ui.note(_('\nauction for merging merge bids\n'))
1093 1197 actions = {}
1094 1198 dms = [] # filenames that have dm actions
1095 1199 for f, bids in sorted(fbids.items()):
1096 1200 # bids is a mapping from action method to list af actions
1097 1201 # Consensus?
1098 1202 if len(bids) == 1: # all bids are the same kind of method
1099 1203 m, l = list(bids.items())[0]
1100 1204 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1101 1205 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1102 1206 actions[f] = l[0]
1103 1207 if m == 'dm':
1104 1208 dms.append(f)
1105 1209 continue
1106 1210 # If keep is an option, just do it.
1107 1211 if 'k' in bids:
1108 1212 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1109 1213 actions[f] = bids['k'][0]
1110 1214 continue
1111 1215 # If there are gets and they all agree [how could they not?], do it.
1112 1216 if 'g' in bids:
1113 1217 ga0 = bids['g'][0]
1114 1218 if all(a == ga0 for a in bids['g'][1:]):
1115 1219 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1116 1220 actions[f] = ga0
1117 1221 continue
1118 1222 # TODO: Consider other simple actions such as mode changes
1119 1223 # Handle inefficient democrazy.
1120 1224 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1121 1225 for m, l in sorted(bids.items()):
1122 1226 for _f, args, msg in l:
1123 1227 repo.ui.note(' %s -> %s\n' % (msg, m))
1124 1228 # Pick random action. TODO: Instead, prompt user when resolving
1125 1229 m, l = list(bids.items())[0]
1126 1230 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1127 1231 (f, m))
1128 1232 actions[f] = l[0]
1129 1233 if m == 'dm':
1130 1234 dms.append(f)
1131 1235 continue
1132 1236 # Work around 'dm' that can cause multiple actions for the same file
1133 1237 for f in dms:
1134 1238 dm, (f0, flags), msg = actions[f]
1135 1239 assert dm == 'dm', dm
1136 1240 if f0 in actions and actions[f0][0] == 'r':
1137 1241 # We have one bid for removing a file and another for moving it.
1138 1242 # These two could be merged as first move and then delete ...
1139 1243 # but instead drop moving and just delete.
1140 1244 del actions[f]
1141 1245 repo.ui.note(_('end of auction\n\n'))
1142 1246
1143 1247 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1144 1248
1145 1249 if wctx.rev() is None:
1146 1250 fractions = _forgetremoved(wctx, mctx, branchmerge)
1147 1251 actions.update(fractions)
1148 1252
1149 1253 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1150 1254 actions)
1151 1255
1152 1256 return prunedactions, diverge, renamedelete
1153 1257
1154 1258 def _getcwd():
1155 1259 try:
1156 1260 return pycompat.getcwd()
1157 1261 except OSError as err:
1158 1262 if err.errno == errno.ENOENT:
1159 1263 return None
1160 1264 raise
1161 1265
1162 1266 def batchremove(repo, wctx, actions):
1163 1267 """apply removes to the working directory
1164 1268
1165 1269 yields tuples for progress updates
1166 1270 """
1167 1271 verbose = repo.ui.verbose
1168 1272 cwd = _getcwd()
1169 1273 i = 0
1170 1274 for f, args, msg in actions:
1171 1275 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1172 1276 if verbose:
1173 1277 repo.ui.note(_("removing %s\n") % f)
1174 1278 wctx[f].audit()
1175 1279 try:
1176 1280 wctx[f].remove(ignoremissing=True)
1177 1281 except OSError as inst:
1178 1282 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1179 1283 (f, inst.strerror))
1180 1284 if i == 100:
1181 1285 yield i, f
1182 1286 i = 0
1183 1287 i += 1
1184 1288 if i > 0:
1185 1289 yield i, f
1186 1290
1187 1291 if cwd and not _getcwd():
1188 1292 # cwd was removed in the course of removing files; print a helpful
1189 1293 # warning.
1190 1294 repo.ui.warn(_("current directory was removed\n"
1191 1295 "(consider changing to repo root: %s)\n") % repo.root)
1192 1296
1193 1297 # It's necessary to flush here in case we're inside a worker fork and will
1194 1298 # quit after this function.
1195 1299 wctx.flushall()
1196 1300
1197 1301 def batchget(repo, mctx, wctx, actions):
1198 1302 """apply gets to the working directory
1199 1303
1200 1304 mctx is the context to get from
1201 1305
1202 1306 yields tuples for progress updates
1203 1307 """
1204 1308 verbose = repo.ui.verbose
1205 1309 fctx = mctx.filectx
1206 1310 ui = repo.ui
1207 1311 i = 0
1208 1312 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1209 1313 for f, (flags, backup), msg in actions:
1210 1314 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1211 1315 if verbose:
1212 1316 repo.ui.note(_("getting %s\n") % f)
1213 1317
1214 1318 if backup:
1215 1319 # If a file or directory exists with the same name, back that
1216 1320 # up. Otherwise, look to see if there is a file that conflicts
1217 1321 # with a directory this file is in, and if so, back that up.
1218 1322 absf = repo.wjoin(f)
1219 1323 if not repo.wvfs.lexists(f):
1220 1324 for p in util.finddirs(f):
1221 1325 if repo.wvfs.isfileorlink(p):
1222 1326 absf = repo.wjoin(p)
1223 1327 break
1224 1328 orig = scmutil.origpath(ui, repo, absf)
1225 1329 if repo.wvfs.lexists(absf):
1226 1330 util.rename(absf, orig)
1227 1331 wctx[f].clearunknown()
1228 1332 wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
1229 1333 if i == 100:
1230 1334 yield i, f
1231 1335 i = 0
1232 1336 i += 1
1233 1337 if i > 0:
1234 1338 yield i, f
1235 1339
1236 1340 # It's necessary to flush here in case we're inside a worker fork and will
1237 1341 # quit after this function.
1238 1342 wctx.flushall()
1239 1343
1240 1344 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1241 1345 """apply the merge action list to the working directory
1242 1346
1243 1347 wctx is the working copy context
1244 1348 mctx is the context to be merged into the working copy
1245 1349
1246 1350 Return a tuple of counts (updated, merged, removed, unresolved) that
1247 1351 describes how many files were affected by the update.
1248 1352 """
1249 1353
1250 1354 updated, merged, removed = 0, 0, 0
1251 1355 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1252 1356 moves = []
1253 1357 for m, l in actions.items():
1254 1358 l.sort()
1255 1359
1256 1360 # 'cd' and 'dc' actions are treated like other merge conflicts
1257 1361 mergeactions = sorted(actions['cd'])
1258 1362 mergeactions.extend(sorted(actions['dc']))
1259 1363 mergeactions.extend(actions['m'])
1260 1364 for f, args, msg in mergeactions:
1261 1365 f1, f2, fa, move, anc = args
1262 1366 if f == '.hgsubstate': # merged internally
1263 1367 continue
1264 1368 if f1 is None:
1265 1369 fcl = filemerge.absentfilectx(wctx, fa)
1266 1370 else:
1267 1371 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1268 1372 fcl = wctx[f1]
1269 1373 if f2 is None:
1270 1374 fco = filemerge.absentfilectx(mctx, fa)
1271 1375 else:
1272 1376 fco = mctx[f2]
1273 1377 actx = repo[anc]
1274 1378 if fa in actx:
1275 1379 fca = actx[fa]
1276 1380 else:
1277 1381 # TODO: move to absentfilectx
1278 1382 fca = repo.filectx(f1, fileid=nullrev)
1279 1383 ms.add(fcl, fco, fca, f)
1280 1384 if f1 != f and move:
1281 1385 moves.append(f1)
1282 1386
1283 1387 _updating = _('updating')
1284 1388 _files = _('files')
1285 1389 progress = repo.ui.progress
1286 1390
1287 1391 # remove renamed files after safely stored
1288 1392 for f in moves:
1289 1393 if wctx[f].lexists():
1290 1394 repo.ui.debug("removing %s\n" % f)
1291 1395 wctx[f].audit()
1292 1396 wctx[f].remove()
1293 1397
1294 1398 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1295 1399 z = 0
1296 1400
1297 1401 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1298 1402 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1299 1403
1300 1404 # record path conflicts
1301 1405 for f, args, msg in actions['p']:
1302 1406 f1, fo = args
1303 1407 s = repo.ui.status
1304 1408 s(_("%s: path conflict - a file or link has the same name as a "
1305 1409 "directory\n") % f)
1306 1410 if fo == 'l':
1307 1411 s(_("the local file has been renamed to %s\n") % f1)
1308 1412 else:
1309 1413 s(_("the remote file has been renamed to %s\n") % f1)
1310 1414 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1311 1415 ms.addpath(f, f1, fo)
1312 1416 z += 1
1313 1417 progress(_updating, z, item=f, total=numupdates, unit=_files)
1314 1418
1315 1419 # remove in parallel (must come before resolving path conflicts and getting)
1316 1420 prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
1317 1421 actions['r'])
1318 1422 for i, item in prog:
1319 1423 z += i
1320 1424 progress(_updating, z, item=item, total=numupdates, unit=_files)
1321 1425 removed = len(actions['r'])
1322 1426
1323 1427 # resolve path conflicts (must come before getting)
1324 1428 for f, args, msg in actions['pr']:
1325 1429 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1326 1430 f0, = args
1327 1431 if wctx[f0].lexists():
1328 1432 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1329 1433 wctx[f].audit()
1330 1434 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1331 1435 wctx[f0].remove()
1332 1436 z += 1
1333 1437 progress(_updating, z, item=f, total=numupdates, unit=_files)
1334 1438
1335 1439 # We should flush before forking into worker processes, since those workers
1336 1440 # flush when they complete, and we don't want to duplicate work.
1337 1441 wctx.flushall()
1338 1442
1339 1443 # get in parallel
1340 1444 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
1341 1445 actions['g'])
1342 1446 for i, item in prog:
1343 1447 z += i
1344 1448 progress(_updating, z, item=item, total=numupdates, unit=_files)
1345 1449 updated = len(actions['g'])
1346 1450
1347 1451 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1348 1452 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1349 1453
1350 1454 # forget (manifest only, just log it) (must come first)
1351 1455 for f, args, msg in actions['f']:
1352 1456 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1353 1457 z += 1
1354 1458 progress(_updating, z, item=f, total=numupdates, unit=_files)
1355 1459
1356 1460 # re-add (manifest only, just log it)
1357 1461 for f, args, msg in actions['a']:
1358 1462 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1359 1463 z += 1
1360 1464 progress(_updating, z, item=f, total=numupdates, unit=_files)
1361 1465
1362 1466 # re-add/mark as modified (manifest only, just log it)
1363 1467 for f, args, msg in actions['am']:
1364 1468 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1365 1469 z += 1
1366 1470 progress(_updating, z, item=f, total=numupdates, unit=_files)
1367 1471
1368 1472 # keep (noop, just log it)
1369 1473 for f, args, msg in actions['k']:
1370 1474 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1371 1475 # no progress
1372 1476
1373 1477 # directory rename, move local
1374 1478 for f, args, msg in actions['dm']:
1375 1479 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1376 1480 z += 1
1377 1481 progress(_updating, z, item=f, total=numupdates, unit=_files)
1378 1482 f0, flags = args
1379 1483 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1380 1484 wctx[f].audit()
1381 1485 wctx[f].write(wctx.filectx(f0).data(), flags)
1382 1486 wctx[f0].remove()
1383 1487 updated += 1
1384 1488
1385 1489 # local directory rename, get
1386 1490 for f, args, msg in actions['dg']:
1387 1491 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1388 1492 z += 1
1389 1493 progress(_updating, z, item=f, total=numupdates, unit=_files)
1390 1494 f0, flags = args
1391 1495 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1392 1496 wctx[f].write(mctx.filectx(f0).data(), flags)
1393 1497 updated += 1
1394 1498
1395 1499 # exec
1396 1500 for f, args, msg in actions['e']:
1397 1501 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1398 1502 z += 1
1399 1503 progress(_updating, z, item=f, total=numupdates, unit=_files)
1400 1504 flags, = args
1401 1505 wctx[f].audit()
1402 1506 wctx[f].setflags('l' in flags, 'x' in flags)
1403 1507 updated += 1
1404 1508
1405 1509 # the ordering is important here -- ms.mergedriver will raise if the merge
1406 1510 # driver has changed, and we want to be able to bypass it when overwrite is
1407 1511 # True
1408 1512 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1409 1513
1410 1514 if usemergedriver:
1411 1515 ms.commit()
1412 1516 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1413 1517 # the driver might leave some files unresolved
1414 1518 unresolvedf = set(ms.unresolved())
1415 1519 if not proceed:
1416 1520 # XXX setting unresolved to at least 1 is a hack to make sure we
1417 1521 # error out
1418 1522 return updated, merged, removed, max(len(unresolvedf), 1)
1419 1523 newactions = []
1420 1524 for f, args, msg in mergeactions:
1421 1525 if f in unresolvedf:
1422 1526 newactions.append((f, args, msg))
1423 1527 mergeactions = newactions
1424 1528
1425 1529 # premerge
1426 1530 tocomplete = []
1427 1531 for f, args, msg in mergeactions:
1428 1532 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1429 1533 z += 1
1430 1534 progress(_updating, z, item=f, total=numupdates, unit=_files)
1431 1535 if f == '.hgsubstate': # subrepo states need updating
1432 1536 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1433 1537 overwrite, labels)
1434 1538 continue
1435 1539 wctx[f].audit()
1436 1540 complete, r = ms.preresolve(f, wctx)
1437 1541 if not complete:
1438 1542 numupdates += 1
1439 1543 tocomplete.append((f, args, msg))
1440 1544
1441 1545 # merge
1442 1546 for f, args, msg in tocomplete:
1443 1547 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1444 1548 z += 1
1445 1549 progress(_updating, z, item=f, total=numupdates, unit=_files)
1446 1550 ms.resolve(f, wctx)
1447 1551
1448 1552 ms.commit()
1449 1553
1450 1554 unresolved = ms.unresolvedcount()
1451 1555
1452 1556 if usemergedriver and not unresolved and ms.mdstate() != 's':
1453 1557 if not driverconclude(repo, ms, wctx, labels=labels):
1454 1558 # XXX setting unresolved to at least 1 is a hack to make sure we
1455 1559 # error out
1456 1560 unresolved = max(unresolved, 1)
1457 1561
1458 1562 ms.commit()
1459 1563
1460 1564 msupdated, msmerged, msremoved = ms.counts()
1461 1565 updated += msupdated
1462 1566 merged += msmerged
1463 1567 removed += msremoved
1464 1568
1465 1569 extraactions = ms.actions()
1466 1570 if extraactions:
1467 1571 mfiles = set(a[0] for a in actions['m'])
1468 1572 for k, acts in extraactions.iteritems():
1469 1573 actions[k].extend(acts)
1470 1574 # Remove these files from actions['m'] as well. This is important
1471 1575 # because in recordupdates, files in actions['m'] are processed
1472 1576 # after files in other actions, and the merge driver might add
1473 1577 # files to those actions via extraactions above. This can lead to a
1474 1578 # file being recorded twice, with poor results. This is especially
1475 1579 # problematic for actions['r'] (currently only possible with the
1476 1580 # merge driver in the initial merge process; interrupted merges
1477 1581 # don't go through this flow).
1478 1582 #
1479 1583 # The real fix here is to have indexes by both file and action so
1480 1584 # that when the action for a file is changed it is automatically
1481 1585 # reflected in the other action lists. But that involves a more
1482 1586 # complex data structure, so this will do for now.
1483 1587 #
1484 1588 # We don't need to do the same operation for 'dc' and 'cd' because
1485 1589 # those lists aren't consulted again.
1486 1590 mfiles.difference_update(a[0] for a in acts)
1487 1591
1488 1592 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1489 1593
1490 1594 progress(_updating, None, total=numupdates, unit=_files)
1491 1595
1492 1596 return updated, merged, removed, unresolved
1493 1597
1494 1598 def recordupdates(repo, actions, branchmerge):
1495 1599 "record merge actions to the dirstate"
1496 1600 # remove (must come first)
1497 1601 for f, args, msg in actions.get('r', []):
1498 1602 if branchmerge:
1499 1603 repo.dirstate.remove(f)
1500 1604 else:
1501 1605 repo.dirstate.drop(f)
1502 1606
1503 1607 # forget (must come first)
1504 1608 for f, args, msg in actions.get('f', []):
1505 1609 repo.dirstate.drop(f)
1506 1610
1507 1611 # resolve path conflicts
1508 1612 for f, args, msg in actions.get('pr', []):
1509 1613 f0, = args
1510 1614 origf0 = repo.dirstate.copied(f0) or f0
1511 1615 repo.dirstate.add(f)
1512 1616 repo.dirstate.copy(origf0, f)
1513 1617 if f0 == origf0:
1514 1618 repo.dirstate.remove(f0)
1515 1619 else:
1516 1620 repo.dirstate.drop(f0)
1517 1621
1518 1622 # re-add
1519 1623 for f, args, msg in actions.get('a', []):
1520 1624 repo.dirstate.add(f)
1521 1625
1522 1626 # re-add/mark as modified
1523 1627 for f, args, msg in actions.get('am', []):
1524 1628 if branchmerge:
1525 1629 repo.dirstate.normallookup(f)
1526 1630 else:
1527 1631 repo.dirstate.add(f)
1528 1632
1529 1633 # exec change
1530 1634 for f, args, msg in actions.get('e', []):
1531 1635 repo.dirstate.normallookup(f)
1532 1636
1533 1637 # keep
1534 1638 for f, args, msg in actions.get('k', []):
1535 1639 pass
1536 1640
1537 1641 # get
1538 1642 for f, args, msg in actions.get('g', []):
1539 1643 if branchmerge:
1540 1644 repo.dirstate.otherparent(f)
1541 1645 else:
1542 1646 repo.dirstate.normal(f)
1543 1647
1544 1648 # merge
1545 1649 for f, args, msg in actions.get('m', []):
1546 1650 f1, f2, fa, move, anc = args
1547 1651 if branchmerge:
1548 1652 # We've done a branch merge, mark this file as merged
1549 1653 # so that we properly record the merger later
1550 1654 repo.dirstate.merge(f)
1551 1655 if f1 != f2: # copy/rename
1552 1656 if move:
1553 1657 repo.dirstate.remove(f1)
1554 1658 if f1 != f:
1555 1659 repo.dirstate.copy(f1, f)
1556 1660 else:
1557 1661 repo.dirstate.copy(f2, f)
1558 1662 else:
1559 1663 # We've update-merged a locally modified file, so
1560 1664 # we set the dirstate to emulate a normal checkout
1561 1665 # of that file some time in the past. Thus our
1562 1666 # merge will appear as a normal local file
1563 1667 # modification.
1564 1668 if f2 == f: # file not locally copied/moved
1565 1669 repo.dirstate.normallookup(f)
1566 1670 if move:
1567 1671 repo.dirstate.drop(f1)
1568 1672
1569 1673 # directory rename, move local
1570 1674 for f, args, msg in actions.get('dm', []):
1571 1675 f0, flag = args
1572 1676 if branchmerge:
1573 1677 repo.dirstate.add(f)
1574 1678 repo.dirstate.remove(f0)
1575 1679 repo.dirstate.copy(f0, f)
1576 1680 else:
1577 1681 repo.dirstate.normal(f)
1578 1682 repo.dirstate.drop(f0)
1579 1683
1580 1684 # directory rename, get
1581 1685 for f, args, msg in actions.get('dg', []):
1582 1686 f0, flag = args
1583 1687 if branchmerge:
1584 1688 repo.dirstate.add(f)
1585 1689 repo.dirstate.copy(f0, f)
1586 1690 else:
1587 1691 repo.dirstate.normal(f)
1588 1692
1589 1693 def update(repo, node, branchmerge, force, ancestor=None,
1590 1694 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1591 1695 updatecheck=None, wc=None):
1592 1696 """
1593 1697 Perform a merge between the working directory and the given node
1594 1698
1595 1699 node = the node to update to
1596 1700 branchmerge = whether to merge between branches
1597 1701 force = whether to force branch merging or file overwriting
1598 1702 matcher = a matcher to filter file lists (dirstate not updated)
1599 1703 mergeancestor = whether it is merging with an ancestor. If true,
1600 1704 we should accept the incoming changes for any prompts that occur.
1601 1705 If false, merging with an ancestor (fast-forward) is only allowed
1602 1706 between different named branches. This flag is used by rebase extension
1603 1707 as a temporary fix and should be avoided in general.
1604 1708 labels = labels to use for base, local and other
1605 1709 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1606 1710 this is True, then 'force' should be True as well.
1607 1711
1608 1712 The table below shows all the behaviors of the update command
1609 1713 given the -c and -C or no options, whether the working directory
1610 1714 is dirty, whether a revision is specified, and the relationship of
1611 1715 the parent rev to the target rev (linear or not). Match from top first. The
1612 1716 -n option doesn't exist on the command line, but represents the
1613 1717 experimental.updatecheck=noconflict option.
1614 1718
1615 1719 This logic is tested by test-update-branches.t.
1616 1720
1617 1721 -c -C -n -m dirty rev linear | result
1618 1722 y y * * * * * | (1)
1619 1723 y * y * * * * | (1)
1620 1724 y * * y * * * | (1)
1621 1725 * y y * * * * | (1)
1622 1726 * y * y * * * | (1)
1623 1727 * * y y * * * | (1)
1624 1728 * * * * * n n | x
1625 1729 * * * * n * * | ok
1626 1730 n n n n y * y | merge
1627 1731 n n n n y y n | (2)
1628 1732 n n n y y * * | merge
1629 1733 n n y n y * * | merge if no conflict
1630 1734 n y n n y * * | discard
1631 1735 y n n n y * * | (3)
1632 1736
1633 1737 x = can't happen
1634 1738 * = don't-care
1635 1739 1 = incompatible options (checked in commands.py)
1636 1740 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1637 1741 3 = abort: uncommitted changes (checked in commands.py)
1638 1742
1639 1743 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1640 1744 to repo[None] if None is passed.
1641 1745
1642 1746 Return the same tuple as applyupdates().
1643 1747 """
1644 1748 # Avoid cycle.
1645 1749 from . import sparse
1646 1750
1647 1751 # This function used to find the default destination if node was None, but
1648 1752 # that's now in destutil.py.
1649 1753 assert node is not None
1650 1754 if not branchmerge and not force:
1651 1755 # TODO: remove the default once all callers that pass branchmerge=False
1652 1756 # and force=False pass a value for updatecheck. We may want to allow
1653 1757 # updatecheck='abort' to better suppport some of these callers.
1654 1758 if updatecheck is None:
1655 1759 updatecheck = 'linear'
1656 1760 assert updatecheck in ('none', 'linear', 'noconflict')
1657 1761 # If we're doing a partial update, we need to skip updating
1658 1762 # the dirstate, so make a note of any partial-ness to the
1659 1763 # update here.
1660 1764 if matcher is None or matcher.always():
1661 1765 partial = False
1662 1766 else:
1663 1767 partial = True
1664 1768 with repo.wlock():
1665 1769 if wc is None:
1666 1770 wc = repo[None]
1667 1771 pl = wc.parents()
1668 1772 p1 = pl[0]
1669 1773 pas = [None]
1670 1774 if ancestor is not None:
1671 1775 pas = [repo[ancestor]]
1672 1776
1673 1777 overwrite = force and not branchmerge
1674 1778
1675 1779 p2 = repo[node]
1676 1780 if pas[0] is None:
1677 1781 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
1678 1782 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1679 1783 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1680 1784 else:
1681 1785 pas = [p1.ancestor(p2, warn=branchmerge)]
1682 1786
1683 1787 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1684 1788
1685 1789 ### check phase
1686 1790 if not overwrite:
1687 1791 if len(pl) > 1:
1688 1792 raise error.Abort(_("outstanding uncommitted merge"))
1689 1793 ms = mergestate.read(repo)
1690 1794 if list(ms.unresolved()):
1691 1795 raise error.Abort(_("outstanding merge conflicts"))
1692 1796 if branchmerge:
1693 1797 if pas == [p2]:
1694 1798 raise error.Abort(_("merging with a working directory ancestor"
1695 1799 " has no effect"))
1696 1800 elif pas == [p1]:
1697 1801 if not mergeancestor and wc.branch() == p2.branch():
1698 1802 raise error.Abort(_("nothing to merge"),
1699 1803 hint=_("use 'hg update' "
1700 1804 "or check 'hg heads'"))
1701 1805 if not force and (wc.files() or wc.deleted()):
1702 1806 raise error.Abort(_("uncommitted changes"),
1703 1807 hint=_("use 'hg status' to list changes"))
1704 1808 for s in sorted(wc.substate):
1705 1809 wc.sub(s).bailifchanged()
1706 1810
1707 1811 elif not overwrite:
1708 1812 if p1 == p2: # no-op update
1709 1813 # call the hooks and exit early
1710 1814 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1711 1815 repo.hook('update', parent1=xp2, parent2='', error=0)
1712 1816 return 0, 0, 0, 0
1713 1817
1714 1818 if (updatecheck == 'linear' and
1715 1819 pas not in ([p1], [p2])): # nonlinear
1716 1820 dirty = wc.dirty(missing=True)
1717 1821 if dirty:
1718 1822 # Branching is a bit strange to ensure we do the minimal
1719 1823 # amount of call to obsutil.foreground.
1720 1824 foreground = obsutil.foreground(repo, [p1.node()])
1721 1825 # note: the <node> variable contains a random identifier
1722 1826 if repo[node].node() in foreground:
1723 1827 pass # allow updating to successors
1724 1828 else:
1725 1829 msg = _("uncommitted changes")
1726 1830 hint = _("commit or update --clean to discard changes")
1727 1831 raise error.UpdateAbort(msg, hint=hint)
1728 1832 else:
1729 1833 # Allow jumping branches if clean and specific rev given
1730 1834 pass
1731 1835
1732 1836 if overwrite:
1733 1837 pas = [wc]
1734 1838 elif not branchmerge:
1735 1839 pas = [p1]
1736 1840
1737 1841 # deprecated config: merge.followcopies
1738 1842 followcopies = repo.ui.configbool('merge', 'followcopies')
1739 1843 if overwrite:
1740 1844 followcopies = False
1741 1845 elif not pas[0]:
1742 1846 followcopies = False
1743 1847 if not branchmerge and not wc.dirty(missing=True):
1744 1848 followcopies = False
1745 1849
1746 1850 ### calculate phase
1747 1851 actionbyfile, diverge, renamedelete = calculateupdates(
1748 1852 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1749 1853 followcopies, matcher=matcher, mergeforce=mergeforce)
1750 1854
1751 1855 if updatecheck == 'noconflict':
1752 1856 for f, (m, args, msg) in actionbyfile.iteritems():
1753 1857 if m not in ('g', 'k', 'e', 'r', 'pr'):
1754 1858 msg = _("conflicting changes")
1755 1859 hint = _("commit or update --clean to discard changes")
1756 1860 raise error.Abort(msg, hint=hint)
1757 1861
1758 1862 # Prompt and create actions. Most of this is in the resolve phase
1759 1863 # already, but we can't handle .hgsubstate in filemerge or
1760 1864 # subrepo.submerge yet so we have to keep prompting for it.
1761 1865 if '.hgsubstate' in actionbyfile:
1762 1866 f = '.hgsubstate'
1763 1867 m, args, msg = actionbyfile[f]
1764 1868 prompts = filemerge.partextras(labels)
1765 1869 prompts['f'] = f
1766 1870 if m == 'cd':
1767 1871 if repo.ui.promptchoice(
1768 1872 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1769 1873 "use (c)hanged version or (d)elete?"
1770 1874 "$$ &Changed $$ &Delete") % prompts, 0):
1771 1875 actionbyfile[f] = ('r', None, "prompt delete")
1772 1876 elif f in p1:
1773 1877 actionbyfile[f] = ('am', None, "prompt keep")
1774 1878 else:
1775 1879 actionbyfile[f] = ('a', None, "prompt keep")
1776 1880 elif m == 'dc':
1777 1881 f1, f2, fa, move, anc = args
1778 1882 flags = p2[f2].flags()
1779 1883 if repo.ui.promptchoice(
1780 1884 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1781 1885 "use (c)hanged version or leave (d)eleted?"
1782 1886 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1783 1887 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1784 1888 else:
1785 1889 del actionbyfile[f]
1786 1890
1787 1891 # Convert to dictionary-of-lists format
1788 1892 actions = dict((m, [])
1789 1893 for m in 'a am f g cd dc r dm dg m e k p pr'.split())
1790 1894 for f, (m, args, msg) in actionbyfile.iteritems():
1791 1895 if m not in actions:
1792 1896 actions[m] = []
1793 1897 actions[m].append((f, args, msg))
1794 1898
1795 1899 if not util.fscasesensitive(repo.path):
1796 1900 # check collision between files only in p2 for clean update
1797 1901 if (not branchmerge and
1798 1902 (force or not wc.dirty(missing=True, branch=False))):
1799 1903 _checkcollision(repo, p2.manifest(), None)
1800 1904 else:
1801 1905 _checkcollision(repo, wc.manifest(), actions)
1802 1906
1803 1907 # divergent renames
1804 1908 for f, fl in sorted(diverge.iteritems()):
1805 1909 repo.ui.warn(_("note: possible conflict - %s was renamed "
1806 1910 "multiple times to:\n") % f)
1807 1911 for nf in fl:
1808 1912 repo.ui.warn(" %s\n" % nf)
1809 1913
1810 1914 # rename and delete
1811 1915 for f, fl in sorted(renamedelete.iteritems()):
1812 1916 repo.ui.warn(_("note: possible conflict - %s was deleted "
1813 1917 "and renamed to:\n") % f)
1814 1918 for nf in fl:
1815 1919 repo.ui.warn(" %s\n" % nf)
1816 1920
1817 1921 ### apply phase
1818 1922 if not branchmerge: # just jump to the new rev
1819 1923 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1820 1924 if not partial:
1821 1925 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1822 1926 # note that we're in the middle of an update
1823 1927 repo.vfs.write('updatestate', p2.hex())
1824 1928
1825 1929 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1826 1930 wc.flushall()
1827 1931
1828 1932 if not partial:
1829 1933 with repo.dirstate.parentchange():
1830 1934 repo.setparents(fp1, fp2)
1831 1935 recordupdates(repo, actions, branchmerge)
1832 1936 # update completed, clear state
1833 1937 util.unlink(repo.vfs.join('updatestate'))
1834 1938
1835 1939 if not branchmerge:
1836 1940 repo.dirstate.setbranch(p2.branch())
1837 1941
1838 1942 # If we're updating to a location, clean up any stale temporary includes
1839 1943 # (ex: this happens during hg rebase --abort).
1840 1944 if not branchmerge:
1841 1945 sparse.prunetemporaryincludes(repo)
1842 1946
1843 1947 if not partial:
1844 1948 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1845 1949 return stats
1846 1950
1847 1951 def graft(repo, ctx, pctx, labels, keepparent=False):
1848 1952 """Do a graft-like merge.
1849 1953
1850 1954 This is a merge where the merge ancestor is chosen such that one
1851 1955 or more changesets are grafted onto the current changeset. In
1852 1956 addition to the merge, this fixes up the dirstate to include only
1853 1957 a single parent (if keepparent is False) and tries to duplicate any
1854 1958 renames/copies appropriately.
1855 1959
1856 1960 ctx - changeset to rebase
1857 1961 pctx - merge base, usually ctx.p1()
1858 1962 labels - merge labels eg ['local', 'graft']
1859 1963 keepparent - keep second parent if any
1860 1964
1861 1965 """
1862 1966 # If we're grafting a descendant onto an ancestor, be sure to pass
1863 1967 # mergeancestor=True to update. This does two things: 1) allows the merge if
1864 1968 # the destination is the same as the parent of the ctx (so we can use graft
1865 1969 # to copy commits), and 2) informs update that the incoming changes are
1866 1970 # newer than the destination so it doesn't prompt about "remote changed foo
1867 1971 # which local deleted".
1868 1972 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1869 1973
1870 1974 stats = update(repo, ctx.node(), True, True, pctx.node(),
1871 1975 mergeancestor=mergeancestor, labels=labels)
1872 1976
1873 1977 pother = nullid
1874 1978 parents = ctx.parents()
1875 1979 if keepparent and len(parents) == 2 and pctx in parents:
1876 1980 parents.remove(pctx)
1877 1981 pother = parents[0].node()
1878 1982
1879 1983 with repo.dirstate.parentchange():
1880 1984 repo.setparents(repo['.'].node(), pother)
1881 1985 repo.dirstate.write(repo.currenttransaction())
1882 1986 # fix up dirstate for copies and renames
1883 1987 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1884 1988 return stats
@@ -1,231 +1,242 b''
1 1 $ hg init
2 2
3 3 audit of .hg
4 4
5 5 $ hg add .hg/00changelog.i
6 6 abort: path contains illegal component: .hg/00changelog.i (glob)
7 7 [255]
8 8
9 9 #if symlink
10 10
11 11 Symlinks
12 12
13 13 $ mkdir a
14 14 $ echo a > a/a
15 15 $ hg ci -Ama
16 16 adding a/a
17 17 $ ln -s a b
18 18 $ echo b > a/b
19 19 $ hg add b/b
20 20 abort: path 'b/b' traverses symbolic link 'b' (glob)
21 21 [255]
22 22 $ hg add b
23 23
24 24 should still fail - maybe
25 25
26 26 $ hg add b/b
27 27 abort: path 'b/b' traverses symbolic link 'b' (glob)
28 28 [255]
29 29
30 30 $ hg commit -m 'add symlink b'
31 31
32 32
33 33 Test symlink traversing when accessing history:
34 34 -----------------------------------------------
35 35
36 36 (build a changeset where the path exists as a directory)
37 37
38 38 $ hg up 0
39 39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 40 $ mkdir b
41 41 $ echo c > b/a
42 42 $ hg add b/a
43 43 $ hg ci -m 'add directory b'
44 44 created new head
45 45
46 46 Test that hg cat does not do anything wrong the working copy has 'b' as directory
47 47
48 48 $ hg cat b/a
49 49 c
50 50 $ hg cat -r "desc(directory)" b/a
51 51 c
52 52 $ hg cat -r "desc(symlink)" b/a
53 53 b/a: no such file in rev bc151a1f53bd
54 54 [1]
55 55
56 56 Test that hg cat does not do anything wrong the working copy has 'b' as a symlink (issue4749)
57 57
58 58 $ hg up 'desc(symlink)'
59 59 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
60 60 $ hg cat b/a
61 61 b/a: no such file in rev bc151a1f53bd
62 62 [1]
63 63 $ hg cat -r "desc(directory)" b/a
64 64 c
65 65 $ hg cat -r "desc(symlink)" b/a
66 66 b/a: no such file in rev bc151a1f53bd
67 67 [1]
68 68
69 69 #endif
70 70
71 71
72 72 unbundle tampered bundle
73 73
74 74 $ hg init target
75 75 $ cd target
76 76 $ hg unbundle "$TESTDIR/bundles/tampered.hg"
77 77 adding changesets
78 78 adding manifests
79 79 adding file changes
80 80 added 5 changesets with 6 changes to 6 files (+4 heads)
81 81 (run 'hg heads' to see heads, 'hg merge' to merge)
82 82
83 83 attack .hg/test
84 84
85 85 $ hg manifest -r0
86 86 .hg/test
87 87 $ hg update -Cr0
88 88 abort: path contains illegal component: .hg/test (glob)
89 89 [255]
90 90
91 91 attack foo/.hg/test
92 92
93 93 $ hg manifest -r1
94 94 foo/.hg/test
95 95 $ hg update -Cr1
96 96 abort: path 'foo/.hg/test' is inside nested repo 'foo' (glob)
97 97 [255]
98 98
99 99 attack back/test where back symlinks to ..
100 100
101 101 $ hg manifest -r2
102 102 back
103 103 back/test
104 104 #if symlink
105 105 $ hg update -Cr2
106 abort: path 'back/test' traverses symbolic link 'back'
106 back: is both a file and a directory
107 abort: destination manifest contains path conflicts
107 108 [255]
108 109 #else
109 110 ('back' will be a file and cause some other system specific error)
110 111 $ hg update -Cr2
111 112 abort: * (glob)
112 113 [255]
113 114 #endif
114 115
115 116 attack ../test
116 117
117 118 $ hg manifest -r3
118 119 ../test
119 120 $ hg update -Cr3
120 121 abort: path contains illegal component: ../test (glob)
121 122 [255]
122 123
123 124 attack /tmp/test
124 125
125 126 $ hg manifest -r4
126 127 /tmp/test
127 128 $ hg update -Cr4
128 129 abort: path contains illegal component: /tmp/test (glob)
129 130 [255]
130 131
131 132 $ cd ..
132 133
133 134 Test symlink traversal on merge:
134 135 --------------------------------
135 136
136 137 #if symlink
137 138
138 139 set up symlink hell
139 140
140 141 $ mkdir merge-symlink-out
141 142 $ hg init merge-symlink
142 143 $ cd merge-symlink
143 144 $ touch base
144 145 $ hg commit -qAm base
145 146 $ ln -s ../merge-symlink-out a
146 147 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
147 148 $ hg up -q 0
148 149 $ mkdir a
149 150 $ touch a/poisoned
150 151 $ hg commit -qAm 'file a/poisoned'
151 152 $ hg log -G -T '{rev}: {desc}\n'
152 153 @ 2: file a/poisoned
153 154 |
154 155 | o 1: symlink a -> ../merge-symlink-out
155 156 |/
156 157 o 0: base
157 158
158 159
159 160 try trivial merge
160 161
161 162 $ hg up -qC 1
162 163 $ hg merge 2
163 abort: path 'a/poisoned' traverses symbolic link 'a'
164 [255]
164 a: path conflict - a file or link has the same name as a directory
165 the local file has been renamed to a~aa04623eb0c3
166 resolve manually then use 'hg resolve --mark a'
167 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
168 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
169 [1]
165 170
166 171 try rebase onto other revision: cache of audited paths should be discarded,
167 172 and the rebase should fail (issue5628)
168 173
169 174 $ hg up -qC 2
170 175 $ hg rebase -s 2 -d 1 --config extensions.rebase=
171 176 rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
172 abort: path 'a/poisoned' traverses symbolic link 'a'
173 [255]
177 a: path conflict - a file or link has the same name as a directory
178 the local file has been renamed to a~aa04623eb0c3
179 resolve manually then use 'hg resolve --mark a'
180 unresolved conflicts (see hg resolve, then hg rebase --continue)
181 [1]
174 182 $ ls ../merge-symlink-out
175 183
176 184 $ cd ..
177 185
178 186 Test symlink traversal on update:
179 187 ---------------------------------
180 188
181 189 $ mkdir update-symlink-out
182 190 $ hg init update-symlink
183 191 $ cd update-symlink
184 192 $ ln -s ../update-symlink-out a
185 193 $ hg commit -qAm 'symlink a -> ../update-symlink-out'
186 194 $ hg rm a
187 195 $ mkdir a && touch a/b
188 196 $ hg ci -qAm 'file a/b' a/b
189 197 $ hg up -qC 0
190 198 $ hg rm a
191 199 $ mkdir a && touch a/c
192 200 $ hg ci -qAm 'rm a, file a/c'
193 201 $ hg log -G -T '{rev}: {desc}\n'
194 202 @ 2: rm a, file a/c
195 203 |
196 204 | o 1: file a/b
197 205 |/
198 206 o 0: symlink a -> ../update-symlink-out
199 207
200 208
201 209 try linear update where symlink already exists:
202 210
203 211 $ hg up -qC 0
204 212 $ hg up 1
205 abort: path 'a/b' traverses symbolic link 'a'
213 a: is both a file and a directory
214 abort: destination manifest contains path conflicts
206 215 [255]
207 216
208 217 try linear update including symlinked directory and its content: paths are
209 218 audited first by calculateupdates(), where no symlink is created so both
210 219 'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
211 220
212 221 $ hg up -qC null
213 222 $ hg up 1
214 abort: path 'a/b' traverses symbolic link 'a'
223 a: is both a file and a directory
224 abort: destination manifest contains path conflicts
215 225 [255]
216 226 $ ls ../update-symlink-out
217 227
218 228 try branch update replacing directory with symlink, and its content: the
219 229 path 'a' is audited as a directory first, which should be audited again as
220 230 a symlink.
221 231
222 232 $ rm -f a
223 233 $ hg up -qC 2
224 234 $ hg up 1
225 abort: path 'a/b' traverses symbolic link 'a'
235 a: is both a file and a directory
236 abort: destination manifest contains path conflicts
226 237 [255]
227 238 $ ls ../update-symlink-out
228 239
229 240 $ cd ..
230 241
231 242 #endif
@@ -1,1000 +1,1004 b''
1 1 #if windows
2 2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 3 #else
4 4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 5 #endif
6 6 $ export PYTHONPATH
7 7
8 8 typical client does not want echo-back messages, so test without it:
9 9
10 10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 11 $ mv $HGRCPATH.new $HGRCPATH
12 12
13 13 $ hg init repo
14 14 $ cd repo
15 15
16 16 >>> from __future__ import absolute_import, print_function
17 17 >>> import os
18 18 >>> import sys
19 19 >>> from hgclient import check, readchannel, runcommand
20 20 >>> @check
21 21 ... def hellomessage(server):
22 22 ... ch, data = readchannel(server)
23 23 ... print('%c, %r' % (ch, data))
24 24 ... # run an arbitrary command to make sure the next thing the server
25 25 ... # sends isn't part of the hello message
26 26 ... runcommand(server, ['id'])
27 27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 28 *** runcommand id
29 29 000000000000 tip
30 30
31 31 >>> from hgclient import check
32 32 >>> @check
33 33 ... def unknowncommand(server):
34 34 ... server.stdin.write('unknowncommand\n')
35 35 abort: unknown command unknowncommand
36 36
37 37 >>> from hgclient import check, readchannel, runcommand
38 38 >>> @check
39 39 ... def checkruncommand(server):
40 40 ... # hello block
41 41 ... readchannel(server)
42 42 ...
43 43 ... # no args
44 44 ... runcommand(server, [])
45 45 ...
46 46 ... # global options
47 47 ... runcommand(server, ['id', '--quiet'])
48 48 ...
49 49 ... # make sure global options don't stick through requests
50 50 ... runcommand(server, ['id'])
51 51 ...
52 52 ... # --config
53 53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
54 54 ...
55 55 ... # make sure --config doesn't stick
56 56 ... runcommand(server, ['id'])
57 57 ...
58 58 ... # negative return code should be masked
59 59 ... runcommand(server, ['id', '-runknown'])
60 60 *** runcommand
61 61 Mercurial Distributed SCM
62 62
63 63 basic commands:
64 64
65 65 add add the specified files on the next commit
66 66 annotate show changeset information by line for each file
67 67 clone make a copy of an existing repository
68 68 commit commit the specified files or all outstanding changes
69 69 diff diff repository (or selected files)
70 70 export dump the header and diffs for one or more changesets
71 71 forget forget the specified files on the next commit
72 72 init create a new repository in the given directory
73 73 log show revision history of entire repository or files
74 74 merge merge another revision into working directory
75 75 pull pull changes from the specified source
76 76 push push changes to the specified destination
77 77 remove remove the specified files on the next commit
78 78 serve start stand-alone webserver
79 79 status show changed files in the working directory
80 80 summary summarize working directory state
81 81 update update working directory (or switch revisions)
82 82
83 83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 84 *** runcommand id --quiet
85 85 000000000000
86 86 *** runcommand id
87 87 000000000000 tip
88 88 *** runcommand id --config ui.quiet=True
89 89 000000000000
90 90 *** runcommand id
91 91 000000000000 tip
92 92 *** runcommand id -runknown
93 93 abort: unknown revision 'unknown'!
94 94 [255]
95 95
96 96 >>> from hgclient import check, readchannel
97 97 >>> @check
98 98 ... def inputeof(server):
99 99 ... readchannel(server)
100 100 ... server.stdin.write('runcommand\n')
101 101 ... # close stdin while server is waiting for input
102 102 ... server.stdin.close()
103 103 ...
104 104 ... # server exits with 1 if the pipe closed while reading the command
105 105 ... print('server exit code =', server.wait())
106 106 server exit code = 1
107 107
108 108 >>> from hgclient import check, readchannel, runcommand, stringio
109 109 >>> @check
110 110 ... def serverinput(server):
111 111 ... readchannel(server)
112 112 ...
113 113 ... patch = """
114 114 ... # HG changeset patch
115 115 ... # User test
116 116 ... # Date 0 0
117 117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 118 ... # Parent 0000000000000000000000000000000000000000
119 119 ... 1
120 120 ...
121 121 ... diff -r 000000000000 -r c103a3dec114 a
122 122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 124 ... @@ -0,0 +1,1 @@
125 125 ... +1
126 126 ... """
127 127 ...
128 128 ... runcommand(server, ['import', '-'], input=stringio(patch))
129 129 ... runcommand(server, ['log'])
130 130 *** runcommand import -
131 131 applying patch from stdin
132 132 *** runcommand log
133 133 changeset: 0:eff892de26ec
134 134 tag: tip
135 135 user: test
136 136 date: Thu Jan 01 00:00:00 1970 +0000
137 137 summary: 1
138 138
139 139
140 140 check that "histedit --commands=-" can read rules from the input channel:
141 141
142 142 >>> import cStringIO
143 143 >>> from hgclient import check, readchannel, runcommand
144 144 >>> @check
145 145 ... def serverinput(server):
146 146 ... readchannel(server)
147 147 ... rules = 'pick eff892de26ec\n'
148 148 ... runcommand(server, ['histedit', '0', '--commands=-',
149 149 ... '--config', 'extensions.histedit='],
150 150 ... input=cStringIO.StringIO(rules))
151 151 *** runcommand histedit 0 --commands=- --config extensions.histedit=
152 152
153 153 check that --cwd doesn't persist between requests:
154 154
155 155 $ mkdir foo
156 156 $ touch foo/bar
157 157 >>> from hgclient import check, readchannel, runcommand
158 158 >>> @check
159 159 ... def cwd(server):
160 160 ... readchannel(server)
161 161 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
162 162 ... runcommand(server, ['st', 'foo/bar'])
163 163 *** runcommand --cwd foo st bar
164 164 ? bar
165 165 *** runcommand st foo/bar
166 166 ? foo/bar
167 167
168 168 $ rm foo/bar
169 169
170 170
171 171 check that local configs for the cached repo aren't inherited when -R is used:
172 172
173 173 $ cat <<EOF >> .hg/hgrc
174 174 > [ui]
175 175 > foo = bar
176 176 > EOF
177 177
178 178 >>> from hgclient import check, readchannel, runcommand, sep
179 179 >>> @check
180 180 ... def localhgrc(server):
181 181 ... readchannel(server)
182 182 ...
183 183 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
184 184 ... # show it
185 185 ... runcommand(server, ['showconfig'], outfilter=sep)
186 186 ...
187 187 ... # but not for this repo
188 188 ... runcommand(server, ['init', 'foo'])
189 189 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
190 190 *** runcommand showconfig
191 191 bundle.mainreporoot=$TESTTMP/repo
192 192 devel.all-warnings=true
193 193 devel.default-date=0 0
194 194 extensions.fsmonitor= (fsmonitor !)
195 195 largefiles.usercache=$TESTTMP/.cache/largefiles
196 196 ui.slash=True
197 197 ui.interactive=False
198 198 ui.mergemarkers=detailed
199 199 ui.usehttp2=true (?)
200 200 ui.foo=bar
201 201 ui.nontty=true
202 202 web.address=localhost
203 203 web\.ipv6=(?:True|False) (re)
204 204 *** runcommand init foo
205 205 *** runcommand -R foo showconfig ui defaults
206 206 ui.slash=True
207 207 ui.interactive=False
208 208 ui.mergemarkers=detailed
209 209 ui.usehttp2=true (?)
210 210 ui.nontty=true
211 211
212 212 $ rm -R foo
213 213
214 214 #if windows
215 215 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
216 216 #else
217 217 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
218 218 #endif
219 219
220 220 $ cat <<EOF > hook.py
221 221 > from __future__ import print_function
222 222 > import sys
223 223 > def hook(**args):
224 224 > print('hook talking')
225 225 > print('now try to read something: %r' % sys.stdin.read())
226 226 > EOF
227 227
228 228 >>> from hgclient import check, readchannel, runcommand, stringio
229 229 >>> @check
230 230 ... def hookoutput(server):
231 231 ... readchannel(server)
232 232 ... runcommand(server, ['--config',
233 233 ... 'hooks.pre-identify=python:hook.hook',
234 234 ... 'id'],
235 235 ... input=stringio('some input'))
236 236 *** runcommand --config hooks.pre-identify=python:hook.hook id
237 237 eff892de26ec tip
238 238
239 239 Clean hook cached version
240 240 $ rm hook.py*
241 241 $ rm -Rf __pycache__
242 242
243 243 $ echo a >> a
244 244 >>> import os
245 245 >>> from hgclient import check, readchannel, runcommand
246 246 >>> @check
247 247 ... def outsidechanges(server):
248 248 ... readchannel(server)
249 249 ... runcommand(server, ['status'])
250 250 ... os.system('hg ci -Am2')
251 251 ... runcommand(server, ['tip'])
252 252 ... runcommand(server, ['status'])
253 253 *** runcommand status
254 254 M a
255 255 *** runcommand tip
256 256 changeset: 1:d3a0a68be6de
257 257 tag: tip
258 258 user: test
259 259 date: Thu Jan 01 00:00:00 1970 +0000
260 260 summary: 2
261 261
262 262 *** runcommand status
263 263
264 264 >>> import os
265 265 >>> from hgclient import check, readchannel, runcommand
266 266 >>> @check
267 267 ... def bookmarks(server):
268 268 ... readchannel(server)
269 269 ... runcommand(server, ['bookmarks'])
270 270 ...
271 271 ... # changes .hg/bookmarks
272 272 ... os.system('hg bookmark -i bm1')
273 273 ... os.system('hg bookmark -i bm2')
274 274 ... runcommand(server, ['bookmarks'])
275 275 ...
276 276 ... # changes .hg/bookmarks.current
277 277 ... os.system('hg upd bm1 -q')
278 278 ... runcommand(server, ['bookmarks'])
279 279 ...
280 280 ... runcommand(server, ['bookmarks', 'bm3'])
281 281 ... f = open('a', 'ab')
282 282 ... f.write('a\n')
283 283 ... f.close()
284 284 ... runcommand(server, ['commit', '-Amm'])
285 285 ... runcommand(server, ['bookmarks'])
286 286 ... print('')
287 287 *** runcommand bookmarks
288 288 no bookmarks set
289 289 *** runcommand bookmarks
290 290 bm1 1:d3a0a68be6de
291 291 bm2 1:d3a0a68be6de
292 292 *** runcommand bookmarks
293 293 * bm1 1:d3a0a68be6de
294 294 bm2 1:d3a0a68be6de
295 295 *** runcommand bookmarks bm3
296 296 *** runcommand commit -Amm
297 297 *** runcommand bookmarks
298 298 bm1 1:d3a0a68be6de
299 299 bm2 1:d3a0a68be6de
300 300 * bm3 2:aef17e88f5f0
301 301
302 302
303 303 >>> import os
304 304 >>> from hgclient import check, readchannel, runcommand
305 305 >>> @check
306 306 ... def tagscache(server):
307 307 ... readchannel(server)
308 308 ... runcommand(server, ['id', '-t', '-r', '0'])
309 309 ... os.system('hg tag -r 0 foo')
310 310 ... runcommand(server, ['id', '-t', '-r', '0'])
311 311 *** runcommand id -t -r 0
312 312
313 313 *** runcommand id -t -r 0
314 314 foo
315 315
316 316 >>> import os
317 317 >>> from hgclient import check, readchannel, runcommand
318 318 >>> @check
319 319 ... def setphase(server):
320 320 ... readchannel(server)
321 321 ... runcommand(server, ['phase', '-r', '.'])
322 322 ... os.system('hg phase -r . -p')
323 323 ... runcommand(server, ['phase', '-r', '.'])
324 324 *** runcommand phase -r .
325 325 3: draft
326 326 *** runcommand phase -r .
327 327 3: public
328 328
329 329 $ echo a >> a
330 330 >>> from hgclient import check, readchannel, runcommand
331 331 >>> @check
332 332 ... def rollback(server):
333 333 ... readchannel(server)
334 334 ... runcommand(server, ['phase', '-r', '.', '-p'])
335 335 ... runcommand(server, ['commit', '-Am.'])
336 336 ... runcommand(server, ['rollback'])
337 337 ... runcommand(server, ['phase', '-r', '.'])
338 338 ... print('')
339 339 *** runcommand phase -r . -p
340 340 no phases changed
341 341 *** runcommand commit -Am.
342 342 *** runcommand rollback
343 343 repository tip rolled back to revision 3 (undo commit)
344 344 working directory now based on revision 3
345 345 *** runcommand phase -r .
346 346 3: public
347 347
348 348
349 349 >>> import os
350 350 >>> from hgclient import check, readchannel, runcommand
351 351 >>> @check
352 352 ... def branch(server):
353 353 ... readchannel(server)
354 354 ... runcommand(server, ['branch'])
355 355 ... os.system('hg branch foo')
356 356 ... runcommand(server, ['branch'])
357 357 ... os.system('hg branch default')
358 358 *** runcommand branch
359 359 default
360 360 marked working directory as branch foo
361 361 (branches are permanent and global, did you want a bookmark?)
362 362 *** runcommand branch
363 363 foo
364 364 marked working directory as branch default
365 365 (branches are permanent and global, did you want a bookmark?)
366 366
367 367 $ touch .hgignore
368 368 >>> import os
369 369 >>> from hgclient import check, readchannel, runcommand
370 370 >>> @check
371 371 ... def hgignore(server):
372 372 ... readchannel(server)
373 373 ... runcommand(server, ['commit', '-Am.'])
374 374 ... f = open('ignored-file', 'ab')
375 375 ... f.write('')
376 376 ... f.close()
377 377 ... f = open('.hgignore', 'ab')
378 378 ... f.write('ignored-file')
379 379 ... f.close()
380 380 ... runcommand(server, ['status', '-i', '-u'])
381 381 ... print('')
382 382 *** runcommand commit -Am.
383 383 adding .hgignore
384 384 *** runcommand status -i -u
385 385 I ignored-file
386 386
387 387
388 388 cache of non-public revisions should be invalidated on repository change
389 389 (issue4855):
390 390
391 391 >>> import os
392 392 >>> from hgclient import check, readchannel, runcommand
393 393 >>> @check
394 394 ... def phasesetscacheaftercommit(server):
395 395 ... readchannel(server)
396 396 ... # load _phasecache._phaserevs and _phasesets
397 397 ... runcommand(server, ['log', '-qr', 'draft()'])
398 398 ... # create draft commits by another process
399 399 ... for i in xrange(5, 7):
400 400 ... f = open('a', 'ab')
401 401 ... f.seek(0, os.SEEK_END)
402 402 ... f.write('a\n')
403 403 ... f.close()
404 404 ... os.system('hg commit -Aqm%d' % i)
405 405 ... # new commits should be listed as draft revisions
406 406 ... runcommand(server, ['log', '-qr', 'draft()'])
407 407 ... print('')
408 408 *** runcommand log -qr draft()
409 409 4:7966c8e3734d
410 410 *** runcommand log -qr draft()
411 411 4:7966c8e3734d
412 412 5:41f6602d1c4f
413 413 6:10501e202c35
414 414
415 415
416 416 >>> import os
417 417 >>> from hgclient import check, readchannel, runcommand
418 418 >>> @check
419 419 ... def phasesetscacheafterstrip(server):
420 420 ... readchannel(server)
421 421 ... # load _phasecache._phaserevs and _phasesets
422 422 ... runcommand(server, ['log', '-qr', 'draft()'])
423 423 ... # strip cached revisions by another process
424 424 ... os.system('hg --config extensions.strip= strip -q 5')
425 425 ... # shouldn't abort by "unknown revision '6'"
426 426 ... runcommand(server, ['log', '-qr', 'draft()'])
427 427 ... print('')
428 428 *** runcommand log -qr draft()
429 429 4:7966c8e3734d
430 430 5:41f6602d1c4f
431 431 6:10501e202c35
432 432 *** runcommand log -qr draft()
433 433 4:7966c8e3734d
434 434
435 435
436 436 cache of phase roots should be invalidated on strip (issue3827):
437 437
438 438 >>> import os
439 439 >>> from hgclient import check, readchannel, runcommand, sep
440 440 >>> @check
441 441 ... def phasecacheafterstrip(server):
442 442 ... readchannel(server)
443 443 ...
444 444 ... # create new head, 5:731265503d86
445 445 ... runcommand(server, ['update', '-C', '0'])
446 446 ... f = open('a', 'ab')
447 447 ... f.write('a\n')
448 448 ... f.close()
449 449 ... runcommand(server, ['commit', '-Am.', 'a'])
450 450 ... runcommand(server, ['log', '-Gq'])
451 451 ...
452 452 ... # make it public; draft marker moves to 4:7966c8e3734d
453 453 ... runcommand(server, ['phase', '-p', '.'])
454 454 ... # load _phasecache.phaseroots
455 455 ... runcommand(server, ['phase', '.'], outfilter=sep)
456 456 ...
457 457 ... # strip 1::4 outside server
458 458 ... os.system('hg -q --config extensions.mq= strip 1')
459 459 ...
460 460 ... # shouldn't raise "7966c8e3734d: no node!"
461 461 ... runcommand(server, ['branches'])
462 462 *** runcommand update -C 0
463 463 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
464 464 (leaving bookmark bm3)
465 465 *** runcommand commit -Am. a
466 466 created new head
467 467 *** runcommand log -Gq
468 468 @ 5:731265503d86
469 469 |
470 470 | o 4:7966c8e3734d
471 471 | |
472 472 | o 3:b9b85890c400
473 473 | |
474 474 | o 2:aef17e88f5f0
475 475 | |
476 476 | o 1:d3a0a68be6de
477 477 |/
478 478 o 0:eff892de26ec
479 479
480 480 *** runcommand phase -p .
481 481 *** runcommand phase .
482 482 5: public
483 483 *** runcommand branches
484 484 default 1:731265503d86
485 485
486 486 in-memory cache must be reloaded if transaction is aborted. otherwise
487 487 changelog and manifest would have invalid node:
488 488
489 489 $ echo a >> a
490 490 >>> from hgclient import check, readchannel, runcommand
491 491 >>> @check
492 492 ... def txabort(server):
493 493 ... readchannel(server)
494 494 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
495 495 ... '-mfoo'])
496 496 ... runcommand(server, ['verify'])
497 497 *** runcommand commit --config hooks.pretxncommit=false -mfoo
498 498 transaction abort!
499 499 rollback completed
500 500 abort: pretxncommit hook exited with status 1
501 501 [255]
502 502 *** runcommand verify
503 503 checking changesets
504 504 checking manifests
505 505 crosschecking files in changesets and manifests
506 506 checking files
507 507 1 files, 2 changesets, 2 total revisions
508 508 $ hg revert --no-backup -aq
509 509
510 510 $ cat >> .hg/hgrc << EOF
511 511 > [experimental]
512 512 > stabilization=createmarkers
513 513 > EOF
514 514
515 515 >>> import os
516 516 >>> from hgclient import check, readchannel, runcommand
517 517 >>> @check
518 518 ... def obsolete(server):
519 519 ... readchannel(server)
520 520 ...
521 521 ... runcommand(server, ['up', 'null'])
522 522 ... runcommand(server, ['phase', '-df', 'tip'])
523 523 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
524 524 ... if os.name == 'nt':
525 525 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
526 526 ... os.system(cmd)
527 527 ... runcommand(server, ['log', '--hidden'])
528 528 ... runcommand(server, ['log'])
529 529 *** runcommand up null
530 530 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
531 531 *** runcommand phase -df tip
532 532 obsoleted 1 changesets
533 533 *** runcommand log --hidden
534 534 changeset: 1:731265503d86
535 535 tag: tip
536 536 user: test
537 537 date: Thu Jan 01 00:00:00 1970 +0000
538 538 summary: .
539 539
540 540 changeset: 0:eff892de26ec
541 541 bookmark: bm1
542 542 bookmark: bm2
543 543 bookmark: bm3
544 544 user: test
545 545 date: Thu Jan 01 00:00:00 1970 +0000
546 546 summary: 1
547 547
548 548 *** runcommand log
549 549 changeset: 0:eff892de26ec
550 550 bookmark: bm1
551 551 bookmark: bm2
552 552 bookmark: bm3
553 553 tag: tip
554 554 user: test
555 555 date: Thu Jan 01 00:00:00 1970 +0000
556 556 summary: 1
557 557
558 558
559 559 $ cat <<EOF >> .hg/hgrc
560 560 > [extensions]
561 561 > mq =
562 562 > EOF
563 563
564 564 >>> import os
565 565 >>> from hgclient import check, readchannel, runcommand
566 566 >>> @check
567 567 ... def mqoutsidechanges(server):
568 568 ... readchannel(server)
569 569 ...
570 570 ... # load repo.mq
571 571 ... runcommand(server, ['qapplied'])
572 572 ... os.system('hg qnew 0.diff')
573 573 ... # repo.mq should be invalidated
574 574 ... runcommand(server, ['qapplied'])
575 575 ...
576 576 ... runcommand(server, ['qpop', '--all'])
577 577 ... os.system('hg qqueue --create foo')
578 578 ... # repo.mq should be recreated to point to new queue
579 579 ... runcommand(server, ['qqueue', '--active'])
580 580 *** runcommand qapplied
581 581 *** runcommand qapplied
582 582 0.diff
583 583 *** runcommand qpop --all
584 584 popping 0.diff
585 585 patch queue now empty
586 586 *** runcommand qqueue --active
587 587 foo
588 588
589 589 $ cat <<EOF > dbgui.py
590 590 > import os
591 591 > import sys
592 592 > from mercurial import commands, registrar
593 593 > cmdtable = {}
594 594 > command = registrar.command(cmdtable)
595 595 > @command(b"debuggetpass", norepo=True)
596 596 > def debuggetpass(ui):
597 597 > ui.write("%s\\n" % ui.getpass())
598 598 > @command(b"debugprompt", norepo=True)
599 599 > def debugprompt(ui):
600 600 > ui.write("%s\\n" % ui.prompt("prompt:"))
601 601 > @command(b"debugreadstdin", norepo=True)
602 602 > def debugreadstdin(ui):
603 603 > ui.write("read: %r\n" % sys.stdin.read(1))
604 604 > @command(b"debugwritestdout", norepo=True)
605 605 > def debugwritestdout(ui):
606 606 > os.write(1, "low-level stdout fd and\n")
607 607 > sys.stdout.write("stdout should be redirected to /dev/null\n")
608 608 > sys.stdout.flush()
609 609 > EOF
610 610 $ cat <<EOF >> .hg/hgrc
611 611 > [extensions]
612 612 > dbgui = dbgui.py
613 613 > EOF
614 614
615 615 >>> from hgclient import check, readchannel, runcommand, stringio
616 616 >>> @check
617 617 ... def getpass(server):
618 618 ... readchannel(server)
619 619 ... runcommand(server, ['debuggetpass', '--config',
620 620 ... 'ui.interactive=True'],
621 621 ... input=stringio('1234\n'))
622 622 ... runcommand(server, ['debuggetpass', '--config',
623 623 ... 'ui.interactive=True'],
624 624 ... input=stringio('\n'))
625 625 ... runcommand(server, ['debuggetpass', '--config',
626 626 ... 'ui.interactive=True'],
627 627 ... input=stringio(''))
628 628 ... runcommand(server, ['debugprompt', '--config',
629 629 ... 'ui.interactive=True'],
630 630 ... input=stringio('5678\n'))
631 631 ... runcommand(server, ['debugreadstdin'])
632 632 ... runcommand(server, ['debugwritestdout'])
633 633 *** runcommand debuggetpass --config ui.interactive=True
634 634 password: 1234
635 635 *** runcommand debuggetpass --config ui.interactive=True
636 636 password:
637 637 *** runcommand debuggetpass --config ui.interactive=True
638 638 password: abort: response expected
639 639 [255]
640 640 *** runcommand debugprompt --config ui.interactive=True
641 641 prompt: 5678
642 642 *** runcommand debugreadstdin
643 643 read: ''
644 644 *** runcommand debugwritestdout
645 645
646 646
647 647 run commandserver in commandserver, which is silly but should work:
648 648
649 649 >>> from __future__ import print_function
650 650 >>> from hgclient import check, readchannel, runcommand, stringio
651 651 >>> @check
652 652 ... def nested(server):
653 653 ... print('%c, %r' % readchannel(server))
654 654 ... class nestedserver(object):
655 655 ... stdin = stringio('getencoding\n')
656 656 ... stdout = stringio()
657 657 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
658 658 ... output=nestedserver.stdout, input=nestedserver.stdin)
659 659 ... nestedserver.stdout.seek(0)
660 660 ... print('%c, %r' % readchannel(nestedserver)) # hello
661 661 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
662 662 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
663 663 *** runcommand serve --cmdserver pipe
664 664 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
665 665 r, '*' (glob)
666 666
667 667
668 668 start without repository:
669 669
670 670 $ cd ..
671 671
672 672 >>> from __future__ import print_function
673 673 >>> from hgclient import check, readchannel, runcommand
674 674 >>> @check
675 675 ... def hellomessage(server):
676 676 ... ch, data = readchannel(server)
677 677 ... print('%c, %r' % (ch, data))
678 678 ... # run an arbitrary command to make sure the next thing the server
679 679 ... # sends isn't part of the hello message
680 680 ... runcommand(server, ['id'])
681 681 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
682 682 *** runcommand id
683 683 abort: there is no Mercurial repository here (.hg not found)
684 684 [255]
685 685
686 686 >>> from hgclient import check, readchannel, runcommand
687 687 >>> @check
688 688 ... def startwithoutrepo(server):
689 689 ... readchannel(server)
690 690 ... runcommand(server, ['init', 'repo2'])
691 691 ... runcommand(server, ['id', '-R', 'repo2'])
692 692 *** runcommand init repo2
693 693 *** runcommand id -R repo2
694 694 000000000000 tip
695 695
696 696
697 697 don't fall back to cwd if invalid -R path is specified (issue4805):
698 698
699 699 $ cd repo
700 700 $ hg serve --cmdserver pipe -R ../nonexistent
701 701 abort: repository ../nonexistent not found!
702 702 [255]
703 703 $ cd ..
704 704
705 705
706 706 unix domain socket:
707 707
708 708 $ cd repo
709 709 $ hg update -q
710 710
711 711 #if unix-socket unix-permissions
712 712
713 713 >>> from __future__ import print_function
714 714 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
715 715 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
716 716 >>> def hellomessage(conn):
717 717 ... ch, data = readchannel(conn)
718 718 ... print('%c, %r' % (ch, data))
719 719 ... runcommand(conn, ['id'])
720 720 >>> check(hellomessage, server.connect)
721 721 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
722 722 *** runcommand id
723 723 eff892de26ec tip bm1/bm2/bm3
724 724 >>> def unknowncommand(conn):
725 725 ... readchannel(conn)
726 726 ... conn.stdin.write('unknowncommand\n')
727 727 >>> check(unknowncommand, server.connect) # error sent to server.log
728 728 >>> def serverinput(conn):
729 729 ... readchannel(conn)
730 730 ... patch = """
731 731 ... # HG changeset patch
732 732 ... # User test
733 733 ... # Date 0 0
734 734 ... 2
735 735 ...
736 736 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
737 737 ... --- a/a
738 738 ... +++ b/a
739 739 ... @@ -1,1 +1,2 @@
740 740 ... 1
741 741 ... +2
742 742 ... """
743 743 ... runcommand(conn, ['import', '-'], input=stringio(patch))
744 744 ... runcommand(conn, ['log', '-rtip', '-q'])
745 745 >>> check(serverinput, server.connect)
746 746 *** runcommand import -
747 747 applying patch from stdin
748 748 *** runcommand log -rtip -q
749 749 2:1ed24be7e7a0
750 750 >>> server.shutdown()
751 751
752 752 $ cat .hg/server.log
753 753 listening at .hg/server.sock
754 754 abort: unknown command unknowncommand
755 755 killed!
756 756 $ rm .hg/server.log
757 757
758 758 if server crashed before hello, traceback will be sent to 'e' channel as
759 759 last ditch:
760 760
761 761 $ cat <<EOF >> .hg/hgrc
762 762 > [cmdserver]
763 763 > log = inexistent/path.log
764 764 > EOF
765 765 >>> from __future__ import print_function
766 766 >>> from hgclient import check, readchannel, unixserver
767 767 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
768 768 >>> def earlycrash(conn):
769 769 ... while True:
770 770 ... try:
771 771 ... ch, data = readchannel(conn)
772 772 ... if not data.startswith(' '):
773 773 ... print('%c, %r' % (ch, data))
774 774 ... except EOFError:
775 775 ... break
776 776 >>> check(earlycrash, server.connect)
777 777 e, 'Traceback (most recent call last):\n'
778 778 e, "IOError: *" (glob)
779 779 >>> server.shutdown()
780 780
781 781 $ cat .hg/server.log | grep -v '^ '
782 782 listening at .hg/server.sock
783 783 Traceback (most recent call last):
784 784 IOError: * (glob)
785 785 killed!
786 786 #endif
787 787 #if no-unix-socket
788 788
789 789 $ hg serve --cmdserver unix -a .hg/server.sock
790 790 abort: unsupported platform
791 791 [255]
792 792
793 793 #endif
794 794
795 795 $ cd ..
796 796
797 797 Test that accessing to invalid changelog cache is avoided at
798 798 subsequent operations even if repo object is reused even after failure
799 799 of transaction (see 0a7610758c42 also)
800 800
801 801 "hg log" after failure of transaction is needed to detect invalid
802 802 cache in repoview: this can't detect by "hg verify" only.
803 803
804 804 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
805 805 4) are tested, because '00changelog.i' are differently changed in each
806 806 cases.
807 807
808 808 $ cat > $TESTTMP/failafterfinalize.py <<EOF
809 809 > # extension to abort transaction after finalization forcibly
810 810 > from mercurial import commands, error, extensions, lock as lockmod
811 811 > def fail(tr):
812 812 > raise error.Abort('fail after finalization')
813 813 > def reposetup(ui, repo):
814 814 > class failrepo(repo.__class__):
815 815 > def commitctx(self, ctx, error=False):
816 816 > if self.ui.configbool('failafterfinalize', 'fail'):
817 817 > # 'sorted()' by ASCII code on category names causes
818 818 > # invoking 'fail' after finalization of changelog
819 819 > # using "'cl-%i' % id(self)" as category name
820 820 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
821 821 > return super(failrepo, self).commitctx(ctx, error)
822 822 > repo.__class__ = failrepo
823 823 > EOF
824 824
825 825 $ hg init repo3
826 826 $ cd repo3
827 827
828 828 $ cat <<EOF >> $HGRCPATH
829 829 > [ui]
830 830 > logtemplate = {rev} {desc|firstline} ({files})\n
831 831 >
832 832 > [extensions]
833 833 > failafterfinalize = $TESTTMP/failafterfinalize.py
834 834 > EOF
835 835
836 836 - test failure with "empty changelog"
837 837
838 838 $ echo foo > foo
839 839 $ hg add foo
840 840
841 841 (failure before finalization)
842 842
843 843 >>> from hgclient import check, readchannel, runcommand
844 844 >>> @check
845 845 ... def abort(server):
846 846 ... readchannel(server)
847 847 ... runcommand(server, ['commit',
848 848 ... '--config', 'hooks.pretxncommit=false',
849 849 ... '-mfoo'])
850 850 ... runcommand(server, ['log'])
851 851 ... runcommand(server, ['verify', '-q'])
852 852 *** runcommand commit --config hooks.pretxncommit=false -mfoo
853 853 transaction abort!
854 854 rollback completed
855 855 abort: pretxncommit hook exited with status 1
856 856 [255]
857 857 *** runcommand log
858 858 *** runcommand verify -q
859 859
860 860 (failure after finalization)
861 861
862 862 >>> from hgclient import check, readchannel, runcommand
863 863 >>> @check
864 864 ... def abort(server):
865 865 ... readchannel(server)
866 866 ... runcommand(server, ['commit',
867 867 ... '--config', 'failafterfinalize.fail=true',
868 868 ... '-mfoo'])
869 869 ... runcommand(server, ['log'])
870 870 ... runcommand(server, ['verify', '-q'])
871 871 *** runcommand commit --config failafterfinalize.fail=true -mfoo
872 872 transaction abort!
873 873 rollback completed
874 874 abort: fail after finalization
875 875 [255]
876 876 *** runcommand log
877 877 *** runcommand verify -q
878 878
879 879 - test failure with "not-empty changelog"
880 880
881 881 $ echo bar > bar
882 882 $ hg add bar
883 883 $ hg commit -mbar bar
884 884
885 885 (failure before finalization)
886 886
887 887 >>> from hgclient import check, readchannel, runcommand
888 888 >>> @check
889 889 ... def abort(server):
890 890 ... readchannel(server)
891 891 ... runcommand(server, ['commit',
892 892 ... '--config', 'hooks.pretxncommit=false',
893 893 ... '-mfoo', 'foo'])
894 894 ... runcommand(server, ['log'])
895 895 ... runcommand(server, ['verify', '-q'])
896 896 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
897 897 transaction abort!
898 898 rollback completed
899 899 abort: pretxncommit hook exited with status 1
900 900 [255]
901 901 *** runcommand log
902 902 0 bar (bar)
903 903 *** runcommand verify -q
904 904
905 905 (failure after finalization)
906 906
907 907 >>> from hgclient import check, readchannel, runcommand
908 908 >>> @check
909 909 ... def abort(server):
910 910 ... readchannel(server)
911 911 ... runcommand(server, ['commit',
912 912 ... '--config', 'failafterfinalize.fail=true',
913 913 ... '-mfoo', 'foo'])
914 914 ... runcommand(server, ['log'])
915 915 ... runcommand(server, ['verify', '-q'])
916 916 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
917 917 transaction abort!
918 918 rollback completed
919 919 abort: fail after finalization
920 920 [255]
921 921 *** runcommand log
922 922 0 bar (bar)
923 923 *** runcommand verify -q
924 924
925 925 $ cd ..
926 926
927 927 Test symlink traversal over cached audited paths:
928 928 -------------------------------------------------
929 929
930 930 #if symlink
931 931
932 932 set up symlink hell
933 933
934 934 $ mkdir merge-symlink-out
935 935 $ hg init merge-symlink
936 936 $ cd merge-symlink
937 937 $ touch base
938 938 $ hg commit -qAm base
939 939 $ ln -s ../merge-symlink-out a
940 940 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
941 941 $ hg up -q 0
942 942 $ mkdir a
943 943 $ touch a/poisoned
944 944 $ hg commit -qAm 'file a/poisoned'
945 945 $ hg log -G -T '{rev}: {desc}\n'
946 946 @ 2: file a/poisoned
947 947 |
948 948 | o 1: symlink a -> ../merge-symlink-out
949 949 |/
950 950 o 0: base
951 951
952 952
953 953 try trivial merge after update: cache of audited paths should be discarded,
954 954 and the merge should fail (issue5628)
955 955
956 956 $ hg up -q null
957 957 >>> from hgclient import check, readchannel, runcommand
958 958 >>> @check
959 959 ... def merge(server):
960 960 ... readchannel(server)
961 961 ... # audit a/poisoned as a good path
962 962 ... runcommand(server, ['up', '-qC', '2'])
963 963 ... runcommand(server, ['up', '-qC', '1'])
964 964 ... # here a is a symlink, so a/poisoned is bad
965 965 ... runcommand(server, ['merge', '2'])
966 966 *** runcommand up -qC 2
967 967 *** runcommand up -qC 1
968 968 *** runcommand merge 2
969 abort: path 'a/poisoned' traverses symbolic link 'a'
970 [255]
969 a: path conflict - a file or link has the same name as a directory
970 the local file has been renamed to a~aa04623eb0c3
971 resolve manually then use 'hg resolve --mark a'
972 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
973 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
974 [1]
971 975 $ ls ../merge-symlink-out
972 976
973 977 cache of repo.auditor should be discarded, so matcher would never traverse
974 978 symlinks:
975 979
976 980 $ hg up -qC 0
977 981 $ touch ../merge-symlink-out/poisoned
978 982 >>> from hgclient import check, readchannel, runcommand
979 983 >>> @check
980 984 ... def files(server):
981 985 ... readchannel(server)
982 986 ... runcommand(server, ['up', '-qC', '2'])
983 987 ... # audit a/poisoned as a good path
984 988 ... runcommand(server, ['files', 'a/poisoned'])
985 989 ... runcommand(server, ['up', '-qC', '0'])
986 990 ... runcommand(server, ['up', '-qC', '1'])
987 991 ... # here 'a' is a symlink, so a/poisoned should be warned
988 992 ... runcommand(server, ['files', 'a/poisoned'])
989 993 *** runcommand up -qC 2
990 994 *** runcommand files a/poisoned
991 995 a/poisoned
992 996 *** runcommand up -qC 0
993 997 *** runcommand up -qC 1
994 998 *** runcommand files a/poisoned
995 999 abort: path 'a/poisoned' traverses symbolic link 'a'
996 1000 [255]
997 1001
998 1002 $ cd ..
999 1003
1000 1004 #endif
@@ -1,78 +1,83 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 a: path conflict - a file or link has the same name as a directory
29 the local file has been renamed to a~853701544ac3
30 resolve manually then use 'hg resolve --mark a'
31 moving a to a~853701544ac3
28 32 getting a/b
29 abort: *: '$TESTTMP/repo/a/b' (glob)
30 [255]
33 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
34 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
35 [1]
31 36 $ hg update --clean .
32 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
33 38
34 39 Basic update - local directory conflicts with remote file
35 40
36 41 $ hg up -q 0
37 42 $ mkdir a
38 43 $ echo 3 > a/b
39 44 $ hg up file
40 45 a: untracked directory conflicts with file
41 46 abort: untracked files in working directory differ from files in requested revision
42 47 [255]
43 48 $ hg up --clean file
44 49 abort: *: '$TESTTMP/repo/a' (glob)
45 50 [255]
46 51
47 52 Repo is in a very bad state now - recover manually
48 53
49 54 $ rm -r a
50 55 $ hg up -q --clean 0
51 56
52 57 Basic update - untracked file conflicts with remote directory
53 58
54 59 $ hg up -q 0
55 60 $ echo untracked > a
56 61 $ hg up --config merge.checkunknown=warn dir
57 62 a: replacing untracked file
58 63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 64 (activating bookmark dir)
60 65
61 66 Basic clean update - local directory conflicts with changed remote file
62 67
63 68 $ hg up -q file
64 69 $ rm a
65 70 $ mkdir a
66 71 $ echo 4 > a/b
67 72 $ hg up file2
68 73 abort: *: '$TESTTMP/repo/a' (glob)
69 74 [255]
70 75 $ hg up --clean file2
71 76 abort: *: '$TESTTMP/repo/a' (glob)
72 77 [255]
73 78
74 79 Repo is in a very bad state now - recover manually
75 80
76 81 $ rm -r a
77 82 $ hg up -q --clean 0
78 83
General Comments 0
You need to be logged in to leave comments. Login now