##// END OF EJS Templates
changelog: parse copy metadata if available in extras...
Martin von Zweigbergk -
r42313:c2165551 default draft
parent child Browse files
Show More
@@ -1,599 +1,621
1 1 # changelog.py - changelog class for mercurial
2 2 #
3 3 # Copyright 2005-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 from .i18n import _
11 11 from .node import (
12 12 bin,
13 13 hex,
14 14 nullid,
15 15 )
16 16 from .thirdparty import (
17 17 attr,
18 18 )
19 19
20 20 from . import (
21 21 encoding,
22 22 error,
23 23 pycompat,
24 24 revlog,
25 25 util,
26 26 )
27 27 from .utils import (
28 28 dateutil,
29 29 stringutil,
30 30 )
31 31
32 32 _defaultextra = {'branch': 'default'}
33 33
34 34 def _string_escape(text):
35 35 """
36 36 >>> from .pycompat import bytechr as chr
37 37 >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
38 38 >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)s12ab%(cr)scd%(bs)s%(nl)s" % d
39 39 >>> s
40 40 'ab\\ncd\\\\\\\\n\\x0012ab\\rcd\\\\\\n'
41 41 >>> res = _string_escape(s)
42 42 >>> s == _string_unescape(res)
43 43 True
44 44 """
45 45 # subset of the string_escape codec
46 46 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
47 47 return text.replace('\0', '\\0')
48 48
49 49 def _string_unescape(text):
50 50 if '\\0' in text:
51 51 # fix up \0 without getting into trouble with \\0
52 52 text = text.replace('\\\\', '\\\\\n')
53 53 text = text.replace('\\0', '\0')
54 54 text = text.replace('\n', '')
55 55 return stringutil.unescapestr(text)
56 56
57 57 def decodeextra(text):
58 58 """
59 59 >>> from .pycompat import bytechr as chr
60 60 >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
61 61 ... ).items())
62 62 [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
63 63 >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
64 64 ... b'baz': chr(92) + chr(0) + b'2'})
65 65 ... ).items())
66 66 [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
67 67 """
68 68 extra = _defaultextra.copy()
69 69 for l in text.split('\0'):
70 70 if l:
71 71 k, v = _string_unescape(l).split(':', 1)
72 72 extra[k] = v
73 73 return extra
74 74
75 75 def encodeextra(d):
76 76 # keys must be sorted to produce a deterministic changelog entry
77 77 items = [
78 78 _string_escape('%s:%s' % (k, pycompat.bytestr(d[k])))
79 79 for k in sorted(d)
80 80 ]
81 81 return "\0".join(items)
82 82
83 83 def encodecopies(copies):
84 84 items = [
85 85 '%s\0%s' % (k, copies[k])
86 86 for k in sorted(copies)
87 87 ]
88 88 return "\n".join(items)
89 89
90 def decodecopies(data):
91 try:
92 copies = {}
93 for l in data.split('\n'):
94 k, v = l.split('\0')
95 copies[k] = v
96 return copies
97 except ValueError:
98 # Perhaps someone had chosen the same key name (e.g. "p1copies") and
99 # used different syntax for the value.
100 return None
101
90 102 def stripdesc(desc):
91 103 """strip trailing whitespace and leading and trailing empty lines"""
92 104 return '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
93 105
94 106 class appender(object):
95 107 '''the changelog index must be updated last on disk, so we use this class
96 108 to delay writes to it'''
97 109 def __init__(self, vfs, name, mode, buf):
98 110 self.data = buf
99 111 fp = vfs(name, mode)
100 112 self.fp = fp
101 113 self.offset = fp.tell()
102 114 self.size = vfs.fstat(fp).st_size
103 115 self._end = self.size
104 116
105 117 def end(self):
106 118 return self._end
107 119 def tell(self):
108 120 return self.offset
109 121 def flush(self):
110 122 pass
111 123
112 124 @property
113 125 def closed(self):
114 126 return self.fp.closed
115 127
116 128 def close(self):
117 129 self.fp.close()
118 130
119 131 def seek(self, offset, whence=0):
120 132 '''virtual file offset spans real file and data'''
121 133 if whence == 0:
122 134 self.offset = offset
123 135 elif whence == 1:
124 136 self.offset += offset
125 137 elif whence == 2:
126 138 self.offset = self.end() + offset
127 139 if self.offset < self.size:
128 140 self.fp.seek(self.offset)
129 141
130 142 def read(self, count=-1):
131 143 '''only trick here is reads that span real file and data'''
132 144 ret = ""
133 145 if self.offset < self.size:
134 146 s = self.fp.read(count)
135 147 ret = s
136 148 self.offset += len(s)
137 149 if count > 0:
138 150 count -= len(s)
139 151 if count != 0:
140 152 doff = self.offset - self.size
141 153 self.data.insert(0, "".join(self.data))
142 154 del self.data[1:]
143 155 s = self.data[0][doff:doff + count]
144 156 self.offset += len(s)
145 157 ret += s
146 158 return ret
147 159
148 160 def write(self, s):
149 161 self.data.append(bytes(s))
150 162 self.offset += len(s)
151 163 self._end += len(s)
152 164
153 165 def __enter__(self):
154 166 self.fp.__enter__()
155 167 return self
156 168
157 169 def __exit__(self, *args):
158 170 return self.fp.__exit__(*args)
159 171
160 172 def _divertopener(opener, target):
161 173 """build an opener that writes in 'target.a' instead of 'target'"""
162 174 def _divert(name, mode='r', checkambig=False):
163 175 if name != target:
164 176 return opener(name, mode)
165 177 return opener(name + ".a", mode)
166 178 return _divert
167 179
168 180 def _delayopener(opener, target, buf):
169 181 """build an opener that stores chunks in 'buf' instead of 'target'"""
170 182 def _delay(name, mode='r', checkambig=False):
171 183 if name != target:
172 184 return opener(name, mode)
173 185 return appender(opener, name, mode, buf)
174 186 return _delay
175 187
176 188 @attr.s
177 189 class _changelogrevision(object):
178 190 # Extensions might modify _defaultextra, so let the constructor below pass
179 191 # it in
180 192 extra = attr.ib()
181 193 manifest = attr.ib(default=nullid)
182 194 user = attr.ib(default='')
183 195 date = attr.ib(default=(0, 0))
184 196 files = attr.ib(default=attr.Factory(list))
185 197 description = attr.ib(default='')
186 198
187 199 class changelogrevision(object):
188 200 """Holds results of a parsed changelog revision.
189 201
190 202 Changelog revisions consist of multiple pieces of data, including
191 203 the manifest node, user, and date. This object exposes a view into
192 204 the parsed object.
193 205 """
194 206
195 207 __slots__ = (
196 208 r'_offsets',
197 209 r'_text',
198 210 )
199 211
200 212 def __new__(cls, text):
201 213 if not text:
202 214 return _changelogrevision(extra=_defaultextra)
203 215
204 216 self = super(changelogrevision, cls).__new__(cls)
205 217 # We could return here and implement the following as an __init__.
206 218 # But doing it here is equivalent and saves an extra function call.
207 219
208 220 # format used:
209 221 # nodeid\n : manifest node in ascii
210 222 # user\n : user, no \n or \r allowed
211 223 # time tz extra\n : date (time is int or float, timezone is int)
212 224 # : extra is metadata, encoded and separated by '\0'
213 225 # : older versions ignore it
214 226 # files\n\n : files modified by the cset, no \n or \r allowed
215 227 # (.*) : comment (free text, ideally utf-8)
216 228 #
217 229 # changelog v0 doesn't use extra
218 230
219 231 nl1 = text.index('\n')
220 232 nl2 = text.index('\n', nl1 + 1)
221 233 nl3 = text.index('\n', nl2 + 1)
222 234
223 235 # The list of files may be empty. Which means nl3 is the first of the
224 236 # double newline that precedes the description.
225 237 if text[nl3 + 1:nl3 + 2] == '\n':
226 238 doublenl = nl3
227 239 else:
228 240 doublenl = text.index('\n\n', nl3 + 1)
229 241
230 242 self._offsets = (nl1, nl2, nl3, doublenl)
231 243 self._text = text
232 244
233 245 return self
234 246
235 247 @property
236 248 def manifest(self):
237 249 return bin(self._text[0:self._offsets[0]])
238 250
239 251 @property
240 252 def user(self):
241 253 off = self._offsets
242 254 return encoding.tolocal(self._text[off[0] + 1:off[1]])
243 255
244 256 @property
245 257 def _rawdate(self):
246 258 off = self._offsets
247 259 dateextra = self._text[off[1] + 1:off[2]]
248 260 return dateextra.split(' ', 2)[0:2]
249 261
250 262 @property
251 263 def _rawextra(self):
252 264 off = self._offsets
253 265 dateextra = self._text[off[1] + 1:off[2]]
254 266 fields = dateextra.split(' ', 2)
255 267 if len(fields) != 3:
256 268 return None
257 269
258 270 return fields[2]
259 271
260 272 @property
261 273 def date(self):
262 274 raw = self._rawdate
263 275 time = float(raw[0])
264 276 # Various tools did silly things with the timezone.
265 277 try:
266 278 timezone = int(raw[1])
267 279 except ValueError:
268 280 timezone = 0
269 281
270 282 return time, timezone
271 283
272 284 @property
273 285 def extra(self):
274 286 raw = self._rawextra
275 287 if raw is None:
276 288 return _defaultextra
277 289
278 290 return decodeextra(raw)
279 291
280 292 @property
281 293 def files(self):
282 294 off = self._offsets
283 295 if off[2] == off[3]:
284 296 return []
285 297
286 298 return self._text[off[2] + 1:off[3]].split('\n')
287 299
288 300 @property
301 def p1copies(self):
302 rawcopies = self.extra.get('p1copies')
303 return rawcopies and decodecopies(rawcopies)
304
305 @property
306 def p2copies(self):
307 rawcopies = self.extra.get('p2copies')
308 return rawcopies and decodecopies(rawcopies)
309
310 @property
289 311 def description(self):
290 312 return encoding.tolocal(self._text[self._offsets[3] + 2:])
291 313
292 314 class changelog(revlog.revlog):
293 315 def __init__(self, opener, trypending=False):
294 316 """Load a changelog revlog using an opener.
295 317
296 318 If ``trypending`` is true, we attempt to load the index from a
297 319 ``00changelog.i.a`` file instead of the default ``00changelog.i``.
298 320 The ``00changelog.i.a`` file contains index (and possibly inline
299 321 revision) data for a transaction that hasn't been finalized yet.
300 322 It exists in a separate file to facilitate readers (such as
301 323 hooks processes) accessing data before a transaction is finalized.
302 324 """
303 325 if trypending and opener.exists('00changelog.i.a'):
304 326 indexfile = '00changelog.i.a'
305 327 else:
306 328 indexfile = '00changelog.i'
307 329
308 330 datafile = '00changelog.d'
309 331 revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
310 332 checkambig=True, mmaplargeindex=True)
311 333
312 334 if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
313 335 # changelogs don't benefit from generaldelta.
314 336
315 337 self.version &= ~revlog.FLAG_GENERALDELTA
316 338 self._generaldelta = False
317 339
318 340 # Delta chains for changelogs tend to be very small because entries
319 341 # tend to be small and don't delta well with each. So disable delta
320 342 # chains.
321 343 self._storedeltachains = False
322 344
323 345 self._realopener = opener
324 346 self._delayed = False
325 347 self._delaybuf = None
326 348 self._divert = False
327 349 self.filteredrevs = frozenset()
328 350
329 351 def tiprev(self):
330 352 for i in pycompat.xrange(len(self) -1, -2, -1):
331 353 if i not in self.filteredrevs:
332 354 return i
333 355
334 356 def tip(self):
335 357 """filtered version of revlog.tip"""
336 358 return self.node(self.tiprev())
337 359
338 360 def __contains__(self, rev):
339 361 """filtered version of revlog.__contains__"""
340 362 return (0 <= rev < len(self)
341 363 and rev not in self.filteredrevs)
342 364
343 365 def __iter__(self):
344 366 """filtered version of revlog.__iter__"""
345 367 if len(self.filteredrevs) == 0:
346 368 return revlog.revlog.__iter__(self)
347 369
348 370 def filterediter():
349 371 for i in pycompat.xrange(len(self)):
350 372 if i not in self.filteredrevs:
351 373 yield i
352 374
353 375 return filterediter()
354 376
355 377 def revs(self, start=0, stop=None):
356 378 """filtered version of revlog.revs"""
357 379 for i in super(changelog, self).revs(start, stop):
358 380 if i not in self.filteredrevs:
359 381 yield i
360 382
361 383 def reachableroots(self, minroot, heads, roots, includepath=False):
362 384 return self.index.reachableroots2(minroot, heads, roots, includepath)
363 385
364 386 def _checknofilteredinrevs(self, revs):
365 387 """raise the appropriate error if 'revs' contains a filtered revision
366 388
367 389 This returns a version of 'revs' to be used thereafter by the caller.
368 390 In particular, if revs is an iterator, it is converted into a set.
369 391 """
370 392 safehasattr = util.safehasattr
371 393 if safehasattr(revs, '__next__'):
372 394 # Note that inspect.isgenerator() is not true for iterators,
373 395 revs = set(revs)
374 396
375 397 filteredrevs = self.filteredrevs
376 398 if safehasattr(revs, 'first'): # smartset
377 399 offenders = revs & filteredrevs
378 400 else:
379 401 offenders = filteredrevs.intersection(revs)
380 402
381 403 for rev in offenders:
382 404 raise error.FilteredIndexError(rev)
383 405 return revs
384 406
385 407 def headrevs(self, revs=None):
386 408 if revs is None and self.filteredrevs:
387 409 try:
388 410 return self.index.headrevsfiltered(self.filteredrevs)
389 411 # AttributeError covers non-c-extension environments and
390 412 # old c extensions without filter handling.
391 413 except AttributeError:
392 414 return self._headrevs()
393 415
394 416 if self.filteredrevs:
395 417 revs = self._checknofilteredinrevs(revs)
396 418 return super(changelog, self).headrevs(revs)
397 419
398 420 def strip(self, *args, **kwargs):
399 421 # XXX make something better than assert
400 422 # We can't expect proper strip behavior if we are filtered.
401 423 assert not self.filteredrevs
402 424 super(changelog, self).strip(*args, **kwargs)
403 425
404 426 def rev(self, node):
405 427 """filtered version of revlog.rev"""
406 428 r = super(changelog, self).rev(node)
407 429 if r in self.filteredrevs:
408 430 raise error.FilteredLookupError(hex(node), self.indexfile,
409 431 _('filtered node'))
410 432 return r
411 433
412 434 def node(self, rev):
413 435 """filtered version of revlog.node"""
414 436 if rev in self.filteredrevs:
415 437 raise error.FilteredIndexError(rev)
416 438 return super(changelog, self).node(rev)
417 439
418 440 def linkrev(self, rev):
419 441 """filtered version of revlog.linkrev"""
420 442 if rev in self.filteredrevs:
421 443 raise error.FilteredIndexError(rev)
422 444 return super(changelog, self).linkrev(rev)
423 445
424 446 def parentrevs(self, rev):
425 447 """filtered version of revlog.parentrevs"""
426 448 if rev in self.filteredrevs:
427 449 raise error.FilteredIndexError(rev)
428 450 return super(changelog, self).parentrevs(rev)
429 451
430 452 def flags(self, rev):
431 453 """filtered version of revlog.flags"""
432 454 if rev in self.filteredrevs:
433 455 raise error.FilteredIndexError(rev)
434 456 return super(changelog, self).flags(rev)
435 457
436 458 def delayupdate(self, tr):
437 459 "delay visibility of index updates to other readers"
438 460
439 461 if not self._delayed:
440 462 if len(self) == 0:
441 463 self._divert = True
442 464 if self._realopener.exists(self.indexfile + '.a'):
443 465 self._realopener.unlink(self.indexfile + '.a')
444 466 self.opener = _divertopener(self._realopener, self.indexfile)
445 467 else:
446 468 self._delaybuf = []
447 469 self.opener = _delayopener(self._realopener, self.indexfile,
448 470 self._delaybuf)
449 471 self._delayed = True
450 472 tr.addpending('cl-%i' % id(self), self._writepending)
451 473 tr.addfinalize('cl-%i' % id(self), self._finalize)
452 474
453 475 def _finalize(self, tr):
454 476 "finalize index updates"
455 477 self._delayed = False
456 478 self.opener = self._realopener
457 479 # move redirected index data back into place
458 480 if self._divert:
459 481 assert not self._delaybuf
460 482 tmpname = self.indexfile + ".a"
461 483 nfile = self.opener.open(tmpname)
462 484 nfile.close()
463 485 self.opener.rename(tmpname, self.indexfile, checkambig=True)
464 486 elif self._delaybuf:
465 487 fp = self.opener(self.indexfile, 'a', checkambig=True)
466 488 fp.write("".join(self._delaybuf))
467 489 fp.close()
468 490 self._delaybuf = None
469 491 self._divert = False
470 492 # split when we're done
471 493 self._enforceinlinesize(tr)
472 494
473 495 def _writepending(self, tr):
474 496 "create a file containing the unfinalized state for pretxnchangegroup"
475 497 if self._delaybuf:
476 498 # make a temporary copy of the index
477 499 fp1 = self._realopener(self.indexfile)
478 500 pendingfilename = self.indexfile + ".a"
479 501 # register as a temp file to ensure cleanup on failure
480 502 tr.registertmp(pendingfilename)
481 503 # write existing data
482 504 fp2 = self._realopener(pendingfilename, "w")
483 505 fp2.write(fp1.read())
484 506 # add pending data
485 507 fp2.write("".join(self._delaybuf))
486 508 fp2.close()
487 509 # switch modes so finalize can simply rename
488 510 self._delaybuf = None
489 511 self._divert = True
490 512 self.opener = _divertopener(self._realopener, self.indexfile)
491 513
492 514 if self._divert:
493 515 return True
494 516
495 517 return False
496 518
497 519 def _enforceinlinesize(self, tr, fp=None):
498 520 if not self._delayed:
499 521 revlog.revlog._enforceinlinesize(self, tr, fp)
500 522
501 523 def read(self, node):
502 524 """Obtain data from a parsed changelog revision.
503 525
504 526 Returns a 6-tuple of:
505 527
506 528 - manifest node in binary
507 529 - author/user as a localstr
508 530 - date as a 2-tuple of (time, timezone)
509 531 - list of files
510 532 - commit message as a localstr
511 533 - dict of extra metadata
512 534
513 535 Unless you need to access all fields, consider calling
514 536 ``changelogrevision`` instead, as it is faster for partial object
515 537 access.
516 538 """
517 539 c = changelogrevision(self.revision(node))
518 540 return (
519 541 c.manifest,
520 542 c.user,
521 543 c.date,
522 544 c.files,
523 545 c.description,
524 546 c.extra
525 547 )
526 548
527 549 def changelogrevision(self, nodeorrev):
528 550 """Obtain a ``changelogrevision`` for a node or revision."""
529 551 return changelogrevision(self.revision(nodeorrev))
530 552
531 553 def readfiles(self, node):
532 554 """
533 555 short version of read that only returns the files modified by the cset
534 556 """
535 557 text = self.revision(node)
536 558 if not text:
537 559 return []
538 560 last = text.index("\n\n")
539 561 l = text[:last].split('\n')
540 562 return l[3:]
541 563
542 564 def add(self, manifest, files, desc, transaction, p1, p2,
543 565 user, date=None, extra=None, p1copies=None, p2copies=None):
544 566 # Convert to UTF-8 encoded bytestrings as the very first
545 567 # thing: calling any method on a localstr object will turn it
546 568 # into a str object and the cached UTF-8 string is thus lost.
547 569 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
548 570
549 571 user = user.strip()
550 572 # An empty username or a username with a "\n" will make the
551 573 # revision text contain two "\n\n" sequences -> corrupt
552 574 # repository since read cannot unpack the revision.
553 575 if not user:
554 576 raise error.StorageError(_("empty username"))
555 577 if "\n" in user:
556 578 raise error.StorageError(_("username %r contains a newline")
557 579 % pycompat.bytestr(user))
558 580
559 581 desc = stripdesc(desc)
560 582
561 583 if date:
562 584 parseddate = "%d %d" % dateutil.parsedate(date)
563 585 else:
564 586 parseddate = "%d %d" % dateutil.makedate()
565 587 if extra:
566 588 branch = extra.get("branch")
567 589 if branch in ("default", ""):
568 590 del extra["branch"]
569 591 elif branch in (".", "null", "tip"):
570 592 raise error.StorageError(_('the name \'%s\' is reserved')
571 593 % branch)
572 594 if (p1copies or p2copies) and extra is None:
573 595 extra = {}
574 596 if p1copies:
575 597 extra['p1copies'] = encodecopies(p1copies)
576 598 if p2copies:
577 599 extra['p2copies'] = encodecopies(p2copies)
578 600
579 601 if extra:
580 602 extra = encodeextra(extra)
581 603 parseddate = "%s %s" % (parseddate, extra)
582 604 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
583 605 text = "\n".join(l)
584 606 return self.addrevision(text, transaction, len(self), p1, p2)
585 607
586 608 def branchinfo(self, rev):
587 609 """return the branch name and open/close state of a revision
588 610
589 611 This function exists because creating a changectx object
590 612 just to access this is costly."""
591 613 extra = self.read(rev)[5]
592 614 return encoding.tolocal(extra.get("branch")), 'close' in extra
593 615
594 616 def _nodeduplicatecallback(self, transaction, node):
595 617 # keep track of revisions that got "re-added", eg: unbunde of know rev.
596 618 #
597 619 # We track them in a list to preserve their order from the source bundle
598 620 duplicates = transaction.changes.setdefault('revduplicates', [])
599 621 duplicates.append(self.rev(node))
@@ -1,2551 +1,2566
1 1 # context.py - changeset and file context objects 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 filecmp
12 12 import os
13 13 import stat
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 addednodeid,
18 18 hex,
19 19 modifiednodeid,
20 20 nullid,
21 21 nullrev,
22 22 short,
23 23 wdirfilenodeids,
24 24 wdirhex,
25 25 )
26 26 from . import (
27 27 dagop,
28 28 encoding,
29 29 error,
30 30 fileset,
31 31 match as matchmod,
32 32 obsolete as obsmod,
33 33 patch,
34 34 pathutil,
35 35 phases,
36 36 pycompat,
37 37 repoview,
38 38 scmutil,
39 39 sparse,
40 40 subrepo,
41 41 subrepoutil,
42 42 util,
43 43 )
44 44 from .utils import (
45 45 dateutil,
46 46 stringutil,
47 47 )
48 48
49 49 propertycache = util.propertycache
50 50
51 51 class basectx(object):
52 52 """A basectx object represents the common logic for its children:
53 53 changectx: read-only context that is already present in the repo,
54 54 workingctx: a context that represents the working directory and can
55 55 be committed,
56 56 memctx: a context that represents changes in-memory and can also
57 57 be committed."""
58 58
59 59 def __init__(self, repo):
60 60 self._repo = repo
61 61
62 62 def __bytes__(self):
63 63 return short(self.node())
64 64
65 65 __str__ = encoding.strmethod(__bytes__)
66 66
67 67 def __repr__(self):
68 68 return r"<%s %s>" % (type(self).__name__, str(self))
69 69
70 70 def __eq__(self, other):
71 71 try:
72 72 return type(self) == type(other) and self._rev == other._rev
73 73 except AttributeError:
74 74 return False
75 75
76 76 def __ne__(self, other):
77 77 return not (self == other)
78 78
79 79 def __contains__(self, key):
80 80 return key in self._manifest
81 81
82 82 def __getitem__(self, key):
83 83 return self.filectx(key)
84 84
85 85 def __iter__(self):
86 86 return iter(self._manifest)
87 87
88 88 def _buildstatusmanifest(self, status):
89 89 """Builds a manifest that includes the given status results, if this is
90 90 a working copy context. For non-working copy contexts, it just returns
91 91 the normal manifest."""
92 92 return self.manifest()
93 93
94 94 def _matchstatus(self, other, match):
95 95 """This internal method provides a way for child objects to override the
96 96 match operator.
97 97 """
98 98 return match
99 99
100 100 def _buildstatus(self, other, s, match, listignored, listclean,
101 101 listunknown):
102 102 """build a status with respect to another context"""
103 103 # Load earliest manifest first for caching reasons. More specifically,
104 104 # if you have revisions 1000 and 1001, 1001 is probably stored as a
105 105 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
106 106 # 1000 and cache it so that when you read 1001, we just need to apply a
107 107 # delta to what's in the cache. So that's one full reconstruction + one
108 108 # delta application.
109 109 mf2 = None
110 110 if self.rev() is not None and self.rev() < other.rev():
111 111 mf2 = self._buildstatusmanifest(s)
112 112 mf1 = other._buildstatusmanifest(s)
113 113 if mf2 is None:
114 114 mf2 = self._buildstatusmanifest(s)
115 115
116 116 modified, added = [], []
117 117 removed = []
118 118 clean = []
119 119 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
120 120 deletedset = set(deleted)
121 121 d = mf1.diff(mf2, match=match, clean=listclean)
122 122 for fn, value in d.iteritems():
123 123 if fn in deletedset:
124 124 continue
125 125 if value is None:
126 126 clean.append(fn)
127 127 continue
128 128 (node1, flag1), (node2, flag2) = value
129 129 if node1 is None:
130 130 added.append(fn)
131 131 elif node2 is None:
132 132 removed.append(fn)
133 133 elif flag1 != flag2:
134 134 modified.append(fn)
135 135 elif node2 not in wdirfilenodeids:
136 136 # When comparing files between two commits, we save time by
137 137 # not comparing the file contents when the nodeids differ.
138 138 # Note that this means we incorrectly report a reverted change
139 139 # to a file as a modification.
140 140 modified.append(fn)
141 141 elif self[fn].cmp(other[fn]):
142 142 modified.append(fn)
143 143 else:
144 144 clean.append(fn)
145 145
146 146 if removed:
147 147 # need to filter files if they are already reported as removed
148 148 unknown = [fn for fn in unknown if fn not in mf1 and
149 149 (not match or match(fn))]
150 150 ignored = [fn for fn in ignored if fn not in mf1 and
151 151 (not match or match(fn))]
152 152 # if they're deleted, don't report them as removed
153 153 removed = [fn for fn in removed if fn not in deletedset]
154 154
155 155 return scmutil.status(modified, added, removed, deleted, unknown,
156 156 ignored, clean)
157 157
158 158 @propertycache
159 159 def substate(self):
160 160 return subrepoutil.state(self, self._repo.ui)
161 161
162 162 def subrev(self, subpath):
163 163 return self.substate[subpath][1]
164 164
165 165 def rev(self):
166 166 return self._rev
167 167 def node(self):
168 168 return self._node
169 169 def hex(self):
170 170 return hex(self.node())
171 171 def manifest(self):
172 172 return self._manifest
173 173 def manifestctx(self):
174 174 return self._manifestctx
175 175 def repo(self):
176 176 return self._repo
177 177 def phasestr(self):
178 178 return phases.phasenames[self.phase()]
179 179 def mutable(self):
180 180 return self.phase() > phases.public
181 181
182 182 def matchfileset(self, expr, badfn=None):
183 183 return fileset.match(self, expr, badfn=badfn)
184 184
185 185 def obsolete(self):
186 186 """True if the changeset is obsolete"""
187 187 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
188 188
189 189 def extinct(self):
190 190 """True if the changeset is extinct"""
191 191 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
192 192
193 193 def orphan(self):
194 194 """True if the changeset is not obsolete, but its ancestor is"""
195 195 return self.rev() in obsmod.getrevs(self._repo, 'orphan')
196 196
197 197 def phasedivergent(self):
198 198 """True if the changeset tries to be a successor of a public changeset
199 199
200 200 Only non-public and non-obsolete changesets may be phase-divergent.
201 201 """
202 202 return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
203 203
204 204 def contentdivergent(self):
205 205 """Is a successor of a changeset with multiple possible successor sets
206 206
207 207 Only non-public and non-obsolete changesets may be content-divergent.
208 208 """
209 209 return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
210 210
211 211 def isunstable(self):
212 212 """True if the changeset is either orphan, phase-divergent or
213 213 content-divergent"""
214 214 return self.orphan() or self.phasedivergent() or self.contentdivergent()
215 215
216 216 def instabilities(self):
217 217 """return the list of instabilities affecting this changeset.
218 218
219 219 Instabilities are returned as strings. possible values are:
220 220 - orphan,
221 221 - phase-divergent,
222 222 - content-divergent.
223 223 """
224 224 instabilities = []
225 225 if self.orphan():
226 226 instabilities.append('orphan')
227 227 if self.phasedivergent():
228 228 instabilities.append('phase-divergent')
229 229 if self.contentdivergent():
230 230 instabilities.append('content-divergent')
231 231 return instabilities
232 232
233 233 def parents(self):
234 234 """return contexts for each parent changeset"""
235 235 return self._parents
236 236
237 237 def p1(self):
238 238 return self._parents[0]
239 239
240 240 def p2(self):
241 241 parents = self._parents
242 242 if len(parents) == 2:
243 243 return parents[1]
244 244 return self._repo[nullrev]
245 245
246 246 def _fileinfo(self, path):
247 247 if r'_manifest' in self.__dict__:
248 248 try:
249 249 return self._manifest[path], self._manifest.flags(path)
250 250 except KeyError:
251 251 raise error.ManifestLookupError(self._node, path,
252 252 _('not found in manifest'))
253 253 if r'_manifestdelta' in self.__dict__ or path in self.files():
254 254 if path in self._manifestdelta:
255 255 return (self._manifestdelta[path],
256 256 self._manifestdelta.flags(path))
257 257 mfl = self._repo.manifestlog
258 258 try:
259 259 node, flag = mfl[self._changeset.manifest].find(path)
260 260 except KeyError:
261 261 raise error.ManifestLookupError(self._node, path,
262 262 _('not found in manifest'))
263 263
264 264 return node, flag
265 265
266 266 def filenode(self, path):
267 267 return self._fileinfo(path)[0]
268 268
269 269 def flags(self, path):
270 270 try:
271 271 return self._fileinfo(path)[1]
272 272 except error.LookupError:
273 273 return ''
274 274
275 275 def sub(self, path, allowcreate=True):
276 276 '''return a subrepo for the stored revision of path, never wdir()'''
277 277 return subrepo.subrepo(self, path, allowcreate=allowcreate)
278 278
279 279 def nullsub(self, path, pctx):
280 280 return subrepo.nullsubrepo(self, path, pctx)
281 281
282 282 def workingsub(self, path):
283 283 '''return a subrepo for the stored revision, or wdir if this is a wdir
284 284 context.
285 285 '''
286 286 return subrepo.subrepo(self, path, allowwdir=True)
287 287
288 288 def match(self, pats=None, include=None, exclude=None, default='glob',
289 289 listsubrepos=False, badfn=None):
290 290 r = self._repo
291 291 return matchmod.match(r.root, r.getcwd(), pats,
292 292 include, exclude, default,
293 293 auditor=r.nofsauditor, ctx=self,
294 294 listsubrepos=listsubrepos, badfn=badfn)
295 295
296 296 def diff(self, ctx2=None, match=None, changes=None, opts=None,
297 297 losedatafn=None, pathfn=None, copy=None,
298 298 copysourcematch=None, hunksfilterfn=None):
299 299 """Returns a diff generator for the given contexts and matcher"""
300 300 if ctx2 is None:
301 301 ctx2 = self.p1()
302 302 if ctx2 is not None:
303 303 ctx2 = self._repo[ctx2]
304 304 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
305 305 opts=opts, losedatafn=losedatafn, pathfn=pathfn,
306 306 copy=copy, copysourcematch=copysourcematch,
307 307 hunksfilterfn=hunksfilterfn)
308 308
309 309 def dirs(self):
310 310 return self._manifest.dirs()
311 311
312 312 def hasdir(self, dir):
313 313 return self._manifest.hasdir(dir)
314 314
315 315 def status(self, other=None, match=None, listignored=False,
316 316 listclean=False, listunknown=False, listsubrepos=False):
317 317 """return status of files between two nodes or node and working
318 318 directory.
319 319
320 320 If other is None, compare this node with working directory.
321 321
322 322 returns (modified, added, removed, deleted, unknown, ignored, clean)
323 323 """
324 324
325 325 ctx1 = self
326 326 ctx2 = self._repo[other]
327 327
328 328 # This next code block is, admittedly, fragile logic that tests for
329 329 # reversing the contexts and wouldn't need to exist if it weren't for
330 330 # the fast (and common) code path of comparing the working directory
331 331 # with its first parent.
332 332 #
333 333 # What we're aiming for here is the ability to call:
334 334 #
335 335 # workingctx.status(parentctx)
336 336 #
337 337 # If we always built the manifest for each context and compared those,
338 338 # then we'd be done. But the special case of the above call means we
339 339 # just copy the manifest of the parent.
340 340 reversed = False
341 341 if (not isinstance(ctx1, changectx)
342 342 and isinstance(ctx2, changectx)):
343 343 reversed = True
344 344 ctx1, ctx2 = ctx2, ctx1
345 345
346 346 match = self._repo.narrowmatch(match)
347 347 match = ctx2._matchstatus(ctx1, match)
348 348 r = scmutil.status([], [], [], [], [], [], [])
349 349 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
350 350 listunknown)
351 351
352 352 if reversed:
353 353 # Reverse added and removed. Clear deleted, unknown and ignored as
354 354 # these make no sense to reverse.
355 355 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
356 356 r.clean)
357 357
358 358 if listsubrepos:
359 359 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
360 360 try:
361 361 rev2 = ctx2.subrev(subpath)
362 362 except KeyError:
363 363 # A subrepo that existed in node1 was deleted between
364 364 # node1 and node2 (inclusive). Thus, ctx2's substate
365 365 # won't contain that subpath. The best we can do ignore it.
366 366 rev2 = None
367 367 submatch = matchmod.subdirmatcher(subpath, match)
368 368 s = sub.status(rev2, match=submatch, ignored=listignored,
369 369 clean=listclean, unknown=listunknown,
370 370 listsubrepos=True)
371 371 for rfiles, sfiles in zip(r, s):
372 372 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
373 373
374 374 for l in r:
375 375 l.sort()
376 376
377 377 return r
378 378
379 379 class changectx(basectx):
380 380 """A changecontext object makes access to data related to a particular
381 381 changeset convenient. It represents a read-only context already present in
382 382 the repo."""
383 383 def __init__(self, repo, rev, node):
384 384 super(changectx, self).__init__(repo)
385 385 self._rev = rev
386 386 self._node = node
387 387
388 388 def __hash__(self):
389 389 try:
390 390 return hash(self._rev)
391 391 except AttributeError:
392 392 return id(self)
393 393
394 394 def __nonzero__(self):
395 395 return self._rev != nullrev
396 396
397 397 __bool__ = __nonzero__
398 398
399 399 @propertycache
400 400 def _changeset(self):
401 401 return self._repo.changelog.changelogrevision(self.rev())
402 402
403 403 @propertycache
404 404 def _manifest(self):
405 405 return self._manifestctx.read()
406 406
407 407 @property
408 408 def _manifestctx(self):
409 409 return self._repo.manifestlog[self._changeset.manifest]
410 410
411 411 @propertycache
412 412 def _manifestdelta(self):
413 413 return self._manifestctx.readdelta()
414 414
415 415 @propertycache
416 416 def _parents(self):
417 417 repo = self._repo
418 418 p1, p2 = repo.changelog.parentrevs(self._rev)
419 419 if p2 == nullrev:
420 420 return [repo[p1]]
421 421 return [repo[p1], repo[p2]]
422 422
423 423 def changeset(self):
424 424 c = self._changeset
425 425 return (
426 426 c.manifest,
427 427 c.user,
428 428 c.date,
429 429 c.files,
430 430 c.description,
431 431 c.extra,
432 432 )
433 433 def manifestnode(self):
434 434 return self._changeset.manifest
435 435
436 436 def user(self):
437 437 return self._changeset.user
438 438 def date(self):
439 439 return self._changeset.date
440 440 def files(self):
441 441 return self._changeset.files
442 442 @propertycache
443 443 def _copies(self):
444 source = self._repo.ui.config('experimental', 'copies.read-from')
445 p1copies = self._changeset.p1copies
446 p2copies = self._changeset.p2copies
447 # If config says to get copy metadata only from changeset, then return
448 # that, defaulting to {} if there was no copy metadata.
449 # In compatibility mode, we return copy data from the changeset if
450 # it was recorded there, and otherwise we fall back to getting it from
451 # the filelogs (below).
452 if (source == 'changeset-only' or
453 (source == 'compatibility' and p1copies is not None)):
454 return p1copies or {}, p2copies or {}
455
456 # Otherwise (config said to read only from filelog, or we are in
457 # compatiblity mode and there is not data in the changeset), we get
458 # the copy metadata from the filelogs.
444 459 p1copies = {}
445 460 p2copies = {}
446 461 p1 = self.p1()
447 462 p2 = self.p2()
448 463 narrowmatch = self._repo.narrowmatch()
449 464 for dst in self.files():
450 465 if not narrowmatch(dst) or dst not in self:
451 466 continue
452 467 copied = self[dst].renamed()
453 468 if not copied:
454 469 continue
455 470 src, srcnode = copied
456 471 if src in p1 and p1[src].filenode() == srcnode:
457 472 p1copies[dst] = src
458 473 elif src in p2 and p2[src].filenode() == srcnode:
459 474 p2copies[dst] = src
460 475 return p1copies, p2copies
461 476 def p1copies(self):
462 477 return self._copies[0]
463 478 def p2copies(self):
464 479 return self._copies[1]
465 480 def description(self):
466 481 return self._changeset.description
467 482 def branch(self):
468 483 return encoding.tolocal(self._changeset.extra.get("branch"))
469 484 def closesbranch(self):
470 485 return 'close' in self._changeset.extra
471 486 def extra(self):
472 487 """Return a dict of extra information."""
473 488 return self._changeset.extra
474 489 def tags(self):
475 490 """Return a list of byte tag names"""
476 491 return self._repo.nodetags(self._node)
477 492 def bookmarks(self):
478 493 """Return a list of byte bookmark names."""
479 494 return self._repo.nodebookmarks(self._node)
480 495 def phase(self):
481 496 return self._repo._phasecache.phase(self._repo, self._rev)
482 497 def hidden(self):
483 498 return self._rev in repoview.filterrevs(self._repo, 'visible')
484 499
485 500 def isinmemory(self):
486 501 return False
487 502
488 503 def children(self):
489 504 """return list of changectx contexts for each child changeset.
490 505
491 506 This returns only the immediate child changesets. Use descendants() to
492 507 recursively walk children.
493 508 """
494 509 c = self._repo.changelog.children(self._node)
495 510 return [self._repo[x] for x in c]
496 511
497 512 def ancestors(self):
498 513 for a in self._repo.changelog.ancestors([self._rev]):
499 514 yield self._repo[a]
500 515
501 516 def descendants(self):
502 517 """Recursively yield all children of the changeset.
503 518
504 519 For just the immediate children, use children()
505 520 """
506 521 for d in self._repo.changelog.descendants([self._rev]):
507 522 yield self._repo[d]
508 523
509 524 def filectx(self, path, fileid=None, filelog=None):
510 525 """get a file context from this changeset"""
511 526 if fileid is None:
512 527 fileid = self.filenode(path)
513 528 return filectx(self._repo, path, fileid=fileid,
514 529 changectx=self, filelog=filelog)
515 530
516 531 def ancestor(self, c2, warn=False):
517 532 """return the "best" ancestor context of self and c2
518 533
519 534 If there are multiple candidates, it will show a message and check
520 535 merge.preferancestor configuration before falling back to the
521 536 revlog ancestor."""
522 537 # deal with workingctxs
523 538 n2 = c2._node
524 539 if n2 is None:
525 540 n2 = c2._parents[0]._node
526 541 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
527 542 if not cahs:
528 543 anc = nullid
529 544 elif len(cahs) == 1:
530 545 anc = cahs[0]
531 546 else:
532 547 # experimental config: merge.preferancestor
533 548 for r in self._repo.ui.configlist('merge', 'preferancestor'):
534 549 try:
535 550 ctx = scmutil.revsymbol(self._repo, r)
536 551 except error.RepoLookupError:
537 552 continue
538 553 anc = ctx.node()
539 554 if anc in cahs:
540 555 break
541 556 else:
542 557 anc = self._repo.changelog.ancestor(self._node, n2)
543 558 if warn:
544 559 self._repo.ui.status(
545 560 (_("note: using %s as ancestor of %s and %s\n") %
546 561 (short(anc), short(self._node), short(n2))) +
547 562 ''.join(_(" alternatively, use --config "
548 563 "merge.preferancestor=%s\n") %
549 564 short(n) for n in sorted(cahs) if n != anc))
550 565 return self._repo[anc]
551 566
552 567 def isancestorof(self, other):
553 568 """True if this changeset is an ancestor of other"""
554 569 return self._repo.changelog.isancestorrev(self._rev, other._rev)
555 570
556 571 def walk(self, match):
557 572 '''Generates matching file names.'''
558 573
559 574 # Wrap match.bad method to have message with nodeid
560 575 def bad(fn, msg):
561 576 # The manifest doesn't know about subrepos, so don't complain about
562 577 # paths into valid subrepos.
563 578 if any(fn == s or fn.startswith(s + '/')
564 579 for s in self.substate):
565 580 return
566 581 match.bad(fn, _('no such file in rev %s') % self)
567 582
568 583 m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
569 584 return self._manifest.walk(m)
570 585
571 586 def matches(self, match):
572 587 return self.walk(match)
573 588
574 589 class basefilectx(object):
575 590 """A filecontext object represents the common logic for its children:
576 591 filectx: read-only access to a filerevision that is already present
577 592 in the repo,
578 593 workingfilectx: a filecontext that represents files from the working
579 594 directory,
580 595 memfilectx: a filecontext that represents files in-memory,
581 596 """
582 597 @propertycache
583 598 def _filelog(self):
584 599 return self._repo.file(self._path)
585 600
586 601 @propertycache
587 602 def _changeid(self):
588 603 if r'_changectx' in self.__dict__:
589 604 return self._changectx.rev()
590 605 elif r'_descendantrev' in self.__dict__:
591 606 # this file context was created from a revision with a known
592 607 # descendant, we can (lazily) correct for linkrev aliases
593 608 return self._adjustlinkrev(self._descendantrev)
594 609 else:
595 610 return self._filelog.linkrev(self._filerev)
596 611
597 612 @propertycache
598 613 def _filenode(self):
599 614 if r'_fileid' in self.__dict__:
600 615 return self._filelog.lookup(self._fileid)
601 616 else:
602 617 return self._changectx.filenode(self._path)
603 618
604 619 @propertycache
605 620 def _filerev(self):
606 621 return self._filelog.rev(self._filenode)
607 622
608 623 @propertycache
609 624 def _repopath(self):
610 625 return self._path
611 626
612 627 def __nonzero__(self):
613 628 try:
614 629 self._filenode
615 630 return True
616 631 except error.LookupError:
617 632 # file is missing
618 633 return False
619 634
620 635 __bool__ = __nonzero__
621 636
622 637 def __bytes__(self):
623 638 try:
624 639 return "%s@%s" % (self.path(), self._changectx)
625 640 except error.LookupError:
626 641 return "%s@???" % self.path()
627 642
628 643 __str__ = encoding.strmethod(__bytes__)
629 644
630 645 def __repr__(self):
631 646 return r"<%s %s>" % (type(self).__name__, str(self))
632 647
633 648 def __hash__(self):
634 649 try:
635 650 return hash((self._path, self._filenode))
636 651 except AttributeError:
637 652 return id(self)
638 653
639 654 def __eq__(self, other):
640 655 try:
641 656 return (type(self) == type(other) and self._path == other._path
642 657 and self._filenode == other._filenode)
643 658 except AttributeError:
644 659 return False
645 660
646 661 def __ne__(self, other):
647 662 return not (self == other)
648 663
649 664 def filerev(self):
650 665 return self._filerev
651 666 def filenode(self):
652 667 return self._filenode
653 668 @propertycache
654 669 def _flags(self):
655 670 return self._changectx.flags(self._path)
656 671 def flags(self):
657 672 return self._flags
658 673 def filelog(self):
659 674 return self._filelog
660 675 def rev(self):
661 676 return self._changeid
662 677 def linkrev(self):
663 678 return self._filelog.linkrev(self._filerev)
664 679 def node(self):
665 680 return self._changectx.node()
666 681 def hex(self):
667 682 return self._changectx.hex()
668 683 def user(self):
669 684 return self._changectx.user()
670 685 def date(self):
671 686 return self._changectx.date()
672 687 def files(self):
673 688 return self._changectx.files()
674 689 def description(self):
675 690 return self._changectx.description()
676 691 def branch(self):
677 692 return self._changectx.branch()
678 693 def extra(self):
679 694 return self._changectx.extra()
680 695 def phase(self):
681 696 return self._changectx.phase()
682 697 def phasestr(self):
683 698 return self._changectx.phasestr()
684 699 def obsolete(self):
685 700 return self._changectx.obsolete()
686 701 def instabilities(self):
687 702 return self._changectx.instabilities()
688 703 def manifest(self):
689 704 return self._changectx.manifest()
690 705 def changectx(self):
691 706 return self._changectx
692 707 def renamed(self):
693 708 return self._copied
694 709 def copysource(self):
695 710 return self._copied and self._copied[0]
696 711 def repo(self):
697 712 return self._repo
698 713 def size(self):
699 714 return len(self.data())
700 715
701 716 def path(self):
702 717 return self._path
703 718
704 719 def isbinary(self):
705 720 try:
706 721 return stringutil.binary(self.data())
707 722 except IOError:
708 723 return False
709 724 def isexec(self):
710 725 return 'x' in self.flags()
711 726 def islink(self):
712 727 return 'l' in self.flags()
713 728
714 729 def isabsent(self):
715 730 """whether this filectx represents a file not in self._changectx
716 731
717 732 This is mainly for merge code to detect change/delete conflicts. This is
718 733 expected to be True for all subclasses of basectx."""
719 734 return False
720 735
721 736 _customcmp = False
722 737 def cmp(self, fctx):
723 738 """compare with other file context
724 739
725 740 returns True if different than fctx.
726 741 """
727 742 if fctx._customcmp:
728 743 return fctx.cmp(self)
729 744
730 745 if self._filenode is None:
731 746 raise error.ProgrammingError(
732 747 'filectx.cmp() must be reimplemented if not backed by revlog')
733 748
734 749 if fctx._filenode is None:
735 750 if self._repo._encodefilterpats:
736 751 # can't rely on size() because wdir content may be decoded
737 752 return self._filelog.cmp(self._filenode, fctx.data())
738 753 if self.size() - 4 == fctx.size():
739 754 # size() can match:
740 755 # if file data starts with '\1\n', empty metadata block is
741 756 # prepended, which adds 4 bytes to filelog.size().
742 757 return self._filelog.cmp(self._filenode, fctx.data())
743 758 if self.size() == fctx.size():
744 759 # size() matches: need to compare content
745 760 return self._filelog.cmp(self._filenode, fctx.data())
746 761
747 762 # size() differs
748 763 return True
749 764
750 765 def _adjustlinkrev(self, srcrev, inclusive=False, stoprev=None):
751 766 """return the first ancestor of <srcrev> introducing <fnode>
752 767
753 768 If the linkrev of the file revision does not point to an ancestor of
754 769 srcrev, we'll walk down the ancestors until we find one introducing
755 770 this file revision.
756 771
757 772 :srcrev: the changeset revision we search ancestors from
758 773 :inclusive: if true, the src revision will also be checked
759 774 :stoprev: an optional revision to stop the walk at. If no introduction
760 775 of this file content could be found before this floor
761 776 revision, the function will returns "None" and stops its
762 777 iteration.
763 778 """
764 779 repo = self._repo
765 780 cl = repo.unfiltered().changelog
766 781 mfl = repo.manifestlog
767 782 # fetch the linkrev
768 783 lkr = self.linkrev()
769 784 if srcrev == lkr:
770 785 return lkr
771 786 # hack to reuse ancestor computation when searching for renames
772 787 memberanc = getattr(self, '_ancestrycontext', None)
773 788 iteranc = None
774 789 if srcrev is None:
775 790 # wctx case, used by workingfilectx during mergecopy
776 791 revs = [p.rev() for p in self._repo[None].parents()]
777 792 inclusive = True # we skipped the real (revless) source
778 793 else:
779 794 revs = [srcrev]
780 795 if memberanc is None:
781 796 memberanc = iteranc = cl.ancestors(revs, lkr,
782 797 inclusive=inclusive)
783 798 # check if this linkrev is an ancestor of srcrev
784 799 if lkr not in memberanc:
785 800 if iteranc is None:
786 801 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
787 802 fnode = self._filenode
788 803 path = self._path
789 804 for a in iteranc:
790 805 if stoprev is not None and a < stoprev:
791 806 return None
792 807 ac = cl.read(a) # get changeset data (we avoid object creation)
793 808 if path in ac[3]: # checking the 'files' field.
794 809 # The file has been touched, check if the content is
795 810 # similar to the one we search for.
796 811 if fnode == mfl[ac[0]].readfast().get(path):
797 812 return a
798 813 # In theory, we should never get out of that loop without a result.
799 814 # But if manifest uses a buggy file revision (not children of the
800 815 # one it replaces) we could. Such a buggy situation will likely
801 816 # result is crash somewhere else at to some point.
802 817 return lkr
803 818
804 819 def isintroducedafter(self, changelogrev):
805 820 """True if a filectx has been introduced after a given floor revision
806 821 """
807 822 if self.linkrev() >= changelogrev:
808 823 return True
809 824 introrev = self._introrev(stoprev=changelogrev)
810 825 if introrev is None:
811 826 return False
812 827 return introrev >= changelogrev
813 828
814 829 def introrev(self):
815 830 """return the rev of the changeset which introduced this file revision
816 831
817 832 This method is different from linkrev because it take into account the
818 833 changeset the filectx was created from. It ensures the returned
819 834 revision is one of its ancestors. This prevents bugs from
820 835 'linkrev-shadowing' when a file revision is used by multiple
821 836 changesets.
822 837 """
823 838 return self._introrev()
824 839
825 840 def _introrev(self, stoprev=None):
826 841 """
827 842 Same as `introrev` but, with an extra argument to limit changelog
828 843 iteration range in some internal usecase.
829 844
830 845 If `stoprev` is set, the `introrev` will not be searched past that
831 846 `stoprev` revision and "None" might be returned. This is useful to
832 847 limit the iteration range.
833 848 """
834 849 toprev = None
835 850 attrs = vars(self)
836 851 if r'_changeid' in attrs:
837 852 # We have a cached value already
838 853 toprev = self._changeid
839 854 elif r'_changectx' in attrs:
840 855 # We know which changelog entry we are coming from
841 856 toprev = self._changectx.rev()
842 857
843 858 if toprev is not None:
844 859 return self._adjustlinkrev(toprev, inclusive=True, stoprev=stoprev)
845 860 elif r'_descendantrev' in attrs:
846 861 introrev = self._adjustlinkrev(self._descendantrev, stoprev=stoprev)
847 862 # be nice and cache the result of the computation
848 863 if introrev is not None:
849 864 self._changeid = introrev
850 865 return introrev
851 866 else:
852 867 return self.linkrev()
853 868
854 869 def introfilectx(self):
855 870 """Return filectx having identical contents, but pointing to the
856 871 changeset revision where this filectx was introduced"""
857 872 introrev = self.introrev()
858 873 if self.rev() == introrev:
859 874 return self
860 875 return self.filectx(self.filenode(), changeid=introrev)
861 876
862 877 def _parentfilectx(self, path, fileid, filelog):
863 878 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
864 879 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
865 880 if r'_changeid' in vars(self) or r'_changectx' in vars(self):
866 881 # If self is associated with a changeset (probably explicitly
867 882 # fed), ensure the created filectx is associated with a
868 883 # changeset that is an ancestor of self.changectx.
869 884 # This lets us later use _adjustlinkrev to get a correct link.
870 885 fctx._descendantrev = self.rev()
871 886 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
872 887 elif r'_descendantrev' in vars(self):
873 888 # Otherwise propagate _descendantrev if we have one associated.
874 889 fctx._descendantrev = self._descendantrev
875 890 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
876 891 return fctx
877 892
878 893 def parents(self):
879 894 _path = self._path
880 895 fl = self._filelog
881 896 parents = self._filelog.parents(self._filenode)
882 897 pl = [(_path, node, fl) for node in parents if node != nullid]
883 898
884 899 r = fl.renamed(self._filenode)
885 900 if r:
886 901 # - In the simple rename case, both parent are nullid, pl is empty.
887 902 # - In case of merge, only one of the parent is null id and should
888 903 # be replaced with the rename information. This parent is -always-
889 904 # the first one.
890 905 #
891 906 # As null id have always been filtered out in the previous list
892 907 # comprehension, inserting to 0 will always result in "replacing
893 908 # first nullid parent with rename information.
894 909 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
895 910
896 911 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
897 912
898 913 def p1(self):
899 914 return self.parents()[0]
900 915
901 916 def p2(self):
902 917 p = self.parents()
903 918 if len(p) == 2:
904 919 return p[1]
905 920 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
906 921
907 922 def annotate(self, follow=False, skiprevs=None, diffopts=None):
908 923 """Returns a list of annotateline objects for each line in the file
909 924
910 925 - line.fctx is the filectx of the node where that line was last changed
911 926 - line.lineno is the line number at the first appearance in the managed
912 927 file
913 928 - line.text is the data on that line (including newline character)
914 929 """
915 930 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
916 931
917 932 def parents(f):
918 933 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
919 934 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
920 935 # from the topmost introrev (= srcrev) down to p.linkrev() if it
921 936 # isn't an ancestor of the srcrev.
922 937 f._changeid
923 938 pl = f.parents()
924 939
925 940 # Don't return renamed parents if we aren't following.
926 941 if not follow:
927 942 pl = [p for p in pl if p.path() == f.path()]
928 943
929 944 # renamed filectx won't have a filelog yet, so set it
930 945 # from the cache to save time
931 946 for p in pl:
932 947 if not r'_filelog' in p.__dict__:
933 948 p._filelog = getlog(p.path())
934 949
935 950 return pl
936 951
937 952 # use linkrev to find the first changeset where self appeared
938 953 base = self.introfilectx()
939 954 if getattr(base, '_ancestrycontext', None) is None:
940 955 cl = self._repo.changelog
941 956 if base.rev() is None:
942 957 # wctx is not inclusive, but works because _ancestrycontext
943 958 # is used to test filelog revisions
944 959 ac = cl.ancestors([p.rev() for p in base.parents()],
945 960 inclusive=True)
946 961 else:
947 962 ac = cl.ancestors([base.rev()], inclusive=True)
948 963 base._ancestrycontext = ac
949 964
950 965 return dagop.annotate(base, parents, skiprevs=skiprevs,
951 966 diffopts=diffopts)
952 967
953 968 def ancestors(self, followfirst=False):
954 969 visit = {}
955 970 c = self
956 971 if followfirst:
957 972 cut = 1
958 973 else:
959 974 cut = None
960 975
961 976 while True:
962 977 for parent in c.parents()[:cut]:
963 978 visit[(parent.linkrev(), parent.filenode())] = parent
964 979 if not visit:
965 980 break
966 981 c = visit.pop(max(visit))
967 982 yield c
968 983
969 984 def decodeddata(self):
970 985 """Returns `data()` after running repository decoding filters.
971 986
972 987 This is often equivalent to how the data would be expressed on disk.
973 988 """
974 989 return self._repo.wwritedata(self.path(), self.data())
975 990
976 991 class filectx(basefilectx):
977 992 """A filecontext object makes access to data related to a particular
978 993 filerevision convenient."""
979 994 def __init__(self, repo, path, changeid=None, fileid=None,
980 995 filelog=None, changectx=None):
981 996 """changeid must be a revision number, if specified.
982 997 fileid can be a file revision or node."""
983 998 self._repo = repo
984 999 self._path = path
985 1000
986 1001 assert (changeid is not None
987 1002 or fileid is not None
988 1003 or changectx is not None), (
989 1004 "bad args: changeid=%r, fileid=%r, changectx=%r"
990 1005 % (changeid, fileid, changectx))
991 1006
992 1007 if filelog is not None:
993 1008 self._filelog = filelog
994 1009
995 1010 if changeid is not None:
996 1011 self._changeid = changeid
997 1012 if changectx is not None:
998 1013 self._changectx = changectx
999 1014 if fileid is not None:
1000 1015 self._fileid = fileid
1001 1016
1002 1017 @propertycache
1003 1018 def _changectx(self):
1004 1019 try:
1005 1020 return self._repo[self._changeid]
1006 1021 except error.FilteredRepoLookupError:
1007 1022 # Linkrev may point to any revision in the repository. When the
1008 1023 # repository is filtered this may lead to `filectx` trying to build
1009 1024 # `changectx` for filtered revision. In such case we fallback to
1010 1025 # creating `changectx` on the unfiltered version of the reposition.
1011 1026 # This fallback should not be an issue because `changectx` from
1012 1027 # `filectx` are not used in complex operations that care about
1013 1028 # filtering.
1014 1029 #
1015 1030 # This fallback is a cheap and dirty fix that prevent several
1016 1031 # crashes. It does not ensure the behavior is correct. However the
1017 1032 # behavior was not correct before filtering either and "incorrect
1018 1033 # behavior" is seen as better as "crash"
1019 1034 #
1020 1035 # Linkrevs have several serious troubles with filtering that are
1021 1036 # complicated to solve. Proper handling of the issue here should be
1022 1037 # considered when solving linkrev issue are on the table.
1023 1038 return self._repo.unfiltered()[self._changeid]
1024 1039
1025 1040 def filectx(self, fileid, changeid=None):
1026 1041 '''opens an arbitrary revision of the file without
1027 1042 opening a new filelog'''
1028 1043 return filectx(self._repo, self._path, fileid=fileid,
1029 1044 filelog=self._filelog, changeid=changeid)
1030 1045
1031 1046 def rawdata(self):
1032 1047 return self._filelog.revision(self._filenode, raw=True)
1033 1048
1034 1049 def rawflags(self):
1035 1050 """low-level revlog flags"""
1036 1051 return self._filelog.flags(self._filerev)
1037 1052
1038 1053 def data(self):
1039 1054 try:
1040 1055 return self._filelog.read(self._filenode)
1041 1056 except error.CensoredNodeError:
1042 1057 if self._repo.ui.config("censor", "policy") == "ignore":
1043 1058 return ""
1044 1059 raise error.Abort(_("censored node: %s") % short(self._filenode),
1045 1060 hint=_("set censor.policy to ignore errors"))
1046 1061
1047 1062 def size(self):
1048 1063 return self._filelog.size(self._filerev)
1049 1064
1050 1065 @propertycache
1051 1066 def _copied(self):
1052 1067 """check if file was actually renamed in this changeset revision
1053 1068
1054 1069 If rename logged in file revision, we report copy for changeset only
1055 1070 if file revisions linkrev points back to the changeset in question
1056 1071 or both changeset parents contain different file revisions.
1057 1072 """
1058 1073
1059 1074 renamed = self._filelog.renamed(self._filenode)
1060 1075 if not renamed:
1061 1076 return None
1062 1077
1063 1078 if self.rev() == self.linkrev():
1064 1079 return renamed
1065 1080
1066 1081 name = self.path()
1067 1082 fnode = self._filenode
1068 1083 for p in self._changectx.parents():
1069 1084 try:
1070 1085 if fnode == p.filenode(name):
1071 1086 return None
1072 1087 except error.LookupError:
1073 1088 pass
1074 1089 return renamed
1075 1090
1076 1091 def children(self):
1077 1092 # hard for renames
1078 1093 c = self._filelog.children(self._filenode)
1079 1094 return [filectx(self._repo, self._path, fileid=x,
1080 1095 filelog=self._filelog) for x in c]
1081 1096
1082 1097 class committablectx(basectx):
1083 1098 """A committablectx object provides common functionality for a context that
1084 1099 wants the ability to commit, e.g. workingctx or memctx."""
1085 1100 def __init__(self, repo, text="", user=None, date=None, extra=None,
1086 1101 changes=None):
1087 1102 super(committablectx, self).__init__(repo)
1088 1103 self._rev = None
1089 1104 self._node = None
1090 1105 self._text = text
1091 1106 if date:
1092 1107 self._date = dateutil.parsedate(date)
1093 1108 if user:
1094 1109 self._user = user
1095 1110 if changes:
1096 1111 self._status = changes
1097 1112
1098 1113 self._extra = {}
1099 1114 if extra:
1100 1115 self._extra = extra.copy()
1101 1116 if 'branch' not in self._extra:
1102 1117 try:
1103 1118 branch = encoding.fromlocal(self._repo.dirstate.branch())
1104 1119 except UnicodeDecodeError:
1105 1120 raise error.Abort(_('branch name not in UTF-8!'))
1106 1121 self._extra['branch'] = branch
1107 1122 if self._extra['branch'] == '':
1108 1123 self._extra['branch'] = 'default'
1109 1124
1110 1125 def __bytes__(self):
1111 1126 return bytes(self._parents[0]) + "+"
1112 1127
1113 1128 __str__ = encoding.strmethod(__bytes__)
1114 1129
1115 1130 def __nonzero__(self):
1116 1131 return True
1117 1132
1118 1133 __bool__ = __nonzero__
1119 1134
1120 1135 def _buildflagfunc(self):
1121 1136 # Create a fallback function for getting file flags when the
1122 1137 # filesystem doesn't support them
1123 1138
1124 1139 copiesget = self._repo.dirstate.copies().get
1125 1140 parents = self.parents()
1126 1141 if len(parents) < 2:
1127 1142 # when we have one parent, it's easy: copy from parent
1128 1143 man = parents[0].manifest()
1129 1144 def func(f):
1130 1145 f = copiesget(f, f)
1131 1146 return man.flags(f)
1132 1147 else:
1133 1148 # merges are tricky: we try to reconstruct the unstored
1134 1149 # result from the merge (issue1802)
1135 1150 p1, p2 = parents
1136 1151 pa = p1.ancestor(p2)
1137 1152 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1138 1153
1139 1154 def func(f):
1140 1155 f = copiesget(f, f) # may be wrong for merges with copies
1141 1156 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1142 1157 if fl1 == fl2:
1143 1158 return fl1
1144 1159 if fl1 == fla:
1145 1160 return fl2
1146 1161 if fl2 == fla:
1147 1162 return fl1
1148 1163 return '' # punt for conflicts
1149 1164
1150 1165 return func
1151 1166
1152 1167 @propertycache
1153 1168 def _flagfunc(self):
1154 1169 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1155 1170
1156 1171 @propertycache
1157 1172 def _status(self):
1158 1173 return self._repo.status()
1159 1174
1160 1175 @propertycache
1161 1176 def _user(self):
1162 1177 return self._repo.ui.username()
1163 1178
1164 1179 @propertycache
1165 1180 def _date(self):
1166 1181 ui = self._repo.ui
1167 1182 date = ui.configdate('devel', 'default-date')
1168 1183 if date is None:
1169 1184 date = dateutil.makedate()
1170 1185 return date
1171 1186
1172 1187 def subrev(self, subpath):
1173 1188 return None
1174 1189
1175 1190 def manifestnode(self):
1176 1191 return None
1177 1192 def user(self):
1178 1193 return self._user or self._repo.ui.username()
1179 1194 def date(self):
1180 1195 return self._date
1181 1196 def description(self):
1182 1197 return self._text
1183 1198 def files(self):
1184 1199 return sorted(self._status.modified + self._status.added +
1185 1200 self._status.removed)
1186 1201 def modified(self):
1187 1202 return self._status.modified
1188 1203 def added(self):
1189 1204 return self._status.added
1190 1205 def removed(self):
1191 1206 return self._status.removed
1192 1207 def deleted(self):
1193 1208 return self._status.deleted
1194 1209 @propertycache
1195 1210 def _copies(self):
1196 1211 p1copies = {}
1197 1212 p2copies = {}
1198 1213 parents = self._repo.dirstate.parents()
1199 1214 p1manifest = self._repo[parents[0]].manifest()
1200 1215 p2manifest = self._repo[parents[1]].manifest()
1201 1216 narrowmatch = self._repo.narrowmatch()
1202 1217 for dst, src in self._repo.dirstate.copies().items():
1203 1218 if not narrowmatch(dst):
1204 1219 continue
1205 1220 if src in p1manifest:
1206 1221 p1copies[dst] = src
1207 1222 elif src in p2manifest:
1208 1223 p2copies[dst] = src
1209 1224 return p1copies, p2copies
1210 1225 def p1copies(self):
1211 1226 return self._copies[0]
1212 1227 def p2copies(self):
1213 1228 return self._copies[1]
1214 1229 def branch(self):
1215 1230 return encoding.tolocal(self._extra['branch'])
1216 1231 def closesbranch(self):
1217 1232 return 'close' in self._extra
1218 1233 def extra(self):
1219 1234 return self._extra
1220 1235
1221 1236 def isinmemory(self):
1222 1237 return False
1223 1238
1224 1239 def tags(self):
1225 1240 return []
1226 1241
1227 1242 def bookmarks(self):
1228 1243 b = []
1229 1244 for p in self.parents():
1230 1245 b.extend(p.bookmarks())
1231 1246 return b
1232 1247
1233 1248 def phase(self):
1234 1249 phase = phases.draft # default phase to draft
1235 1250 for p in self.parents():
1236 1251 phase = max(phase, p.phase())
1237 1252 return phase
1238 1253
1239 1254 def hidden(self):
1240 1255 return False
1241 1256
1242 1257 def children(self):
1243 1258 return []
1244 1259
1245 1260 def flags(self, path):
1246 1261 if r'_manifest' in self.__dict__:
1247 1262 try:
1248 1263 return self._manifest.flags(path)
1249 1264 except KeyError:
1250 1265 return ''
1251 1266
1252 1267 try:
1253 1268 return self._flagfunc(path)
1254 1269 except OSError:
1255 1270 return ''
1256 1271
1257 1272 def ancestor(self, c2):
1258 1273 """return the "best" ancestor context of self and c2"""
1259 1274 return self._parents[0].ancestor(c2) # punt on two parents for now
1260 1275
1261 1276 def walk(self, match):
1262 1277 '''Generates matching file names.'''
1263 1278 return sorted(self._repo.dirstate.walk(self._repo.narrowmatch(match),
1264 1279 subrepos=sorted(self.substate),
1265 1280 unknown=True, ignored=False))
1266 1281
1267 1282 def matches(self, match):
1268 1283 match = self._repo.narrowmatch(match)
1269 1284 ds = self._repo.dirstate
1270 1285 return sorted(f for f in ds.matches(match) if ds[f] != 'r')
1271 1286
1272 1287 def ancestors(self):
1273 1288 for p in self._parents:
1274 1289 yield p
1275 1290 for a in self._repo.changelog.ancestors(
1276 1291 [p.rev() for p in self._parents]):
1277 1292 yield self._repo[a]
1278 1293
1279 1294 def markcommitted(self, node):
1280 1295 """Perform post-commit cleanup necessary after committing this ctx
1281 1296
1282 1297 Specifically, this updates backing stores this working context
1283 1298 wraps to reflect the fact that the changes reflected by this
1284 1299 workingctx have been committed. For example, it marks
1285 1300 modified and added files as normal in the dirstate.
1286 1301
1287 1302 """
1288 1303
1289 1304 with self._repo.dirstate.parentchange():
1290 1305 for f in self.modified() + self.added():
1291 1306 self._repo.dirstate.normal(f)
1292 1307 for f in self.removed():
1293 1308 self._repo.dirstate.drop(f)
1294 1309 self._repo.dirstate.setparents(node)
1295 1310
1296 1311 # write changes out explicitly, because nesting wlock at
1297 1312 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1298 1313 # from immediately doing so for subsequent changing files
1299 1314 self._repo.dirstate.write(self._repo.currenttransaction())
1300 1315
1301 1316 def dirty(self, missing=False, merge=True, branch=True):
1302 1317 return False
1303 1318
1304 1319 class workingctx(committablectx):
1305 1320 """A workingctx object makes access to data related to
1306 1321 the current working directory convenient.
1307 1322 date - any valid date string or (unixtime, offset), or None.
1308 1323 user - username string, or None.
1309 1324 extra - a dictionary of extra values, or None.
1310 1325 changes - a list of file lists as returned by localrepo.status()
1311 1326 or None to use the repository status.
1312 1327 """
1313 1328 def __init__(self, repo, text="", user=None, date=None, extra=None,
1314 1329 changes=None):
1315 1330 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1316 1331
1317 1332 def __iter__(self):
1318 1333 d = self._repo.dirstate
1319 1334 for f in d:
1320 1335 if d[f] != 'r':
1321 1336 yield f
1322 1337
1323 1338 def __contains__(self, key):
1324 1339 return self._repo.dirstate[key] not in "?r"
1325 1340
1326 1341 def hex(self):
1327 1342 return wdirhex
1328 1343
1329 1344 @propertycache
1330 1345 def _parents(self):
1331 1346 p = self._repo.dirstate.parents()
1332 1347 if p[1] == nullid:
1333 1348 p = p[:-1]
1334 1349 # use unfiltered repo to delay/avoid loading obsmarkers
1335 1350 unfi = self._repo.unfiltered()
1336 1351 return [changectx(self._repo, unfi.changelog.rev(n), n) for n in p]
1337 1352
1338 1353 def _fileinfo(self, path):
1339 1354 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1340 1355 self._manifest
1341 1356 return super(workingctx, self)._fileinfo(path)
1342 1357
1343 1358 def filectx(self, path, filelog=None):
1344 1359 """get a file context from the working directory"""
1345 1360 return workingfilectx(self._repo, path, workingctx=self,
1346 1361 filelog=filelog)
1347 1362
1348 1363 def dirty(self, missing=False, merge=True, branch=True):
1349 1364 "check whether a working directory is modified"
1350 1365 # check subrepos first
1351 1366 for s in sorted(self.substate):
1352 1367 if self.sub(s).dirty(missing=missing):
1353 1368 return True
1354 1369 # check current working dir
1355 1370 return ((merge and self.p2()) or
1356 1371 (branch and self.branch() != self.p1().branch()) or
1357 1372 self.modified() or self.added() or self.removed() or
1358 1373 (missing and self.deleted()))
1359 1374
1360 1375 def add(self, list, prefix=""):
1361 1376 with self._repo.wlock():
1362 1377 ui, ds = self._repo.ui, self._repo.dirstate
1363 1378 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1364 1379 rejected = []
1365 1380 lstat = self._repo.wvfs.lstat
1366 1381 for f in list:
1367 1382 # ds.pathto() returns an absolute file when this is invoked from
1368 1383 # the keyword extension. That gets flagged as non-portable on
1369 1384 # Windows, since it contains the drive letter and colon.
1370 1385 scmutil.checkportable(ui, os.path.join(prefix, f))
1371 1386 try:
1372 1387 st = lstat(f)
1373 1388 except OSError:
1374 1389 ui.warn(_("%s does not exist!\n") % uipath(f))
1375 1390 rejected.append(f)
1376 1391 continue
1377 1392 limit = ui.configbytes('ui', 'large-file-limit')
1378 1393 if limit != 0 and st.st_size > limit:
1379 1394 ui.warn(_("%s: up to %d MB of RAM may be required "
1380 1395 "to manage this file\n"
1381 1396 "(use 'hg revert %s' to cancel the "
1382 1397 "pending addition)\n")
1383 1398 % (f, 3 * st.st_size // 1000000, uipath(f)))
1384 1399 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1385 1400 ui.warn(_("%s not added: only files and symlinks "
1386 1401 "supported currently\n") % uipath(f))
1387 1402 rejected.append(f)
1388 1403 elif ds[f] in 'amn':
1389 1404 ui.warn(_("%s already tracked!\n") % uipath(f))
1390 1405 elif ds[f] == 'r':
1391 1406 ds.normallookup(f)
1392 1407 else:
1393 1408 ds.add(f)
1394 1409 return rejected
1395 1410
1396 1411 def forget(self, files, prefix=""):
1397 1412 with self._repo.wlock():
1398 1413 ds = self._repo.dirstate
1399 1414 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1400 1415 rejected = []
1401 1416 for f in files:
1402 1417 if f not in ds:
1403 1418 self._repo.ui.warn(_("%s not tracked!\n") % uipath(f))
1404 1419 rejected.append(f)
1405 1420 elif ds[f] != 'a':
1406 1421 ds.remove(f)
1407 1422 else:
1408 1423 ds.drop(f)
1409 1424 return rejected
1410 1425
1411 1426 def copy(self, source, dest):
1412 1427 try:
1413 1428 st = self._repo.wvfs.lstat(dest)
1414 1429 except OSError as err:
1415 1430 if err.errno != errno.ENOENT:
1416 1431 raise
1417 1432 self._repo.ui.warn(_("%s does not exist!\n")
1418 1433 % self._repo.dirstate.pathto(dest))
1419 1434 return
1420 1435 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1421 1436 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1422 1437 "symbolic link\n")
1423 1438 % self._repo.dirstate.pathto(dest))
1424 1439 else:
1425 1440 with self._repo.wlock():
1426 1441 ds = self._repo.dirstate
1427 1442 if ds[dest] in '?':
1428 1443 ds.add(dest)
1429 1444 elif ds[dest] in 'r':
1430 1445 ds.normallookup(dest)
1431 1446 ds.copy(source, dest)
1432 1447
1433 1448 def match(self, pats=None, include=None, exclude=None, default='glob',
1434 1449 listsubrepos=False, badfn=None):
1435 1450 r = self._repo
1436 1451
1437 1452 # Only a case insensitive filesystem needs magic to translate user input
1438 1453 # to actual case in the filesystem.
1439 1454 icasefs = not util.fscasesensitive(r.root)
1440 1455 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1441 1456 default, auditor=r.auditor, ctx=self,
1442 1457 listsubrepos=listsubrepos, badfn=badfn,
1443 1458 icasefs=icasefs)
1444 1459
1445 1460 def _filtersuspectsymlink(self, files):
1446 1461 if not files or self._repo.dirstate._checklink:
1447 1462 return files
1448 1463
1449 1464 # Symlink placeholders may get non-symlink-like contents
1450 1465 # via user error or dereferencing by NFS or Samba servers,
1451 1466 # so we filter out any placeholders that don't look like a
1452 1467 # symlink
1453 1468 sane = []
1454 1469 for f in files:
1455 1470 if self.flags(f) == 'l':
1456 1471 d = self[f].data()
1457 1472 if (d == '' or len(d) >= 1024 or '\n' in d
1458 1473 or stringutil.binary(d)):
1459 1474 self._repo.ui.debug('ignoring suspect symlink placeholder'
1460 1475 ' "%s"\n' % f)
1461 1476 continue
1462 1477 sane.append(f)
1463 1478 return sane
1464 1479
1465 1480 def _checklookup(self, files):
1466 1481 # check for any possibly clean files
1467 1482 if not files:
1468 1483 return [], [], []
1469 1484
1470 1485 modified = []
1471 1486 deleted = []
1472 1487 fixup = []
1473 1488 pctx = self._parents[0]
1474 1489 # do a full compare of any files that might have changed
1475 1490 for f in sorted(files):
1476 1491 try:
1477 1492 # This will return True for a file that got replaced by a
1478 1493 # directory in the interim, but fixing that is pretty hard.
1479 1494 if (f not in pctx or self.flags(f) != pctx.flags(f)
1480 1495 or pctx[f].cmp(self[f])):
1481 1496 modified.append(f)
1482 1497 else:
1483 1498 fixup.append(f)
1484 1499 except (IOError, OSError):
1485 1500 # A file become inaccessible in between? Mark it as deleted,
1486 1501 # matching dirstate behavior (issue5584).
1487 1502 # The dirstate has more complex behavior around whether a
1488 1503 # missing file matches a directory, etc, but we don't need to
1489 1504 # bother with that: if f has made it to this point, we're sure
1490 1505 # it's in the dirstate.
1491 1506 deleted.append(f)
1492 1507
1493 1508 return modified, deleted, fixup
1494 1509
1495 1510 def _poststatusfixup(self, status, fixup):
1496 1511 """update dirstate for files that are actually clean"""
1497 1512 poststatus = self._repo.postdsstatus()
1498 1513 if fixup or poststatus:
1499 1514 try:
1500 1515 oldid = self._repo.dirstate.identity()
1501 1516
1502 1517 # updating the dirstate is optional
1503 1518 # so we don't wait on the lock
1504 1519 # wlock can invalidate the dirstate, so cache normal _after_
1505 1520 # taking the lock
1506 1521 with self._repo.wlock(False):
1507 1522 if self._repo.dirstate.identity() == oldid:
1508 1523 if fixup:
1509 1524 normal = self._repo.dirstate.normal
1510 1525 for f in fixup:
1511 1526 normal(f)
1512 1527 # write changes out explicitly, because nesting
1513 1528 # wlock at runtime may prevent 'wlock.release()'
1514 1529 # after this block from doing so for subsequent
1515 1530 # changing files
1516 1531 tr = self._repo.currenttransaction()
1517 1532 self._repo.dirstate.write(tr)
1518 1533
1519 1534 if poststatus:
1520 1535 for ps in poststatus:
1521 1536 ps(self, status)
1522 1537 else:
1523 1538 # in this case, writing changes out breaks
1524 1539 # consistency, because .hg/dirstate was
1525 1540 # already changed simultaneously after last
1526 1541 # caching (see also issue5584 for detail)
1527 1542 self._repo.ui.debug('skip updating dirstate: '
1528 1543 'identity mismatch\n')
1529 1544 except error.LockError:
1530 1545 pass
1531 1546 finally:
1532 1547 # Even if the wlock couldn't be grabbed, clear out the list.
1533 1548 self._repo.clearpostdsstatus()
1534 1549
1535 1550 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1536 1551 '''Gets the status from the dirstate -- internal use only.'''
1537 1552 subrepos = []
1538 1553 if '.hgsub' in self:
1539 1554 subrepos = sorted(self.substate)
1540 1555 cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
1541 1556 clean=clean, unknown=unknown)
1542 1557
1543 1558 # check for any possibly clean files
1544 1559 fixup = []
1545 1560 if cmp:
1546 1561 modified2, deleted2, fixup = self._checklookup(cmp)
1547 1562 s.modified.extend(modified2)
1548 1563 s.deleted.extend(deleted2)
1549 1564
1550 1565 if fixup and clean:
1551 1566 s.clean.extend(fixup)
1552 1567
1553 1568 self._poststatusfixup(s, fixup)
1554 1569
1555 1570 if match.always():
1556 1571 # cache for performance
1557 1572 if s.unknown or s.ignored or s.clean:
1558 1573 # "_status" is cached with list*=False in the normal route
1559 1574 self._status = scmutil.status(s.modified, s.added, s.removed,
1560 1575 s.deleted, [], [], [])
1561 1576 else:
1562 1577 self._status = s
1563 1578
1564 1579 return s
1565 1580
1566 1581 @propertycache
1567 1582 def _manifest(self):
1568 1583 """generate a manifest corresponding to the values in self._status
1569 1584
1570 1585 This reuse the file nodeid from parent, but we use special node
1571 1586 identifiers for added and modified files. This is used by manifests
1572 1587 merge to see that files are different and by update logic to avoid
1573 1588 deleting newly added files.
1574 1589 """
1575 1590 return self._buildstatusmanifest(self._status)
1576 1591
1577 1592 def _buildstatusmanifest(self, status):
1578 1593 """Builds a manifest that includes the given status results."""
1579 1594 parents = self.parents()
1580 1595
1581 1596 man = parents[0].manifest().copy()
1582 1597
1583 1598 ff = self._flagfunc
1584 1599 for i, l in ((addednodeid, status.added),
1585 1600 (modifiednodeid, status.modified)):
1586 1601 for f in l:
1587 1602 man[f] = i
1588 1603 try:
1589 1604 man.setflag(f, ff(f))
1590 1605 except OSError:
1591 1606 pass
1592 1607
1593 1608 for f in status.deleted + status.removed:
1594 1609 if f in man:
1595 1610 del man[f]
1596 1611
1597 1612 return man
1598 1613
1599 1614 def _buildstatus(self, other, s, match, listignored, listclean,
1600 1615 listunknown):
1601 1616 """build a status with respect to another context
1602 1617
1603 1618 This includes logic for maintaining the fast path of status when
1604 1619 comparing the working directory against its parent, which is to skip
1605 1620 building a new manifest if self (working directory) is not comparing
1606 1621 against its parent (repo['.']).
1607 1622 """
1608 1623 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1609 1624 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1610 1625 # might have accidentally ended up with the entire contents of the file
1611 1626 # they are supposed to be linking to.
1612 1627 s.modified[:] = self._filtersuspectsymlink(s.modified)
1613 1628 if other != self._repo['.']:
1614 1629 s = super(workingctx, self)._buildstatus(other, s, match,
1615 1630 listignored, listclean,
1616 1631 listunknown)
1617 1632 return s
1618 1633
1619 1634 def _matchstatus(self, other, match):
1620 1635 """override the match method with a filter for directory patterns
1621 1636
1622 1637 We use inheritance to customize the match.bad method only in cases of
1623 1638 workingctx since it belongs only to the working directory when
1624 1639 comparing against the parent changeset.
1625 1640
1626 1641 If we aren't comparing against the working directory's parent, then we
1627 1642 just use the default match object sent to us.
1628 1643 """
1629 1644 if other != self._repo['.']:
1630 1645 def bad(f, msg):
1631 1646 # 'f' may be a directory pattern from 'match.files()',
1632 1647 # so 'f not in ctx1' is not enough
1633 1648 if f not in other and not other.hasdir(f):
1634 1649 self._repo.ui.warn('%s: %s\n' %
1635 1650 (self._repo.dirstate.pathto(f), msg))
1636 1651 match.bad = bad
1637 1652 return match
1638 1653
1639 1654 def markcommitted(self, node):
1640 1655 super(workingctx, self).markcommitted(node)
1641 1656
1642 1657 sparse.aftercommit(self._repo, node)
1643 1658
1644 1659 class committablefilectx(basefilectx):
1645 1660 """A committablefilectx provides common functionality for a file context
1646 1661 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1647 1662 def __init__(self, repo, path, filelog=None, ctx=None):
1648 1663 self._repo = repo
1649 1664 self._path = path
1650 1665 self._changeid = None
1651 1666 self._filerev = self._filenode = None
1652 1667
1653 1668 if filelog is not None:
1654 1669 self._filelog = filelog
1655 1670 if ctx:
1656 1671 self._changectx = ctx
1657 1672
1658 1673 def __nonzero__(self):
1659 1674 return True
1660 1675
1661 1676 __bool__ = __nonzero__
1662 1677
1663 1678 def linkrev(self):
1664 1679 # linked to self._changectx no matter if file is modified or not
1665 1680 return self.rev()
1666 1681
1667 1682 def renamed(self):
1668 1683 path = self.copysource()
1669 1684 if not path:
1670 1685 return None
1671 1686 return path, self._changectx._parents[0]._manifest.get(path, nullid)
1672 1687
1673 1688 def parents(self):
1674 1689 '''return parent filectxs, following copies if necessary'''
1675 1690 def filenode(ctx, path):
1676 1691 return ctx._manifest.get(path, nullid)
1677 1692
1678 1693 path = self._path
1679 1694 fl = self._filelog
1680 1695 pcl = self._changectx._parents
1681 1696 renamed = self.renamed()
1682 1697
1683 1698 if renamed:
1684 1699 pl = [renamed + (None,)]
1685 1700 else:
1686 1701 pl = [(path, filenode(pcl[0], path), fl)]
1687 1702
1688 1703 for pc in pcl[1:]:
1689 1704 pl.append((path, filenode(pc, path), fl))
1690 1705
1691 1706 return [self._parentfilectx(p, fileid=n, filelog=l)
1692 1707 for p, n, l in pl if n != nullid]
1693 1708
1694 1709 def children(self):
1695 1710 return []
1696 1711
1697 1712 class workingfilectx(committablefilectx):
1698 1713 """A workingfilectx object makes access to data related to a particular
1699 1714 file in the working directory convenient."""
1700 1715 def __init__(self, repo, path, filelog=None, workingctx=None):
1701 1716 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1702 1717
1703 1718 @propertycache
1704 1719 def _changectx(self):
1705 1720 return workingctx(self._repo)
1706 1721
1707 1722 def data(self):
1708 1723 return self._repo.wread(self._path)
1709 1724 def copysource(self):
1710 1725 return self._repo.dirstate.copied(self._path)
1711 1726
1712 1727 def size(self):
1713 1728 return self._repo.wvfs.lstat(self._path).st_size
1714 1729 def date(self):
1715 1730 t, tz = self._changectx.date()
1716 1731 try:
1717 1732 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1718 1733 except OSError as err:
1719 1734 if err.errno != errno.ENOENT:
1720 1735 raise
1721 1736 return (t, tz)
1722 1737
1723 1738 def exists(self):
1724 1739 return self._repo.wvfs.exists(self._path)
1725 1740
1726 1741 def lexists(self):
1727 1742 return self._repo.wvfs.lexists(self._path)
1728 1743
1729 1744 def audit(self):
1730 1745 return self._repo.wvfs.audit(self._path)
1731 1746
1732 1747 def cmp(self, fctx):
1733 1748 """compare with other file context
1734 1749
1735 1750 returns True if different than fctx.
1736 1751 """
1737 1752 # fctx should be a filectx (not a workingfilectx)
1738 1753 # invert comparison to reuse the same code path
1739 1754 return fctx.cmp(self)
1740 1755
1741 1756 def remove(self, ignoremissing=False):
1742 1757 """wraps unlink for a repo's working directory"""
1743 1758 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1744 1759 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1745 1760 rmdir=rmdir)
1746 1761
1747 1762 def write(self, data, flags, backgroundclose=False, **kwargs):
1748 1763 """wraps repo.wwrite"""
1749 1764 self._repo.wwrite(self._path, data, flags,
1750 1765 backgroundclose=backgroundclose,
1751 1766 **kwargs)
1752 1767
1753 1768 def markcopied(self, src):
1754 1769 """marks this file a copy of `src`"""
1755 1770 if self._repo.dirstate[self._path] in "nma":
1756 1771 self._repo.dirstate.copy(src, self._path)
1757 1772
1758 1773 def clearunknown(self):
1759 1774 """Removes conflicting items in the working directory so that
1760 1775 ``write()`` can be called successfully.
1761 1776 """
1762 1777 wvfs = self._repo.wvfs
1763 1778 f = self._path
1764 1779 wvfs.audit(f)
1765 1780 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1766 1781 # remove files under the directory as they should already be
1767 1782 # warned and backed up
1768 1783 if wvfs.isdir(f) and not wvfs.islink(f):
1769 1784 wvfs.rmtree(f, forcibly=True)
1770 1785 for p in reversed(list(util.finddirs(f))):
1771 1786 if wvfs.isfileorlink(p):
1772 1787 wvfs.unlink(p)
1773 1788 break
1774 1789 else:
1775 1790 # don't remove files if path conflicts are not processed
1776 1791 if wvfs.isdir(f) and not wvfs.islink(f):
1777 1792 wvfs.removedirs(f)
1778 1793
1779 1794 def setflags(self, l, x):
1780 1795 self._repo.wvfs.setflags(self._path, l, x)
1781 1796
1782 1797 class overlayworkingctx(committablectx):
1783 1798 """Wraps another mutable context with a write-back cache that can be
1784 1799 converted into a commit context.
1785 1800
1786 1801 self._cache[path] maps to a dict with keys: {
1787 1802 'exists': bool?
1788 1803 'date': date?
1789 1804 'data': str?
1790 1805 'flags': str?
1791 1806 'copied': str? (path or None)
1792 1807 }
1793 1808 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
1794 1809 is `False`, the file was deleted.
1795 1810 """
1796 1811
1797 1812 def __init__(self, repo):
1798 1813 super(overlayworkingctx, self).__init__(repo)
1799 1814 self.clean()
1800 1815
1801 1816 def setbase(self, wrappedctx):
1802 1817 self._wrappedctx = wrappedctx
1803 1818 self._parents = [wrappedctx]
1804 1819 # Drop old manifest cache as it is now out of date.
1805 1820 # This is necessary when, e.g., rebasing several nodes with one
1806 1821 # ``overlayworkingctx`` (e.g. with --collapse).
1807 1822 util.clearcachedproperty(self, '_manifest')
1808 1823
1809 1824 def data(self, path):
1810 1825 if self.isdirty(path):
1811 1826 if self._cache[path]['exists']:
1812 1827 if self._cache[path]['data']:
1813 1828 return self._cache[path]['data']
1814 1829 else:
1815 1830 # Must fallback here, too, because we only set flags.
1816 1831 return self._wrappedctx[path].data()
1817 1832 else:
1818 1833 raise error.ProgrammingError("No such file or directory: %s" %
1819 1834 path)
1820 1835 else:
1821 1836 return self._wrappedctx[path].data()
1822 1837
1823 1838 @propertycache
1824 1839 def _manifest(self):
1825 1840 parents = self.parents()
1826 1841 man = parents[0].manifest().copy()
1827 1842
1828 1843 flag = self._flagfunc
1829 1844 for path in self.added():
1830 1845 man[path] = addednodeid
1831 1846 man.setflag(path, flag(path))
1832 1847 for path in self.modified():
1833 1848 man[path] = modifiednodeid
1834 1849 man.setflag(path, flag(path))
1835 1850 for path in self.removed():
1836 1851 del man[path]
1837 1852 return man
1838 1853
1839 1854 @propertycache
1840 1855 def _flagfunc(self):
1841 1856 def f(path):
1842 1857 return self._cache[path]['flags']
1843 1858 return f
1844 1859
1845 1860 def files(self):
1846 1861 return sorted(self.added() + self.modified() + self.removed())
1847 1862
1848 1863 def modified(self):
1849 1864 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1850 1865 self._existsinparent(f)]
1851 1866
1852 1867 def added(self):
1853 1868 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1854 1869 not self._existsinparent(f)]
1855 1870
1856 1871 def removed(self):
1857 1872 return [f for f in self._cache.keys() if
1858 1873 not self._cache[f]['exists'] and self._existsinparent(f)]
1859 1874
1860 1875 def p1copies(self):
1861 1876 copies = self._repo._wrappedctx.p1copies().copy()
1862 1877 narrowmatch = self._repo.narrowmatch()
1863 1878 for f in self._cache.keys():
1864 1879 if not narrowmatch(f):
1865 1880 continue
1866 1881 copies.pop(f, None) # delete if it exists
1867 1882 source = self._cache[f]['copied']
1868 1883 if source:
1869 1884 copies[f] = source
1870 1885 return copies
1871 1886
1872 1887 def p2copies(self):
1873 1888 copies = self._repo._wrappedctx.p2copies().copy()
1874 1889 narrowmatch = self._repo.narrowmatch()
1875 1890 for f in self._cache.keys():
1876 1891 if not narrowmatch(f):
1877 1892 continue
1878 1893 copies.pop(f, None) # delete if it exists
1879 1894 source = self._cache[f]['copied']
1880 1895 if source:
1881 1896 copies[f] = source
1882 1897 return copies
1883 1898
1884 1899 def isinmemory(self):
1885 1900 return True
1886 1901
1887 1902 def filedate(self, path):
1888 1903 if self.isdirty(path):
1889 1904 return self._cache[path]['date']
1890 1905 else:
1891 1906 return self._wrappedctx[path].date()
1892 1907
1893 1908 def markcopied(self, path, origin):
1894 1909 self._markdirty(path, exists=True, date=self.filedate(path),
1895 1910 flags=self.flags(path), copied=origin)
1896 1911
1897 1912 def copydata(self, path):
1898 1913 if self.isdirty(path):
1899 1914 return self._cache[path]['copied']
1900 1915 else:
1901 1916 raise error.ProgrammingError('copydata() called on clean context')
1902 1917
1903 1918 def flags(self, path):
1904 1919 if self.isdirty(path):
1905 1920 if self._cache[path]['exists']:
1906 1921 return self._cache[path]['flags']
1907 1922 else:
1908 1923 raise error.ProgrammingError("No such file or directory: %s" %
1909 1924 self._path)
1910 1925 else:
1911 1926 return self._wrappedctx[path].flags()
1912 1927
1913 1928 def __contains__(self, key):
1914 1929 if key in self._cache:
1915 1930 return self._cache[key]['exists']
1916 1931 return key in self.p1()
1917 1932
1918 1933 def _existsinparent(self, path):
1919 1934 try:
1920 1935 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1921 1936 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1922 1937 # with an ``exists()`` function.
1923 1938 self._wrappedctx[path]
1924 1939 return True
1925 1940 except error.ManifestLookupError:
1926 1941 return False
1927 1942
1928 1943 def _auditconflicts(self, path):
1929 1944 """Replicates conflict checks done by wvfs.write().
1930 1945
1931 1946 Since we never write to the filesystem and never call `applyupdates` in
1932 1947 IMM, we'll never check that a path is actually writable -- e.g., because
1933 1948 it adds `a/foo`, but `a` is actually a file in the other commit.
1934 1949 """
1935 1950 def fail(path, component):
1936 1951 # p1() is the base and we're receiving "writes" for p2()'s
1937 1952 # files.
1938 1953 if 'l' in self.p1()[component].flags():
1939 1954 raise error.Abort("error: %s conflicts with symlink %s "
1940 1955 "in %d." % (path, component,
1941 1956 self.p1().rev()))
1942 1957 else:
1943 1958 raise error.Abort("error: '%s' conflicts with file '%s' in "
1944 1959 "%d." % (path, component,
1945 1960 self.p1().rev()))
1946 1961
1947 1962 # Test that each new directory to be created to write this path from p2
1948 1963 # is not a file in p1.
1949 1964 components = path.split('/')
1950 1965 for i in pycompat.xrange(len(components)):
1951 1966 component = "/".join(components[0:i])
1952 1967 if component in self:
1953 1968 fail(path, component)
1954 1969
1955 1970 # Test the other direction -- that this path from p2 isn't a directory
1956 1971 # in p1 (test that p1 doesn't have any paths matching `path/*`).
1957 1972 match = self.match([path], default=b'path')
1958 1973 matches = self.p1().manifest().matches(match)
1959 1974 mfiles = matches.keys()
1960 1975 if len(mfiles) > 0:
1961 1976 if len(mfiles) == 1 and mfiles[0] == path:
1962 1977 return
1963 1978 # omit the files which are deleted in current IMM wctx
1964 1979 mfiles = [m for m in mfiles if m in self]
1965 1980 if not mfiles:
1966 1981 return
1967 1982 raise error.Abort("error: file '%s' cannot be written because "
1968 1983 " '%s/' is a directory in %s (containing %d "
1969 1984 "entries: %s)"
1970 1985 % (path, path, self.p1(), len(mfiles),
1971 1986 ', '.join(mfiles)))
1972 1987
1973 1988 def write(self, path, data, flags='', **kwargs):
1974 1989 if data is None:
1975 1990 raise error.ProgrammingError("data must be non-None")
1976 1991 self._auditconflicts(path)
1977 1992 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1978 1993 flags=flags)
1979 1994
1980 1995 def setflags(self, path, l, x):
1981 1996 flag = ''
1982 1997 if l:
1983 1998 flag = 'l'
1984 1999 elif x:
1985 2000 flag = 'x'
1986 2001 self._markdirty(path, exists=True, date=dateutil.makedate(),
1987 2002 flags=flag)
1988 2003
1989 2004 def remove(self, path):
1990 2005 self._markdirty(path, exists=False)
1991 2006
1992 2007 def exists(self, path):
1993 2008 """exists behaves like `lexists`, but needs to follow symlinks and
1994 2009 return False if they are broken.
1995 2010 """
1996 2011 if self.isdirty(path):
1997 2012 # If this path exists and is a symlink, "follow" it by calling
1998 2013 # exists on the destination path.
1999 2014 if (self._cache[path]['exists'] and
2000 2015 'l' in self._cache[path]['flags']):
2001 2016 return self.exists(self._cache[path]['data'].strip())
2002 2017 else:
2003 2018 return self._cache[path]['exists']
2004 2019
2005 2020 return self._existsinparent(path)
2006 2021
2007 2022 def lexists(self, path):
2008 2023 """lexists returns True if the path exists"""
2009 2024 if self.isdirty(path):
2010 2025 return self._cache[path]['exists']
2011 2026
2012 2027 return self._existsinparent(path)
2013 2028
2014 2029 def size(self, path):
2015 2030 if self.isdirty(path):
2016 2031 if self._cache[path]['exists']:
2017 2032 return len(self._cache[path]['data'])
2018 2033 else:
2019 2034 raise error.ProgrammingError("No such file or directory: %s" %
2020 2035 self._path)
2021 2036 return self._wrappedctx[path].size()
2022 2037
2023 2038 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
2024 2039 user=None, editor=None):
2025 2040 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2026 2041 committed.
2027 2042
2028 2043 ``text`` is the commit message.
2029 2044 ``parents`` (optional) are rev numbers.
2030 2045 """
2031 2046 # Default parents to the wrapped contexts' if not passed.
2032 2047 if parents is None:
2033 2048 parents = self._wrappedctx.parents()
2034 2049 if len(parents) == 1:
2035 2050 parents = (parents[0], None)
2036 2051
2037 2052 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2038 2053 if parents[1] is None:
2039 2054 parents = (self._repo[parents[0]], None)
2040 2055 else:
2041 2056 parents = (self._repo[parents[0]], self._repo[parents[1]])
2042 2057
2043 2058 files = self._cache.keys()
2044 2059 def getfile(repo, memctx, path):
2045 2060 if self._cache[path]['exists']:
2046 2061 return memfilectx(repo, memctx, path,
2047 2062 self._cache[path]['data'],
2048 2063 'l' in self._cache[path]['flags'],
2049 2064 'x' in self._cache[path]['flags'],
2050 2065 self._cache[path]['copied'])
2051 2066 else:
2052 2067 # Returning None, but including the path in `files`, is
2053 2068 # necessary for memctx to register a deletion.
2054 2069 return None
2055 2070 return memctx(self._repo, parents, text, files, getfile, date=date,
2056 2071 extra=extra, user=user, branch=branch, editor=editor)
2057 2072
2058 2073 def isdirty(self, path):
2059 2074 return path in self._cache
2060 2075
2061 2076 def isempty(self):
2062 2077 # We need to discard any keys that are actually clean before the empty
2063 2078 # commit check.
2064 2079 self._compact()
2065 2080 return len(self._cache) == 0
2066 2081
2067 2082 def clean(self):
2068 2083 self._cache = {}
2069 2084
2070 2085 def _compact(self):
2071 2086 """Removes keys from the cache that are actually clean, by comparing
2072 2087 them with the underlying context.
2073 2088
2074 2089 This can occur during the merge process, e.g. by passing --tool :local
2075 2090 to resolve a conflict.
2076 2091 """
2077 2092 keys = []
2078 2093 # This won't be perfect, but can help performance significantly when
2079 2094 # using things like remotefilelog.
2080 2095 scmutil.prefetchfiles(
2081 2096 self.repo(), [self.p1().rev()],
2082 2097 scmutil.matchfiles(self.repo(), self._cache.keys()))
2083 2098
2084 2099 for path in self._cache.keys():
2085 2100 cache = self._cache[path]
2086 2101 try:
2087 2102 underlying = self._wrappedctx[path]
2088 2103 if (underlying.data() == cache['data'] and
2089 2104 underlying.flags() == cache['flags']):
2090 2105 keys.append(path)
2091 2106 except error.ManifestLookupError:
2092 2107 # Path not in the underlying manifest (created).
2093 2108 continue
2094 2109
2095 2110 for path in keys:
2096 2111 del self._cache[path]
2097 2112 return keys
2098 2113
2099 2114 def _markdirty(self, path, exists, data=None, date=None, flags='',
2100 2115 copied=None):
2101 2116 # data not provided, let's see if we already have some; if not, let's
2102 2117 # grab it from our underlying context, so that we always have data if
2103 2118 # the file is marked as existing.
2104 2119 if exists and data is None:
2105 2120 oldentry = self._cache.get(path) or {}
2106 2121 data = oldentry.get('data') or self._wrappedctx[path].data()
2107 2122
2108 2123 self._cache[path] = {
2109 2124 'exists': exists,
2110 2125 'data': data,
2111 2126 'date': date,
2112 2127 'flags': flags,
2113 2128 'copied': copied,
2114 2129 }
2115 2130
2116 2131 def filectx(self, path, filelog=None):
2117 2132 return overlayworkingfilectx(self._repo, path, parent=self,
2118 2133 filelog=filelog)
2119 2134
2120 2135 class overlayworkingfilectx(committablefilectx):
2121 2136 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2122 2137 cache, which can be flushed through later by calling ``flush()``."""
2123 2138
2124 2139 def __init__(self, repo, path, filelog=None, parent=None):
2125 2140 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2126 2141 parent)
2127 2142 self._repo = repo
2128 2143 self._parent = parent
2129 2144 self._path = path
2130 2145
2131 2146 def cmp(self, fctx):
2132 2147 return self.data() != fctx.data()
2133 2148
2134 2149 def changectx(self):
2135 2150 return self._parent
2136 2151
2137 2152 def data(self):
2138 2153 return self._parent.data(self._path)
2139 2154
2140 2155 def date(self):
2141 2156 return self._parent.filedate(self._path)
2142 2157
2143 2158 def exists(self):
2144 2159 return self.lexists()
2145 2160
2146 2161 def lexists(self):
2147 2162 return self._parent.exists(self._path)
2148 2163
2149 2164 def copysource(self):
2150 2165 return self._parent.copydata(self._path)
2151 2166
2152 2167 def size(self):
2153 2168 return self._parent.size(self._path)
2154 2169
2155 2170 def markcopied(self, origin):
2156 2171 self._parent.markcopied(self._path, origin)
2157 2172
2158 2173 def audit(self):
2159 2174 pass
2160 2175
2161 2176 def flags(self):
2162 2177 return self._parent.flags(self._path)
2163 2178
2164 2179 def setflags(self, islink, isexec):
2165 2180 return self._parent.setflags(self._path, islink, isexec)
2166 2181
2167 2182 def write(self, data, flags, backgroundclose=False, **kwargs):
2168 2183 return self._parent.write(self._path, data, flags, **kwargs)
2169 2184
2170 2185 def remove(self, ignoremissing=False):
2171 2186 return self._parent.remove(self._path)
2172 2187
2173 2188 def clearunknown(self):
2174 2189 pass
2175 2190
2176 2191 class workingcommitctx(workingctx):
2177 2192 """A workingcommitctx object makes access to data related to
2178 2193 the revision being committed convenient.
2179 2194
2180 2195 This hides changes in the working directory, if they aren't
2181 2196 committed in this context.
2182 2197 """
2183 2198 def __init__(self, repo, changes,
2184 2199 text="", user=None, date=None, extra=None):
2185 2200 super(workingcommitctx, self).__init__(repo, text, user, date, extra,
2186 2201 changes)
2187 2202
2188 2203 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2189 2204 """Return matched files only in ``self._status``
2190 2205
2191 2206 Uncommitted files appear "clean" via this context, even if
2192 2207 they aren't actually so in the working directory.
2193 2208 """
2194 2209 if clean:
2195 2210 clean = [f for f in self._manifest if f not in self._changedset]
2196 2211 else:
2197 2212 clean = []
2198 2213 return scmutil.status([f for f in self._status.modified if match(f)],
2199 2214 [f for f in self._status.added if match(f)],
2200 2215 [f for f in self._status.removed if match(f)],
2201 2216 [], [], [], clean)
2202 2217
2203 2218 @propertycache
2204 2219 def _changedset(self):
2205 2220 """Return the set of files changed in this context
2206 2221 """
2207 2222 changed = set(self._status.modified)
2208 2223 changed.update(self._status.added)
2209 2224 changed.update(self._status.removed)
2210 2225 return changed
2211 2226
2212 2227 def makecachingfilectxfn(func):
2213 2228 """Create a filectxfn that caches based on the path.
2214 2229
2215 2230 We can't use util.cachefunc because it uses all arguments as the cache
2216 2231 key and this creates a cycle since the arguments include the repo and
2217 2232 memctx.
2218 2233 """
2219 2234 cache = {}
2220 2235
2221 2236 def getfilectx(repo, memctx, path):
2222 2237 if path not in cache:
2223 2238 cache[path] = func(repo, memctx, path)
2224 2239 return cache[path]
2225 2240
2226 2241 return getfilectx
2227 2242
2228 2243 def memfilefromctx(ctx):
2229 2244 """Given a context return a memfilectx for ctx[path]
2230 2245
2231 2246 This is a convenience method for building a memctx based on another
2232 2247 context.
2233 2248 """
2234 2249 def getfilectx(repo, memctx, path):
2235 2250 fctx = ctx[path]
2236 2251 copysource = fctx.copysource()
2237 2252 return memfilectx(repo, memctx, path, fctx.data(),
2238 2253 islink=fctx.islink(), isexec=fctx.isexec(),
2239 2254 copysource=copysource)
2240 2255
2241 2256 return getfilectx
2242 2257
2243 2258 def memfilefrompatch(patchstore):
2244 2259 """Given a patch (e.g. patchstore object) return a memfilectx
2245 2260
2246 2261 This is a convenience method for building a memctx based on a patchstore.
2247 2262 """
2248 2263 def getfilectx(repo, memctx, path):
2249 2264 data, mode, copysource = patchstore.getfile(path)
2250 2265 if data is None:
2251 2266 return None
2252 2267 islink, isexec = mode
2253 2268 return memfilectx(repo, memctx, path, data, islink=islink,
2254 2269 isexec=isexec, copysource=copysource)
2255 2270
2256 2271 return getfilectx
2257 2272
2258 2273 class memctx(committablectx):
2259 2274 """Use memctx to perform in-memory commits via localrepo.commitctx().
2260 2275
2261 2276 Revision information is supplied at initialization time while
2262 2277 related files data and is made available through a callback
2263 2278 mechanism. 'repo' is the current localrepo, 'parents' is a
2264 2279 sequence of two parent revisions identifiers (pass None for every
2265 2280 missing parent), 'text' is the commit message and 'files' lists
2266 2281 names of files touched by the revision (normalized and relative to
2267 2282 repository root).
2268 2283
2269 2284 filectxfn(repo, memctx, path) is a callable receiving the
2270 2285 repository, the current memctx object and the normalized path of
2271 2286 requested file, relative to repository root. It is fired by the
2272 2287 commit function for every file in 'files', but calls order is
2273 2288 undefined. If the file is available in the revision being
2274 2289 committed (updated or added), filectxfn returns a memfilectx
2275 2290 object. If the file was removed, filectxfn return None for recent
2276 2291 Mercurial. Moved files are represented by marking the source file
2277 2292 removed and the new file added with copy information (see
2278 2293 memfilectx).
2279 2294
2280 2295 user receives the committer name and defaults to current
2281 2296 repository username, date is the commit date in any format
2282 2297 supported by dateutil.parsedate() and defaults to current date, extra
2283 2298 is a dictionary of metadata or is left empty.
2284 2299 """
2285 2300
2286 2301 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2287 2302 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2288 2303 # this field to determine what to do in filectxfn.
2289 2304 _returnnoneformissingfiles = True
2290 2305
2291 2306 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2292 2307 date=None, extra=None, branch=None, editor=False):
2293 2308 super(memctx, self).__init__(repo, text, user, date, extra)
2294 2309 self._rev = None
2295 2310 self._node = None
2296 2311 parents = [(p or nullid) for p in parents]
2297 2312 p1, p2 = parents
2298 2313 self._parents = [self._repo[p] for p in (p1, p2)]
2299 2314 files = sorted(set(files))
2300 2315 self._files = files
2301 2316 if branch is not None:
2302 2317 self._extra['branch'] = encoding.fromlocal(branch)
2303 2318 self.substate = {}
2304 2319
2305 2320 if isinstance(filectxfn, patch.filestore):
2306 2321 filectxfn = memfilefrompatch(filectxfn)
2307 2322 elif not callable(filectxfn):
2308 2323 # if store is not callable, wrap it in a function
2309 2324 filectxfn = memfilefromctx(filectxfn)
2310 2325
2311 2326 # memoizing increases performance for e.g. vcs convert scenarios.
2312 2327 self._filectxfn = makecachingfilectxfn(filectxfn)
2313 2328
2314 2329 if editor:
2315 2330 self._text = editor(self._repo, self, [])
2316 2331 self._repo.savecommitmessage(self._text)
2317 2332
2318 2333 def filectx(self, path, filelog=None):
2319 2334 """get a file context from the working directory
2320 2335
2321 2336 Returns None if file doesn't exist and should be removed."""
2322 2337 return self._filectxfn(self._repo, self, path)
2323 2338
2324 2339 def commit(self):
2325 2340 """commit context to the repo"""
2326 2341 return self._repo.commitctx(self)
2327 2342
2328 2343 @propertycache
2329 2344 def _manifest(self):
2330 2345 """generate a manifest based on the return values of filectxfn"""
2331 2346
2332 2347 # keep this simple for now; just worry about p1
2333 2348 pctx = self._parents[0]
2334 2349 man = pctx.manifest().copy()
2335 2350
2336 2351 for f in self._status.modified:
2337 2352 man[f] = modifiednodeid
2338 2353
2339 2354 for f in self._status.added:
2340 2355 man[f] = addednodeid
2341 2356
2342 2357 for f in self._status.removed:
2343 2358 if f in man:
2344 2359 del man[f]
2345 2360
2346 2361 return man
2347 2362
2348 2363 @propertycache
2349 2364 def _status(self):
2350 2365 """Calculate exact status from ``files`` specified at construction
2351 2366 """
2352 2367 man1 = self.p1().manifest()
2353 2368 p2 = self._parents[1]
2354 2369 # "1 < len(self._parents)" can't be used for checking
2355 2370 # existence of the 2nd parent, because "memctx._parents" is
2356 2371 # explicitly initialized by the list, of which length is 2.
2357 2372 if p2.node() != nullid:
2358 2373 man2 = p2.manifest()
2359 2374 managing = lambda f: f in man1 or f in man2
2360 2375 else:
2361 2376 managing = lambda f: f in man1
2362 2377
2363 2378 modified, added, removed = [], [], []
2364 2379 for f in self._files:
2365 2380 if not managing(f):
2366 2381 added.append(f)
2367 2382 elif self[f]:
2368 2383 modified.append(f)
2369 2384 else:
2370 2385 removed.append(f)
2371 2386
2372 2387 return scmutil.status(modified, added, removed, [], [], [], [])
2373 2388
2374 2389 class memfilectx(committablefilectx):
2375 2390 """memfilectx represents an in-memory file to commit.
2376 2391
2377 2392 See memctx and committablefilectx for more details.
2378 2393 """
2379 2394 def __init__(self, repo, changectx, path, data, islink=False,
2380 2395 isexec=False, copysource=None):
2381 2396 """
2382 2397 path is the normalized file path relative to repository root.
2383 2398 data is the file content as a string.
2384 2399 islink is True if the file is a symbolic link.
2385 2400 isexec is True if the file is executable.
2386 2401 copied is the source file path if current file was copied in the
2387 2402 revision being committed, or None."""
2388 2403 super(memfilectx, self).__init__(repo, path, None, changectx)
2389 2404 self._data = data
2390 2405 if islink:
2391 2406 self._flags = 'l'
2392 2407 elif isexec:
2393 2408 self._flags = 'x'
2394 2409 else:
2395 2410 self._flags = ''
2396 2411 self._copysource = copysource
2397 2412
2398 2413 def copysource(self):
2399 2414 return self._copysource
2400 2415
2401 2416 def cmp(self, fctx):
2402 2417 return self.data() != fctx.data()
2403 2418
2404 2419 def data(self):
2405 2420 return self._data
2406 2421
2407 2422 def remove(self, ignoremissing=False):
2408 2423 """wraps unlink for a repo's working directory"""
2409 2424 # need to figure out what to do here
2410 2425 del self._changectx[self._path]
2411 2426
2412 2427 def write(self, data, flags, **kwargs):
2413 2428 """wraps repo.wwrite"""
2414 2429 self._data = data
2415 2430
2416 2431
2417 2432 class metadataonlyctx(committablectx):
2418 2433 """Like memctx but it's reusing the manifest of different commit.
2419 2434 Intended to be used by lightweight operations that are creating
2420 2435 metadata-only changes.
2421 2436
2422 2437 Revision information is supplied at initialization time. 'repo' is the
2423 2438 current localrepo, 'ctx' is original revision which manifest we're reuisng
2424 2439 'parents' is a sequence of two parent revisions identifiers (pass None for
2425 2440 every missing parent), 'text' is the commit.
2426 2441
2427 2442 user receives the committer name and defaults to current repository
2428 2443 username, date is the commit date in any format supported by
2429 2444 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2430 2445 metadata or is left empty.
2431 2446 """
2432 2447 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2433 2448 date=None, extra=None, editor=False):
2434 2449 if text is None:
2435 2450 text = originalctx.description()
2436 2451 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2437 2452 self._rev = None
2438 2453 self._node = None
2439 2454 self._originalctx = originalctx
2440 2455 self._manifestnode = originalctx.manifestnode()
2441 2456 if parents is None:
2442 2457 parents = originalctx.parents()
2443 2458 else:
2444 2459 parents = [repo[p] for p in parents if p is not None]
2445 2460 parents = parents[:]
2446 2461 while len(parents) < 2:
2447 2462 parents.append(repo[nullid])
2448 2463 p1, p2 = self._parents = parents
2449 2464
2450 2465 # sanity check to ensure that the reused manifest parents are
2451 2466 # manifests of our commit parents
2452 2467 mp1, mp2 = self.manifestctx().parents
2453 2468 if p1 != nullid and p1.manifestnode() != mp1:
2454 2469 raise RuntimeError(r"can't reuse the manifest: its p1 "
2455 2470 r"doesn't match the new ctx p1")
2456 2471 if p2 != nullid and p2.manifestnode() != mp2:
2457 2472 raise RuntimeError(r"can't reuse the manifest: "
2458 2473 r"its p2 doesn't match the new ctx p2")
2459 2474
2460 2475 self._files = originalctx.files()
2461 2476 self.substate = {}
2462 2477
2463 2478 if editor:
2464 2479 self._text = editor(self._repo, self, [])
2465 2480 self._repo.savecommitmessage(self._text)
2466 2481
2467 2482 def manifestnode(self):
2468 2483 return self._manifestnode
2469 2484
2470 2485 @property
2471 2486 def _manifestctx(self):
2472 2487 return self._repo.manifestlog[self._manifestnode]
2473 2488
2474 2489 def filectx(self, path, filelog=None):
2475 2490 return self._originalctx.filectx(path, filelog=filelog)
2476 2491
2477 2492 def commit(self):
2478 2493 """commit context to the repo"""
2479 2494 return self._repo.commitctx(self)
2480 2495
2481 2496 @property
2482 2497 def _manifest(self):
2483 2498 return self._originalctx.manifest()
2484 2499
2485 2500 @propertycache
2486 2501 def _status(self):
2487 2502 """Calculate exact status from ``files`` specified in the ``origctx``
2488 2503 and parents manifests.
2489 2504 """
2490 2505 man1 = self.p1().manifest()
2491 2506 p2 = self._parents[1]
2492 2507 # "1 < len(self._parents)" can't be used for checking
2493 2508 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2494 2509 # explicitly initialized by the list, of which length is 2.
2495 2510 if p2.node() != nullid:
2496 2511 man2 = p2.manifest()
2497 2512 managing = lambda f: f in man1 or f in man2
2498 2513 else:
2499 2514 managing = lambda f: f in man1
2500 2515
2501 2516 modified, added, removed = [], [], []
2502 2517 for f in self._files:
2503 2518 if not managing(f):
2504 2519 added.append(f)
2505 2520 elif f in self:
2506 2521 modified.append(f)
2507 2522 else:
2508 2523 removed.append(f)
2509 2524
2510 2525 return scmutil.status(modified, added, removed, [], [], [], [])
2511 2526
2512 2527 class arbitraryfilectx(object):
2513 2528 """Allows you to use filectx-like functions on a file in an arbitrary
2514 2529 location on disk, possibly not in the working directory.
2515 2530 """
2516 2531 def __init__(self, path, repo=None):
2517 2532 # Repo is optional because contrib/simplemerge uses this class.
2518 2533 self._repo = repo
2519 2534 self._path = path
2520 2535
2521 2536 def cmp(self, fctx):
2522 2537 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2523 2538 # path if either side is a symlink.
2524 2539 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2525 2540 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2526 2541 # Add a fast-path for merge if both sides are disk-backed.
2527 2542 # Note that filecmp uses the opposite return values (True if same)
2528 2543 # from our cmp functions (True if different).
2529 2544 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2530 2545 return self.data() != fctx.data()
2531 2546
2532 2547 def path(self):
2533 2548 return self._path
2534 2549
2535 2550 def flags(self):
2536 2551 return ''
2537 2552
2538 2553 def data(self):
2539 2554 return util.readfile(self._path)
2540 2555
2541 2556 def decodeddata(self):
2542 2557 with open(self._path, "rb") as f:
2543 2558 return f.read()
2544 2559
2545 2560 def remove(self):
2546 2561 util.unlink(self._path)
2547 2562
2548 2563 def write(self, data, flags, **kwargs):
2549 2564 assert not flags
2550 2565 with open(self._path, "wb") as f:
2551 2566 f.write(data)
@@ -1,1012 +1,1012
1 1 # copies.py - copy detection for Mercurial
2 2 #
3 3 # Copyright 2008 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 collections
11 11 import heapq
12 12 import os
13 13
14 14 from .i18n import _
15 15
16 16 from . import (
17 17 match as matchmod,
18 18 node,
19 19 pathutil,
20 20 util,
21 21 )
22 22 from .utils import (
23 23 stringutil,
24 24 )
25 25
26 26 def _findlimit(repo, ctxa, ctxb):
27 27 """
28 28 Find the last revision that needs to be checked to ensure that a full
29 29 transitive closure for file copies can be properly calculated.
30 30 Generally, this means finding the earliest revision number that's an
31 31 ancestor of a or b but not both, except when a or b is a direct descendent
32 32 of the other, in which case we can return the minimum revnum of a and b.
33 33 """
34 34
35 35 # basic idea:
36 36 # - mark a and b with different sides
37 37 # - if a parent's children are all on the same side, the parent is
38 38 # on that side, otherwise it is on no side
39 39 # - walk the graph in topological order with the help of a heap;
40 40 # - add unseen parents to side map
41 41 # - clear side of any parent that has children on different sides
42 42 # - track number of interesting revs that might still be on a side
43 43 # - track the lowest interesting rev seen
44 44 # - quit when interesting revs is zero
45 45
46 46 cl = repo.changelog
47 47 wdirparents = None
48 48 a = ctxa.rev()
49 49 b = ctxb.rev()
50 50 if a is None:
51 51 wdirparents = (ctxa.p1(), ctxa.p2())
52 52 a = node.wdirrev
53 53 if b is None:
54 54 assert not wdirparents
55 55 wdirparents = (ctxb.p1(), ctxb.p2())
56 56 b = node.wdirrev
57 57
58 58 side = {a: -1, b: 1}
59 59 visit = [-a, -b]
60 60 heapq.heapify(visit)
61 61 interesting = len(visit)
62 62 limit = node.wdirrev
63 63
64 64 while interesting:
65 65 r = -heapq.heappop(visit)
66 66 if r == node.wdirrev:
67 67 parents = [pctx.rev() for pctx in wdirparents]
68 68 else:
69 69 parents = cl.parentrevs(r)
70 70 if parents[1] == node.nullrev:
71 71 parents = parents[:1]
72 72 for p in parents:
73 73 if p not in side:
74 74 # first time we see p; add it to visit
75 75 side[p] = side[r]
76 76 if side[p]:
77 77 interesting += 1
78 78 heapq.heappush(visit, -p)
79 79 elif side[p] and side[p] != side[r]:
80 80 # p was interesting but now we know better
81 81 side[p] = 0
82 82 interesting -= 1
83 83 if side[r]:
84 84 limit = r # lowest rev visited
85 85 interesting -= 1
86 86
87 87 # Consider the following flow (see test-commit-amend.t under issue4405):
88 88 # 1/ File 'a0' committed
89 89 # 2/ File renamed from 'a0' to 'a1' in a new commit (call it 'a1')
90 90 # 3/ Move back to first commit
91 91 # 4/ Create a new commit via revert to contents of 'a1' (call it 'a1-amend')
92 92 # 5/ Rename file from 'a1' to 'a2' and commit --amend 'a1-msg'
93 93 #
94 94 # During the amend in step five, we will be in this state:
95 95 #
96 96 # @ 3 temporary amend commit for a1-amend
97 97 # |
98 98 # o 2 a1-amend
99 99 # |
100 100 # | o 1 a1
101 101 # |/
102 102 # o 0 a0
103 103 #
104 104 # When _findlimit is called, a and b are revs 3 and 0, so limit will be 2,
105 105 # yet the filelog has the copy information in rev 1 and we will not look
106 106 # back far enough unless we also look at the a and b as candidates.
107 107 # This only occurs when a is a descendent of b or visa-versa.
108 108 return min(limit, a, b)
109 109
110 110 def _chain(src, dst, a, b):
111 111 """chain two sets of copies a->b"""
112 112 t = a.copy()
113 113 for k, v in b.iteritems():
114 114 if v in t:
115 115 # found a chain
116 116 if t[v] != k:
117 117 # file wasn't renamed back to itself
118 118 t[k] = t[v]
119 119 if v not in dst:
120 120 # chain was a rename, not a copy
121 121 del t[v]
122 122 if v in src:
123 123 # file is a copy of an existing file
124 124 t[k] = v
125 125
126 126 for k, v in list(t.items()):
127 127 # remove criss-crossed copies
128 128 if k in src and v in dst:
129 129 del t[k]
130 130 # remove copies to files that were then removed
131 131 elif k not in dst:
132 132 del t[k]
133 133
134 134 return t
135 135
136 136 def _tracefile(fctx, am, limit=node.nullrev):
137 137 """return file context that is the ancestor of fctx present in ancestor
138 138 manifest am, stopping after the first ancestor lower than limit"""
139 139
140 140 for f in fctx.ancestors():
141 141 if am.get(f.path(), None) == f.filenode():
142 142 return f
143 143 if limit >= 0 and not f.isintroducedafter(limit):
144 144 return None
145 145
146 146 def _dirstatecopies(repo, match=None):
147 147 ds = repo.dirstate
148 148 c = ds.copies().copy()
149 149 for k in list(c):
150 150 if ds[k] not in 'anm' or (match and not match(k)):
151 151 del c[k]
152 152 return c
153 153
154 154 def _computeforwardmissing(a, b, match=None):
155 155 """Computes which files are in b but not a.
156 156 This is its own function so extensions can easily wrap this call to see what
157 157 files _forwardcopies is about to process.
158 158 """
159 159 ma = a.manifest()
160 160 mb = b.manifest()
161 161 return mb.filesnotin(ma, match=match)
162 162
163 163 def usechangesetcentricalgo(repo):
164 164 """Checks if we should use changeset-centric copy algorithms"""
165 return (repo.ui.config('experimental', 'copies.read-from') ==
166 'compatibility')
165 return (repo.ui.config('experimental', 'copies.read-from') in
166 ('changeset-only', 'compatibility'))
167 167
168 168 def _committedforwardcopies(a, b, match):
169 169 """Like _forwardcopies(), but b.rev() cannot be None (working copy)"""
170 170 # files might have to be traced back to the fctx parent of the last
171 171 # one-side-only changeset, but not further back than that
172 172 repo = a._repo
173 173
174 174 if usechangesetcentricalgo(repo):
175 175 return _changesetforwardcopies(a, b, match)
176 176
177 177 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
178 178 dbg = repo.ui.debug
179 179 if debug:
180 180 dbg('debug.copies: looking into rename from %s to %s\n'
181 181 % (a, b))
182 182 limit = _findlimit(repo, a, b)
183 183 if debug:
184 184 dbg('debug.copies: search limit: %d\n' % limit)
185 185 am = a.manifest()
186 186
187 187 # find where new files came from
188 188 # we currently don't try to find where old files went, too expensive
189 189 # this means we can miss a case like 'hg rm b; hg cp a b'
190 190 cm = {}
191 191
192 192 # Computing the forward missing is quite expensive on large manifests, since
193 193 # it compares the entire manifests. We can optimize it in the common use
194 194 # case of computing what copies are in a commit versus its parent (like
195 195 # during a rebase or histedit). Note, we exclude merge commits from this
196 196 # optimization, since the ctx.files() for a merge commit is not correct for
197 197 # this comparison.
198 198 forwardmissingmatch = match
199 199 if b.p1() == a and b.p2().node() == node.nullid:
200 200 filesmatcher = matchmod.exact(b.files())
201 201 forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
202 202 missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
203 203
204 204 ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
205 205
206 206 if debug:
207 207 dbg('debug.copies: missing file to search: %d\n' % len(missing))
208 208
209 209 for f in missing:
210 210 if debug:
211 211 dbg('debug.copies: tracing file: %s\n' % f)
212 212 fctx = b[f]
213 213 fctx._ancestrycontext = ancestrycontext
214 214
215 215 if debug:
216 216 start = util.timer()
217 217 ofctx = _tracefile(fctx, am, limit)
218 218 if ofctx:
219 219 if debug:
220 220 dbg('debug.copies: rename of: %s\n' % ofctx._path)
221 221 cm[f] = ofctx.path()
222 222 if debug:
223 223 dbg('debug.copies: time: %f seconds\n'
224 224 % (util.timer() - start))
225 225 return cm
226 226
227 227 def _changesetforwardcopies(a, b, match):
228 228 if a.rev() == node.nullrev:
229 229 return {}
230 230
231 231 repo = a.repo()
232 232 children = {}
233 233 cl = repo.changelog
234 234 missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
235 235 for r in missingrevs:
236 236 for p in cl.parentrevs(r):
237 237 if p == node.nullrev:
238 238 continue
239 239 if p not in children:
240 240 children[p] = [r]
241 241 else:
242 242 children[p].append(r)
243 243
244 244 roots = set(children) - set(missingrevs)
245 245 # 'work' contains 3-tuples of a (revision number, parent number, copies).
246 246 # The parent number is only used for knowing which parent the copies dict
247 247 # came from.
248 248 work = [(r, 1, {}) for r in roots]
249 249 heapq.heapify(work)
250 250 while work:
251 251 r, i1, copies1 = heapq.heappop(work)
252 252 if work and work[0][0] == r:
253 253 # We are tracing copies from both parents
254 254 r, i2, copies2 = heapq.heappop(work)
255 255 copies = {}
256 256 ctx = repo[r]
257 257 p1man, p2man = ctx.p1().manifest(), ctx.p2().manifest()
258 258 allcopies = set(copies1) | set(copies2)
259 259 # TODO: perhaps this filtering should be done as long as ctx
260 260 # is merge, whether or not we're tracing from both parent.
261 261 for dst in allcopies:
262 262 if not match(dst):
263 263 continue
264 264 if dst not in copies2:
265 265 # Copied on p1 side: mark as copy from p1 side if it didn't
266 266 # already exist on p2 side
267 267 if dst not in p2man:
268 268 copies[dst] = copies1[dst]
269 269 elif dst not in copies1:
270 270 # Copied on p2 side: mark as copy from p2 side if it didn't
271 271 # already exist on p1 side
272 272 if dst not in p1man:
273 273 copies[dst] = copies2[dst]
274 274 else:
275 275 # Copied on both sides: mark as copy from p1 side
276 276 copies[dst] = copies1[dst]
277 277 else:
278 278 copies = copies1
279 279 if r == b.rev():
280 280 return copies
281 281 for c in children[r]:
282 282 childctx = repo[c]
283 283 if r == childctx.p1().rev():
284 284 parent = 1
285 285 childcopies = childctx.p1copies()
286 286 else:
287 287 assert r == childctx.p2().rev()
288 288 parent = 2
289 289 childcopies = childctx.p2copies()
290 290 if not match.always():
291 291 childcopies = {dst: src for dst, src in childcopies.items()
292 292 if match(dst)}
293 293 childcopies = _chain(a, childctx, copies, childcopies)
294 294 heapq.heappush(work, (c, parent, childcopies))
295 295 assert False
296 296
297 297 def _forwardcopies(a, b, match=None):
298 298 """find {dst@b: src@a} copy mapping where a is an ancestor of b"""
299 299
300 300 match = a.repo().narrowmatch(match)
301 301 # check for working copy
302 302 if b.rev() is None:
303 303 if a == b.p1():
304 304 # short-circuit to avoid issues with merge states
305 305 return _dirstatecopies(b._repo, match)
306 306
307 307 cm = _committedforwardcopies(a, b.p1(), match)
308 308 # combine copies from dirstate if necessary
309 309 return _chain(a, b, cm, _dirstatecopies(b._repo, match))
310 310 return _committedforwardcopies(a, b, match)
311 311
312 312 def _backwardrenames(a, b, match):
313 313 if a._repo.ui.config('experimental', 'copytrace') == 'off':
314 314 return {}
315 315
316 316 # Even though we're not taking copies into account, 1:n rename situations
317 317 # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
318 318 # arbitrarily pick one of the renames.
319 319 # We don't want to pass in "match" here, since that would filter
320 320 # the destination by it. Since we're reversing the copies, we want
321 321 # to filter the source instead.
322 322 f = _forwardcopies(b, a)
323 323 r = {}
324 324 for k, v in sorted(f.iteritems()):
325 325 if match and not match(v):
326 326 continue
327 327 # remove copies
328 328 if v in a:
329 329 continue
330 330 r[v] = k
331 331 return r
332 332
333 333 def pathcopies(x, y, match=None):
334 334 """find {dst@y: src@x} copy mapping for directed compare"""
335 335 repo = x._repo
336 336 debug = repo.ui.debugflag and repo.ui.configbool('devel', 'debug.copies')
337 337 if debug:
338 338 repo.ui.debug('debug.copies: searching copies from %s to %s\n'
339 339 % (x, y))
340 340 if x == y or not x or not y:
341 341 return {}
342 342 a = y.ancestor(x)
343 343 if a == x:
344 344 if debug:
345 345 repo.ui.debug('debug.copies: search mode: forward\n')
346 346 return _forwardcopies(x, y, match=match)
347 347 if a == y:
348 348 if debug:
349 349 repo.ui.debug('debug.copies: search mode: backward\n')
350 350 return _backwardrenames(x, y, match=match)
351 351 if debug:
352 352 repo.ui.debug('debug.copies: search mode: combined\n')
353 353 return _chain(x, y, _backwardrenames(x, a, match=match),
354 354 _forwardcopies(a, y, match=match))
355 355
356 356 def _computenonoverlap(repo, c1, c2, addedinm1, addedinm2, baselabel=''):
357 357 """Computes, based on addedinm1 and addedinm2, the files exclusive to c1
358 358 and c2. This is its own function so extensions can easily wrap this call
359 359 to see what files mergecopies is about to process.
360 360
361 361 Even though c1 and c2 are not used in this function, they are useful in
362 362 other extensions for being able to read the file nodes of the changed files.
363 363
364 364 "baselabel" can be passed to help distinguish the multiple computations
365 365 done in the graft case.
366 366 """
367 367 u1 = sorted(addedinm1 - addedinm2)
368 368 u2 = sorted(addedinm2 - addedinm1)
369 369
370 370 header = " unmatched files in %s"
371 371 if baselabel:
372 372 header += ' (from %s)' % baselabel
373 373 if u1:
374 374 repo.ui.debug("%s:\n %s\n" % (header % 'local', "\n ".join(u1)))
375 375 if u2:
376 376 repo.ui.debug("%s:\n %s\n" % (header % 'other', "\n ".join(u2)))
377 377
378 378 return u1, u2
379 379
380 380 def _makegetfctx(ctx):
381 381 """return a 'getfctx' function suitable for _checkcopies usage
382 382
383 383 We have to re-setup the function building 'filectx' for each
384 384 '_checkcopies' to ensure the linkrev adjustment is properly setup for
385 385 each. Linkrev adjustment is important to avoid bug in rename
386 386 detection. Moreover, having a proper '_ancestrycontext' setup ensures
387 387 the performance impact of this adjustment is kept limited. Without it,
388 388 each file could do a full dag traversal making the time complexity of
389 389 the operation explode (see issue4537).
390 390
391 391 This function exists here mostly to limit the impact on stable. Feel
392 392 free to refactor on default.
393 393 """
394 394 rev = ctx.rev()
395 395 repo = ctx._repo
396 396 ac = getattr(ctx, '_ancestrycontext', None)
397 397 if ac is None:
398 398 revs = [rev]
399 399 if rev is None:
400 400 revs = [p.rev() for p in ctx.parents()]
401 401 ac = repo.changelog.ancestors(revs, inclusive=True)
402 402 ctx._ancestrycontext = ac
403 403 def makectx(f, n):
404 404 if n in node.wdirfilenodeids: # in a working context?
405 405 if ctx.rev() is None:
406 406 return ctx.filectx(f)
407 407 return repo[None][f]
408 408 fctx = repo.filectx(f, fileid=n)
409 409 # setup only needed for filectx not create from a changectx
410 410 fctx._ancestrycontext = ac
411 411 fctx._descendantrev = rev
412 412 return fctx
413 413 return util.lrucachefunc(makectx)
414 414
415 415 def _combinecopies(copyfrom, copyto, finalcopy, diverge, incompletediverge):
416 416 """combine partial copy paths"""
417 417 remainder = {}
418 418 for f in copyfrom:
419 419 if f in copyto:
420 420 finalcopy[copyto[f]] = copyfrom[f]
421 421 del copyto[f]
422 422 for f in incompletediverge:
423 423 assert f not in diverge
424 424 ic = incompletediverge[f]
425 425 if ic[0] in copyto:
426 426 diverge[f] = [copyto[ic[0]], ic[1]]
427 427 else:
428 428 remainder[f] = ic
429 429 return remainder
430 430
431 431 def mergecopies(repo, c1, c2, base):
432 432 """
433 433 Finds moves and copies between context c1 and c2 that are relevant for
434 434 merging. 'base' will be used as the merge base.
435 435
436 436 Copytracing is used in commands like rebase, merge, unshelve, etc to merge
437 437 files that were moved/ copied in one merge parent and modified in another.
438 438 For example:
439 439
440 440 o ---> 4 another commit
441 441 |
442 442 | o ---> 3 commit that modifies a.txt
443 443 | /
444 444 o / ---> 2 commit that moves a.txt to b.txt
445 445 |/
446 446 o ---> 1 merge base
447 447
448 448 If we try to rebase revision 3 on revision 4, since there is no a.txt in
449 449 revision 4, and if user have copytrace disabled, we prints the following
450 450 message:
451 451
452 452 ```other changed <file> which local deleted```
453 453
454 454 Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
455 455 "dirmove".
456 456
457 457 "copy" is a mapping from destination name -> source name,
458 458 where source is in c1 and destination is in c2 or vice-versa.
459 459
460 460 "movewithdir" is a mapping from source name -> destination name,
461 461 where the file at source present in one context but not the other
462 462 needs to be moved to destination by the merge process, because the
463 463 other context moved the directory it is in.
464 464
465 465 "diverge" is a mapping of source name -> list of destination names
466 466 for divergent renames.
467 467
468 468 "renamedelete" is a mapping of source name -> list of destination
469 469 names for files deleted in c1 that were renamed in c2 or vice-versa.
470 470
471 471 "dirmove" is a mapping of detected source dir -> destination dir renames.
472 472 This is needed for handling changes to new files previously grafted into
473 473 renamed directories.
474 474
475 475 This function calls different copytracing algorithms based on config.
476 476 """
477 477 # avoid silly behavior for update from empty dir
478 478 if not c1 or not c2 or c1 == c2:
479 479 return {}, {}, {}, {}, {}
480 480
481 481 narrowmatch = c1.repo().narrowmatch()
482 482
483 483 # avoid silly behavior for parent -> working dir
484 484 if c2.node() is None and c1.node() == repo.dirstate.p1():
485 485 return _dirstatecopies(repo, narrowmatch), {}, {}, {}, {}
486 486
487 487 copytracing = repo.ui.config('experimental', 'copytrace')
488 488 boolctrace = stringutil.parsebool(copytracing)
489 489
490 490 # Copy trace disabling is explicitly below the node == p1 logic above
491 491 # because the logic above is required for a simple copy to be kept across a
492 492 # rebase.
493 493 if copytracing == 'heuristics':
494 494 # Do full copytracing if only non-public revisions are involved as
495 495 # that will be fast enough and will also cover the copies which could
496 496 # be missed by heuristics
497 497 if _isfullcopytraceable(repo, c1, base):
498 498 return _fullcopytracing(repo, c1, c2, base)
499 499 return _heuristicscopytracing(repo, c1, c2, base)
500 500 elif boolctrace is False:
501 501 # stringutil.parsebool() returns None when it is unable to parse the
502 502 # value, so we should rely on making sure copytracing is on such cases
503 503 return {}, {}, {}, {}, {}
504 504 else:
505 505 return _fullcopytracing(repo, c1, c2, base)
506 506
507 507 def _isfullcopytraceable(repo, c1, base):
508 508 """ Checks that if base, source and destination are all no-public branches,
509 509 if yes let's use the full copytrace algorithm for increased capabilities
510 510 since it will be fast enough.
511 511
512 512 `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
513 513 number of changesets from c1 to base such that if number of changesets are
514 514 more than the limit, full copytracing algorithm won't be used.
515 515 """
516 516 if c1.rev() is None:
517 517 c1 = c1.p1()
518 518 if c1.mutable() and base.mutable():
519 519 sourcecommitlimit = repo.ui.configint('experimental',
520 520 'copytrace.sourcecommitlimit')
521 521 commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
522 522 return commits < sourcecommitlimit
523 523 return False
524 524
525 525 def _fullcopytracing(repo, c1, c2, base):
526 526 """ The full copytracing algorithm which finds all the new files that were
527 527 added from merge base up to the top commit and for each file it checks if
528 528 this file was copied from another file.
529 529
530 530 This is pretty slow when a lot of changesets are involved but will track all
531 531 the copies.
532 532 """
533 533 # In certain scenarios (e.g. graft, update or rebase), base can be
534 534 # overridden We still need to know a real common ancestor in this case We
535 535 # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
536 536 # can be multiple common ancestors, e.g. in case of bidmerge. Because our
537 537 # caller may not know if the revision passed in lieu of the CA is a genuine
538 538 # common ancestor or not without explicitly checking it, it's better to
539 539 # determine that here.
540 540 #
541 541 # base.isancestorof(wc) is False, work around that
542 542 _c1 = c1.p1() if c1.rev() is None else c1
543 543 _c2 = c2.p1() if c2.rev() is None else c2
544 544 # an endpoint is "dirty" if it isn't a descendant of the merge base
545 545 # if we have a dirty endpoint, we need to trigger graft logic, and also
546 546 # keep track of which endpoint is dirty
547 547 dirtyc1 = not base.isancestorof(_c1)
548 548 dirtyc2 = not base.isancestorof(_c2)
549 549 graft = dirtyc1 or dirtyc2
550 550 tca = base
551 551 if graft:
552 552 tca = _c1.ancestor(_c2)
553 553
554 554 limit = _findlimit(repo, c1, c2)
555 555 repo.ui.debug(" searching for copies back to rev %d\n" % limit)
556 556
557 557 m1 = c1.manifest()
558 558 m2 = c2.manifest()
559 559 mb = base.manifest()
560 560
561 561 # gather data from _checkcopies:
562 562 # - diverge = record all diverges in this dict
563 563 # - copy = record all non-divergent copies in this dict
564 564 # - fullcopy = record all copies in this dict
565 565 # - incomplete = record non-divergent partial copies here
566 566 # - incompletediverge = record divergent partial copies here
567 567 diverge = {} # divergence data is shared
568 568 incompletediverge = {}
569 569 data1 = {'copy': {},
570 570 'fullcopy': {},
571 571 'incomplete': {},
572 572 'diverge': diverge,
573 573 'incompletediverge': incompletediverge,
574 574 }
575 575 data2 = {'copy': {},
576 576 'fullcopy': {},
577 577 'incomplete': {},
578 578 'diverge': diverge,
579 579 'incompletediverge': incompletediverge,
580 580 }
581 581
582 582 # find interesting file sets from manifests
583 583 addedinm1 = m1.filesnotin(mb, repo.narrowmatch())
584 584 addedinm2 = m2.filesnotin(mb, repo.narrowmatch())
585 585 bothnew = sorted(addedinm1 & addedinm2)
586 586 if tca == base:
587 587 # unmatched file from base
588 588 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
589 589 u1u, u2u = u1r, u2r
590 590 else:
591 591 # unmatched file from base (DAG rotation in the graft case)
592 592 u1r, u2r = _computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
593 593 baselabel='base')
594 594 # unmatched file from topological common ancestors (no DAG rotation)
595 595 # need to recompute this for directory move handling when grafting
596 596 mta = tca.manifest()
597 597 u1u, u2u = _computenonoverlap(repo, c1, c2,
598 598 m1.filesnotin(mta, repo.narrowmatch()),
599 599 m2.filesnotin(mta, repo.narrowmatch()),
600 600 baselabel='topological common ancestor')
601 601
602 602 for f in u1u:
603 603 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
604 604
605 605 for f in u2u:
606 606 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
607 607
608 608 copy = dict(data1['copy'])
609 609 copy.update(data2['copy'])
610 610 fullcopy = dict(data1['fullcopy'])
611 611 fullcopy.update(data2['fullcopy'])
612 612
613 613 if dirtyc1:
614 614 _combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
615 615 incompletediverge)
616 616 if dirtyc2:
617 617 _combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
618 618 incompletediverge)
619 619
620 620 renamedelete = {}
621 621 renamedeleteset = set()
622 622 divergeset = set()
623 623 for of, fl in list(diverge.items()):
624 624 if len(fl) == 1 or of in c1 or of in c2:
625 625 del diverge[of] # not actually divergent, or not a rename
626 626 if of not in c1 and of not in c2:
627 627 # renamed on one side, deleted on the other side, but filter
628 628 # out files that have been renamed and then deleted
629 629 renamedelete[of] = [f for f in fl if f in c1 or f in c2]
630 630 renamedeleteset.update(fl) # reverse map for below
631 631 else:
632 632 divergeset.update(fl) # reverse map for below
633 633
634 634 if bothnew:
635 635 repo.ui.debug(" unmatched files new in both:\n %s\n"
636 636 % "\n ".join(bothnew))
637 637 bothdiverge = {}
638 638 bothincompletediverge = {}
639 639 remainder = {}
640 640 both1 = {'copy': {},
641 641 'fullcopy': {},
642 642 'incomplete': {},
643 643 'diverge': bothdiverge,
644 644 'incompletediverge': bothincompletediverge
645 645 }
646 646 both2 = {'copy': {},
647 647 'fullcopy': {},
648 648 'incomplete': {},
649 649 'diverge': bothdiverge,
650 650 'incompletediverge': bothincompletediverge
651 651 }
652 652 for f in bothnew:
653 653 _checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
654 654 _checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
655 655 if dirtyc1 and dirtyc2:
656 656 remainder = _combinecopies(both2['incomplete'], both1['incomplete'],
657 657 copy, bothdiverge, bothincompletediverge)
658 658 remainder1 = _combinecopies(both1['incomplete'], both2['incomplete'],
659 659 copy, bothdiverge, bothincompletediverge)
660 660 remainder.update(remainder1)
661 661 elif dirtyc1:
662 662 # incomplete copies may only be found on the "dirty" side for bothnew
663 663 assert not both2['incomplete']
664 664 remainder = _combinecopies({}, both1['incomplete'], copy, bothdiverge,
665 665 bothincompletediverge)
666 666 elif dirtyc2:
667 667 assert not both1['incomplete']
668 668 remainder = _combinecopies({}, both2['incomplete'], copy, bothdiverge,
669 669 bothincompletediverge)
670 670 else:
671 671 # incomplete copies and divergences can't happen outside grafts
672 672 assert not both1['incomplete']
673 673 assert not both2['incomplete']
674 674 assert not bothincompletediverge
675 675 for f in remainder:
676 676 assert f not in bothdiverge
677 677 ic = remainder[f]
678 678 if ic[0] in (m1 if dirtyc1 else m2):
679 679 # backed-out rename on one side, but watch out for deleted files
680 680 bothdiverge[f] = ic
681 681 for of, fl in bothdiverge.items():
682 682 if len(fl) == 2 and fl[0] == fl[1]:
683 683 copy[fl[0]] = of # not actually divergent, just matching renames
684 684
685 685 if fullcopy and repo.ui.debugflag:
686 686 repo.ui.debug(" all copies found (* = to merge, ! = divergent, "
687 687 "% = renamed and deleted):\n")
688 688 for f in sorted(fullcopy):
689 689 note = ""
690 690 if f in copy:
691 691 note += "*"
692 692 if f in divergeset:
693 693 note += "!"
694 694 if f in renamedeleteset:
695 695 note += "%"
696 696 repo.ui.debug(" src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
697 697 note))
698 698 del divergeset
699 699
700 700 if not fullcopy:
701 701 return copy, {}, diverge, renamedelete, {}
702 702
703 703 repo.ui.debug(" checking for directory renames\n")
704 704
705 705 # generate a directory move map
706 706 d1, d2 = c1.dirs(), c2.dirs()
707 707 # Hack for adding '', which is not otherwise added, to d1 and d2
708 708 d1.addpath('/')
709 709 d2.addpath('/')
710 710 invalid = set()
711 711 dirmove = {}
712 712
713 713 # examine each file copy for a potential directory move, which is
714 714 # when all the files in a directory are moved to a new directory
715 715 for dst, src in fullcopy.iteritems():
716 716 dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
717 717 if dsrc in invalid:
718 718 # already seen to be uninteresting
719 719 continue
720 720 elif dsrc in d1 and ddst in d1:
721 721 # directory wasn't entirely moved locally
722 722 invalid.add(dsrc)
723 723 elif dsrc in d2 and ddst in d2:
724 724 # directory wasn't entirely moved remotely
725 725 invalid.add(dsrc)
726 726 elif dsrc in dirmove and dirmove[dsrc] != ddst:
727 727 # files from the same directory moved to two different places
728 728 invalid.add(dsrc)
729 729 else:
730 730 # looks good so far
731 731 dirmove[dsrc] = ddst
732 732
733 733 for i in invalid:
734 734 if i in dirmove:
735 735 del dirmove[i]
736 736 del d1, d2, invalid
737 737
738 738 if not dirmove:
739 739 return copy, {}, diverge, renamedelete, {}
740 740
741 741 dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
742 742
743 743 for d in dirmove:
744 744 repo.ui.debug(" discovered dir src: '%s' -> dst: '%s'\n" %
745 745 (d, dirmove[d]))
746 746
747 747 movewithdir = {}
748 748 # check unaccounted nonoverlapping files against directory moves
749 749 for f in u1r + u2r:
750 750 if f not in fullcopy:
751 751 for d in dirmove:
752 752 if f.startswith(d):
753 753 # new file added in a directory that was moved, move it
754 754 df = dirmove[d] + f[len(d):]
755 755 if df not in copy:
756 756 movewithdir[f] = df
757 757 repo.ui.debug((" pending file src: '%s' -> "
758 758 "dst: '%s'\n") % (f, df))
759 759 break
760 760
761 761 return copy, movewithdir, diverge, renamedelete, dirmove
762 762
763 763 def _heuristicscopytracing(repo, c1, c2, base):
764 764 """ Fast copytracing using filename heuristics
765 765
766 766 Assumes that moves or renames are of following two types:
767 767
768 768 1) Inside a directory only (same directory name but different filenames)
769 769 2) Move from one directory to another
770 770 (same filenames but different directory names)
771 771
772 772 Works only when there are no merge commits in the "source branch".
773 773 Source branch is commits from base up to c2 not including base.
774 774
775 775 If merge is involved it fallbacks to _fullcopytracing().
776 776
777 777 Can be used by setting the following config:
778 778
779 779 [experimental]
780 780 copytrace = heuristics
781 781
782 782 In some cases the copy/move candidates found by heuristics can be very large
783 783 in number and that will make the algorithm slow. The number of possible
784 784 candidates to check can be limited by using the config
785 785 `experimental.copytrace.movecandidateslimit` which defaults to 100.
786 786 """
787 787
788 788 if c1.rev() is None:
789 789 c1 = c1.p1()
790 790 if c2.rev() is None:
791 791 c2 = c2.p1()
792 792
793 793 copies = {}
794 794
795 795 changedfiles = set()
796 796 m1 = c1.manifest()
797 797 if not repo.revs('%d::%d', base.rev(), c2.rev()):
798 798 # If base is not in c2 branch, we switch to fullcopytracing
799 799 repo.ui.debug("switching to full copytracing as base is not "
800 800 "an ancestor of c2\n")
801 801 return _fullcopytracing(repo, c1, c2, base)
802 802
803 803 ctx = c2
804 804 while ctx != base:
805 805 if len(ctx.parents()) == 2:
806 806 # To keep things simple let's not handle merges
807 807 repo.ui.debug("switching to full copytracing because of merges\n")
808 808 return _fullcopytracing(repo, c1, c2, base)
809 809 changedfiles.update(ctx.files())
810 810 ctx = ctx.p1()
811 811
812 812 cp = _forwardcopies(base, c2)
813 813 for dst, src in cp.iteritems():
814 814 if src in m1:
815 815 copies[dst] = src
816 816
817 817 # file is missing if it isn't present in the destination, but is present in
818 818 # the base and present in the source.
819 819 # Presence in the base is important to exclude added files, presence in the
820 820 # source is important to exclude removed files.
821 821 filt = lambda f: f not in m1 and f in base and f in c2
822 822 missingfiles = [f for f in changedfiles if filt(f)]
823 823
824 824 if missingfiles:
825 825 basenametofilename = collections.defaultdict(list)
826 826 dirnametofilename = collections.defaultdict(list)
827 827
828 828 for f in m1.filesnotin(base.manifest()):
829 829 basename = os.path.basename(f)
830 830 dirname = os.path.dirname(f)
831 831 basenametofilename[basename].append(f)
832 832 dirnametofilename[dirname].append(f)
833 833
834 834 for f in missingfiles:
835 835 basename = os.path.basename(f)
836 836 dirname = os.path.dirname(f)
837 837 samebasename = basenametofilename[basename]
838 838 samedirname = dirnametofilename[dirname]
839 839 movecandidates = samebasename + samedirname
840 840 # f is guaranteed to be present in c2, that's why
841 841 # c2.filectx(f) won't fail
842 842 f2 = c2.filectx(f)
843 843 # we can have a lot of candidates which can slow down the heuristics
844 844 # config value to limit the number of candidates moves to check
845 845 maxcandidates = repo.ui.configint('experimental',
846 846 'copytrace.movecandidateslimit')
847 847
848 848 if len(movecandidates) > maxcandidates:
849 849 repo.ui.status(_("skipping copytracing for '%s', more "
850 850 "candidates than the limit: %d\n")
851 851 % (f, len(movecandidates)))
852 852 continue
853 853
854 854 for candidate in movecandidates:
855 855 f1 = c1.filectx(candidate)
856 856 if _related(f1, f2):
857 857 # if there are a few related copies then we'll merge
858 858 # changes into all of them. This matches the behaviour
859 859 # of upstream copytracing
860 860 copies[candidate] = f
861 861
862 862 return copies, {}, {}, {}, {}
863 863
864 864 def _related(f1, f2):
865 865 """return True if f1 and f2 filectx have a common ancestor
866 866
867 867 Walk back to common ancestor to see if the two files originate
868 868 from the same file. Since workingfilectx's rev() is None it messes
869 869 up the integer comparison logic, hence the pre-step check for
870 870 None (f1 and f2 can only be workingfilectx's initially).
871 871 """
872 872
873 873 if f1 == f2:
874 874 return True # a match
875 875
876 876 g1, g2 = f1.ancestors(), f2.ancestors()
877 877 try:
878 878 f1r, f2r = f1.linkrev(), f2.linkrev()
879 879
880 880 if f1r is None:
881 881 f1 = next(g1)
882 882 if f2r is None:
883 883 f2 = next(g2)
884 884
885 885 while True:
886 886 f1r, f2r = f1.linkrev(), f2.linkrev()
887 887 if f1r > f2r:
888 888 f1 = next(g1)
889 889 elif f2r > f1r:
890 890 f2 = next(g2)
891 891 else: # f1 and f2 point to files in the same linkrev
892 892 return f1 == f2 # true if they point to the same file
893 893 except StopIteration:
894 894 return False
895 895
896 896 def _checkcopies(srcctx, dstctx, f, base, tca, remotebase, limit, data):
897 897 """
898 898 check possible copies of f from msrc to mdst
899 899
900 900 srcctx = starting context for f in msrc
901 901 dstctx = destination context for f in mdst
902 902 f = the filename to check (as in msrc)
903 903 base = the changectx used as a merge base
904 904 tca = topological common ancestor for graft-like scenarios
905 905 remotebase = True if base is outside tca::srcctx, False otherwise
906 906 limit = the rev number to not search beyond
907 907 data = dictionary of dictionary to store copy data. (see mergecopies)
908 908
909 909 note: limit is only an optimization, and provides no guarantee that
910 910 irrelevant revisions will not be visited
911 911 there is no easy way to make this algorithm stop in a guaranteed way
912 912 once it "goes behind a certain revision".
913 913 """
914 914
915 915 msrc = srcctx.manifest()
916 916 mdst = dstctx.manifest()
917 917 mb = base.manifest()
918 918 mta = tca.manifest()
919 919 # Might be true if this call is about finding backward renames,
920 920 # This happens in the case of grafts because the DAG is then rotated.
921 921 # If the file exists in both the base and the source, we are not looking
922 922 # for a rename on the source side, but on the part of the DAG that is
923 923 # traversed backwards.
924 924 #
925 925 # In the case there is both backward and forward renames (before and after
926 926 # the base) this is more complicated as we must detect a divergence.
927 927 # We use 'backwards = False' in that case.
928 928 backwards = not remotebase and base != tca and f in mb
929 929 getsrcfctx = _makegetfctx(srcctx)
930 930 getdstfctx = _makegetfctx(dstctx)
931 931
932 932 if msrc[f] == mb.get(f) and not remotebase:
933 933 # Nothing to merge
934 934 return
935 935
936 936 of = None
937 937 seen = {f}
938 938 for oc in getsrcfctx(f, msrc[f]).ancestors():
939 939 of = oc.path()
940 940 if of in seen:
941 941 # check limit late - grab last rename before
942 942 if oc.linkrev() < limit:
943 943 break
944 944 continue
945 945 seen.add(of)
946 946
947 947 # remember for dir rename detection
948 948 if backwards:
949 949 data['fullcopy'][of] = f # grafting backwards through renames
950 950 else:
951 951 data['fullcopy'][f] = of
952 952 if of not in mdst:
953 953 continue # no match, keep looking
954 954 if mdst[of] == mb.get(of):
955 955 return # no merge needed, quit early
956 956 c2 = getdstfctx(of, mdst[of])
957 957 # c2 might be a plain new file on added on destination side that is
958 958 # unrelated to the droids we are looking for.
959 959 cr = _related(oc, c2)
960 960 if cr and (of == f or of == c2.path()): # non-divergent
961 961 if backwards:
962 962 data['copy'][of] = f
963 963 elif of in mb:
964 964 data['copy'][f] = of
965 965 elif remotebase: # special case: a <- b <- a -> b "ping-pong" rename
966 966 data['copy'][of] = f
967 967 del data['fullcopy'][f]
968 968 data['fullcopy'][of] = f
969 969 else: # divergence w.r.t. graft CA on one side of topological CA
970 970 for sf in seen:
971 971 if sf in mb:
972 972 assert sf not in data['diverge']
973 973 data['diverge'][sf] = [f, of]
974 974 break
975 975 return
976 976
977 977 if of in mta:
978 978 if backwards or remotebase:
979 979 data['incomplete'][of] = f
980 980 else:
981 981 for sf in seen:
982 982 if sf in mb:
983 983 if tca == base:
984 984 data['diverge'].setdefault(sf, []).append(f)
985 985 else:
986 986 data['incompletediverge'][sf] = [of, f]
987 987 return
988 988
989 989 def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
990 990 """reproduce copies from fromrev to rev in the dirstate
991 991
992 992 If skiprev is specified, it's a revision that should be used to
993 993 filter copy records. Any copies that occur between fromrev and
994 994 skiprev will not be duplicated, even if they appear in the set of
995 995 copies between fromrev and rev.
996 996 """
997 997 exclude = {}
998 998 ctraceconfig = repo.ui.config('experimental', 'copytrace')
999 999 bctrace = stringutil.parsebool(ctraceconfig)
1000 1000 if (skiprev is not None and
1001 1001 (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
1002 1002 # copytrace='off' skips this line, but not the entire function because
1003 1003 # the line below is O(size of the repo) during a rebase, while the rest
1004 1004 # of the function is much faster (and is required for carrying copy
1005 1005 # metadata across the rebase anyway).
1006 1006 exclude = pathcopies(repo[fromrev], repo[skiprev])
1007 1007 for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
1008 1008 # copies.pathcopies returns backward renames, so dst might not
1009 1009 # actually be in the dirstate
1010 1010 if dst in exclude:
1011 1011 continue
1012 1012 wctx[dst].markcopied(src)
@@ -1,105 +1,135
1 1
2 2 $ cat >> $HGRCPATH << EOF
3 3 > [experimental]
4 4 > copies.write-to=changeset-only
5 > copies.read-from=changeset-only
5 6 > [alias]
6 7 > changesetcopies = log -r . -T 'files: {files}
7 8 > {extras % "{ifcontains("copies", key, "{key}: {value}\n")}"}'
9 > showcopies = log -r . -T '{file_copies % "{source} -> {name}\n"}'
8 10 > EOF
9 11
10 12 Check that copies are recorded correctly
11 13
12 14 $ hg init repo
13 15 $ cd repo
14 16 $ echo a > a
15 17 $ hg add a
16 18 $ hg ci -m initial
17 19 $ hg cp a b
18 20 $ hg cp a c
19 21 $ hg cp a d
20 22 $ hg ci -m 'copy a to b, c, and d'
21 23 $ hg changesetcopies
22 24 files: b c d
23 25 p1copies: b\x00a (esc)
24 26 c\x00a (esc)
25 27 d\x00a (esc)
28 $ hg showcopies
29 a -> b
30 a -> c
31 a -> d
32 $ hg showcopies --config experimental.copies.read-from=compatibility
33 a -> b
34 a -> c
35 a -> d
36 $ hg showcopies --config experimental.copies.read-from=filelog-only
26 37
27 38 Check that renames are recorded correctly
28 39
29 40 $ hg mv b b2
30 41 $ hg ci -m 'rename b to b2'
31 42 $ hg changesetcopies
32 43 files: b b2
33 44 p1copies: b2\x00b (esc)
45 $ hg showcopies
46 b -> b2
34 47
35 48 Rename onto existing file. This should get recorded in the changeset files list and in the extras,
36 49 even though there is no filelog entry.
37 50
38 51 $ hg cp b2 c --force
39 52 $ hg st --copies
40 53 M c
41 54 b2
42 55 $ hg debugindex c
43 56 rev linkrev nodeid p1 p2
44 57 0 1 b789fdd96dc2 000000000000 000000000000
45 58 $ hg ci -m 'move b onto d'
46 59 $ hg changesetcopies
47 60 files: c
48 61 p1copies: c\x00b2 (esc)
62 $ hg showcopies
63 b2 -> c
49 64 $ hg debugindex c
50 65 rev linkrev nodeid p1 p2
51 66 0 1 b789fdd96dc2 000000000000 000000000000
52 67
53 68 Create a merge commit with copying done during merge.
54 69
55 70 $ hg co 0
56 71 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
57 72 $ hg cp a e
58 73 $ hg cp a f
59 74 $ hg ci -m 'copy a to e and f'
60 75 created new head
61 76 $ hg merge 3
62 77 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 78 (branch merge, don't forget to commit)
64 79 File 'a' exists on both sides, so 'g' could be recorded as being from p1 or p2, but we currently
65 80 always record it as being from p1
66 81 $ hg cp a g
67 82 File 'd' exists only in p2, so 'h' should be from p2
68 83 $ hg cp d h
69 84 File 'f' exists only in p1, so 'i' should be from p1
70 85 $ hg cp f i
71 86 $ hg ci -m 'merge'
72 87 $ hg changesetcopies
73 88 files: g h i
74 89 p1copies: g\x00a (esc)
75 90 i\x00f (esc)
76 91 p2copies: h\x00d (esc)
92 $ hg showcopies
93 a -> g
94 d -> h
95 f -> i
77 96
78 97 Test writing to both changeset and filelog
79 98
80 99 $ hg cp a j
81 100 $ hg ci -m 'copy a to j' --config experimental.copies.write-to=compatibility
82 101 $ hg changesetcopies
83 102 files: j
84 103 p1copies: j\x00a (esc)
85 104 $ hg debugdata j 0
86 105 \x01 (esc)
87 106 copy: a
88 107 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
89 108 \x01 (esc)
90 109 a
110 $ hg showcopies
111 a -> j
112 $ hg showcopies --config experimental.copies.read-from=compatibility
113 a -> j
114 $ hg showcopies --config experimental.copies.read-from=filelog-only
115 a -> j
91 116
92 117 Test writing only to filelog
93 118
94 119 $ hg cp a k
95 120 $ hg ci -m 'copy a to k' --config experimental.copies.write-to=filelog-only
96 121 $ hg changesetcopies
97 122 files: k
98 123 $ hg debugdata k 0
99 124 \x01 (esc)
100 125 copy: a
101 126 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
102 127 \x01 (esc)
103 128 a
129 $ hg showcopies
130 $ hg showcopies --config experimental.copies.read-from=compatibility
131 a -> k
132 $ hg showcopies --config experimental.copies.read-from=filelog-only
133 a -> k
104 134
105 135 $ cd ..
@@ -1,633 +1,648
1 #testcases filelog compatibility
1 #testcases filelog compatibility changeset
2 2
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > rebase=
6 6 > [alias]
7 7 > l = log -G -T '{rev} {desc}\n{files}\n'
8 8 > EOF
9 9
10 10 #if compatibility
11 11 $ cat >> $HGRCPATH << EOF
12 12 > [experimental]
13 13 > copies.read-from = compatibility
14 14 > EOF
15 15 #endif
16 16
17 #if changeset
18 $ cat >> $HGRCPATH << EOF
19 > [experimental]
20 > copies.read-from = changeset-only
21 > copies.write-to = changeset-only
22 > EOF
23 #endif
24
17 25 $ REPONUM=0
18 26 $ newrepo() {
19 27 > cd $TESTTMP
20 28 > REPONUM=`expr $REPONUM + 1`
21 29 > hg init repo-$REPONUM
22 30 > cd repo-$REPONUM
23 31 > }
24 32
25 33 Simple rename case
26 34 $ newrepo
27 35 $ echo x > x
28 36 $ hg ci -Aqm 'add x'
29 37 $ hg mv x y
30 38 $ hg debugp1copies
31 39 x -> y
32 40 $ hg debugp2copies
33 41 $ hg ci -m 'rename x to y'
34 42 $ hg l
35 43 @ 1 rename x to y
36 44 | x y
37 45 o 0 add x
38 46 x
39 47 $ hg debugp1copies -r 1
40 48 x -> y
41 49 $ hg debugpathcopies 0 1
42 50 x -> y
43 51 $ hg debugpathcopies 1 0
44 52 y -> x
45 53 Test filtering copies by path. We do filtering by destination.
46 54 $ hg debugpathcopies 0 1 x
47 55 $ hg debugpathcopies 1 0 x
48 56 y -> x
49 57 $ hg debugpathcopies 0 1 y
50 58 x -> y
51 59 $ hg debugpathcopies 1 0 y
52 60
53 61 Copy a file onto another file
54 62 $ newrepo
55 63 $ echo x > x
56 64 $ echo y > y
57 65 $ hg ci -Aqm 'add x and y'
58 66 $ hg cp -f x y
59 67 $ hg debugp1copies
60 68 x -> y
61 69 $ hg debugp2copies
62 70 $ hg ci -m 'copy x onto y'
63 71 $ hg l
64 72 @ 1 copy x onto y
65 73 | y
66 74 o 0 add x and y
67 75 x y
68 76 $ hg debugp1copies -r 1
69 77 x -> y
70 78 Incorrectly doesn't show the rename
71 79 $ hg debugpathcopies 0 1
72 80
73 81 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
74 82 produce a new filelog entry. The changeset's "files" entry should still list the file.
75 83 $ newrepo
76 84 $ echo x > x
77 85 $ echo x > x2
78 86 $ hg ci -Aqm 'add x and x2 with same content'
79 87 $ hg cp -f x x2
80 88 $ hg ci -m 'copy x onto x2'
81 89 $ hg l
82 90 @ 1 copy x onto x2
83 91 | x2
84 92 o 0 add x and x2 with same content
85 93 x x2
86 94 $ hg debugp1copies -r 1
87 95 x -> x2
88 96 Incorrectly doesn't show the rename
89 97 $ hg debugpathcopies 0 1
90 98
91 99 Copy a file, then delete destination, then copy again. This does not create a new filelog entry.
92 100 $ newrepo
93 101 $ echo x > x
94 102 $ hg ci -Aqm 'add x'
95 103 $ hg cp x y
96 104 $ hg ci -m 'copy x to y'
97 105 $ hg rm y
98 106 $ hg ci -m 'remove y'
99 107 $ hg cp -f x y
100 108 $ hg ci -m 'copy x onto y (again)'
101 109 $ hg l
102 110 @ 3 copy x onto y (again)
103 111 | y
104 112 o 2 remove y
105 113 | y
106 114 o 1 copy x to y
107 115 | y
108 116 o 0 add x
109 117 x
110 118 $ hg debugp1copies -r 3
111 119 x -> y
112 120 $ hg debugpathcopies 0 3
113 121 x -> y
114 122
115 123 Rename file in a loop: x->y->z->x
116 124 $ newrepo
117 125 $ echo x > x
118 126 $ hg ci -Aqm 'add x'
119 127 $ hg mv x y
120 128 $ hg debugp1copies
121 129 x -> y
122 130 $ hg debugp2copies
123 131 $ hg ci -m 'rename x to y'
124 132 $ hg mv y z
125 133 $ hg ci -m 'rename y to z'
126 134 $ hg mv z x
127 135 $ hg ci -m 'rename z to x'
128 136 $ hg l
129 137 @ 3 rename z to x
130 138 | x z
131 139 o 2 rename y to z
132 140 | y z
133 141 o 1 rename x to y
134 142 | x y
135 143 o 0 add x
136 144 x
137 145 $ hg debugpathcopies 0 3
138 146
139 147 Copy x to y, then remove y, then add back y. With copy metadata in the changeset, this could easily
140 148 end up reporting y as copied from x (if we don't unmark it as a copy when it's removed).
141 149 $ newrepo
142 150 $ echo x > x
143 151 $ hg ci -Aqm 'add x'
144 152 $ hg mv x y
145 153 $ hg ci -m 'rename x to y'
146 154 $ hg rm y
147 155 $ hg ci -qm 'remove y'
148 156 $ echo x > y
149 157 $ hg ci -Aqm 'add back y'
150 158 $ hg l
151 159 @ 3 add back y
152 160 | y
153 161 o 2 remove y
154 162 | y
155 163 o 1 rename x to y
156 164 | x y
157 165 o 0 add x
158 166 x
159 167 $ hg debugp1copies -r 3
160 168 $ hg debugpathcopies 0 3
161 169
162 170 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
163 171 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
164 172 to the first commit that added the file. We should still report the copy as being from x2.
165 173 $ newrepo
166 174 $ echo x > x
167 175 $ echo x > x2
168 176 $ hg ci -Aqm 'add x and x2 with same content'
169 177 $ hg cp x z
170 178 $ hg ci -qm 'copy x to z'
171 179 $ hg rm z
172 180 $ hg ci -m 'remove z'
173 181 $ hg cp x2 z
174 182 $ hg ci -m 'copy x2 to z'
175 183 $ hg l
176 184 @ 3 copy x2 to z
177 185 | z
178 186 o 2 remove z
179 187 | z
180 188 o 1 copy x to z
181 189 | z
182 190 o 0 add x and x2 with same content
183 191 x x2
184 192 $ hg debugp1copies -r 3
185 193 x2 -> z
186 194 $ hg debugpathcopies 0 3
187 195 x2 -> z
188 196
189 197 Create x and y, then rename them both to the same name, but on different sides of a fork
190 198 $ newrepo
191 199 $ echo x > x
192 200 $ echo y > y
193 201 $ hg ci -Aqm 'add x and y'
194 202 $ hg mv x z
195 203 $ hg ci -qm 'rename x to z'
196 204 $ hg co -q 0
197 205 $ hg mv y z
198 206 $ hg ci -qm 'rename y to z'
199 207 $ hg l
200 208 @ 2 rename y to z
201 209 | y z
202 210 | o 1 rename x to z
203 211 |/ x z
204 212 o 0 add x and y
205 213 x y
206 214 $ hg debugpathcopies 1 2
207 215 z -> x
208 216 y -> z
209 217
210 218 Fork renames x to y on one side and removes x on the other
211 219 $ newrepo
212 220 $ echo x > x
213 221 $ hg ci -Aqm 'add x'
214 222 $ hg mv x y
215 223 $ hg ci -m 'rename x to y'
216 224 $ hg co -q 0
217 225 $ hg rm x
218 226 $ hg ci -m 'remove x'
219 227 created new head
220 228 $ hg l
221 229 @ 2 remove x
222 230 | x
223 231 | o 1 rename x to y
224 232 |/ x y
225 233 o 0 add x
226 234 x
227 235 $ hg debugpathcopies 1 2
228 236
229 237 Copies via null revision (there shouldn't be any)
230 238 $ newrepo
231 239 $ echo x > x
232 240 $ hg ci -Aqm 'add x'
233 241 $ hg cp x y
234 242 $ hg ci -m 'copy x to y'
235 243 $ hg co -q null
236 244 $ echo x > x
237 245 $ hg ci -Aqm 'add x (again)'
238 246 $ hg l
239 247 @ 2 add x (again)
240 248 x
241 249 o 1 copy x to y
242 250 | y
243 251 o 0 add x
244 252 x
245 253 $ hg debugpathcopies 1 2
246 254 $ hg debugpathcopies 2 1
247 255
248 256 Merge rename from other branch
249 257 $ newrepo
250 258 $ echo x > x
251 259 $ hg ci -Aqm 'add x'
252 260 $ hg mv x y
253 261 $ hg ci -m 'rename x to y'
254 262 $ hg co -q 0
255 263 $ echo z > z
256 264 $ hg ci -Aqm 'add z'
257 265 $ hg merge -q 1
258 266 $ hg debugp1copies
259 267 $ hg debugp2copies
260 268 $ hg ci -m 'merge rename from p2'
261 269 $ hg l
262 270 @ 3 merge rename from p2
263 271 |\ x
264 272 | o 2 add z
265 273 | | z
266 274 o | 1 rename x to y
267 275 |/ x y
268 276 o 0 add x
269 277 x
270 278 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
271 279 merges, so...
272 280 $ hg debugp1copies -r 3
273 281 $ hg debugp2copies -r 3
274 282 $ hg debugpathcopies 0 3
275 283 x -> y
276 284 $ hg debugpathcopies 1 2
277 285 y -> x
278 286 $ hg debugpathcopies 1 3
279 287 $ hg debugpathcopies 2 3
280 288 x -> y
281 289
282 290 Copy file from either side in a merge
283 291 $ newrepo
284 292 $ echo x > x
285 293 $ hg ci -Aqm 'add x'
286 294 $ hg co -q null
287 295 $ echo y > y
288 296 $ hg ci -Aqm 'add y'
289 297 $ hg merge -q 0
290 298 $ hg cp y z
291 299 $ hg debugp1copies
292 300 y -> z
293 301 $ hg debugp2copies
294 302 $ hg ci -m 'copy file from p1 in merge'
295 303 $ hg co -q 1
296 304 $ hg merge -q 0
297 305 $ hg cp x z
298 306 $ hg debugp1copies
299 307 $ hg debugp2copies
300 308 x -> z
301 309 $ hg ci -qm 'copy file from p2 in merge'
302 310 $ hg l
303 311 @ 3 copy file from p2 in merge
304 312 |\ z
305 313 +---o 2 copy file from p1 in merge
306 314 | |/ z
307 315 | o 1 add y
308 316 | y
309 317 o 0 add x
310 318 x
311 319 $ hg debugp1copies -r 2
312 320 y -> z
313 321 $ hg debugp2copies -r 2
314 322 $ hg debugpathcopies 1 2
315 323 y -> z
316 324 $ hg debugpathcopies 0 2
317 325 $ hg debugp1copies -r 3
318 326 $ hg debugp2copies -r 3
319 327 x -> z
320 328 $ hg debugpathcopies 1 3
321 329 $ hg debugpathcopies 0 3
322 330 x -> z
323 331
324 332 Copy file that exists on both sides of the merge, same content on both sides
325 333 $ newrepo
326 334 $ echo x > x
327 335 $ hg ci -Aqm 'add x on branch 1'
328 336 $ hg co -q null
329 337 $ echo x > x
330 338 $ hg ci -Aqm 'add x on branch 2'
331 339 $ hg merge -q 0
332 340 $ hg cp x z
333 341 $ hg debugp1copies
334 342 x -> z
335 343 $ hg debugp2copies
336 344 $ hg ci -qm 'merge'
337 345 $ hg l
338 346 @ 2 merge
339 347 |\ z
340 348 | o 1 add x on branch 2
341 349 | x
342 350 o 0 add x on branch 1
343 351 x
344 352 $ hg debugp1copies -r 2
345 353 x -> z
346 354 $ hg debugp2copies -r 2
347 355 It's a little weird that it shows up on both sides
348 356 $ hg debugpathcopies 1 2
349 357 x -> z
350 358 $ hg debugpathcopies 0 2
351 359 x -> z (filelog !)
352 360
353 361 Copy file that exists on both sides of the merge, different content
354 362 $ newrepo
355 363 $ echo branch1 > x
356 364 $ hg ci -Aqm 'add x on branch 1'
357 365 $ hg co -q null
358 366 $ echo branch2 > x
359 367 $ hg ci -Aqm 'add x on branch 2'
360 368 $ hg merge -q 0
361 369 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
362 370 [1]
363 371 $ echo resolved > x
364 372 $ hg resolve -m x
365 373 (no more unresolved files)
366 374 $ hg cp x z
367 375 $ hg debugp1copies
368 376 x -> z
369 377 $ hg debugp2copies
370 378 $ hg ci -qm 'merge'
371 379 $ hg l
372 380 @ 2 merge
373 381 |\ x z
374 382 | o 1 add x on branch 2
375 383 | x
376 384 o 0 add x on branch 1
377 385 x
378 386 $ hg debugp1copies -r 2
387 x -> z (changeset !)
379 388 $ hg debugp2copies -r 2
380 x -> z
389 x -> z (no-changeset !)
381 390 $ hg debugpathcopies 1 2
391 x -> z (changeset !)
382 392 $ hg debugpathcopies 0 2
383 x -> z
393 x -> z (no-changeset !)
384 394
385 395 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
386 396 of the merge to the merge should include the copy from the other side.
387 397 $ newrepo
388 398 $ echo x > x
389 399 $ hg ci -Aqm 'add x'
390 400 $ hg cp x y
391 401 $ hg ci -qm 'copy x to y'
392 402 $ hg co -q 0
393 403 $ hg cp x z
394 404 $ hg ci -qm 'copy x to z'
395 405 $ hg merge -q 1
396 406 $ hg ci -m 'merge copy x->y and copy x->z'
397 407 $ hg l
398 408 @ 3 merge copy x->y and copy x->z
399 409 |\
400 410 | o 2 copy x to z
401 411 | | z
402 412 o | 1 copy x to y
403 413 |/ y
404 414 o 0 add x
405 415 x
406 416 $ hg debugp1copies -r 3
407 417 $ hg debugp2copies -r 3
408 418 $ hg debugpathcopies 2 3
409 419 x -> y
410 420 $ hg debugpathcopies 1 3
411 421 x -> z
412 422
413 423 Copy x to y on one side of merge, create y and rename to z on the other side. Pathcopies from the
414 424 first side should not include the y->z rename since y didn't exist in the merge base.
415 425 $ newrepo
416 426 $ echo x > x
417 427 $ hg ci -Aqm 'add x'
418 428 $ hg cp x y
419 429 $ hg ci -qm 'copy x to y'
420 430 $ hg co -q 0
421 431 $ echo y > y
422 432 $ hg ci -Aqm 'add y'
423 433 $ hg mv y z
424 434 $ hg ci -m 'rename y to z'
425 435 $ hg merge -q 1
426 436 $ hg ci -m 'merge'
427 437 $ hg l
428 438 @ 4 merge
429 439 |\
430 440 | o 3 rename y to z
431 441 | | y z
432 442 | o 2 add y
433 443 | | y
434 444 o | 1 copy x to y
435 445 |/ y
436 446 o 0 add x
437 447 x
438 448 $ hg debugp1copies -r 3
439 449 y -> z
440 450 $ hg debugp2copies -r 3
441 451 $ hg debugpathcopies 2 3
442 452 y -> z
443 453 $ hg debugpathcopies 1 3
444 454
445 455 Create x and y, then rename x to z on one side of merge, and rename y to z and modify z on the
446 456 other side.
447 457 $ newrepo
448 458 $ echo x > x
449 459 $ echo y > y
450 460 $ hg ci -Aqm 'add x and y'
451 461 $ hg mv x z
452 462 $ hg ci -qm 'rename x to z'
453 463 $ hg co -q 0
454 464 $ hg mv y z
455 465 $ hg ci -qm 'rename y to z'
456 466 $ echo z >> z
457 467 $ hg ci -m 'modify z'
458 468 $ hg merge -q 1
459 469 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
460 470 [1]
461 471 $ echo z > z
462 472 $ hg resolve -qm z
463 473 $ hg ci -m 'merge 1 into 3'
464 474 Try merging the other direction too
465 475 $ hg co -q 1
466 476 $ hg merge -q 3
467 477 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
468 478 [1]
469 479 $ echo z > z
470 480 $ hg resolve -qm z
471 481 $ hg ci -m 'merge 3 into 1'
472 482 created new head
473 483 $ hg l
474 484 @ 5 merge 3 into 1
475 485 |\ y z
476 486 +---o 4 merge 1 into 3
477 487 | |/ x z
478 488 | o 3 modify z
479 489 | | z
480 490 | o 2 rename y to z
481 491 | | y z
482 492 o | 1 rename x to z
483 493 |/ x z
484 494 o 0 add x and y
485 495 x y
486 496 $ hg debugpathcopies 1 4
487 497 $ hg debugpathcopies 2 4
488 498 $ hg debugpathcopies 0 4
489 499 x -> z (filelog !)
490 500 y -> z (compatibility !)
491 501 $ hg debugpathcopies 1 5
492 502 $ hg debugpathcopies 2 5
493 503 $ hg debugpathcopies 0 5
494 504 x -> z
495 505
496 506
497 507 Test for a case in fullcopytracing algorithm where both the merging csets are
498 508 "dirty"; where a dirty cset means that cset is descendant of merge base. This
499 509 test reflect that for this particular case this algorithm correctly find the copies:
500 510
501 511 $ cat >> $HGRCPATH << EOF
502 512 > [experimental]
503 513 > evolution.createmarkers=True
504 514 > evolution.allowunstable=True
505 515 > EOF
506 516
507 517 $ newrepo
508 518 $ echo a > a
509 519 $ hg add a
510 520 $ hg ci -m "added a"
511 521 $ echo b > b
512 522 $ hg add b
513 523 $ hg ci -m "added b"
514 524
515 525 $ hg mv b b1
516 526 $ hg ci -m "rename b to b1"
517 527
518 528 $ hg up ".^"
519 529 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
520 530 $ echo d > d
521 531 $ hg add d
522 532 $ hg ci -m "added d"
523 533 created new head
524 534
525 535 $ echo baba >> b
526 536 $ hg ci --amend -m "added d, modified b"
527 537
528 538 $ hg l --hidden
529 539 @ 4 added d, modified b
530 540 | b d
531 541 | x 3 added d
532 542 |/ d
533 543 | o 2 rename b to b1
534 544 |/ b b1
535 545 o 1 added b
536 546 | b
537 547 o 0 added a
538 548 a
539 549
540 550 Grafting revision 4 on top of revision 2, showing that it respect the rename:
541 551
552 TODO: Make this work with copy info in changesets (probably by writing a
553 changeset-centric version of copies.mergecopies())
554 #if no-changeset
542 555 $ hg up 2 -q
543 556 $ hg graft -r 4 --base 3 --hidden
544 557 grafting 4:af28412ec03c "added d, modified b" (tip)
545 558 merging b1 and b to b1
546 559
547 560 $ hg l -l1 -p
548 561 @ 5 added d, modified b
549 562 | b1
550 563 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1
551 564 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
552 565 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
553 566 @@ -1,1 +1,2 @@
554 567 b
555 568 +baba
556 569
570 #endif
571
557 572 Test to make sure that fullcopytracing algorithm don't fail when both the merging csets are dirty
558 573 (a dirty cset is one who is not the descendant of merge base)
559 574 -------------------------------------------------------------------------------------------------
560 575
561 576 $ newrepo
562 577 $ echo a > a
563 578 $ hg add a
564 579 $ hg ci -m "added a"
565 580 $ echo b > b
566 581 $ hg add b
567 582 $ hg ci -m "added b"
568 583
569 584 $ echo foobar > willconflict
570 585 $ hg add willconflict
571 586 $ hg ci -m "added willconflict"
572 587 $ echo c > c
573 588 $ hg add c
574 589 $ hg ci -m "added c"
575 590
576 591 $ hg l
577 592 @ 3 added c
578 593 | c
579 594 o 2 added willconflict
580 595 | willconflict
581 596 o 1 added b
582 597 | b
583 598 o 0 added a
584 599 a
585 600
586 601 $ hg up ".^^"
587 602 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
588 603 $ echo d > d
589 604 $ hg add d
590 605 $ hg ci -m "added d"
591 606 created new head
592 607
593 608 $ echo barfoo > willconflict
594 609 $ hg add willconflict
595 610 $ hg ci --amend -m "added willconflict and d"
596 611
597 612 $ hg l
598 613 @ 5 added willconflict and d
599 614 | d willconflict
600 615 | o 3 added c
601 616 | | c
602 617 | o 2 added willconflict
603 618 |/ willconflict
604 619 o 1 added b
605 620 | b
606 621 o 0 added a
607 622 a
608 623
609 624 $ hg rebase -r . -d 2 -t :other
610 625 rebasing 5:5018b1509e94 "added willconflict and d" (tip)
611 626
612 627 $ hg up 3 -q
613 628 $ hg l --hidden
614 629 o 6 added willconflict and d
615 630 | d willconflict
616 631 | x 5 added willconflict and d
617 632 | | d willconflict
618 633 | | x 4 added d
619 634 | |/ d
620 635 +---@ 3 added c
621 636 | | c
622 637 o | 2 added willconflict
623 638 |/ willconflict
624 639 o 1 added b
625 640 | b
626 641 o 0 added a
627 642 a
628 643
629 644 Now if we trigger a merge between cset revision 3 and 6 using base revision 4, in this case
630 645 both the merging csets will be dirty as no one is descendent of base revision:
631 646
632 647 $ hg graft -r 6 --base 4 --hidden -t :other
633 648 grafting 6:99802e4f1e46 "added willconflict and d" (tip)
General Comments 0
You need to be logged in to leave comments. Login now