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