##// END OF EJS Templates
diff: use second filename for --stat reporting on git patches (issue4221)
Matt Mackall -
r20972:4e2fb0ad default
parent child Browse files
Show More
@@ -1,1931 +1,1931 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import cStringIO, email, os, errno, re, posixpath
10 10 import tempfile, zlib, shutil
11 11 # On python2.4 you have to import these by name or they fail to
12 12 # load. This was not a problem on Python 2.7.
13 13 import email.Generator
14 14 import email.Parser
15 15
16 16 from i18n import _
17 17 from node import hex, short
18 18 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19 19
20 20 gitre = re.compile('diff --git a/(.*) b/(.*)')
21 21
22 22 class PatchError(Exception):
23 23 pass
24 24
25 25
26 26 # public functions
27 27
28 28 def split(stream):
29 29 '''return an iterator of individual patches from a stream'''
30 30 def isheader(line, inheader):
31 31 if inheader and line[0] in (' ', '\t'):
32 32 # continuation
33 33 return True
34 34 if line[0] in (' ', '-', '+'):
35 35 # diff line - don't check for header pattern in there
36 36 return False
37 37 l = line.split(': ', 1)
38 38 return len(l) == 2 and ' ' not in l[0]
39 39
40 40 def chunk(lines):
41 41 return cStringIO.StringIO(''.join(lines))
42 42
43 43 def hgsplit(stream, cur):
44 44 inheader = True
45 45
46 46 for line in stream:
47 47 if not line.strip():
48 48 inheader = False
49 49 if not inheader and line.startswith('# HG changeset patch'):
50 50 yield chunk(cur)
51 51 cur = []
52 52 inheader = True
53 53
54 54 cur.append(line)
55 55
56 56 if cur:
57 57 yield chunk(cur)
58 58
59 59 def mboxsplit(stream, cur):
60 60 for line in stream:
61 61 if line.startswith('From '):
62 62 for c in split(chunk(cur[1:])):
63 63 yield c
64 64 cur = []
65 65
66 66 cur.append(line)
67 67
68 68 if cur:
69 69 for c in split(chunk(cur[1:])):
70 70 yield c
71 71
72 72 def mimesplit(stream, cur):
73 73 def msgfp(m):
74 74 fp = cStringIO.StringIO()
75 75 g = email.Generator.Generator(fp, mangle_from_=False)
76 76 g.flatten(m)
77 77 fp.seek(0)
78 78 return fp
79 79
80 80 for line in stream:
81 81 cur.append(line)
82 82 c = chunk(cur)
83 83
84 84 m = email.Parser.Parser().parse(c)
85 85 if not m.is_multipart():
86 86 yield msgfp(m)
87 87 else:
88 88 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
89 89 for part in m.walk():
90 90 ct = part.get_content_type()
91 91 if ct not in ok_types:
92 92 continue
93 93 yield msgfp(part)
94 94
95 95 def headersplit(stream, cur):
96 96 inheader = False
97 97
98 98 for line in stream:
99 99 if not inheader and isheader(line, inheader):
100 100 yield chunk(cur)
101 101 cur = []
102 102 inheader = True
103 103 if inheader and not isheader(line, inheader):
104 104 inheader = False
105 105
106 106 cur.append(line)
107 107
108 108 if cur:
109 109 yield chunk(cur)
110 110
111 111 def remainder(cur):
112 112 yield chunk(cur)
113 113
114 114 class fiter(object):
115 115 def __init__(self, fp):
116 116 self.fp = fp
117 117
118 118 def __iter__(self):
119 119 return self
120 120
121 121 def next(self):
122 122 l = self.fp.readline()
123 123 if not l:
124 124 raise StopIteration
125 125 return l
126 126
127 127 inheader = False
128 128 cur = []
129 129
130 130 mimeheaders = ['content-type']
131 131
132 132 if not util.safehasattr(stream, 'next'):
133 133 # http responses, for example, have readline but not next
134 134 stream = fiter(stream)
135 135
136 136 for line in stream:
137 137 cur.append(line)
138 138 if line.startswith('# HG changeset patch'):
139 139 return hgsplit(stream, cur)
140 140 elif line.startswith('From '):
141 141 return mboxsplit(stream, cur)
142 142 elif isheader(line, inheader):
143 143 inheader = True
144 144 if line.split(':', 1)[0].lower() in mimeheaders:
145 145 # let email parser handle this
146 146 return mimesplit(stream, cur)
147 147 elif line.startswith('--- ') and inheader:
148 148 # No evil headers seen by diff start, split by hand
149 149 return headersplit(stream, cur)
150 150 # Not enough info, keep reading
151 151
152 152 # if we are here, we have a very plain patch
153 153 return remainder(cur)
154 154
155 155 def extract(ui, fileobj):
156 156 '''extract patch from data read from fileobj.
157 157
158 158 patch can be a normal patch or contained in an email message.
159 159
160 160 return tuple (filename, message, user, date, branch, node, p1, p2).
161 161 Any item in the returned tuple can be None. If filename is None,
162 162 fileobj did not contain a patch. Caller must unlink filename when done.'''
163 163
164 164 # attempt to detect the start of a patch
165 165 # (this heuristic is borrowed from quilt)
166 166 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
167 167 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
168 168 r'---[ \t].*?^\+\+\+[ \t]|'
169 169 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
170 170
171 171 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
172 172 tmpfp = os.fdopen(fd, 'w')
173 173 try:
174 174 msg = email.Parser.Parser().parse(fileobj)
175 175
176 176 subject = msg['Subject']
177 177 user = msg['From']
178 178 if not subject and not user:
179 179 # Not an email, restore parsed headers if any
180 180 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
181 181
182 182 # should try to parse msg['Date']
183 183 date = None
184 184 nodeid = None
185 185 branch = None
186 186 parents = []
187 187
188 188 if subject:
189 189 if subject.startswith('[PATCH'):
190 190 pend = subject.find(']')
191 191 if pend >= 0:
192 192 subject = subject[pend + 1:].lstrip()
193 193 subject = re.sub(r'\n[ \t]+', ' ', subject)
194 194 ui.debug('Subject: %s\n' % subject)
195 195 if user:
196 196 ui.debug('From: %s\n' % user)
197 197 diffs_seen = 0
198 198 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
199 199 message = ''
200 200 for part in msg.walk():
201 201 content_type = part.get_content_type()
202 202 ui.debug('Content-Type: %s\n' % content_type)
203 203 if content_type not in ok_types:
204 204 continue
205 205 payload = part.get_payload(decode=True)
206 206 m = diffre.search(payload)
207 207 if m:
208 208 hgpatch = False
209 209 hgpatchheader = False
210 210 ignoretext = False
211 211
212 212 ui.debug('found patch at byte %d\n' % m.start(0))
213 213 diffs_seen += 1
214 214 cfp = cStringIO.StringIO()
215 215 for line in payload[:m.start(0)].splitlines():
216 216 if line.startswith('# HG changeset patch') and not hgpatch:
217 217 ui.debug('patch generated by hg export\n')
218 218 hgpatch = True
219 219 hgpatchheader = True
220 220 # drop earlier commit message content
221 221 cfp.seek(0)
222 222 cfp.truncate()
223 223 subject = None
224 224 elif hgpatchheader:
225 225 if line.startswith('# User '):
226 226 user = line[7:]
227 227 ui.debug('From: %s\n' % user)
228 228 elif line.startswith("# Date "):
229 229 date = line[7:]
230 230 elif line.startswith("# Branch "):
231 231 branch = line[9:]
232 232 elif line.startswith("# Node ID "):
233 233 nodeid = line[10:]
234 234 elif line.startswith("# Parent "):
235 235 parents.append(line[9:].lstrip())
236 236 elif not line.startswith("# "):
237 237 hgpatchheader = False
238 238 elif line == '---':
239 239 ignoretext = True
240 240 if not hgpatchheader and not ignoretext:
241 241 cfp.write(line)
242 242 cfp.write('\n')
243 243 message = cfp.getvalue()
244 244 if tmpfp:
245 245 tmpfp.write(payload)
246 246 if not payload.endswith('\n'):
247 247 tmpfp.write('\n')
248 248 elif not diffs_seen and message and content_type == 'text/plain':
249 249 message += '\n' + payload
250 250 except: # re-raises
251 251 tmpfp.close()
252 252 os.unlink(tmpname)
253 253 raise
254 254
255 255 if subject and not message.startswith(subject):
256 256 message = '%s\n%s' % (subject, message)
257 257 tmpfp.close()
258 258 if not diffs_seen:
259 259 os.unlink(tmpname)
260 260 return None, message, user, date, branch, None, None, None
261 261 p1 = parents and parents.pop(0) or None
262 262 p2 = parents and parents.pop(0) or None
263 263 return tmpname, message, user, date, branch, nodeid, p1, p2
264 264
265 265 class patchmeta(object):
266 266 """Patched file metadata
267 267
268 268 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
269 269 or COPY. 'path' is patched file path. 'oldpath' is set to the
270 270 origin file when 'op' is either COPY or RENAME, None otherwise. If
271 271 file mode is changed, 'mode' is a tuple (islink, isexec) where
272 272 'islink' is True if the file is a symlink and 'isexec' is True if
273 273 the file is executable. Otherwise, 'mode' is None.
274 274 """
275 275 def __init__(self, path):
276 276 self.path = path
277 277 self.oldpath = None
278 278 self.mode = None
279 279 self.op = 'MODIFY'
280 280 self.binary = False
281 281
282 282 def setmode(self, mode):
283 283 islink = mode & 020000
284 284 isexec = mode & 0100
285 285 self.mode = (islink, isexec)
286 286
287 287 def copy(self):
288 288 other = patchmeta(self.path)
289 289 other.oldpath = self.oldpath
290 290 other.mode = self.mode
291 291 other.op = self.op
292 292 other.binary = self.binary
293 293 return other
294 294
295 295 def _ispatchinga(self, afile):
296 296 if afile == '/dev/null':
297 297 return self.op == 'ADD'
298 298 return afile == 'a/' + (self.oldpath or self.path)
299 299
300 300 def _ispatchingb(self, bfile):
301 301 if bfile == '/dev/null':
302 302 return self.op == 'DELETE'
303 303 return bfile == 'b/' + self.path
304 304
305 305 def ispatching(self, afile, bfile):
306 306 return self._ispatchinga(afile) and self._ispatchingb(bfile)
307 307
308 308 def __repr__(self):
309 309 return "<patchmeta %s %r>" % (self.op, self.path)
310 310
311 311 def readgitpatch(lr):
312 312 """extract git-style metadata about patches from <patchname>"""
313 313
314 314 # Filter patch for git information
315 315 gp = None
316 316 gitpatches = []
317 317 for line in lr:
318 318 line = line.rstrip(' \r\n')
319 319 if line.startswith('diff --git a/'):
320 320 m = gitre.match(line)
321 321 if m:
322 322 if gp:
323 323 gitpatches.append(gp)
324 324 dst = m.group(2)
325 325 gp = patchmeta(dst)
326 326 elif gp:
327 327 if line.startswith('--- '):
328 328 gitpatches.append(gp)
329 329 gp = None
330 330 continue
331 331 if line.startswith('rename from '):
332 332 gp.op = 'RENAME'
333 333 gp.oldpath = line[12:]
334 334 elif line.startswith('rename to '):
335 335 gp.path = line[10:]
336 336 elif line.startswith('copy from '):
337 337 gp.op = 'COPY'
338 338 gp.oldpath = line[10:]
339 339 elif line.startswith('copy to '):
340 340 gp.path = line[8:]
341 341 elif line.startswith('deleted file'):
342 342 gp.op = 'DELETE'
343 343 elif line.startswith('new file mode '):
344 344 gp.op = 'ADD'
345 345 gp.setmode(int(line[-6:], 8))
346 346 elif line.startswith('new mode '):
347 347 gp.setmode(int(line[-6:], 8))
348 348 elif line.startswith('GIT binary patch'):
349 349 gp.binary = True
350 350 if gp:
351 351 gitpatches.append(gp)
352 352
353 353 return gitpatches
354 354
355 355 class linereader(object):
356 356 # simple class to allow pushing lines back into the input stream
357 357 def __init__(self, fp):
358 358 self.fp = fp
359 359 self.buf = []
360 360
361 361 def push(self, line):
362 362 if line is not None:
363 363 self.buf.append(line)
364 364
365 365 def readline(self):
366 366 if self.buf:
367 367 l = self.buf[0]
368 368 del self.buf[0]
369 369 return l
370 370 return self.fp.readline()
371 371
372 372 def __iter__(self):
373 373 while True:
374 374 l = self.readline()
375 375 if not l:
376 376 break
377 377 yield l
378 378
379 379 class abstractbackend(object):
380 380 def __init__(self, ui):
381 381 self.ui = ui
382 382
383 383 def getfile(self, fname):
384 384 """Return target file data and flags as a (data, (islink,
385 385 isexec)) tuple.
386 386 """
387 387 raise NotImplementedError
388 388
389 389 def setfile(self, fname, data, mode, copysource):
390 390 """Write data to target file fname and set its mode. mode is a
391 391 (islink, isexec) tuple. If data is None, the file content should
392 392 be left unchanged. If the file is modified after being copied,
393 393 copysource is set to the original file name.
394 394 """
395 395 raise NotImplementedError
396 396
397 397 def unlink(self, fname):
398 398 """Unlink target file."""
399 399 raise NotImplementedError
400 400
401 401 def writerej(self, fname, failed, total, lines):
402 402 """Write rejected lines for fname. total is the number of hunks
403 403 which failed to apply and total the total number of hunks for this
404 404 files.
405 405 """
406 406 pass
407 407
408 408 def exists(self, fname):
409 409 raise NotImplementedError
410 410
411 411 class fsbackend(abstractbackend):
412 412 def __init__(self, ui, basedir):
413 413 super(fsbackend, self).__init__(ui)
414 414 self.opener = scmutil.opener(basedir)
415 415
416 416 def _join(self, f):
417 417 return os.path.join(self.opener.base, f)
418 418
419 419 def getfile(self, fname):
420 420 path = self._join(fname)
421 421 if os.path.islink(path):
422 422 return (os.readlink(path), (True, False))
423 423 isexec = False
424 424 try:
425 425 isexec = os.lstat(path).st_mode & 0100 != 0
426 426 except OSError, e:
427 427 if e.errno != errno.ENOENT:
428 428 raise
429 429 return (self.opener.read(fname), (False, isexec))
430 430
431 431 def setfile(self, fname, data, mode, copysource):
432 432 islink, isexec = mode
433 433 if data is None:
434 434 util.setflags(self._join(fname), islink, isexec)
435 435 return
436 436 if islink:
437 437 self.opener.symlink(data, fname)
438 438 else:
439 439 self.opener.write(fname, data)
440 440 if isexec:
441 441 util.setflags(self._join(fname), False, True)
442 442
443 443 def unlink(self, fname):
444 444 util.unlinkpath(self._join(fname), ignoremissing=True)
445 445
446 446 def writerej(self, fname, failed, total, lines):
447 447 fname = fname + ".rej"
448 448 self.ui.warn(
449 449 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
450 450 (failed, total, fname))
451 451 fp = self.opener(fname, 'w')
452 452 fp.writelines(lines)
453 453 fp.close()
454 454
455 455 def exists(self, fname):
456 456 return os.path.lexists(self._join(fname))
457 457
458 458 class workingbackend(fsbackend):
459 459 def __init__(self, ui, repo, similarity):
460 460 super(workingbackend, self).__init__(ui, repo.root)
461 461 self.repo = repo
462 462 self.similarity = similarity
463 463 self.removed = set()
464 464 self.changed = set()
465 465 self.copied = []
466 466
467 467 def _checkknown(self, fname):
468 468 if self.repo.dirstate[fname] == '?' and self.exists(fname):
469 469 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
470 470
471 471 def setfile(self, fname, data, mode, copysource):
472 472 self._checkknown(fname)
473 473 super(workingbackend, self).setfile(fname, data, mode, copysource)
474 474 if copysource is not None:
475 475 self.copied.append((copysource, fname))
476 476 self.changed.add(fname)
477 477
478 478 def unlink(self, fname):
479 479 self._checkknown(fname)
480 480 super(workingbackend, self).unlink(fname)
481 481 self.removed.add(fname)
482 482 self.changed.add(fname)
483 483
484 484 def close(self):
485 485 wctx = self.repo[None]
486 486 changed = set(self.changed)
487 487 for src, dst in self.copied:
488 488 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
489 489 if self.removed:
490 490 wctx.forget(sorted(self.removed))
491 491 for f in self.removed:
492 492 if f not in self.repo.dirstate:
493 493 # File was deleted and no longer belongs to the
494 494 # dirstate, it was probably marked added then
495 495 # deleted, and should not be considered by
496 496 # marktouched().
497 497 changed.discard(f)
498 498 if changed:
499 499 scmutil.marktouched(self.repo, changed, self.similarity)
500 500 return sorted(self.changed)
501 501
502 502 class filestore(object):
503 503 def __init__(self, maxsize=None):
504 504 self.opener = None
505 505 self.files = {}
506 506 self.created = 0
507 507 self.maxsize = maxsize
508 508 if self.maxsize is None:
509 509 self.maxsize = 4*(2**20)
510 510 self.size = 0
511 511 self.data = {}
512 512
513 513 def setfile(self, fname, data, mode, copied=None):
514 514 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
515 515 self.data[fname] = (data, mode, copied)
516 516 self.size += len(data)
517 517 else:
518 518 if self.opener is None:
519 519 root = tempfile.mkdtemp(prefix='hg-patch-')
520 520 self.opener = scmutil.opener(root)
521 521 # Avoid filename issues with these simple names
522 522 fn = str(self.created)
523 523 self.opener.write(fn, data)
524 524 self.created += 1
525 525 self.files[fname] = (fn, mode, copied)
526 526
527 527 def getfile(self, fname):
528 528 if fname in self.data:
529 529 return self.data[fname]
530 530 if not self.opener or fname not in self.files:
531 531 raise IOError
532 532 fn, mode, copied = self.files[fname]
533 533 return self.opener.read(fn), mode, copied
534 534
535 535 def close(self):
536 536 if self.opener:
537 537 shutil.rmtree(self.opener.base)
538 538
539 539 class repobackend(abstractbackend):
540 540 def __init__(self, ui, repo, ctx, store):
541 541 super(repobackend, self).__init__(ui)
542 542 self.repo = repo
543 543 self.ctx = ctx
544 544 self.store = store
545 545 self.changed = set()
546 546 self.removed = set()
547 547 self.copied = {}
548 548
549 549 def _checkknown(self, fname):
550 550 if fname not in self.ctx:
551 551 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
552 552
553 553 def getfile(self, fname):
554 554 try:
555 555 fctx = self.ctx[fname]
556 556 except error.LookupError:
557 557 raise IOError
558 558 flags = fctx.flags()
559 559 return fctx.data(), ('l' in flags, 'x' in flags)
560 560
561 561 def setfile(self, fname, data, mode, copysource):
562 562 if copysource:
563 563 self._checkknown(copysource)
564 564 if data is None:
565 565 data = self.ctx[fname].data()
566 566 self.store.setfile(fname, data, mode, copysource)
567 567 self.changed.add(fname)
568 568 if copysource:
569 569 self.copied[fname] = copysource
570 570
571 571 def unlink(self, fname):
572 572 self._checkknown(fname)
573 573 self.removed.add(fname)
574 574
575 575 def exists(self, fname):
576 576 return fname in self.ctx
577 577
578 578 def close(self):
579 579 return self.changed | self.removed
580 580
581 581 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
582 582 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
583 583 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
584 584 eolmodes = ['strict', 'crlf', 'lf', 'auto']
585 585
586 586 class patchfile(object):
587 587 def __init__(self, ui, gp, backend, store, eolmode='strict'):
588 588 self.fname = gp.path
589 589 self.eolmode = eolmode
590 590 self.eol = None
591 591 self.backend = backend
592 592 self.ui = ui
593 593 self.lines = []
594 594 self.exists = False
595 595 self.missing = True
596 596 self.mode = gp.mode
597 597 self.copysource = gp.oldpath
598 598 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
599 599 self.remove = gp.op == 'DELETE'
600 600 try:
601 601 if self.copysource is None:
602 602 data, mode = backend.getfile(self.fname)
603 603 self.exists = True
604 604 else:
605 605 data, mode = store.getfile(self.copysource)[:2]
606 606 self.exists = backend.exists(self.fname)
607 607 self.missing = False
608 608 if data:
609 609 self.lines = mdiff.splitnewlines(data)
610 610 if self.mode is None:
611 611 self.mode = mode
612 612 if self.lines:
613 613 # Normalize line endings
614 614 if self.lines[0].endswith('\r\n'):
615 615 self.eol = '\r\n'
616 616 elif self.lines[0].endswith('\n'):
617 617 self.eol = '\n'
618 618 if eolmode != 'strict':
619 619 nlines = []
620 620 for l in self.lines:
621 621 if l.endswith('\r\n'):
622 622 l = l[:-2] + '\n'
623 623 nlines.append(l)
624 624 self.lines = nlines
625 625 except IOError:
626 626 if self.create:
627 627 self.missing = False
628 628 if self.mode is None:
629 629 self.mode = (False, False)
630 630 if self.missing:
631 631 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
632 632
633 633 self.hash = {}
634 634 self.dirty = 0
635 635 self.offset = 0
636 636 self.skew = 0
637 637 self.rej = []
638 638 self.fileprinted = False
639 639 self.printfile(False)
640 640 self.hunks = 0
641 641
642 642 def writelines(self, fname, lines, mode):
643 643 if self.eolmode == 'auto':
644 644 eol = self.eol
645 645 elif self.eolmode == 'crlf':
646 646 eol = '\r\n'
647 647 else:
648 648 eol = '\n'
649 649
650 650 if self.eolmode != 'strict' and eol and eol != '\n':
651 651 rawlines = []
652 652 for l in lines:
653 653 if l and l[-1] == '\n':
654 654 l = l[:-1] + eol
655 655 rawlines.append(l)
656 656 lines = rawlines
657 657
658 658 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
659 659
660 660 def printfile(self, warn):
661 661 if self.fileprinted:
662 662 return
663 663 if warn or self.ui.verbose:
664 664 self.fileprinted = True
665 665 s = _("patching file %s\n") % self.fname
666 666 if warn:
667 667 self.ui.warn(s)
668 668 else:
669 669 self.ui.note(s)
670 670
671 671
672 672 def findlines(self, l, linenum):
673 673 # looks through the hash and finds candidate lines. The
674 674 # result is a list of line numbers sorted based on distance
675 675 # from linenum
676 676
677 677 cand = self.hash.get(l, [])
678 678 if len(cand) > 1:
679 679 # resort our list of potentials forward then back.
680 680 cand.sort(key=lambda x: abs(x - linenum))
681 681 return cand
682 682
683 683 def write_rej(self):
684 684 # our rejects are a little different from patch(1). This always
685 685 # creates rejects in the same form as the original patch. A file
686 686 # header is inserted so that you can run the reject through patch again
687 687 # without having to type the filename.
688 688 if not self.rej:
689 689 return
690 690 base = os.path.basename(self.fname)
691 691 lines = ["--- %s\n+++ %s\n" % (base, base)]
692 692 for x in self.rej:
693 693 for l in x.hunk:
694 694 lines.append(l)
695 695 if l[-1] != '\n':
696 696 lines.append("\n\ No newline at end of file\n")
697 697 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
698 698
699 699 def apply(self, h):
700 700 if not h.complete():
701 701 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
702 702 (h.number, h.desc, len(h.a), h.lena, len(h.b),
703 703 h.lenb))
704 704
705 705 self.hunks += 1
706 706
707 707 if self.missing:
708 708 self.rej.append(h)
709 709 return -1
710 710
711 711 if self.exists and self.create:
712 712 if self.copysource:
713 713 self.ui.warn(_("cannot create %s: destination already "
714 714 "exists\n") % self.fname)
715 715 else:
716 716 self.ui.warn(_("file %s already exists\n") % self.fname)
717 717 self.rej.append(h)
718 718 return -1
719 719
720 720 if isinstance(h, binhunk):
721 721 if self.remove:
722 722 self.backend.unlink(self.fname)
723 723 else:
724 724 l = h.new(self.lines)
725 725 self.lines[:] = l
726 726 self.offset += len(l)
727 727 self.dirty = True
728 728 return 0
729 729
730 730 horig = h
731 731 if (self.eolmode in ('crlf', 'lf')
732 732 or self.eolmode == 'auto' and self.eol):
733 733 # If new eols are going to be normalized, then normalize
734 734 # hunk data before patching. Otherwise, preserve input
735 735 # line-endings.
736 736 h = h.getnormalized()
737 737
738 738 # fast case first, no offsets, no fuzz
739 739 old, oldstart, new, newstart = h.fuzzit(0, False)
740 740 oldstart += self.offset
741 741 orig_start = oldstart
742 742 # if there's skew we want to emit the "(offset %d lines)" even
743 743 # when the hunk cleanly applies at start + skew, so skip the
744 744 # fast case code
745 745 if (self.skew == 0 and
746 746 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
747 747 if self.remove:
748 748 self.backend.unlink(self.fname)
749 749 else:
750 750 self.lines[oldstart:oldstart + len(old)] = new
751 751 self.offset += len(new) - len(old)
752 752 self.dirty = True
753 753 return 0
754 754
755 755 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
756 756 self.hash = {}
757 757 for x, s in enumerate(self.lines):
758 758 self.hash.setdefault(s, []).append(x)
759 759
760 760 for fuzzlen in xrange(3):
761 761 for toponly in [True, False]:
762 762 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
763 763 oldstart = oldstart + self.offset + self.skew
764 764 oldstart = min(oldstart, len(self.lines))
765 765 if old:
766 766 cand = self.findlines(old[0][1:], oldstart)
767 767 else:
768 768 # Only adding lines with no or fuzzed context, just
769 769 # take the skew in account
770 770 cand = [oldstart]
771 771
772 772 for l in cand:
773 773 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
774 774 self.lines[l : l + len(old)] = new
775 775 self.offset += len(new) - len(old)
776 776 self.skew = l - orig_start
777 777 self.dirty = True
778 778 offset = l - orig_start - fuzzlen
779 779 if fuzzlen:
780 780 msg = _("Hunk #%d succeeded at %d "
781 781 "with fuzz %d "
782 782 "(offset %d lines).\n")
783 783 self.printfile(True)
784 784 self.ui.warn(msg %
785 785 (h.number, l + 1, fuzzlen, offset))
786 786 else:
787 787 msg = _("Hunk #%d succeeded at %d "
788 788 "(offset %d lines).\n")
789 789 self.ui.note(msg % (h.number, l + 1, offset))
790 790 return fuzzlen
791 791 self.printfile(True)
792 792 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
793 793 self.rej.append(horig)
794 794 return -1
795 795
796 796 def close(self):
797 797 if self.dirty:
798 798 self.writelines(self.fname, self.lines, self.mode)
799 799 self.write_rej()
800 800 return len(self.rej)
801 801
802 802 class hunk(object):
803 803 def __init__(self, desc, num, lr, context):
804 804 self.number = num
805 805 self.desc = desc
806 806 self.hunk = [desc]
807 807 self.a = []
808 808 self.b = []
809 809 self.starta = self.lena = None
810 810 self.startb = self.lenb = None
811 811 if lr is not None:
812 812 if context:
813 813 self.read_context_hunk(lr)
814 814 else:
815 815 self.read_unified_hunk(lr)
816 816
817 817 def getnormalized(self):
818 818 """Return a copy with line endings normalized to LF."""
819 819
820 820 def normalize(lines):
821 821 nlines = []
822 822 for line in lines:
823 823 if line.endswith('\r\n'):
824 824 line = line[:-2] + '\n'
825 825 nlines.append(line)
826 826 return nlines
827 827
828 828 # Dummy object, it is rebuilt manually
829 829 nh = hunk(self.desc, self.number, None, None)
830 830 nh.number = self.number
831 831 nh.desc = self.desc
832 832 nh.hunk = self.hunk
833 833 nh.a = normalize(self.a)
834 834 nh.b = normalize(self.b)
835 835 nh.starta = self.starta
836 836 nh.startb = self.startb
837 837 nh.lena = self.lena
838 838 nh.lenb = self.lenb
839 839 return nh
840 840
841 841 def read_unified_hunk(self, lr):
842 842 m = unidesc.match(self.desc)
843 843 if not m:
844 844 raise PatchError(_("bad hunk #%d") % self.number)
845 845 self.starta, self.lena, self.startb, self.lenb = m.groups()
846 846 if self.lena is None:
847 847 self.lena = 1
848 848 else:
849 849 self.lena = int(self.lena)
850 850 if self.lenb is None:
851 851 self.lenb = 1
852 852 else:
853 853 self.lenb = int(self.lenb)
854 854 self.starta = int(self.starta)
855 855 self.startb = int(self.startb)
856 856 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
857 857 self.b)
858 858 # if we hit eof before finishing out the hunk, the last line will
859 859 # be zero length. Lets try to fix it up.
860 860 while len(self.hunk[-1]) == 0:
861 861 del self.hunk[-1]
862 862 del self.a[-1]
863 863 del self.b[-1]
864 864 self.lena -= 1
865 865 self.lenb -= 1
866 866 self._fixnewline(lr)
867 867
868 868 def read_context_hunk(self, lr):
869 869 self.desc = lr.readline()
870 870 m = contextdesc.match(self.desc)
871 871 if not m:
872 872 raise PatchError(_("bad hunk #%d") % self.number)
873 873 self.starta, aend = m.groups()
874 874 self.starta = int(self.starta)
875 875 if aend is None:
876 876 aend = self.starta
877 877 self.lena = int(aend) - self.starta
878 878 if self.starta:
879 879 self.lena += 1
880 880 for x in xrange(self.lena):
881 881 l = lr.readline()
882 882 if l.startswith('---'):
883 883 # lines addition, old block is empty
884 884 lr.push(l)
885 885 break
886 886 s = l[2:]
887 887 if l.startswith('- ') or l.startswith('! '):
888 888 u = '-' + s
889 889 elif l.startswith(' '):
890 890 u = ' ' + s
891 891 else:
892 892 raise PatchError(_("bad hunk #%d old text line %d") %
893 893 (self.number, x))
894 894 self.a.append(u)
895 895 self.hunk.append(u)
896 896
897 897 l = lr.readline()
898 898 if l.startswith('\ '):
899 899 s = self.a[-1][:-1]
900 900 self.a[-1] = s
901 901 self.hunk[-1] = s
902 902 l = lr.readline()
903 903 m = contextdesc.match(l)
904 904 if not m:
905 905 raise PatchError(_("bad hunk #%d") % self.number)
906 906 self.startb, bend = m.groups()
907 907 self.startb = int(self.startb)
908 908 if bend is None:
909 909 bend = self.startb
910 910 self.lenb = int(bend) - self.startb
911 911 if self.startb:
912 912 self.lenb += 1
913 913 hunki = 1
914 914 for x in xrange(self.lenb):
915 915 l = lr.readline()
916 916 if l.startswith('\ '):
917 917 # XXX: the only way to hit this is with an invalid line range.
918 918 # The no-eol marker is not counted in the line range, but I
919 919 # guess there are diff(1) out there which behave differently.
920 920 s = self.b[-1][:-1]
921 921 self.b[-1] = s
922 922 self.hunk[hunki - 1] = s
923 923 continue
924 924 if not l:
925 925 # line deletions, new block is empty and we hit EOF
926 926 lr.push(l)
927 927 break
928 928 s = l[2:]
929 929 if l.startswith('+ ') or l.startswith('! '):
930 930 u = '+' + s
931 931 elif l.startswith(' '):
932 932 u = ' ' + s
933 933 elif len(self.b) == 0:
934 934 # line deletions, new block is empty
935 935 lr.push(l)
936 936 break
937 937 else:
938 938 raise PatchError(_("bad hunk #%d old text line %d") %
939 939 (self.number, x))
940 940 self.b.append(s)
941 941 while True:
942 942 if hunki >= len(self.hunk):
943 943 h = ""
944 944 else:
945 945 h = self.hunk[hunki]
946 946 hunki += 1
947 947 if h == u:
948 948 break
949 949 elif h.startswith('-'):
950 950 continue
951 951 else:
952 952 self.hunk.insert(hunki - 1, u)
953 953 break
954 954
955 955 if not self.a:
956 956 # this happens when lines were only added to the hunk
957 957 for x in self.hunk:
958 958 if x.startswith('-') or x.startswith(' '):
959 959 self.a.append(x)
960 960 if not self.b:
961 961 # this happens when lines were only deleted from the hunk
962 962 for x in self.hunk:
963 963 if x.startswith('+') or x.startswith(' '):
964 964 self.b.append(x[1:])
965 965 # @@ -start,len +start,len @@
966 966 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
967 967 self.startb, self.lenb)
968 968 self.hunk[0] = self.desc
969 969 self._fixnewline(lr)
970 970
971 971 def _fixnewline(self, lr):
972 972 l = lr.readline()
973 973 if l.startswith('\ '):
974 974 diffhelpers.fix_newline(self.hunk, self.a, self.b)
975 975 else:
976 976 lr.push(l)
977 977
978 978 def complete(self):
979 979 return len(self.a) == self.lena and len(self.b) == self.lenb
980 980
981 981 def _fuzzit(self, old, new, fuzz, toponly):
982 982 # this removes context lines from the top and bottom of list 'l'. It
983 983 # checks the hunk to make sure only context lines are removed, and then
984 984 # returns a new shortened list of lines.
985 985 fuzz = min(fuzz, len(old))
986 986 if fuzz:
987 987 top = 0
988 988 bot = 0
989 989 hlen = len(self.hunk)
990 990 for x in xrange(hlen - 1):
991 991 # the hunk starts with the @@ line, so use x+1
992 992 if self.hunk[x + 1][0] == ' ':
993 993 top += 1
994 994 else:
995 995 break
996 996 if not toponly:
997 997 for x in xrange(hlen - 1):
998 998 if self.hunk[hlen - bot - 1][0] == ' ':
999 999 bot += 1
1000 1000 else:
1001 1001 break
1002 1002
1003 1003 bot = min(fuzz, bot)
1004 1004 top = min(fuzz, top)
1005 1005 return old[top:len(old) - bot], new[top:len(new) - bot], top
1006 1006 return old, new, 0
1007 1007
1008 1008 def fuzzit(self, fuzz, toponly):
1009 1009 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1010 1010 oldstart = self.starta + top
1011 1011 newstart = self.startb + top
1012 1012 # zero length hunk ranges already have their start decremented
1013 1013 if self.lena and oldstart > 0:
1014 1014 oldstart -= 1
1015 1015 if self.lenb and newstart > 0:
1016 1016 newstart -= 1
1017 1017 return old, oldstart, new, newstart
1018 1018
1019 1019 class binhunk(object):
1020 1020 'A binary patch file.'
1021 1021 def __init__(self, lr, fname):
1022 1022 self.text = None
1023 1023 self.delta = False
1024 1024 self.hunk = ['GIT binary patch\n']
1025 1025 self._fname = fname
1026 1026 self._read(lr)
1027 1027
1028 1028 def complete(self):
1029 1029 return self.text is not None
1030 1030
1031 1031 def new(self, lines):
1032 1032 if self.delta:
1033 1033 return [applybindelta(self.text, ''.join(lines))]
1034 1034 return [self.text]
1035 1035
1036 1036 def _read(self, lr):
1037 1037 def getline(lr, hunk):
1038 1038 l = lr.readline()
1039 1039 hunk.append(l)
1040 1040 return l.rstrip('\r\n')
1041 1041
1042 1042 size = 0
1043 1043 while True:
1044 1044 line = getline(lr, self.hunk)
1045 1045 if not line:
1046 1046 raise PatchError(_('could not extract "%s" binary data')
1047 1047 % self._fname)
1048 1048 if line.startswith('literal '):
1049 1049 size = int(line[8:].rstrip())
1050 1050 break
1051 1051 if line.startswith('delta '):
1052 1052 size = int(line[6:].rstrip())
1053 1053 self.delta = True
1054 1054 break
1055 1055 dec = []
1056 1056 line = getline(lr, self.hunk)
1057 1057 while len(line) > 1:
1058 1058 l = line[0]
1059 1059 if l <= 'Z' and l >= 'A':
1060 1060 l = ord(l) - ord('A') + 1
1061 1061 else:
1062 1062 l = ord(l) - ord('a') + 27
1063 1063 try:
1064 1064 dec.append(base85.b85decode(line[1:])[:l])
1065 1065 except ValueError, e:
1066 1066 raise PatchError(_('could not decode "%s" binary patch: %s')
1067 1067 % (self._fname, str(e)))
1068 1068 line = getline(lr, self.hunk)
1069 1069 text = zlib.decompress(''.join(dec))
1070 1070 if len(text) != size:
1071 1071 raise PatchError(_('"%s" length is %d bytes, should be %d')
1072 1072 % (self._fname, len(text), size))
1073 1073 self.text = text
1074 1074
1075 1075 def parsefilename(str):
1076 1076 # --- filename \t|space stuff
1077 1077 s = str[4:].rstrip('\r\n')
1078 1078 i = s.find('\t')
1079 1079 if i < 0:
1080 1080 i = s.find(' ')
1081 1081 if i < 0:
1082 1082 return s
1083 1083 return s[:i]
1084 1084
1085 1085 def pathstrip(path, strip):
1086 1086 pathlen = len(path)
1087 1087 i = 0
1088 1088 if strip == 0:
1089 1089 return '', path.rstrip()
1090 1090 count = strip
1091 1091 while count > 0:
1092 1092 i = path.find('/', i)
1093 1093 if i == -1:
1094 1094 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1095 1095 (count, strip, path))
1096 1096 i += 1
1097 1097 # consume '//' in the path
1098 1098 while i < pathlen - 1 and path[i] == '/':
1099 1099 i += 1
1100 1100 count -= 1
1101 1101 return path[:i].lstrip(), path[i:].rstrip()
1102 1102
1103 1103 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1104 1104 nulla = afile_orig == "/dev/null"
1105 1105 nullb = bfile_orig == "/dev/null"
1106 1106 create = nulla and hunk.starta == 0 and hunk.lena == 0
1107 1107 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1108 1108 abase, afile = pathstrip(afile_orig, strip)
1109 1109 gooda = not nulla and backend.exists(afile)
1110 1110 bbase, bfile = pathstrip(bfile_orig, strip)
1111 1111 if afile == bfile:
1112 1112 goodb = gooda
1113 1113 else:
1114 1114 goodb = not nullb and backend.exists(bfile)
1115 1115 missing = not goodb and not gooda and not create
1116 1116
1117 1117 # some diff programs apparently produce patches where the afile is
1118 1118 # not /dev/null, but afile starts with bfile
1119 1119 abasedir = afile[:afile.rfind('/') + 1]
1120 1120 bbasedir = bfile[:bfile.rfind('/') + 1]
1121 1121 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1122 1122 and hunk.starta == 0 and hunk.lena == 0):
1123 1123 create = True
1124 1124 missing = False
1125 1125
1126 1126 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1127 1127 # diff is between a file and its backup. In this case, the original
1128 1128 # file should be patched (see original mpatch code).
1129 1129 isbackup = (abase == bbase and bfile.startswith(afile))
1130 1130 fname = None
1131 1131 if not missing:
1132 1132 if gooda and goodb:
1133 1133 fname = isbackup and afile or bfile
1134 1134 elif gooda:
1135 1135 fname = afile
1136 1136
1137 1137 if not fname:
1138 1138 if not nullb:
1139 1139 fname = isbackup and afile or bfile
1140 1140 elif not nulla:
1141 1141 fname = afile
1142 1142 else:
1143 1143 raise PatchError(_("undefined source and destination files"))
1144 1144
1145 1145 gp = patchmeta(fname)
1146 1146 if create:
1147 1147 gp.op = 'ADD'
1148 1148 elif remove:
1149 1149 gp.op = 'DELETE'
1150 1150 return gp
1151 1151
1152 1152 def scangitpatch(lr, firstline):
1153 1153 """
1154 1154 Git patches can emit:
1155 1155 - rename a to b
1156 1156 - change b
1157 1157 - copy a to c
1158 1158 - change c
1159 1159
1160 1160 We cannot apply this sequence as-is, the renamed 'a' could not be
1161 1161 found for it would have been renamed already. And we cannot copy
1162 1162 from 'b' instead because 'b' would have been changed already. So
1163 1163 we scan the git patch for copy and rename commands so we can
1164 1164 perform the copies ahead of time.
1165 1165 """
1166 1166 pos = 0
1167 1167 try:
1168 1168 pos = lr.fp.tell()
1169 1169 fp = lr.fp
1170 1170 except IOError:
1171 1171 fp = cStringIO.StringIO(lr.fp.read())
1172 1172 gitlr = linereader(fp)
1173 1173 gitlr.push(firstline)
1174 1174 gitpatches = readgitpatch(gitlr)
1175 1175 fp.seek(pos)
1176 1176 return gitpatches
1177 1177
1178 1178 def iterhunks(fp):
1179 1179 """Read a patch and yield the following events:
1180 1180 - ("file", afile, bfile, firsthunk): select a new target file.
1181 1181 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1182 1182 "file" event.
1183 1183 - ("git", gitchanges): current diff is in git format, gitchanges
1184 1184 maps filenames to gitpatch records. Unique event.
1185 1185 """
1186 1186 afile = ""
1187 1187 bfile = ""
1188 1188 state = None
1189 1189 hunknum = 0
1190 1190 emitfile = newfile = False
1191 1191 gitpatches = None
1192 1192
1193 1193 # our states
1194 1194 BFILE = 1
1195 1195 context = None
1196 1196 lr = linereader(fp)
1197 1197
1198 1198 while True:
1199 1199 x = lr.readline()
1200 1200 if not x:
1201 1201 break
1202 1202 if state == BFILE and (
1203 1203 (not context and x[0] == '@')
1204 1204 or (context is not False and x.startswith('***************'))
1205 1205 or x.startswith('GIT binary patch')):
1206 1206 gp = None
1207 1207 if (gitpatches and
1208 1208 gitpatches[-1].ispatching(afile, bfile)):
1209 1209 gp = gitpatches.pop()
1210 1210 if x.startswith('GIT binary patch'):
1211 1211 h = binhunk(lr, gp.path)
1212 1212 else:
1213 1213 if context is None and x.startswith('***************'):
1214 1214 context = True
1215 1215 h = hunk(x, hunknum + 1, lr, context)
1216 1216 hunknum += 1
1217 1217 if emitfile:
1218 1218 emitfile = False
1219 1219 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1220 1220 yield 'hunk', h
1221 1221 elif x.startswith('diff --git a/'):
1222 1222 m = gitre.match(x.rstrip(' \r\n'))
1223 1223 if not m:
1224 1224 continue
1225 1225 if gitpatches is None:
1226 1226 # scan whole input for git metadata
1227 1227 gitpatches = scangitpatch(lr, x)
1228 1228 yield 'git', [g.copy() for g in gitpatches
1229 1229 if g.op in ('COPY', 'RENAME')]
1230 1230 gitpatches.reverse()
1231 1231 afile = 'a/' + m.group(1)
1232 1232 bfile = 'b/' + m.group(2)
1233 1233 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1234 1234 gp = gitpatches.pop()
1235 1235 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1236 1236 if not gitpatches:
1237 1237 raise PatchError(_('failed to synchronize metadata for "%s"')
1238 1238 % afile[2:])
1239 1239 gp = gitpatches[-1]
1240 1240 newfile = True
1241 1241 elif x.startswith('---'):
1242 1242 # check for a unified diff
1243 1243 l2 = lr.readline()
1244 1244 if not l2.startswith('+++'):
1245 1245 lr.push(l2)
1246 1246 continue
1247 1247 newfile = True
1248 1248 context = False
1249 1249 afile = parsefilename(x)
1250 1250 bfile = parsefilename(l2)
1251 1251 elif x.startswith('***'):
1252 1252 # check for a context diff
1253 1253 l2 = lr.readline()
1254 1254 if not l2.startswith('---'):
1255 1255 lr.push(l2)
1256 1256 continue
1257 1257 l3 = lr.readline()
1258 1258 lr.push(l3)
1259 1259 if not l3.startswith("***************"):
1260 1260 lr.push(l2)
1261 1261 continue
1262 1262 newfile = True
1263 1263 context = True
1264 1264 afile = parsefilename(x)
1265 1265 bfile = parsefilename(l2)
1266 1266
1267 1267 if newfile:
1268 1268 newfile = False
1269 1269 emitfile = True
1270 1270 state = BFILE
1271 1271 hunknum = 0
1272 1272
1273 1273 while gitpatches:
1274 1274 gp = gitpatches.pop()
1275 1275 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1276 1276
1277 1277 def applybindelta(binchunk, data):
1278 1278 """Apply a binary delta hunk
1279 1279 The algorithm used is the algorithm from git's patch-delta.c
1280 1280 """
1281 1281 def deltahead(binchunk):
1282 1282 i = 0
1283 1283 for c in binchunk:
1284 1284 i += 1
1285 1285 if not (ord(c) & 0x80):
1286 1286 return i
1287 1287 return i
1288 1288 out = ""
1289 1289 s = deltahead(binchunk)
1290 1290 binchunk = binchunk[s:]
1291 1291 s = deltahead(binchunk)
1292 1292 binchunk = binchunk[s:]
1293 1293 i = 0
1294 1294 while i < len(binchunk):
1295 1295 cmd = ord(binchunk[i])
1296 1296 i += 1
1297 1297 if (cmd & 0x80):
1298 1298 offset = 0
1299 1299 size = 0
1300 1300 if (cmd & 0x01):
1301 1301 offset = ord(binchunk[i])
1302 1302 i += 1
1303 1303 if (cmd & 0x02):
1304 1304 offset |= ord(binchunk[i]) << 8
1305 1305 i += 1
1306 1306 if (cmd & 0x04):
1307 1307 offset |= ord(binchunk[i]) << 16
1308 1308 i += 1
1309 1309 if (cmd & 0x08):
1310 1310 offset |= ord(binchunk[i]) << 24
1311 1311 i += 1
1312 1312 if (cmd & 0x10):
1313 1313 size = ord(binchunk[i])
1314 1314 i += 1
1315 1315 if (cmd & 0x20):
1316 1316 size |= ord(binchunk[i]) << 8
1317 1317 i += 1
1318 1318 if (cmd & 0x40):
1319 1319 size |= ord(binchunk[i]) << 16
1320 1320 i += 1
1321 1321 if size == 0:
1322 1322 size = 0x10000
1323 1323 offset_end = offset + size
1324 1324 out += data[offset:offset_end]
1325 1325 elif cmd != 0:
1326 1326 offset_end = i + cmd
1327 1327 out += binchunk[i:offset_end]
1328 1328 i += cmd
1329 1329 else:
1330 1330 raise PatchError(_('unexpected delta opcode 0'))
1331 1331 return out
1332 1332
1333 1333 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1334 1334 """Reads a patch from fp and tries to apply it.
1335 1335
1336 1336 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1337 1337 there was any fuzz.
1338 1338
1339 1339 If 'eolmode' is 'strict', the patch content and patched file are
1340 1340 read in binary mode. Otherwise, line endings are ignored when
1341 1341 patching then normalized according to 'eolmode'.
1342 1342 """
1343 1343 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1344 1344 eolmode=eolmode)
1345 1345
1346 1346 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1347 1347 eolmode='strict'):
1348 1348
1349 1349 def pstrip(p):
1350 1350 return pathstrip(p, strip - 1)[1]
1351 1351
1352 1352 rejects = 0
1353 1353 err = 0
1354 1354 current_file = None
1355 1355
1356 1356 for state, values in iterhunks(fp):
1357 1357 if state == 'hunk':
1358 1358 if not current_file:
1359 1359 continue
1360 1360 ret = current_file.apply(values)
1361 1361 if ret > 0:
1362 1362 err = 1
1363 1363 elif state == 'file':
1364 1364 if current_file:
1365 1365 rejects += current_file.close()
1366 1366 current_file = None
1367 1367 afile, bfile, first_hunk, gp = values
1368 1368 if gp:
1369 1369 gp.path = pstrip(gp.path)
1370 1370 if gp.oldpath:
1371 1371 gp.oldpath = pstrip(gp.oldpath)
1372 1372 else:
1373 1373 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1374 1374 if gp.op == 'RENAME':
1375 1375 backend.unlink(gp.oldpath)
1376 1376 if not first_hunk:
1377 1377 if gp.op == 'DELETE':
1378 1378 backend.unlink(gp.path)
1379 1379 continue
1380 1380 data, mode = None, None
1381 1381 if gp.op in ('RENAME', 'COPY'):
1382 1382 data, mode = store.getfile(gp.oldpath)[:2]
1383 1383 if gp.mode:
1384 1384 mode = gp.mode
1385 1385 if gp.op == 'ADD':
1386 1386 # Added files without content have no hunk and
1387 1387 # must be created
1388 1388 data = ''
1389 1389 if data or mode:
1390 1390 if (gp.op in ('ADD', 'RENAME', 'COPY')
1391 1391 and backend.exists(gp.path)):
1392 1392 raise PatchError(_("cannot create %s: destination "
1393 1393 "already exists") % gp.path)
1394 1394 backend.setfile(gp.path, data, mode, gp.oldpath)
1395 1395 continue
1396 1396 try:
1397 1397 current_file = patcher(ui, gp, backend, store,
1398 1398 eolmode=eolmode)
1399 1399 except PatchError, inst:
1400 1400 ui.warn(str(inst) + '\n')
1401 1401 current_file = None
1402 1402 rejects += 1
1403 1403 continue
1404 1404 elif state == 'git':
1405 1405 for gp in values:
1406 1406 path = pstrip(gp.oldpath)
1407 1407 try:
1408 1408 data, mode = backend.getfile(path)
1409 1409 except IOError, e:
1410 1410 if e.errno != errno.ENOENT:
1411 1411 raise
1412 1412 # The error ignored here will trigger a getfile()
1413 1413 # error in a place more appropriate for error
1414 1414 # handling, and will not interrupt the patching
1415 1415 # process.
1416 1416 else:
1417 1417 store.setfile(path, data, mode)
1418 1418 else:
1419 1419 raise util.Abort(_('unsupported parser state: %s') % state)
1420 1420
1421 1421 if current_file:
1422 1422 rejects += current_file.close()
1423 1423
1424 1424 if rejects:
1425 1425 return -1
1426 1426 return err
1427 1427
1428 1428 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1429 1429 similarity):
1430 1430 """use <patcher> to apply <patchname> to the working directory.
1431 1431 returns whether patch was applied with fuzz factor."""
1432 1432
1433 1433 fuzz = False
1434 1434 args = []
1435 1435 cwd = repo.root
1436 1436 if cwd:
1437 1437 args.append('-d %s' % util.shellquote(cwd))
1438 1438 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1439 1439 util.shellquote(patchname)))
1440 1440 try:
1441 1441 for line in fp:
1442 1442 line = line.rstrip()
1443 1443 ui.note(line + '\n')
1444 1444 if line.startswith('patching file '):
1445 1445 pf = util.parsepatchoutput(line)
1446 1446 printed_file = False
1447 1447 files.add(pf)
1448 1448 elif line.find('with fuzz') >= 0:
1449 1449 fuzz = True
1450 1450 if not printed_file:
1451 1451 ui.warn(pf + '\n')
1452 1452 printed_file = True
1453 1453 ui.warn(line + '\n')
1454 1454 elif line.find('saving rejects to file') >= 0:
1455 1455 ui.warn(line + '\n')
1456 1456 elif line.find('FAILED') >= 0:
1457 1457 if not printed_file:
1458 1458 ui.warn(pf + '\n')
1459 1459 printed_file = True
1460 1460 ui.warn(line + '\n')
1461 1461 finally:
1462 1462 if files:
1463 1463 scmutil.marktouched(repo, files, similarity)
1464 1464 code = fp.close()
1465 1465 if code:
1466 1466 raise PatchError(_("patch command failed: %s") %
1467 1467 util.explainexit(code)[0])
1468 1468 return fuzz
1469 1469
1470 1470 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1471 1471 if files is None:
1472 1472 files = set()
1473 1473 if eolmode is None:
1474 1474 eolmode = ui.config('patch', 'eol', 'strict')
1475 1475 if eolmode.lower() not in eolmodes:
1476 1476 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1477 1477 eolmode = eolmode.lower()
1478 1478
1479 1479 store = filestore()
1480 1480 try:
1481 1481 fp = open(patchobj, 'rb')
1482 1482 except TypeError:
1483 1483 fp = patchobj
1484 1484 try:
1485 1485 ret = applydiff(ui, fp, backend, store, strip=strip,
1486 1486 eolmode=eolmode)
1487 1487 finally:
1488 1488 if fp != patchobj:
1489 1489 fp.close()
1490 1490 files.update(backend.close())
1491 1491 store.close()
1492 1492 if ret < 0:
1493 1493 raise PatchError(_('patch failed to apply'))
1494 1494 return ret > 0
1495 1495
1496 1496 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1497 1497 similarity=0):
1498 1498 """use builtin patch to apply <patchobj> to the working directory.
1499 1499 returns whether patch was applied with fuzz factor."""
1500 1500 backend = workingbackend(ui, repo, similarity)
1501 1501 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1502 1502
1503 1503 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1504 1504 eolmode='strict'):
1505 1505 backend = repobackend(ui, repo, ctx, store)
1506 1506 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1507 1507
1508 1508 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1509 1509 similarity=0):
1510 1510 """Apply <patchname> to the working directory.
1511 1511
1512 1512 'eolmode' specifies how end of lines should be handled. It can be:
1513 1513 - 'strict': inputs are read in binary mode, EOLs are preserved
1514 1514 - 'crlf': EOLs are ignored when patching and reset to CRLF
1515 1515 - 'lf': EOLs are ignored when patching and reset to LF
1516 1516 - None: get it from user settings, default to 'strict'
1517 1517 'eolmode' is ignored when using an external patcher program.
1518 1518
1519 1519 Returns whether patch was applied with fuzz factor.
1520 1520 """
1521 1521 patcher = ui.config('ui', 'patch')
1522 1522 if files is None:
1523 1523 files = set()
1524 1524 try:
1525 1525 if patcher:
1526 1526 return _externalpatch(ui, repo, patcher, patchname, strip,
1527 1527 files, similarity)
1528 1528 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1529 1529 similarity)
1530 1530 except PatchError, err:
1531 1531 raise util.Abort(str(err))
1532 1532
1533 1533 def changedfiles(ui, repo, patchpath, strip=1):
1534 1534 backend = fsbackend(ui, repo.root)
1535 1535 fp = open(patchpath, 'rb')
1536 1536 try:
1537 1537 changed = set()
1538 1538 for state, values in iterhunks(fp):
1539 1539 if state == 'file':
1540 1540 afile, bfile, first_hunk, gp = values
1541 1541 if gp:
1542 1542 gp.path = pathstrip(gp.path, strip - 1)[1]
1543 1543 if gp.oldpath:
1544 1544 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1545 1545 else:
1546 1546 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1547 1547 changed.add(gp.path)
1548 1548 if gp.op == 'RENAME':
1549 1549 changed.add(gp.oldpath)
1550 1550 elif state not in ('hunk', 'git'):
1551 1551 raise util.Abort(_('unsupported parser state: %s') % state)
1552 1552 return changed
1553 1553 finally:
1554 1554 fp.close()
1555 1555
1556 1556 class GitDiffRequired(Exception):
1557 1557 pass
1558 1558
1559 1559 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1560 1560 def get(key, name=None, getter=ui.configbool):
1561 1561 return ((opts and opts.get(key)) or
1562 1562 getter(section, name or key, None, untrusted=untrusted))
1563 1563 return mdiff.diffopts(
1564 1564 text=opts and opts.get('text'),
1565 1565 git=get('git'),
1566 1566 nodates=get('nodates'),
1567 1567 showfunc=get('show_function', 'showfunc'),
1568 1568 ignorews=get('ignore_all_space', 'ignorews'),
1569 1569 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1570 1570 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1571 1571 context=get('unified', getter=ui.config))
1572 1572
1573 1573 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1574 1574 losedatafn=None, prefix=''):
1575 1575 '''yields diff of changes to files between two nodes, or node and
1576 1576 working directory.
1577 1577
1578 1578 if node1 is None, use first dirstate parent instead.
1579 1579 if node2 is None, compare node1 with working directory.
1580 1580
1581 1581 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1582 1582 every time some change cannot be represented with the current
1583 1583 patch format. Return False to upgrade to git patch format, True to
1584 1584 accept the loss or raise an exception to abort the diff. It is
1585 1585 called with the name of current file being diffed as 'fn'. If set
1586 1586 to None, patches will always be upgraded to git format when
1587 1587 necessary.
1588 1588
1589 1589 prefix is a filename prefix that is prepended to all filenames on
1590 1590 display (used for subrepos).
1591 1591 '''
1592 1592
1593 1593 if opts is None:
1594 1594 opts = mdiff.defaultopts
1595 1595
1596 1596 if not node1 and not node2:
1597 1597 node1 = repo.dirstate.p1()
1598 1598
1599 1599 def lrugetfilectx():
1600 1600 cache = {}
1601 1601 order = util.deque()
1602 1602 def getfilectx(f, ctx):
1603 1603 fctx = ctx.filectx(f, filelog=cache.get(f))
1604 1604 if f not in cache:
1605 1605 if len(cache) > 20:
1606 1606 del cache[order.popleft()]
1607 1607 cache[f] = fctx.filelog()
1608 1608 else:
1609 1609 order.remove(f)
1610 1610 order.append(f)
1611 1611 return fctx
1612 1612 return getfilectx
1613 1613 getfilectx = lrugetfilectx()
1614 1614
1615 1615 ctx1 = repo[node1]
1616 1616 ctx2 = repo[node2]
1617 1617
1618 1618 if not changes:
1619 1619 changes = repo.status(ctx1, ctx2, match=match)
1620 1620 modified, added, removed = changes[:3]
1621 1621
1622 1622 if not modified and not added and not removed:
1623 1623 return []
1624 1624
1625 1625 revs = None
1626 1626 hexfunc = repo.ui.debugflag and hex or short
1627 1627 revs = [hexfunc(node) for node in [node1, node2] if node]
1628 1628
1629 1629 copy = {}
1630 1630 if opts.git or opts.upgrade:
1631 1631 copy = copies.pathcopies(ctx1, ctx2)
1632 1632
1633 1633 def difffn(opts, losedata):
1634 1634 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1635 1635 copy, getfilectx, opts, losedata, prefix)
1636 1636 if opts.upgrade and not opts.git:
1637 1637 try:
1638 1638 def losedata(fn):
1639 1639 if not losedatafn or not losedatafn(fn=fn):
1640 1640 raise GitDiffRequired
1641 1641 # Buffer the whole output until we are sure it can be generated
1642 1642 return list(difffn(opts.copy(git=False), losedata))
1643 1643 except GitDiffRequired:
1644 1644 return difffn(opts.copy(git=True), None)
1645 1645 else:
1646 1646 return difffn(opts, None)
1647 1647
1648 1648 def difflabel(func, *args, **kw):
1649 1649 '''yields 2-tuples of (output, label) based on the output of func()'''
1650 1650 headprefixes = [('diff', 'diff.diffline'),
1651 1651 ('copy', 'diff.extended'),
1652 1652 ('rename', 'diff.extended'),
1653 1653 ('old', 'diff.extended'),
1654 1654 ('new', 'diff.extended'),
1655 1655 ('deleted', 'diff.extended'),
1656 1656 ('---', 'diff.file_a'),
1657 1657 ('+++', 'diff.file_b')]
1658 1658 textprefixes = [('@', 'diff.hunk'),
1659 1659 ('-', 'diff.deleted'),
1660 1660 ('+', 'diff.inserted')]
1661 1661 head = False
1662 1662 for chunk in func(*args, **kw):
1663 1663 lines = chunk.split('\n')
1664 1664 for i, line in enumerate(lines):
1665 1665 if i != 0:
1666 1666 yield ('\n', '')
1667 1667 if head:
1668 1668 if line.startswith('@'):
1669 1669 head = False
1670 1670 else:
1671 1671 if line and line[0] not in ' +-@\\':
1672 1672 head = True
1673 1673 stripline = line
1674 1674 if not head and line and line[0] in '+-':
1675 1675 # highlight trailing whitespace, but only in changed lines
1676 1676 stripline = line.rstrip()
1677 1677 prefixes = textprefixes
1678 1678 if head:
1679 1679 prefixes = headprefixes
1680 1680 for prefix, label in prefixes:
1681 1681 if stripline.startswith(prefix):
1682 1682 yield (stripline, label)
1683 1683 break
1684 1684 else:
1685 1685 yield (line, '')
1686 1686 if line != stripline:
1687 1687 yield (line[len(stripline):], 'diff.trailingwhitespace')
1688 1688
1689 1689 def diffui(*args, **kw):
1690 1690 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1691 1691 return difflabel(diff, *args, **kw)
1692 1692
1693 1693 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1694 1694 copy, getfilectx, opts, losedatafn, prefix):
1695 1695
1696 1696 def join(f):
1697 1697 return posixpath.join(prefix, f)
1698 1698
1699 1699 def addmodehdr(header, omode, nmode):
1700 1700 if omode != nmode:
1701 1701 header.append('old mode %s\n' % omode)
1702 1702 header.append('new mode %s\n' % nmode)
1703 1703
1704 1704 def addindexmeta(meta, revs):
1705 1705 if opts.git:
1706 1706 i = len(revs)
1707 1707 if i==2:
1708 1708 meta.append('index %s..%s\n' % tuple(revs))
1709 1709 elif i==3:
1710 1710 meta.append('index %s,%s..%s\n' % tuple(revs))
1711 1711
1712 1712 def gitindex(text):
1713 1713 if not text:
1714 1714 text = ""
1715 1715 l = len(text)
1716 1716 s = util.sha1('blob %d\0' % l)
1717 1717 s.update(text)
1718 1718 return s.hexdigest()
1719 1719
1720 1720 def diffline(a, b, revs):
1721 1721 if opts.git:
1722 1722 line = 'diff --git a/%s b/%s\n' % (a, b)
1723 1723 elif not repo.ui.quiet:
1724 1724 if revs:
1725 1725 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1726 1726 line = 'diff %s %s\n' % (revinfo, a)
1727 1727 else:
1728 1728 line = 'diff %s\n' % a
1729 1729 else:
1730 1730 line = ''
1731 1731 return line
1732 1732
1733 1733 date1 = util.datestr(ctx1.date())
1734 1734 man1 = ctx1.manifest()
1735 1735
1736 1736 gone = set()
1737 1737 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1738 1738
1739 1739 copyto = dict([(v, k) for k, v in copy.items()])
1740 1740
1741 1741 if opts.git:
1742 1742 revs = None
1743 1743
1744 1744 for f in sorted(modified + added + removed):
1745 1745 to = None
1746 1746 tn = None
1747 1747 dodiff = True
1748 1748 header = []
1749 1749 if f in man1:
1750 1750 to = getfilectx(f, ctx1).data()
1751 1751 if f not in removed:
1752 1752 tn = getfilectx(f, ctx2).data()
1753 1753 a, b = f, f
1754 1754 if opts.git or losedatafn:
1755 1755 if f in added or (f in modified and to is None):
1756 1756 mode = gitmode[ctx2.flags(f)]
1757 1757 if f in copy or f in copyto:
1758 1758 if opts.git:
1759 1759 if f in copy:
1760 1760 a = copy[f]
1761 1761 else:
1762 1762 a = copyto[f]
1763 1763 omode = gitmode[man1.flags(a)]
1764 1764 addmodehdr(header, omode, mode)
1765 1765 if a in removed and a not in gone:
1766 1766 op = 'rename'
1767 1767 gone.add(a)
1768 1768 else:
1769 1769 op = 'copy'
1770 1770 header.append('%s from %s\n' % (op, join(a)))
1771 1771 header.append('%s to %s\n' % (op, join(f)))
1772 1772 to = getfilectx(a, ctx1).data()
1773 1773 else:
1774 1774 losedatafn(f)
1775 1775 else:
1776 1776 if opts.git:
1777 1777 header.append('new file mode %s\n' % mode)
1778 1778 elif ctx2.flags(f):
1779 1779 losedatafn(f)
1780 1780 # In theory, if tn was copied or renamed we should check
1781 1781 # if the source is binary too but the copy record already
1782 1782 # forces git mode.
1783 1783 if util.binary(tn):
1784 1784 if opts.git:
1785 1785 dodiff = 'binary'
1786 1786 else:
1787 1787 losedatafn(f)
1788 1788 if not opts.git and not tn:
1789 1789 # regular diffs cannot represent new empty file
1790 1790 losedatafn(f)
1791 1791 elif f in removed or (f in modified and tn is None):
1792 1792 if opts.git:
1793 1793 # have we already reported a copy above?
1794 1794 if ((f in copy and copy[f] in added
1795 1795 and copyto[copy[f]] == f) or
1796 1796 (f in copyto and copyto[f] in added
1797 1797 and copy[copyto[f]] == f)):
1798 1798 dodiff = False
1799 1799 else:
1800 1800 header.append('deleted file mode %s\n' %
1801 1801 gitmode[man1.flags(f)])
1802 1802 if util.binary(to):
1803 1803 dodiff = 'binary'
1804 1804 elif not to or util.binary(to):
1805 1805 # regular diffs cannot represent empty file deletion
1806 1806 losedatafn(f)
1807 1807 else:
1808 1808 oflag = man1.flags(f)
1809 1809 nflag = ctx2.flags(f)
1810 1810 binary = util.binary(to) or util.binary(tn)
1811 1811 if opts.git:
1812 1812 addmodehdr(header, gitmode[oflag], gitmode[nflag])
1813 1813 if binary:
1814 1814 dodiff = 'binary'
1815 1815 elif binary or nflag != oflag:
1816 1816 losedatafn(f)
1817 1817
1818 1818 if dodiff:
1819 1819 if opts.git or revs:
1820 1820 header.insert(0, diffline(join(a), join(b), revs))
1821 1821 if dodiff == 'binary':
1822 1822 text = mdiff.b85diff(to, tn)
1823 1823 if text:
1824 1824 addindexmeta(header, [gitindex(to), gitindex(tn)])
1825 1825 else:
1826 1826 text = mdiff.unidiff(to, date1,
1827 1827 # ctx2 date may be dynamic
1828 1828 tn, util.datestr(ctx2.date()),
1829 1829 join(a), join(b), opts=opts)
1830 1830 if header and (text or len(header) > 1):
1831 1831 yield ''.join(header)
1832 1832 if text:
1833 1833 yield text
1834 1834
1835 1835 def diffstatsum(stats):
1836 1836 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1837 1837 for f, a, r, b in stats:
1838 1838 maxfile = max(maxfile, encoding.colwidth(f))
1839 1839 maxtotal = max(maxtotal, a + r)
1840 1840 addtotal += a
1841 1841 removetotal += r
1842 1842 binary = binary or b
1843 1843
1844 1844 return maxfile, maxtotal, addtotal, removetotal, binary
1845 1845
1846 1846 def diffstatdata(lines):
1847 1847 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1848 1848
1849 1849 results = []
1850 1850 filename, adds, removes, isbinary = None, 0, 0, False
1851 1851
1852 1852 def addresult():
1853 1853 if filename:
1854 1854 results.append((filename, adds, removes, isbinary))
1855 1855
1856 1856 for line in lines:
1857 1857 if line.startswith('diff'):
1858 1858 addresult()
1859 1859 # set numbers to 0 anyway when starting new file
1860 1860 adds, removes, isbinary = 0, 0, False
1861 1861 if line.startswith('diff --git a/'):
1862 filename = gitre.search(line).group(1)
1862 filename = gitre.search(line).group(2)
1863 1863 elif line.startswith('diff -r'):
1864 1864 # format: "diff -r ... -r ... filename"
1865 1865 filename = diffre.search(line).group(1)
1866 1866 elif line.startswith('+') and not line.startswith('+++ '):
1867 1867 adds += 1
1868 1868 elif line.startswith('-') and not line.startswith('--- '):
1869 1869 removes += 1
1870 1870 elif (line.startswith('GIT binary patch') or
1871 1871 line.startswith('Binary file')):
1872 1872 isbinary = True
1873 1873 addresult()
1874 1874 return results
1875 1875
1876 1876 def diffstat(lines, width=80, git=False):
1877 1877 output = []
1878 1878 stats = diffstatdata(lines)
1879 1879 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1880 1880
1881 1881 countwidth = len(str(maxtotal))
1882 1882 if hasbinary and countwidth < 3:
1883 1883 countwidth = 3
1884 1884 graphwidth = width - countwidth - maxname - 6
1885 1885 if graphwidth < 10:
1886 1886 graphwidth = 10
1887 1887
1888 1888 def scale(i):
1889 1889 if maxtotal <= graphwidth:
1890 1890 return i
1891 1891 # If diffstat runs out of room it doesn't print anything,
1892 1892 # which isn't very useful, so always print at least one + or -
1893 1893 # if there were at least some changes.
1894 1894 return max(i * graphwidth // maxtotal, int(bool(i)))
1895 1895
1896 1896 for filename, adds, removes, isbinary in stats:
1897 1897 if isbinary:
1898 1898 count = 'Bin'
1899 1899 else:
1900 1900 count = adds + removes
1901 1901 pluses = '+' * scale(adds)
1902 1902 minuses = '-' * scale(removes)
1903 1903 output.append(' %s%s | %*s %s%s\n' %
1904 1904 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1905 1905 countwidth, count, pluses, minuses))
1906 1906
1907 1907 if stats:
1908 1908 output.append(_(' %d files changed, %d insertions(+), '
1909 1909 '%d deletions(-)\n')
1910 1910 % (len(stats), totaladds, totalremoves))
1911 1911
1912 1912 return ''.join(output)
1913 1913
1914 1914 def diffstatui(*args, **kw):
1915 1915 '''like diffstat(), but yields 2-tuples of (output, label) for
1916 1916 ui.write()
1917 1917 '''
1918 1918
1919 1919 for line in diffstat(*args, **kw).splitlines():
1920 1920 if line and line[-1] in '+-':
1921 1921 name, graph = line.rsplit(' ', 1)
1922 1922 yield (name + ' ', '')
1923 1923 m = re.search(r'\++', graph)
1924 1924 if m:
1925 1925 yield (m.group(0), 'diffstat.inserted')
1926 1926 m = re.search(r'-+', graph)
1927 1927 if m:
1928 1928 yield (m.group(0), 'diffstat.deleted')
1929 1929 else:
1930 1930 yield (line, '')
1931 1931 yield ('\n', '')
@@ -1,2129 +1,2129 b''
1 1 @ (34) head
2 2 |
3 3 | o (33) head
4 4 | |
5 5 o | (32) expand
6 6 |\ \
7 7 | o \ (31) expand
8 8 | |\ \
9 9 | | o \ (30) expand
10 10 | | |\ \
11 11 | | | o | (29) regular commit
12 12 | | | | |
13 13 | | o | | (28) merge zero known
14 14 | | |\ \ \
15 15 o | | | | | (27) collapse
16 16 |/ / / / /
17 17 | | o---+ (26) merge one known; far right
18 18 | | | | |
19 19 +---o | | (25) merge one known; far left
20 20 | | | | |
21 21 | | o | | (24) merge one known; immediate right
22 22 | | |\| |
23 23 | | o | | (23) merge one known; immediate left
24 24 | |/| | |
25 25 +---o---+ (22) merge two known; one far left, one far right
26 26 | | / /
27 27 o | | | (21) expand
28 28 |\ \ \ \
29 29 | o---+-+ (20) merge two known; two far right
30 30 | / / /
31 31 o | | | (19) expand
32 32 |\ \ \ \
33 33 +---+---o (18) merge two known; two far left
34 34 | | | |
35 35 | o | | (17) expand
36 36 | |\ \ \
37 37 | | o---+ (16) merge two known; one immediate right, one near right
38 38 | | |/ /
39 39 o | | | (15) expand
40 40 |\ \ \ \
41 41 | o-----+ (14) merge two known; one immediate right, one far right
42 42 | |/ / /
43 43 o | | | (13) expand
44 44 |\ \ \ \
45 45 +---o | | (12) merge two known; one immediate right, one far left
46 46 | | |/ /
47 47 | o | | (11) expand
48 48 | |\ \ \
49 49 | | o---+ (10) merge two known; one immediate left, one near right
50 50 | |/ / /
51 51 o | | | (9) expand
52 52 |\ \ \ \
53 53 | o-----+ (8) merge two known; one immediate left, one far right
54 54 |/ / / /
55 55 o | | | (7) expand
56 56 |\ \ \ \
57 57 +---o | | (6) merge two known; one immediate left, one far left
58 58 | |/ / /
59 59 | o | | (5) expand
60 60 | |\ \ \
61 61 | | o | | (4) merge two known; one immediate left, one immediate right
62 62 | |/|/ /
63 63 | o / / (3) collapse
64 64 |/ / /
65 65 o / / (2) collapse
66 66 |/ /
67 67 o / (1) collapse
68 68 |/
69 69 o (0) root
70 70
71 71
72 72 $ commit()
73 73 > {
74 74 > rev=$1
75 75 > msg=$2
76 76 > shift 2
77 77 > if [ "$#" -gt 0 ]; then
78 78 > hg debugsetparents "$@"
79 79 > fi
80 80 > echo $rev > a
81 81 > hg commit -Aqd "$rev 0" -m "($rev) $msg"
82 82 > }
83 83
84 84 $ cat > printrevset.py <<EOF
85 85 > from mercurial import extensions, revset, commands, cmdutil
86 86 >
87 87 > def uisetup(ui):
88 88 > def printrevset(orig, ui, repo, *pats, **opts):
89 89 > if opts.get('print_revset'):
90 90 > expr = cmdutil.getgraphlogrevs(repo, pats, opts)[1]
91 91 > if expr:
92 92 > tree = revset.parse(expr)[0]
93 93 > else:
94 94 > tree = []
95 95 > ui.write('%r\n' % (opts.get('rev', []),))
96 96 > ui.write(revset.prettyformat(tree) + '\n')
97 97 > return 0
98 98 > return orig(ui, repo, *pats, **opts)
99 99 > entry = extensions.wrapcommand(commands.table, 'log', printrevset)
100 100 > entry[1].append(('', 'print-revset', False,
101 101 > 'print generated revset and exit (DEPRECATED)'))
102 102 > EOF
103 103
104 104 $ echo "[extensions]" >> $HGRCPATH
105 105 $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
106 106
107 107 $ hg init repo
108 108 $ cd repo
109 109
110 110 Empty repo:
111 111
112 112 $ hg log -G
113 113
114 114
115 115 Building DAG:
116 116
117 117 $ commit 0 "root"
118 118 $ commit 1 "collapse" 0
119 119 $ commit 2 "collapse" 1
120 120 $ commit 3 "collapse" 2
121 121 $ commit 4 "merge two known; one immediate left, one immediate right" 1 3
122 122 $ commit 5 "expand" 3 4
123 123 $ commit 6 "merge two known; one immediate left, one far left" 2 5
124 124 $ commit 7 "expand" 2 5
125 125 $ commit 8 "merge two known; one immediate left, one far right" 0 7
126 126 $ commit 9 "expand" 7 8
127 127 $ commit 10 "merge two known; one immediate left, one near right" 0 6
128 128 $ commit 11 "expand" 6 10
129 129 $ commit 12 "merge two known; one immediate right, one far left" 1 9
130 130 $ commit 13 "expand" 9 11
131 131 $ commit 14 "merge two known; one immediate right, one far right" 0 12
132 132 $ commit 15 "expand" 13 14
133 133 $ commit 16 "merge two known; one immediate right, one near right" 0 1
134 134 $ commit 17 "expand" 12 16
135 135 $ commit 18 "merge two known; two far left" 1 15
136 136 $ commit 19 "expand" 15 17
137 137 $ commit 20 "merge two known; two far right" 0 18
138 138 $ commit 21 "expand" 19 20
139 139 $ commit 22 "merge two known; one far left, one far right" 18 21
140 140 $ commit 23 "merge one known; immediate left" 1 22
141 141 $ commit 24 "merge one known; immediate right" 0 23
142 142 $ commit 25 "merge one known; far left" 21 24
143 143 $ commit 26 "merge one known; far right" 18 25
144 144 $ commit 27 "collapse" 21
145 145 $ commit 28 "merge zero known" 1 26
146 146 $ commit 29 "regular commit" 0
147 147 $ commit 30 "expand" 28 29
148 148 $ commit 31 "expand" 21 30
149 149 $ commit 32 "expand" 27 31
150 150 $ commit 33 "head" 18
151 151 $ commit 34 "head" 32
152 152
153 153
154 154 $ hg log -G -q
155 155 @ 34:fea3ac5810e0
156 156 |
157 157 | o 33:68608f5145f9
158 158 | |
159 159 o | 32:d06dffa21a31
160 160 |\ \
161 161 | o \ 31:621d83e11f67
162 162 | |\ \
163 163 | | o \ 30:6e11cd4b648f
164 164 | | |\ \
165 165 | | | o | 29:cd9bb2be7593
166 166 | | | | |
167 167 | | o | | 28:44ecd0b9ae99
168 168 | | |\ \ \
169 169 o | | | | | 27:886ed638191b
170 170 |/ / / / /
171 171 | | o---+ 26:7f25b6c2f0b9
172 172 | | | | |
173 173 +---o | | 25:91da8ed57247
174 174 | | | | |
175 175 | | o | | 24:a9c19a3d96b7
176 176 | | |\| |
177 177 | | o | | 23:a01cddf0766d
178 178 | |/| | |
179 179 +---o---+ 22:e0d9cccacb5d
180 180 | | / /
181 181 o | | | 21:d42a756af44d
182 182 |\ \ \ \
183 183 | o---+-+ 20:d30ed6450e32
184 184 | / / /
185 185 o | | | 19:31ddc2c1573b
186 186 |\ \ \ \
187 187 +---+---o 18:1aa84d96232a
188 188 | | | |
189 189 | o | | 17:44765d7c06e0
190 190 | |\ \ \
191 191 | | o---+ 16:3677d192927d
192 192 | | |/ /
193 193 o | | | 15:1dda3f72782d
194 194 |\ \ \ \
195 195 | o-----+ 14:8eac370358ef
196 196 | |/ / /
197 197 o | | | 13:22d8966a97e3
198 198 |\ \ \ \
199 199 +---o | | 12:86b91144a6e9
200 200 | | |/ /
201 201 | o | | 11:832d76e6bdf2
202 202 | |\ \ \
203 203 | | o---+ 10:74c64d036d72
204 204 | |/ / /
205 205 o | | | 9:7010c0af0a35
206 206 |\ \ \ \
207 207 | o-----+ 8:7a0b11f71937
208 208 |/ / / /
209 209 o | | | 7:b632bb1b1224
210 210 |\ \ \ \
211 211 +---o | | 6:b105a072e251
212 212 | |/ / /
213 213 | o | | 5:4409d547b708
214 214 | |\ \ \
215 215 | | o | | 4:26a8bac39d9f
216 216 | |/|/ /
217 217 | o / / 3:27eef8ed80b4
218 218 |/ / /
219 219 o / / 2:3d9a33b8d1e1
220 220 |/ /
221 221 o / 1:6db2ef61d156
222 222 |/
223 223 o 0:e6eb3150255d
224 224
225 225
226 226 $ hg log -G
227 227 @ changeset: 34:fea3ac5810e0
228 228 | tag: tip
229 229 | parent: 32:d06dffa21a31
230 230 | user: test
231 231 | date: Thu Jan 01 00:00:34 1970 +0000
232 232 | summary: (34) head
233 233 |
234 234 | o changeset: 33:68608f5145f9
235 235 | | parent: 18:1aa84d96232a
236 236 | | user: test
237 237 | | date: Thu Jan 01 00:00:33 1970 +0000
238 238 | | summary: (33) head
239 239 | |
240 240 o | changeset: 32:d06dffa21a31
241 241 |\ \ parent: 27:886ed638191b
242 242 | | | parent: 31:621d83e11f67
243 243 | | | user: test
244 244 | | | date: Thu Jan 01 00:00:32 1970 +0000
245 245 | | | summary: (32) expand
246 246 | | |
247 247 | o | changeset: 31:621d83e11f67
248 248 | |\ \ parent: 21:d42a756af44d
249 249 | | | | parent: 30:6e11cd4b648f
250 250 | | | | user: test
251 251 | | | | date: Thu Jan 01 00:00:31 1970 +0000
252 252 | | | | summary: (31) expand
253 253 | | | |
254 254 | | o | changeset: 30:6e11cd4b648f
255 255 | | |\ \ parent: 28:44ecd0b9ae99
256 256 | | | | | parent: 29:cd9bb2be7593
257 257 | | | | | user: test
258 258 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
259 259 | | | | | summary: (30) expand
260 260 | | | | |
261 261 | | | o | changeset: 29:cd9bb2be7593
262 262 | | | | | parent: 0:e6eb3150255d
263 263 | | | | | user: test
264 264 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
265 265 | | | | | summary: (29) regular commit
266 266 | | | | |
267 267 | | o | | changeset: 28:44ecd0b9ae99
268 268 | | |\ \ \ parent: 1:6db2ef61d156
269 269 | | | | | | parent: 26:7f25b6c2f0b9
270 270 | | | | | | user: test
271 271 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
272 272 | | | | | | summary: (28) merge zero known
273 273 | | | | | |
274 274 o | | | | | changeset: 27:886ed638191b
275 275 |/ / / / / parent: 21:d42a756af44d
276 276 | | | | | user: test
277 277 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
278 278 | | | | | summary: (27) collapse
279 279 | | | | |
280 280 | | o---+ changeset: 26:7f25b6c2f0b9
281 281 | | | | | parent: 18:1aa84d96232a
282 282 | | | | | parent: 25:91da8ed57247
283 283 | | | | | user: test
284 284 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
285 285 | | | | | summary: (26) merge one known; far right
286 286 | | | | |
287 287 +---o | | changeset: 25:91da8ed57247
288 288 | | | | | parent: 21:d42a756af44d
289 289 | | | | | parent: 24:a9c19a3d96b7
290 290 | | | | | user: test
291 291 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
292 292 | | | | | summary: (25) merge one known; far left
293 293 | | | | |
294 294 | | o | | changeset: 24:a9c19a3d96b7
295 295 | | |\| | parent: 0:e6eb3150255d
296 296 | | | | | parent: 23:a01cddf0766d
297 297 | | | | | user: test
298 298 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
299 299 | | | | | summary: (24) merge one known; immediate right
300 300 | | | | |
301 301 | | o | | changeset: 23:a01cddf0766d
302 302 | |/| | | parent: 1:6db2ef61d156
303 303 | | | | | parent: 22:e0d9cccacb5d
304 304 | | | | | user: test
305 305 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
306 306 | | | | | summary: (23) merge one known; immediate left
307 307 | | | | |
308 308 +---o---+ changeset: 22:e0d9cccacb5d
309 309 | | | | parent: 18:1aa84d96232a
310 310 | | / / parent: 21:d42a756af44d
311 311 | | | | user: test
312 312 | | | | date: Thu Jan 01 00:00:22 1970 +0000
313 313 | | | | summary: (22) merge two known; one far left, one far right
314 314 | | | |
315 315 o | | | changeset: 21:d42a756af44d
316 316 |\ \ \ \ parent: 19:31ddc2c1573b
317 317 | | | | | parent: 20:d30ed6450e32
318 318 | | | | | user: test
319 319 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
320 320 | | | | | summary: (21) expand
321 321 | | | | |
322 322 | o---+-+ changeset: 20:d30ed6450e32
323 323 | | | | parent: 0:e6eb3150255d
324 324 | / / / parent: 18:1aa84d96232a
325 325 | | | | user: test
326 326 | | | | date: Thu Jan 01 00:00:20 1970 +0000
327 327 | | | | summary: (20) merge two known; two far right
328 328 | | | |
329 329 o | | | changeset: 19:31ddc2c1573b
330 330 |\ \ \ \ parent: 15:1dda3f72782d
331 331 | | | | | parent: 17:44765d7c06e0
332 332 | | | | | user: test
333 333 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
334 334 | | | | | summary: (19) expand
335 335 | | | | |
336 336 +---+---o changeset: 18:1aa84d96232a
337 337 | | | | parent: 1:6db2ef61d156
338 338 | | | | parent: 15:1dda3f72782d
339 339 | | | | user: test
340 340 | | | | date: Thu Jan 01 00:00:18 1970 +0000
341 341 | | | | summary: (18) merge two known; two far left
342 342 | | | |
343 343 | o | | changeset: 17:44765d7c06e0
344 344 | |\ \ \ parent: 12:86b91144a6e9
345 345 | | | | | parent: 16:3677d192927d
346 346 | | | | | user: test
347 347 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
348 348 | | | | | summary: (17) expand
349 349 | | | | |
350 350 | | o---+ changeset: 16:3677d192927d
351 351 | | | | | parent: 0:e6eb3150255d
352 352 | | |/ / parent: 1:6db2ef61d156
353 353 | | | | user: test
354 354 | | | | date: Thu Jan 01 00:00:16 1970 +0000
355 355 | | | | summary: (16) merge two known; one immediate right, one near right
356 356 | | | |
357 357 o | | | changeset: 15:1dda3f72782d
358 358 |\ \ \ \ parent: 13:22d8966a97e3
359 359 | | | | | parent: 14:8eac370358ef
360 360 | | | | | user: test
361 361 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
362 362 | | | | | summary: (15) expand
363 363 | | | | |
364 364 | o-----+ changeset: 14:8eac370358ef
365 365 | | | | | parent: 0:e6eb3150255d
366 366 | |/ / / parent: 12:86b91144a6e9
367 367 | | | | user: test
368 368 | | | | date: Thu Jan 01 00:00:14 1970 +0000
369 369 | | | | summary: (14) merge two known; one immediate right, one far right
370 370 | | | |
371 371 o | | | changeset: 13:22d8966a97e3
372 372 |\ \ \ \ parent: 9:7010c0af0a35
373 373 | | | | | parent: 11:832d76e6bdf2
374 374 | | | | | user: test
375 375 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
376 376 | | | | | summary: (13) expand
377 377 | | | | |
378 378 +---o | | changeset: 12:86b91144a6e9
379 379 | | |/ / parent: 1:6db2ef61d156
380 380 | | | | parent: 9:7010c0af0a35
381 381 | | | | user: test
382 382 | | | | date: Thu Jan 01 00:00:12 1970 +0000
383 383 | | | | summary: (12) merge two known; one immediate right, one far left
384 384 | | | |
385 385 | o | | changeset: 11:832d76e6bdf2
386 386 | |\ \ \ parent: 6:b105a072e251
387 387 | | | | | parent: 10:74c64d036d72
388 388 | | | | | user: test
389 389 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
390 390 | | | | | summary: (11) expand
391 391 | | | | |
392 392 | | o---+ changeset: 10:74c64d036d72
393 393 | | | | | parent: 0:e6eb3150255d
394 394 | |/ / / parent: 6:b105a072e251
395 395 | | | | user: test
396 396 | | | | date: Thu Jan 01 00:00:10 1970 +0000
397 397 | | | | summary: (10) merge two known; one immediate left, one near right
398 398 | | | |
399 399 o | | | changeset: 9:7010c0af0a35
400 400 |\ \ \ \ parent: 7:b632bb1b1224
401 401 | | | | | parent: 8:7a0b11f71937
402 402 | | | | | user: test
403 403 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
404 404 | | | | | summary: (9) expand
405 405 | | | | |
406 406 | o-----+ changeset: 8:7a0b11f71937
407 407 | | | | | parent: 0:e6eb3150255d
408 408 |/ / / / parent: 7:b632bb1b1224
409 409 | | | | user: test
410 410 | | | | date: Thu Jan 01 00:00:08 1970 +0000
411 411 | | | | summary: (8) merge two known; one immediate left, one far right
412 412 | | | |
413 413 o | | | changeset: 7:b632bb1b1224
414 414 |\ \ \ \ parent: 2:3d9a33b8d1e1
415 415 | | | | | parent: 5:4409d547b708
416 416 | | | | | user: test
417 417 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
418 418 | | | | | summary: (7) expand
419 419 | | | | |
420 420 +---o | | changeset: 6:b105a072e251
421 421 | |/ / / parent: 2:3d9a33b8d1e1
422 422 | | | | parent: 5:4409d547b708
423 423 | | | | user: test
424 424 | | | | date: Thu Jan 01 00:00:06 1970 +0000
425 425 | | | | summary: (6) merge two known; one immediate left, one far left
426 426 | | | |
427 427 | o | | changeset: 5:4409d547b708
428 428 | |\ \ \ parent: 3:27eef8ed80b4
429 429 | | | | | parent: 4:26a8bac39d9f
430 430 | | | | | user: test
431 431 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
432 432 | | | | | summary: (5) expand
433 433 | | | | |
434 434 | | o | | changeset: 4:26a8bac39d9f
435 435 | |/|/ / parent: 1:6db2ef61d156
436 436 | | | | parent: 3:27eef8ed80b4
437 437 | | | | user: test
438 438 | | | | date: Thu Jan 01 00:00:04 1970 +0000
439 439 | | | | summary: (4) merge two known; one immediate left, one immediate right
440 440 | | | |
441 441 | o | | changeset: 3:27eef8ed80b4
442 442 |/ / / user: test
443 443 | | | date: Thu Jan 01 00:00:03 1970 +0000
444 444 | | | summary: (3) collapse
445 445 | | |
446 446 o | | changeset: 2:3d9a33b8d1e1
447 447 |/ / user: test
448 448 | | date: Thu Jan 01 00:00:02 1970 +0000
449 449 | | summary: (2) collapse
450 450 | |
451 451 o | changeset: 1:6db2ef61d156
452 452 |/ user: test
453 453 | date: Thu Jan 01 00:00:01 1970 +0000
454 454 | summary: (1) collapse
455 455 |
456 456 o changeset: 0:e6eb3150255d
457 457 user: test
458 458 date: Thu Jan 01 00:00:00 1970 +0000
459 459 summary: (0) root
460 460
461 461
462 462 File glog:
463 463 $ hg log -G a
464 464 @ changeset: 34:fea3ac5810e0
465 465 | tag: tip
466 466 | parent: 32:d06dffa21a31
467 467 | user: test
468 468 | date: Thu Jan 01 00:00:34 1970 +0000
469 469 | summary: (34) head
470 470 |
471 471 | o changeset: 33:68608f5145f9
472 472 | | parent: 18:1aa84d96232a
473 473 | | user: test
474 474 | | date: Thu Jan 01 00:00:33 1970 +0000
475 475 | | summary: (33) head
476 476 | |
477 477 o | changeset: 32:d06dffa21a31
478 478 |\ \ parent: 27:886ed638191b
479 479 | | | parent: 31:621d83e11f67
480 480 | | | user: test
481 481 | | | date: Thu Jan 01 00:00:32 1970 +0000
482 482 | | | summary: (32) expand
483 483 | | |
484 484 | o | changeset: 31:621d83e11f67
485 485 | |\ \ parent: 21:d42a756af44d
486 486 | | | | parent: 30:6e11cd4b648f
487 487 | | | | user: test
488 488 | | | | date: Thu Jan 01 00:00:31 1970 +0000
489 489 | | | | summary: (31) expand
490 490 | | | |
491 491 | | o | changeset: 30:6e11cd4b648f
492 492 | | |\ \ parent: 28:44ecd0b9ae99
493 493 | | | | | parent: 29:cd9bb2be7593
494 494 | | | | | user: test
495 495 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
496 496 | | | | | summary: (30) expand
497 497 | | | | |
498 498 | | | o | changeset: 29:cd9bb2be7593
499 499 | | | | | parent: 0:e6eb3150255d
500 500 | | | | | user: test
501 501 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
502 502 | | | | | summary: (29) regular commit
503 503 | | | | |
504 504 | | o | | changeset: 28:44ecd0b9ae99
505 505 | | |\ \ \ parent: 1:6db2ef61d156
506 506 | | | | | | parent: 26:7f25b6c2f0b9
507 507 | | | | | | user: test
508 508 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
509 509 | | | | | | summary: (28) merge zero known
510 510 | | | | | |
511 511 o | | | | | changeset: 27:886ed638191b
512 512 |/ / / / / parent: 21:d42a756af44d
513 513 | | | | | user: test
514 514 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
515 515 | | | | | summary: (27) collapse
516 516 | | | | |
517 517 | | o---+ changeset: 26:7f25b6c2f0b9
518 518 | | | | | parent: 18:1aa84d96232a
519 519 | | | | | parent: 25:91da8ed57247
520 520 | | | | | user: test
521 521 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
522 522 | | | | | summary: (26) merge one known; far right
523 523 | | | | |
524 524 +---o | | changeset: 25:91da8ed57247
525 525 | | | | | parent: 21:d42a756af44d
526 526 | | | | | parent: 24:a9c19a3d96b7
527 527 | | | | | user: test
528 528 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
529 529 | | | | | summary: (25) merge one known; far left
530 530 | | | | |
531 531 | | o | | changeset: 24:a9c19a3d96b7
532 532 | | |\| | parent: 0:e6eb3150255d
533 533 | | | | | parent: 23:a01cddf0766d
534 534 | | | | | user: test
535 535 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
536 536 | | | | | summary: (24) merge one known; immediate right
537 537 | | | | |
538 538 | | o | | changeset: 23:a01cddf0766d
539 539 | |/| | | parent: 1:6db2ef61d156
540 540 | | | | | parent: 22:e0d9cccacb5d
541 541 | | | | | user: test
542 542 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
543 543 | | | | | summary: (23) merge one known; immediate left
544 544 | | | | |
545 545 +---o---+ changeset: 22:e0d9cccacb5d
546 546 | | | | parent: 18:1aa84d96232a
547 547 | | / / parent: 21:d42a756af44d
548 548 | | | | user: test
549 549 | | | | date: Thu Jan 01 00:00:22 1970 +0000
550 550 | | | | summary: (22) merge two known; one far left, one far right
551 551 | | | |
552 552 o | | | changeset: 21:d42a756af44d
553 553 |\ \ \ \ parent: 19:31ddc2c1573b
554 554 | | | | | parent: 20:d30ed6450e32
555 555 | | | | | user: test
556 556 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
557 557 | | | | | summary: (21) expand
558 558 | | | | |
559 559 | o---+-+ changeset: 20:d30ed6450e32
560 560 | | | | parent: 0:e6eb3150255d
561 561 | / / / parent: 18:1aa84d96232a
562 562 | | | | user: test
563 563 | | | | date: Thu Jan 01 00:00:20 1970 +0000
564 564 | | | | summary: (20) merge two known; two far right
565 565 | | | |
566 566 o | | | changeset: 19:31ddc2c1573b
567 567 |\ \ \ \ parent: 15:1dda3f72782d
568 568 | | | | | parent: 17:44765d7c06e0
569 569 | | | | | user: test
570 570 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
571 571 | | | | | summary: (19) expand
572 572 | | | | |
573 573 +---+---o changeset: 18:1aa84d96232a
574 574 | | | | parent: 1:6db2ef61d156
575 575 | | | | parent: 15:1dda3f72782d
576 576 | | | | user: test
577 577 | | | | date: Thu Jan 01 00:00:18 1970 +0000
578 578 | | | | summary: (18) merge two known; two far left
579 579 | | | |
580 580 | o | | changeset: 17:44765d7c06e0
581 581 | |\ \ \ parent: 12:86b91144a6e9
582 582 | | | | | parent: 16:3677d192927d
583 583 | | | | | user: test
584 584 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
585 585 | | | | | summary: (17) expand
586 586 | | | | |
587 587 | | o---+ changeset: 16:3677d192927d
588 588 | | | | | parent: 0:e6eb3150255d
589 589 | | |/ / parent: 1:6db2ef61d156
590 590 | | | | user: test
591 591 | | | | date: Thu Jan 01 00:00:16 1970 +0000
592 592 | | | | summary: (16) merge two known; one immediate right, one near right
593 593 | | | |
594 594 o | | | changeset: 15:1dda3f72782d
595 595 |\ \ \ \ parent: 13:22d8966a97e3
596 596 | | | | | parent: 14:8eac370358ef
597 597 | | | | | user: test
598 598 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
599 599 | | | | | summary: (15) expand
600 600 | | | | |
601 601 | o-----+ changeset: 14:8eac370358ef
602 602 | | | | | parent: 0:e6eb3150255d
603 603 | |/ / / parent: 12:86b91144a6e9
604 604 | | | | user: test
605 605 | | | | date: Thu Jan 01 00:00:14 1970 +0000
606 606 | | | | summary: (14) merge two known; one immediate right, one far right
607 607 | | | |
608 608 o | | | changeset: 13:22d8966a97e3
609 609 |\ \ \ \ parent: 9:7010c0af0a35
610 610 | | | | | parent: 11:832d76e6bdf2
611 611 | | | | | user: test
612 612 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
613 613 | | | | | summary: (13) expand
614 614 | | | | |
615 615 +---o | | changeset: 12:86b91144a6e9
616 616 | | |/ / parent: 1:6db2ef61d156
617 617 | | | | parent: 9:7010c0af0a35
618 618 | | | | user: test
619 619 | | | | date: Thu Jan 01 00:00:12 1970 +0000
620 620 | | | | summary: (12) merge two known; one immediate right, one far left
621 621 | | | |
622 622 | o | | changeset: 11:832d76e6bdf2
623 623 | |\ \ \ parent: 6:b105a072e251
624 624 | | | | | parent: 10:74c64d036d72
625 625 | | | | | user: test
626 626 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
627 627 | | | | | summary: (11) expand
628 628 | | | | |
629 629 | | o---+ changeset: 10:74c64d036d72
630 630 | | | | | parent: 0:e6eb3150255d
631 631 | |/ / / parent: 6:b105a072e251
632 632 | | | | user: test
633 633 | | | | date: Thu Jan 01 00:00:10 1970 +0000
634 634 | | | | summary: (10) merge two known; one immediate left, one near right
635 635 | | | |
636 636 o | | | changeset: 9:7010c0af0a35
637 637 |\ \ \ \ parent: 7:b632bb1b1224
638 638 | | | | | parent: 8:7a0b11f71937
639 639 | | | | | user: test
640 640 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
641 641 | | | | | summary: (9) expand
642 642 | | | | |
643 643 | o-----+ changeset: 8:7a0b11f71937
644 644 | | | | | parent: 0:e6eb3150255d
645 645 |/ / / / parent: 7:b632bb1b1224
646 646 | | | | user: test
647 647 | | | | date: Thu Jan 01 00:00:08 1970 +0000
648 648 | | | | summary: (8) merge two known; one immediate left, one far right
649 649 | | | |
650 650 o | | | changeset: 7:b632bb1b1224
651 651 |\ \ \ \ parent: 2:3d9a33b8d1e1
652 652 | | | | | parent: 5:4409d547b708
653 653 | | | | | user: test
654 654 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
655 655 | | | | | summary: (7) expand
656 656 | | | | |
657 657 +---o | | changeset: 6:b105a072e251
658 658 | |/ / / parent: 2:3d9a33b8d1e1
659 659 | | | | parent: 5:4409d547b708
660 660 | | | | user: test
661 661 | | | | date: Thu Jan 01 00:00:06 1970 +0000
662 662 | | | | summary: (6) merge two known; one immediate left, one far left
663 663 | | | |
664 664 | o | | changeset: 5:4409d547b708
665 665 | |\ \ \ parent: 3:27eef8ed80b4
666 666 | | | | | parent: 4:26a8bac39d9f
667 667 | | | | | user: test
668 668 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
669 669 | | | | | summary: (5) expand
670 670 | | | | |
671 671 | | o | | changeset: 4:26a8bac39d9f
672 672 | |/|/ / parent: 1:6db2ef61d156
673 673 | | | | parent: 3:27eef8ed80b4
674 674 | | | | user: test
675 675 | | | | date: Thu Jan 01 00:00:04 1970 +0000
676 676 | | | | summary: (4) merge two known; one immediate left, one immediate right
677 677 | | | |
678 678 | o | | changeset: 3:27eef8ed80b4
679 679 |/ / / user: test
680 680 | | | date: Thu Jan 01 00:00:03 1970 +0000
681 681 | | | summary: (3) collapse
682 682 | | |
683 683 o | | changeset: 2:3d9a33b8d1e1
684 684 |/ / user: test
685 685 | | date: Thu Jan 01 00:00:02 1970 +0000
686 686 | | summary: (2) collapse
687 687 | |
688 688 o | changeset: 1:6db2ef61d156
689 689 |/ user: test
690 690 | date: Thu Jan 01 00:00:01 1970 +0000
691 691 | summary: (1) collapse
692 692 |
693 693 o changeset: 0:e6eb3150255d
694 694 user: test
695 695 date: Thu Jan 01 00:00:00 1970 +0000
696 696 summary: (0) root
697 697
698 698
699 699 File glog per revset:
700 700
701 701 $ hg log -G -r 'file("a")'
702 702 @ changeset: 34:fea3ac5810e0
703 703 | tag: tip
704 704 | parent: 32:d06dffa21a31
705 705 | user: test
706 706 | date: Thu Jan 01 00:00:34 1970 +0000
707 707 | summary: (34) head
708 708 |
709 709 | o changeset: 33:68608f5145f9
710 710 | | parent: 18:1aa84d96232a
711 711 | | user: test
712 712 | | date: Thu Jan 01 00:00:33 1970 +0000
713 713 | | summary: (33) head
714 714 | |
715 715 o | changeset: 32:d06dffa21a31
716 716 |\ \ parent: 27:886ed638191b
717 717 | | | parent: 31:621d83e11f67
718 718 | | | user: test
719 719 | | | date: Thu Jan 01 00:00:32 1970 +0000
720 720 | | | summary: (32) expand
721 721 | | |
722 722 | o | changeset: 31:621d83e11f67
723 723 | |\ \ parent: 21:d42a756af44d
724 724 | | | | parent: 30:6e11cd4b648f
725 725 | | | | user: test
726 726 | | | | date: Thu Jan 01 00:00:31 1970 +0000
727 727 | | | | summary: (31) expand
728 728 | | | |
729 729 | | o | changeset: 30:6e11cd4b648f
730 730 | | |\ \ parent: 28:44ecd0b9ae99
731 731 | | | | | parent: 29:cd9bb2be7593
732 732 | | | | | user: test
733 733 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
734 734 | | | | | summary: (30) expand
735 735 | | | | |
736 736 | | | o | changeset: 29:cd9bb2be7593
737 737 | | | | | parent: 0:e6eb3150255d
738 738 | | | | | user: test
739 739 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
740 740 | | | | | summary: (29) regular commit
741 741 | | | | |
742 742 | | o | | changeset: 28:44ecd0b9ae99
743 743 | | |\ \ \ parent: 1:6db2ef61d156
744 744 | | | | | | parent: 26:7f25b6c2f0b9
745 745 | | | | | | user: test
746 746 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
747 747 | | | | | | summary: (28) merge zero known
748 748 | | | | | |
749 749 o | | | | | changeset: 27:886ed638191b
750 750 |/ / / / / parent: 21:d42a756af44d
751 751 | | | | | user: test
752 752 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
753 753 | | | | | summary: (27) collapse
754 754 | | | | |
755 755 | | o---+ changeset: 26:7f25b6c2f0b9
756 756 | | | | | parent: 18:1aa84d96232a
757 757 | | | | | parent: 25:91da8ed57247
758 758 | | | | | user: test
759 759 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
760 760 | | | | | summary: (26) merge one known; far right
761 761 | | | | |
762 762 +---o | | changeset: 25:91da8ed57247
763 763 | | | | | parent: 21:d42a756af44d
764 764 | | | | | parent: 24:a9c19a3d96b7
765 765 | | | | | user: test
766 766 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
767 767 | | | | | summary: (25) merge one known; far left
768 768 | | | | |
769 769 | | o | | changeset: 24:a9c19a3d96b7
770 770 | | |\| | parent: 0:e6eb3150255d
771 771 | | | | | parent: 23:a01cddf0766d
772 772 | | | | | user: test
773 773 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
774 774 | | | | | summary: (24) merge one known; immediate right
775 775 | | | | |
776 776 | | o | | changeset: 23:a01cddf0766d
777 777 | |/| | | parent: 1:6db2ef61d156
778 778 | | | | | parent: 22:e0d9cccacb5d
779 779 | | | | | user: test
780 780 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
781 781 | | | | | summary: (23) merge one known; immediate left
782 782 | | | | |
783 783 +---o---+ changeset: 22:e0d9cccacb5d
784 784 | | | | parent: 18:1aa84d96232a
785 785 | | / / parent: 21:d42a756af44d
786 786 | | | | user: test
787 787 | | | | date: Thu Jan 01 00:00:22 1970 +0000
788 788 | | | | summary: (22) merge two known; one far left, one far right
789 789 | | | |
790 790 o | | | changeset: 21:d42a756af44d
791 791 |\ \ \ \ parent: 19:31ddc2c1573b
792 792 | | | | | parent: 20:d30ed6450e32
793 793 | | | | | user: test
794 794 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
795 795 | | | | | summary: (21) expand
796 796 | | | | |
797 797 | o---+-+ changeset: 20:d30ed6450e32
798 798 | | | | parent: 0:e6eb3150255d
799 799 | / / / parent: 18:1aa84d96232a
800 800 | | | | user: test
801 801 | | | | date: Thu Jan 01 00:00:20 1970 +0000
802 802 | | | | summary: (20) merge two known; two far right
803 803 | | | |
804 804 o | | | changeset: 19:31ddc2c1573b
805 805 |\ \ \ \ parent: 15:1dda3f72782d
806 806 | | | | | parent: 17:44765d7c06e0
807 807 | | | | | user: test
808 808 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
809 809 | | | | | summary: (19) expand
810 810 | | | | |
811 811 +---+---o changeset: 18:1aa84d96232a
812 812 | | | | parent: 1:6db2ef61d156
813 813 | | | | parent: 15:1dda3f72782d
814 814 | | | | user: test
815 815 | | | | date: Thu Jan 01 00:00:18 1970 +0000
816 816 | | | | summary: (18) merge two known; two far left
817 817 | | | |
818 818 | o | | changeset: 17:44765d7c06e0
819 819 | |\ \ \ parent: 12:86b91144a6e9
820 820 | | | | | parent: 16:3677d192927d
821 821 | | | | | user: test
822 822 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
823 823 | | | | | summary: (17) expand
824 824 | | | | |
825 825 | | o---+ changeset: 16:3677d192927d
826 826 | | | | | parent: 0:e6eb3150255d
827 827 | | |/ / parent: 1:6db2ef61d156
828 828 | | | | user: test
829 829 | | | | date: Thu Jan 01 00:00:16 1970 +0000
830 830 | | | | summary: (16) merge two known; one immediate right, one near right
831 831 | | | |
832 832 o | | | changeset: 15:1dda3f72782d
833 833 |\ \ \ \ parent: 13:22d8966a97e3
834 834 | | | | | parent: 14:8eac370358ef
835 835 | | | | | user: test
836 836 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
837 837 | | | | | summary: (15) expand
838 838 | | | | |
839 839 | o-----+ changeset: 14:8eac370358ef
840 840 | | | | | parent: 0:e6eb3150255d
841 841 | |/ / / parent: 12:86b91144a6e9
842 842 | | | | user: test
843 843 | | | | date: Thu Jan 01 00:00:14 1970 +0000
844 844 | | | | summary: (14) merge two known; one immediate right, one far right
845 845 | | | |
846 846 o | | | changeset: 13:22d8966a97e3
847 847 |\ \ \ \ parent: 9:7010c0af0a35
848 848 | | | | | parent: 11:832d76e6bdf2
849 849 | | | | | user: test
850 850 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
851 851 | | | | | summary: (13) expand
852 852 | | | | |
853 853 +---o | | changeset: 12:86b91144a6e9
854 854 | | |/ / parent: 1:6db2ef61d156
855 855 | | | | parent: 9:7010c0af0a35
856 856 | | | | user: test
857 857 | | | | date: Thu Jan 01 00:00:12 1970 +0000
858 858 | | | | summary: (12) merge two known; one immediate right, one far left
859 859 | | | |
860 860 | o | | changeset: 11:832d76e6bdf2
861 861 | |\ \ \ parent: 6:b105a072e251
862 862 | | | | | parent: 10:74c64d036d72
863 863 | | | | | user: test
864 864 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
865 865 | | | | | summary: (11) expand
866 866 | | | | |
867 867 | | o---+ changeset: 10:74c64d036d72
868 868 | | | | | parent: 0:e6eb3150255d
869 869 | |/ / / parent: 6:b105a072e251
870 870 | | | | user: test
871 871 | | | | date: Thu Jan 01 00:00:10 1970 +0000
872 872 | | | | summary: (10) merge two known; one immediate left, one near right
873 873 | | | |
874 874 o | | | changeset: 9:7010c0af0a35
875 875 |\ \ \ \ parent: 7:b632bb1b1224
876 876 | | | | | parent: 8:7a0b11f71937
877 877 | | | | | user: test
878 878 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
879 879 | | | | | summary: (9) expand
880 880 | | | | |
881 881 | o-----+ changeset: 8:7a0b11f71937
882 882 | | | | | parent: 0:e6eb3150255d
883 883 |/ / / / parent: 7:b632bb1b1224
884 884 | | | | user: test
885 885 | | | | date: Thu Jan 01 00:00:08 1970 +0000
886 886 | | | | summary: (8) merge two known; one immediate left, one far right
887 887 | | | |
888 888 o | | | changeset: 7:b632bb1b1224
889 889 |\ \ \ \ parent: 2:3d9a33b8d1e1
890 890 | | | | | parent: 5:4409d547b708
891 891 | | | | | user: test
892 892 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
893 893 | | | | | summary: (7) expand
894 894 | | | | |
895 895 +---o | | changeset: 6:b105a072e251
896 896 | |/ / / parent: 2:3d9a33b8d1e1
897 897 | | | | parent: 5:4409d547b708
898 898 | | | | user: test
899 899 | | | | date: Thu Jan 01 00:00:06 1970 +0000
900 900 | | | | summary: (6) merge two known; one immediate left, one far left
901 901 | | | |
902 902 | o | | changeset: 5:4409d547b708
903 903 | |\ \ \ parent: 3:27eef8ed80b4
904 904 | | | | | parent: 4:26a8bac39d9f
905 905 | | | | | user: test
906 906 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
907 907 | | | | | summary: (5) expand
908 908 | | | | |
909 909 | | o | | changeset: 4:26a8bac39d9f
910 910 | |/|/ / parent: 1:6db2ef61d156
911 911 | | | | parent: 3:27eef8ed80b4
912 912 | | | | user: test
913 913 | | | | date: Thu Jan 01 00:00:04 1970 +0000
914 914 | | | | summary: (4) merge two known; one immediate left, one immediate right
915 915 | | | |
916 916 | o | | changeset: 3:27eef8ed80b4
917 917 |/ / / user: test
918 918 | | | date: Thu Jan 01 00:00:03 1970 +0000
919 919 | | | summary: (3) collapse
920 920 | | |
921 921 o | | changeset: 2:3d9a33b8d1e1
922 922 |/ / user: test
923 923 | | date: Thu Jan 01 00:00:02 1970 +0000
924 924 | | summary: (2) collapse
925 925 | |
926 926 o | changeset: 1:6db2ef61d156
927 927 |/ user: test
928 928 | date: Thu Jan 01 00:00:01 1970 +0000
929 929 | summary: (1) collapse
930 930 |
931 931 o changeset: 0:e6eb3150255d
932 932 user: test
933 933 date: Thu Jan 01 00:00:00 1970 +0000
934 934 summary: (0) root
935 935
936 936
937 937
938 938 File glog per revset (only merges):
939 939
940 940 $ hg log -G -r 'file("a")' -m
941 941 o changeset: 32:d06dffa21a31
942 942 |\ parent: 27:886ed638191b
943 943 | | parent: 31:621d83e11f67
944 944 | | user: test
945 945 | | date: Thu Jan 01 00:00:32 1970 +0000
946 946 | | summary: (32) expand
947 947 | |
948 948 o | changeset: 31:621d83e11f67
949 949 |\| parent: 21:d42a756af44d
950 950 | | parent: 30:6e11cd4b648f
951 951 | | user: test
952 952 | | date: Thu Jan 01 00:00:31 1970 +0000
953 953 | | summary: (31) expand
954 954 | |
955 955 o | changeset: 30:6e11cd4b648f
956 956 |\ \ parent: 28:44ecd0b9ae99
957 957 | | | parent: 29:cd9bb2be7593
958 958 | | | user: test
959 959 | | | date: Thu Jan 01 00:00:30 1970 +0000
960 960 | | | summary: (30) expand
961 961 | | |
962 962 o | | changeset: 28:44ecd0b9ae99
963 963 |\ \ \ parent: 1:6db2ef61d156
964 964 | | | | parent: 26:7f25b6c2f0b9
965 965 | | | | user: test
966 966 | | | | date: Thu Jan 01 00:00:28 1970 +0000
967 967 | | | | summary: (28) merge zero known
968 968 | | | |
969 969 o | | | changeset: 26:7f25b6c2f0b9
970 970 |\ \ \ \ parent: 18:1aa84d96232a
971 971 | | | | | parent: 25:91da8ed57247
972 972 | | | | | user: test
973 973 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
974 974 | | | | | summary: (26) merge one known; far right
975 975 | | | | |
976 976 | o-----+ changeset: 25:91da8ed57247
977 977 | | | | | parent: 21:d42a756af44d
978 978 | | | | | parent: 24:a9c19a3d96b7
979 979 | | | | | user: test
980 980 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
981 981 | | | | | summary: (25) merge one known; far left
982 982 | | | | |
983 983 | o | | | changeset: 24:a9c19a3d96b7
984 984 | |\ \ \ \ parent: 0:e6eb3150255d
985 985 | | | | | | parent: 23:a01cddf0766d
986 986 | | | | | | user: test
987 987 | | | | | | date: Thu Jan 01 00:00:24 1970 +0000
988 988 | | | | | | summary: (24) merge one known; immediate right
989 989 | | | | | |
990 990 | o---+ | | changeset: 23:a01cddf0766d
991 991 | | | | | | parent: 1:6db2ef61d156
992 992 | | | | | | parent: 22:e0d9cccacb5d
993 993 | | | | | | user: test
994 994 | | | | | | date: Thu Jan 01 00:00:23 1970 +0000
995 995 | | | | | | summary: (23) merge one known; immediate left
996 996 | | | | | |
997 997 | o-------+ changeset: 22:e0d9cccacb5d
998 998 | | | | | | parent: 18:1aa84d96232a
999 999 |/ / / / / parent: 21:d42a756af44d
1000 1000 | | | | | user: test
1001 1001 | | | | | date: Thu Jan 01 00:00:22 1970 +0000
1002 1002 | | | | | summary: (22) merge two known; one far left, one far right
1003 1003 | | | | |
1004 1004 | | | | o changeset: 21:d42a756af44d
1005 1005 | | | | |\ parent: 19:31ddc2c1573b
1006 1006 | | | | | | parent: 20:d30ed6450e32
1007 1007 | | | | | | user: test
1008 1008 | | | | | | date: Thu Jan 01 00:00:21 1970 +0000
1009 1009 | | | | | | summary: (21) expand
1010 1010 | | | | | |
1011 1011 +-+-------o changeset: 20:d30ed6450e32
1012 1012 | | | | | parent: 0:e6eb3150255d
1013 1013 | | | | | parent: 18:1aa84d96232a
1014 1014 | | | | | user: test
1015 1015 | | | | | date: Thu Jan 01 00:00:20 1970 +0000
1016 1016 | | | | | summary: (20) merge two known; two far right
1017 1017 | | | | |
1018 1018 | | | | o changeset: 19:31ddc2c1573b
1019 1019 | | | | |\ parent: 15:1dda3f72782d
1020 1020 | | | | | | parent: 17:44765d7c06e0
1021 1021 | | | | | | user: test
1022 1022 | | | | | | date: Thu Jan 01 00:00:19 1970 +0000
1023 1023 | | | | | | summary: (19) expand
1024 1024 | | | | | |
1025 1025 o---+---+ | changeset: 18:1aa84d96232a
1026 1026 | | | | | parent: 1:6db2ef61d156
1027 1027 / / / / / parent: 15:1dda3f72782d
1028 1028 | | | | | user: test
1029 1029 | | | | | date: Thu Jan 01 00:00:18 1970 +0000
1030 1030 | | | | | summary: (18) merge two known; two far left
1031 1031 | | | | |
1032 1032 | | | | o changeset: 17:44765d7c06e0
1033 1033 | | | | |\ parent: 12:86b91144a6e9
1034 1034 | | | | | | parent: 16:3677d192927d
1035 1035 | | | | | | user: test
1036 1036 | | | | | | date: Thu Jan 01 00:00:17 1970 +0000
1037 1037 | | | | | | summary: (17) expand
1038 1038 | | | | | |
1039 1039 +-+-------o changeset: 16:3677d192927d
1040 1040 | | | | | parent: 0:e6eb3150255d
1041 1041 | | | | | parent: 1:6db2ef61d156
1042 1042 | | | | | user: test
1043 1043 | | | | | date: Thu Jan 01 00:00:16 1970 +0000
1044 1044 | | | | | summary: (16) merge two known; one immediate right, one near right
1045 1045 | | | | |
1046 1046 | | | o | changeset: 15:1dda3f72782d
1047 1047 | | | |\ \ parent: 13:22d8966a97e3
1048 1048 | | | | | | parent: 14:8eac370358ef
1049 1049 | | | | | | user: test
1050 1050 | | | | | | date: Thu Jan 01 00:00:15 1970 +0000
1051 1051 | | | | | | summary: (15) expand
1052 1052 | | | | | |
1053 1053 +-------o | changeset: 14:8eac370358ef
1054 1054 | | | | |/ parent: 0:e6eb3150255d
1055 1055 | | | | | parent: 12:86b91144a6e9
1056 1056 | | | | | user: test
1057 1057 | | | | | date: Thu Jan 01 00:00:14 1970 +0000
1058 1058 | | | | | summary: (14) merge two known; one immediate right, one far right
1059 1059 | | | | |
1060 1060 | | | o | changeset: 13:22d8966a97e3
1061 1061 | | | |\ \ parent: 9:7010c0af0a35
1062 1062 | | | | | | parent: 11:832d76e6bdf2
1063 1063 | | | | | | user: test
1064 1064 | | | | | | date: Thu Jan 01 00:00:13 1970 +0000
1065 1065 | | | | | | summary: (13) expand
1066 1066 | | | | | |
1067 1067 | +---+---o changeset: 12:86b91144a6e9
1068 1068 | | | | | parent: 1:6db2ef61d156
1069 1069 | | | | | parent: 9:7010c0af0a35
1070 1070 | | | | | user: test
1071 1071 | | | | | date: Thu Jan 01 00:00:12 1970 +0000
1072 1072 | | | | | summary: (12) merge two known; one immediate right, one far left
1073 1073 | | | | |
1074 1074 | | | | o changeset: 11:832d76e6bdf2
1075 1075 | | | | |\ parent: 6:b105a072e251
1076 1076 | | | | | | parent: 10:74c64d036d72
1077 1077 | | | | | | user: test
1078 1078 | | | | | | date: Thu Jan 01 00:00:11 1970 +0000
1079 1079 | | | | | | summary: (11) expand
1080 1080 | | | | | |
1081 1081 +---------o changeset: 10:74c64d036d72
1082 1082 | | | | |/ parent: 0:e6eb3150255d
1083 1083 | | | | | parent: 6:b105a072e251
1084 1084 | | | | | user: test
1085 1085 | | | | | date: Thu Jan 01 00:00:10 1970 +0000
1086 1086 | | | | | summary: (10) merge two known; one immediate left, one near right
1087 1087 | | | | |
1088 1088 | | | o | changeset: 9:7010c0af0a35
1089 1089 | | | |\ \ parent: 7:b632bb1b1224
1090 1090 | | | | | | parent: 8:7a0b11f71937
1091 1091 | | | | | | user: test
1092 1092 | | | | | | date: Thu Jan 01 00:00:09 1970 +0000
1093 1093 | | | | | | summary: (9) expand
1094 1094 | | | | | |
1095 1095 +-------o | changeset: 8:7a0b11f71937
1096 1096 | | | |/ / parent: 0:e6eb3150255d
1097 1097 | | | | | parent: 7:b632bb1b1224
1098 1098 | | | | | user: test
1099 1099 | | | | | date: Thu Jan 01 00:00:08 1970 +0000
1100 1100 | | | | | summary: (8) merge two known; one immediate left, one far right
1101 1101 | | | | |
1102 1102 | | | o | changeset: 7:b632bb1b1224
1103 1103 | | | |\ \ parent: 2:3d9a33b8d1e1
1104 1104 | | | | | | parent: 5:4409d547b708
1105 1105 | | | | | | user: test
1106 1106 | | | | | | date: Thu Jan 01 00:00:07 1970 +0000
1107 1107 | | | | | | summary: (7) expand
1108 1108 | | | | | |
1109 1109 | | | +---o changeset: 6:b105a072e251
1110 1110 | | | | |/ parent: 2:3d9a33b8d1e1
1111 1111 | | | | | parent: 5:4409d547b708
1112 1112 | | | | | user: test
1113 1113 | | | | | date: Thu Jan 01 00:00:06 1970 +0000
1114 1114 | | | | | summary: (6) merge two known; one immediate left, one far left
1115 1115 | | | | |
1116 1116 | | | o | changeset: 5:4409d547b708
1117 1117 | | | |\ \ parent: 3:27eef8ed80b4
1118 1118 | | | | | | parent: 4:26a8bac39d9f
1119 1119 | | | | | | user: test
1120 1120 | | | | | | date: Thu Jan 01 00:00:05 1970 +0000
1121 1121 | | | | | | summary: (5) expand
1122 1122 | | | | | |
1123 1123 | +---o | | changeset: 4:26a8bac39d9f
1124 1124 | | | |/ / parent: 1:6db2ef61d156
1125 1125 | | | | | parent: 3:27eef8ed80b4
1126 1126 | | | | | user: test
1127 1127 | | | | | date: Thu Jan 01 00:00:04 1970 +0000
1128 1128 | | | | | summary: (4) merge two known; one immediate left, one immediate right
1129 1129 | | | | |
1130 1130
1131 1131
1132 1132 Empty revision range - display nothing:
1133 1133 $ hg log -G -r 1..0
1134 1134
1135 1135 $ cd ..
1136 1136
1137 1137 #if no-outer-repo
1138 1138
1139 1139 From outer space:
1140 1140 $ hg log -G -l1 repo
1141 1141 @ changeset: 34:fea3ac5810e0
1142 1142 | tag: tip
1143 1143 | parent: 32:d06dffa21a31
1144 1144 | user: test
1145 1145 | date: Thu Jan 01 00:00:34 1970 +0000
1146 1146 | summary: (34) head
1147 1147 |
1148 1148 $ hg log -G -l1 repo/a
1149 1149 @ changeset: 34:fea3ac5810e0
1150 1150 | tag: tip
1151 1151 | parent: 32:d06dffa21a31
1152 1152 | user: test
1153 1153 | date: Thu Jan 01 00:00:34 1970 +0000
1154 1154 | summary: (34) head
1155 1155 |
1156 1156 $ hg log -G -l1 repo/missing
1157 1157
1158 1158 #endif
1159 1159
1160 1160 File log with revs != cset revs:
1161 1161 $ hg init flog
1162 1162 $ cd flog
1163 1163 $ echo one >one
1164 1164 $ hg add one
1165 1165 $ hg commit -mone
1166 1166 $ echo two >two
1167 1167 $ hg add two
1168 1168 $ hg commit -mtwo
1169 1169 $ echo more >two
1170 1170 $ hg commit -mmore
1171 1171 $ hg log -G two
1172 1172 @ changeset: 2:12c28321755b
1173 1173 | tag: tip
1174 1174 | user: test
1175 1175 | date: Thu Jan 01 00:00:00 1970 +0000
1176 1176 | summary: more
1177 1177 |
1178 1178 o changeset: 1:5ac72c0599bf
1179 1179 | user: test
1180 1180 | date: Thu Jan 01 00:00:00 1970 +0000
1181 1181 | summary: two
1182 1182 |
1183 1183
1184 1184 Issue1896: File log with explicit style
1185 1185 $ hg log -G --style=default one
1186 1186 o changeset: 0:3d578b4a1f53
1187 1187 user: test
1188 1188 date: Thu Jan 01 00:00:00 1970 +0000
1189 1189 summary: one
1190 1190
1191 1191 Issue2395: glog --style header and footer
1192 1192 $ hg log -G --style=xml one
1193 1193 <?xml version="1.0"?>
1194 1194 <log>
1195 1195 o <logentry revision="0" node="3d578b4a1f537d5fcf7301bfa9c0b97adfaa6fb1">
1196 1196 <author email="test">test</author>
1197 1197 <date>1970-01-01T00:00:00+00:00</date>
1198 1198 <msg xml:space="preserve">one</msg>
1199 1199 </logentry>
1200 1200 </log>
1201 1201
1202 1202 $ cd ..
1203 1203
1204 1204 Incoming and outgoing:
1205 1205
1206 1206 $ hg clone -U -r31 repo repo2
1207 1207 adding changesets
1208 1208 adding manifests
1209 1209 adding file changes
1210 1210 added 31 changesets with 31 changes to 1 files
1211 1211 $ cd repo2
1212 1212
1213 1213 $ hg incoming --graph ../repo
1214 1214 comparing with ../repo
1215 1215 searching for changes
1216 1216 o changeset: 34:fea3ac5810e0
1217 1217 | tag: tip
1218 1218 | parent: 32:d06dffa21a31
1219 1219 | user: test
1220 1220 | date: Thu Jan 01 00:00:34 1970 +0000
1221 1221 | summary: (34) head
1222 1222 |
1223 1223 | o changeset: 33:68608f5145f9
1224 1224 | parent: 18:1aa84d96232a
1225 1225 | user: test
1226 1226 | date: Thu Jan 01 00:00:33 1970 +0000
1227 1227 | summary: (33) head
1228 1228 |
1229 1229 o changeset: 32:d06dffa21a31
1230 1230 | parent: 27:886ed638191b
1231 1231 | parent: 31:621d83e11f67
1232 1232 | user: test
1233 1233 | date: Thu Jan 01 00:00:32 1970 +0000
1234 1234 | summary: (32) expand
1235 1235 |
1236 1236 o changeset: 27:886ed638191b
1237 1237 parent: 21:d42a756af44d
1238 1238 user: test
1239 1239 date: Thu Jan 01 00:00:27 1970 +0000
1240 1240 summary: (27) collapse
1241 1241
1242 1242 $ cd ..
1243 1243
1244 1244 $ hg -R repo outgoing --graph repo2
1245 1245 comparing with repo2
1246 1246 searching for changes
1247 1247 @ changeset: 34:fea3ac5810e0
1248 1248 | tag: tip
1249 1249 | parent: 32:d06dffa21a31
1250 1250 | user: test
1251 1251 | date: Thu Jan 01 00:00:34 1970 +0000
1252 1252 | summary: (34) head
1253 1253 |
1254 1254 | o changeset: 33:68608f5145f9
1255 1255 | parent: 18:1aa84d96232a
1256 1256 | user: test
1257 1257 | date: Thu Jan 01 00:00:33 1970 +0000
1258 1258 | summary: (33) head
1259 1259 |
1260 1260 o changeset: 32:d06dffa21a31
1261 1261 | parent: 27:886ed638191b
1262 1262 | parent: 31:621d83e11f67
1263 1263 | user: test
1264 1264 | date: Thu Jan 01 00:00:32 1970 +0000
1265 1265 | summary: (32) expand
1266 1266 |
1267 1267 o changeset: 27:886ed638191b
1268 1268 parent: 21:d42a756af44d
1269 1269 user: test
1270 1270 date: Thu Jan 01 00:00:27 1970 +0000
1271 1271 summary: (27) collapse
1272 1272
1273 1273
1274 1274 File + limit with revs != cset revs:
1275 1275 $ cd repo
1276 1276 $ touch b
1277 1277 $ hg ci -Aqm0
1278 1278 $ hg log -G -l2 a
1279 1279 o changeset: 34:fea3ac5810e0
1280 1280 | parent: 32:d06dffa21a31
1281 1281 | user: test
1282 1282 | date: Thu Jan 01 00:00:34 1970 +0000
1283 1283 | summary: (34) head
1284 1284 |
1285 1285 | o changeset: 33:68608f5145f9
1286 1286 | | parent: 18:1aa84d96232a
1287 1287 | | user: test
1288 1288 | | date: Thu Jan 01 00:00:33 1970 +0000
1289 1289 | | summary: (33) head
1290 1290 | |
1291 1291
1292 1292 File + limit + -ra:b, (b - a) < limit:
1293 1293 $ hg log -G -l3000 -r32:tip a
1294 1294 o changeset: 34:fea3ac5810e0
1295 1295 | parent: 32:d06dffa21a31
1296 1296 | user: test
1297 1297 | date: Thu Jan 01 00:00:34 1970 +0000
1298 1298 | summary: (34) head
1299 1299 |
1300 1300 | o changeset: 33:68608f5145f9
1301 1301 | | parent: 18:1aa84d96232a
1302 1302 | | user: test
1303 1303 | | date: Thu Jan 01 00:00:33 1970 +0000
1304 1304 | | summary: (33) head
1305 1305 | |
1306 1306 o | changeset: 32:d06dffa21a31
1307 1307 |\ \ parent: 27:886ed638191b
1308 1308 | | | parent: 31:621d83e11f67
1309 1309 | | | user: test
1310 1310 | | | date: Thu Jan 01 00:00:32 1970 +0000
1311 1311 | | | summary: (32) expand
1312 1312 | | |
1313 1313
1314 1314 Point out a common and an uncommon unshown parent
1315 1315
1316 1316 $ hg log -G -r 'rev(8) or rev(9)'
1317 1317 o changeset: 9:7010c0af0a35
1318 1318 |\ parent: 7:b632bb1b1224
1319 1319 | | parent: 8:7a0b11f71937
1320 1320 | | user: test
1321 1321 | | date: Thu Jan 01 00:00:09 1970 +0000
1322 1322 | | summary: (9) expand
1323 1323 | |
1324 1324 o | changeset: 8:7a0b11f71937
1325 1325 |\| parent: 0:e6eb3150255d
1326 1326 | | parent: 7:b632bb1b1224
1327 1327 | | user: test
1328 1328 | | date: Thu Jan 01 00:00:08 1970 +0000
1329 1329 | | summary: (8) merge two known; one immediate left, one far right
1330 1330 | |
1331 1331
1332 1332 File + limit + -ra:b, b < tip:
1333 1333
1334 1334 $ hg log -G -l1 -r32:34 a
1335 1335 o changeset: 34:fea3ac5810e0
1336 1336 | parent: 32:d06dffa21a31
1337 1337 | user: test
1338 1338 | date: Thu Jan 01 00:00:34 1970 +0000
1339 1339 | summary: (34) head
1340 1340 |
1341 1341
1342 1342 file(File) + limit + -ra:b, b < tip:
1343 1343
1344 1344 $ hg log -G -l1 -r32:34 -r 'file("a")'
1345 1345 o changeset: 34:fea3ac5810e0
1346 1346 | parent: 32:d06dffa21a31
1347 1347 | user: test
1348 1348 | date: Thu Jan 01 00:00:34 1970 +0000
1349 1349 | summary: (34) head
1350 1350 |
1351 1351
1352 1352 limit(file(File) and a::b), b < tip:
1353 1353
1354 1354 $ hg log -G -r 'limit(file("a") and 32::34, 1)'
1355 1355 o changeset: 32:d06dffa21a31
1356 1356 |\ parent: 27:886ed638191b
1357 1357 | | parent: 31:621d83e11f67
1358 1358 | | user: test
1359 1359 | | date: Thu Jan 01 00:00:32 1970 +0000
1360 1360 | | summary: (32) expand
1361 1361 | |
1362 1362
1363 1363 File + limit + -ra:b, b < tip:
1364 1364
1365 1365 $ hg log -G -r 'limit(file("a") and 34::32, 1)'
1366 1366
1367 1367 File + limit + -ra:b, b < tip, (b - a) < limit:
1368 1368
1369 1369 $ hg log -G -l10 -r33:34 a
1370 1370 o changeset: 34:fea3ac5810e0
1371 1371 | parent: 32:d06dffa21a31
1372 1372 | user: test
1373 1373 | date: Thu Jan 01 00:00:34 1970 +0000
1374 1374 | summary: (34) head
1375 1375 |
1376 1376 | o changeset: 33:68608f5145f9
1377 1377 | | parent: 18:1aa84d96232a
1378 1378 | | user: test
1379 1379 | | date: Thu Jan 01 00:00:33 1970 +0000
1380 1380 | | summary: (33) head
1381 1381 | |
1382 1382
1383 1383 Do not crash or produce strange graphs if history is buggy
1384 1384
1385 1385 $ hg branch branch
1386 1386 marked working directory as branch branch
1387 1387 (branches are permanent and global, did you want a bookmark?)
1388 1388 $ commit 36 "buggy merge: identical parents" 35 35
1389 1389 $ hg log -G -l5
1390 1390 @ changeset: 36:08a19a744424
1391 1391 | branch: branch
1392 1392 | tag: tip
1393 1393 | parent: 35:9159c3644c5e
1394 1394 | parent: 35:9159c3644c5e
1395 1395 | user: test
1396 1396 | date: Thu Jan 01 00:00:36 1970 +0000
1397 1397 | summary: (36) buggy merge: identical parents
1398 1398 |
1399 1399 o changeset: 35:9159c3644c5e
1400 1400 | user: test
1401 1401 | date: Thu Jan 01 00:00:00 1970 +0000
1402 1402 | summary: 0
1403 1403 |
1404 1404 o changeset: 34:fea3ac5810e0
1405 1405 | parent: 32:d06dffa21a31
1406 1406 | user: test
1407 1407 | date: Thu Jan 01 00:00:34 1970 +0000
1408 1408 | summary: (34) head
1409 1409 |
1410 1410 | o changeset: 33:68608f5145f9
1411 1411 | | parent: 18:1aa84d96232a
1412 1412 | | user: test
1413 1413 | | date: Thu Jan 01 00:00:33 1970 +0000
1414 1414 | | summary: (33) head
1415 1415 | |
1416 1416 o | changeset: 32:d06dffa21a31
1417 1417 |\ \ parent: 27:886ed638191b
1418 1418 | | | parent: 31:621d83e11f67
1419 1419 | | | user: test
1420 1420 | | | date: Thu Jan 01 00:00:32 1970 +0000
1421 1421 | | | summary: (32) expand
1422 1422 | | |
1423 1423
1424 1424 Test log -G options
1425 1425
1426 1426 $ testlog() {
1427 1427 > hg log -G --print-revset "$@"
1428 1428 > hg log --template 'nodetag {rev}\n' "$@" | grep nodetag \
1429 1429 > | sed 's/.*nodetag/nodetag/' > log.nodes
1430 1430 > hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \
1431 1431 > | sed 's/.*nodetag/nodetag/' > glog.nodes
1432 1432 > diff -u log.nodes glog.nodes | grep '^[-+@ ]' || :
1433 1433 > }
1434 1434
1435 1435 glog always reorders nodes which explains the difference with log
1436 1436
1437 1437 $ testlog -r 27 -r 25 -r 21 -r 34 -r 32 -r 31
1438 1438 ['27', '25', '21', '34', '32', '31']
1439 1439 []
1440 1440 --- log.nodes * (glob)
1441 1441 +++ glog.nodes * (glob)
1442 1442 @@ -1,6 +1,6 @@
1443 1443 -nodetag 27
1444 1444 -nodetag 25
1445 1445 -nodetag 21
1446 1446 nodetag 34
1447 1447 nodetag 32
1448 1448 nodetag 31
1449 1449 +nodetag 27
1450 1450 +nodetag 25
1451 1451 +nodetag 21
1452 1452 $ testlog -u test -u not-a-user
1453 1453 []
1454 1454 (group
1455 1455 (group
1456 1456 (or
1457 1457 (func
1458 1458 ('symbol', 'user')
1459 1459 ('string', 'test'))
1460 1460 (func
1461 1461 ('symbol', 'user')
1462 1462 ('string', 'not-a-user')))))
1463 1463 $ testlog -b not-a-branch
1464 1464 abort: unknown revision 'not-a-branch'!
1465 1465 abort: unknown revision 'not-a-branch'!
1466 1466 abort: unknown revision 'not-a-branch'!
1467 1467 $ testlog -b 35 -b 36 --only-branch branch
1468 1468 []
1469 1469 (group
1470 1470 (group
1471 1471 (or
1472 1472 (or
1473 1473 (func
1474 1474 ('symbol', 'branch')
1475 1475 ('string', 'default'))
1476 1476 (func
1477 1477 ('symbol', 'branch')
1478 1478 ('string', 'branch')))
1479 1479 (func
1480 1480 ('symbol', 'branch')
1481 1481 ('string', 'branch')))))
1482 1482 $ testlog -k expand -k merge
1483 1483 []
1484 1484 (group
1485 1485 (group
1486 1486 (or
1487 1487 (func
1488 1488 ('symbol', 'keyword')
1489 1489 ('string', 'expand'))
1490 1490 (func
1491 1491 ('symbol', 'keyword')
1492 1492 ('string', 'merge')))))
1493 1493 $ testlog --only-merges
1494 1494 []
1495 1495 (group
1496 1496 (func
1497 1497 ('symbol', 'merge')
1498 1498 None))
1499 1499 $ testlog --no-merges
1500 1500 []
1501 1501 (group
1502 1502 (not
1503 1503 (func
1504 1504 ('symbol', 'merge')
1505 1505 None)))
1506 1506 $ testlog --date '2 0 to 4 0'
1507 1507 []
1508 1508 (group
1509 1509 (func
1510 1510 ('symbol', 'date')
1511 1511 ('string', '2 0 to 4 0')))
1512 1512 $ hg log -G -d 'brace ) in a date'
1513 1513 abort: invalid date: 'brace ) in a date'
1514 1514 [255]
1515 1515 $ testlog --prune 31 --prune 32
1516 1516 []
1517 1517 (group
1518 1518 (group
1519 1519 (and
1520 1520 (not
1521 1521 (group
1522 1522 (or
1523 1523 ('string', '31')
1524 1524 (func
1525 1525 ('symbol', 'ancestors')
1526 1526 ('string', '31')))))
1527 1527 (not
1528 1528 (group
1529 1529 (or
1530 1530 ('string', '32')
1531 1531 (func
1532 1532 ('symbol', 'ancestors')
1533 1533 ('string', '32'))))))))
1534 1534
1535 1535 Dedicated repo for --follow and paths filtering. The g is crafted to
1536 1536 have 2 filelog topological heads in a linear changeset graph.
1537 1537
1538 1538 $ cd ..
1539 1539 $ hg init follow
1540 1540 $ cd follow
1541 1541 $ testlog --follow
1542 1542 []
1543 1543 []
1544 1544 $ echo a > a
1545 1545 $ echo aa > aa
1546 1546 $ echo f > f
1547 1547 $ hg ci -Am "add a" a aa f
1548 1548 $ hg cp a b
1549 1549 $ hg cp f g
1550 1550 $ hg ci -m "copy a b"
1551 1551 $ mkdir dir
1552 1552 $ hg mv b dir
1553 1553 $ echo g >> g
1554 1554 $ echo f >> f
1555 1555 $ hg ci -m "mv b dir/b"
1556 1556 $ hg mv a b
1557 1557 $ hg cp -f f g
1558 1558 $ echo a > d
1559 1559 $ hg add d
1560 1560 $ hg ci -m "mv a b; add d"
1561 1561 $ hg mv dir/b e
1562 1562 $ hg ci -m "mv dir/b e"
1563 1563 $ hg log -G --template '({rev}) {desc|firstline}\n'
1564 1564 @ (4) mv dir/b e
1565 1565 |
1566 1566 o (3) mv a b; add d
1567 1567 |
1568 1568 o (2) mv b dir/b
1569 1569 |
1570 1570 o (1) copy a b
1571 1571 |
1572 1572 o (0) add a
1573 1573
1574 1574
1575 1575 $ testlog a
1576 1576 []
1577 1577 (group
1578 1578 (group
1579 1579 (func
1580 1580 ('symbol', 'filelog')
1581 1581 ('string', 'a'))))
1582 1582 $ testlog a b
1583 1583 []
1584 1584 (group
1585 1585 (group
1586 1586 (or
1587 1587 (func
1588 1588 ('symbol', 'filelog')
1589 1589 ('string', 'a'))
1590 1590 (func
1591 1591 ('symbol', 'filelog')
1592 1592 ('string', 'b')))))
1593 1593
1594 1594 Test falling back to slow path for non-existing files
1595 1595
1596 1596 $ testlog a c
1597 1597 []
1598 1598 (group
1599 1599 (func
1600 1600 ('symbol', '_matchfiles')
1601 1601 (list
1602 1602 (list
1603 1603 (list
1604 1604 ('string', 'r:')
1605 1605 ('string', 'd:relpath'))
1606 1606 ('string', 'p:a'))
1607 1607 ('string', 'p:c'))))
1608 1608
1609 1609 Test multiple --include/--exclude/paths
1610 1610
1611 1611 $ testlog --include a --include e --exclude b --exclude e a e
1612 1612 []
1613 1613 (group
1614 1614 (func
1615 1615 ('symbol', '_matchfiles')
1616 1616 (list
1617 1617 (list
1618 1618 (list
1619 1619 (list
1620 1620 (list
1621 1621 (list
1622 1622 (list
1623 1623 ('string', 'r:')
1624 1624 ('string', 'd:relpath'))
1625 1625 ('string', 'p:a'))
1626 1626 ('string', 'p:e'))
1627 1627 ('string', 'i:a'))
1628 1628 ('string', 'i:e'))
1629 1629 ('string', 'x:b'))
1630 1630 ('string', 'x:e'))))
1631 1631
1632 1632 Test glob expansion of pats
1633 1633
1634 1634 $ expandglobs=`python -c "import mercurial.util; \
1635 1635 > print mercurial.util.expandglobs and 'true' or 'false'"`
1636 1636 $ if [ $expandglobs = "true" ]; then
1637 1637 > testlog 'a*';
1638 1638 > else
1639 1639 > testlog a*;
1640 1640 > fi;
1641 1641 []
1642 1642 (group
1643 1643 (group
1644 1644 (func
1645 1645 ('symbol', 'filelog')
1646 1646 ('string', 'aa'))))
1647 1647
1648 1648 Test --follow on a directory
1649 1649
1650 1650 $ testlog -f dir
1651 1651 abort: cannot follow file not in parent revision: "dir"
1652 1652 abort: cannot follow file not in parent revision: "dir"
1653 1653 abort: cannot follow file not in parent revision: "dir"
1654 1654
1655 1655 Test --follow on file not in parent revision
1656 1656
1657 1657 $ testlog -f a
1658 1658 abort: cannot follow file not in parent revision: "a"
1659 1659 abort: cannot follow file not in parent revision: "a"
1660 1660 abort: cannot follow file not in parent revision: "a"
1661 1661
1662 1662 Test --follow and patterns
1663 1663
1664 1664 $ testlog -f 'glob:*'
1665 1665 abort: can only follow copies/renames for explicit filenames
1666 1666 abort: can only follow copies/renames for explicit filenames
1667 1667 abort: can only follow copies/renames for explicit filenames
1668 1668
1669 1669 Test --follow on a single rename
1670 1670
1671 1671 $ hg up -q 2
1672 1672 $ testlog -f a
1673 1673 []
1674 1674 (group
1675 1675 (group
1676 1676 (func
1677 1677 ('symbol', 'follow')
1678 1678 ('string', 'a'))))
1679 1679
1680 1680 Test --follow and multiple renames
1681 1681
1682 1682 $ hg up -q tip
1683 1683 $ testlog -f e
1684 1684 []
1685 1685 (group
1686 1686 (group
1687 1687 (func
1688 1688 ('symbol', 'follow')
1689 1689 ('string', 'e'))))
1690 1690
1691 1691 Test --follow and multiple filelog heads
1692 1692
1693 1693 $ hg up -q 2
1694 1694 $ testlog -f g
1695 1695 []
1696 1696 (group
1697 1697 (group
1698 1698 (func
1699 1699 ('symbol', 'follow')
1700 1700 ('string', 'g'))))
1701 1701 $ cat log.nodes
1702 1702 nodetag 2
1703 1703 nodetag 1
1704 1704 nodetag 0
1705 1705 $ hg up -q tip
1706 1706 $ testlog -f g
1707 1707 []
1708 1708 (group
1709 1709 (group
1710 1710 (func
1711 1711 ('symbol', 'follow')
1712 1712 ('string', 'g'))))
1713 1713 $ cat log.nodes
1714 1714 nodetag 3
1715 1715 nodetag 2
1716 1716 nodetag 0
1717 1717
1718 1718 Test --follow and multiple files
1719 1719
1720 1720 $ testlog -f g e
1721 1721 []
1722 1722 (group
1723 1723 (group
1724 1724 (or
1725 1725 (func
1726 1726 ('symbol', 'follow')
1727 1727 ('string', 'g'))
1728 1728 (func
1729 1729 ('symbol', 'follow')
1730 1730 ('string', 'e')))))
1731 1731 $ cat log.nodes
1732 1732 nodetag 4
1733 1733 nodetag 3
1734 1734 nodetag 2
1735 1735 nodetag 1
1736 1736 nodetag 0
1737 1737
1738 1738 Test --follow-first
1739 1739
1740 1740 $ hg up -q 3
1741 1741 $ echo ee > e
1742 1742 $ hg ci -Am "add another e" e
1743 1743 created new head
1744 1744 $ hg merge --tool internal:other 4
1745 1745 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
1746 1746 (branch merge, don't forget to commit)
1747 1747 $ echo merge > e
1748 1748 $ hg ci -m "merge 5 and 4"
1749 1749 $ testlog --follow-first
1750 1750 []
1751 1751 (group
1752 1752 (func
1753 1753 ('symbol', '_firstancestors')
1754 1754 ('symbol', '6')))
1755 1755
1756 1756 Cannot compare with log --follow-first FILE as it never worked
1757 1757
1758 1758 $ hg log -G --print-revset --follow-first e
1759 1759 []
1760 1760 (group
1761 1761 (group
1762 1762 (func
1763 1763 ('symbol', '_followfirst')
1764 1764 ('string', 'e'))))
1765 1765 $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
1766 1766 @ 6 merge 5 and 4
1767 1767 |\
1768 1768 o | 5 add another e
1769 1769 | |
1770 1770
1771 1771 Test --copies
1772 1772
1773 1773 $ hg log -G --copies --template "{rev} {desc|firstline} \
1774 1774 > copies: {file_copies_switch}\n"
1775 1775 @ 6 merge 5 and 4 copies:
1776 1776 |\
1777 1777 | o 5 add another e copies:
1778 1778 | |
1779 1779 o | 4 mv dir/b e copies: e (dir/b)
1780 1780 |/
1781 1781 o 3 mv a b; add d copies: b (a)g (f)
1782 1782 |
1783 1783 o 2 mv b dir/b copies: dir/b (b)
1784 1784 |
1785 1785 o 1 copy a b copies: b (a)g (f)
1786 1786 |
1787 1787 o 0 add a copies:
1788 1788
1789 1789 Test "set:..." and parent revision
1790 1790
1791 1791 $ hg up -q 4
1792 1792 $ testlog "set:copied()"
1793 1793 []
1794 1794 (group
1795 1795 (func
1796 1796 ('symbol', '_matchfiles')
1797 1797 (list
1798 1798 (list
1799 1799 ('string', 'r:')
1800 1800 ('string', 'd:relpath'))
1801 1801 ('string', 'p:set:copied()'))))
1802 1802 $ testlog --include "set:copied()"
1803 1803 []
1804 1804 (group
1805 1805 (func
1806 1806 ('symbol', '_matchfiles')
1807 1807 (list
1808 1808 (list
1809 1809 ('string', 'r:')
1810 1810 ('string', 'd:relpath'))
1811 1811 ('string', 'i:set:copied()'))))
1812 1812 $ testlog -r "sort(file('set:copied()'), -rev)"
1813 1813 ["sort(file('set:copied()'), -rev)"]
1814 1814 []
1815 1815
1816 1816 Test --removed
1817 1817
1818 1818 $ testlog --removed
1819 1819 []
1820 1820 []
1821 1821 $ testlog --removed a
1822 1822 []
1823 1823 (group
1824 1824 (func
1825 1825 ('symbol', '_matchfiles')
1826 1826 (list
1827 1827 (list
1828 1828 ('string', 'r:')
1829 1829 ('string', 'd:relpath'))
1830 1830 ('string', 'p:a'))))
1831 1831 $ testlog --removed --follow a
1832 1832 abort: can only follow copies/renames for explicit filenames
1833 1833 abort: can only follow copies/renames for explicit filenames
1834 1834 abort: can only follow copies/renames for explicit filenames
1835 1835
1836 1836 Test --patch and --stat with --follow and --follow-first
1837 1837
1838 1838 $ hg up -q 3
1839 1839 $ hg log -G --git --patch b
1840 1840 o changeset: 1:216d4c92cf98
1841 1841 | user: test
1842 1842 | date: Thu Jan 01 00:00:00 1970 +0000
1843 1843 | summary: copy a b
1844 1844 |
1845 1845 | diff --git a/a b/b
1846 1846 | copy from a
1847 1847 | copy to b
1848 1848 |
1849 1849
1850 1850 $ hg log -G --git --stat b
1851 1851 o changeset: 1:216d4c92cf98
1852 1852 | user: test
1853 1853 | date: Thu Jan 01 00:00:00 1970 +0000
1854 1854 | summary: copy a b
1855 1855 |
1856 | a | 0
1856 | b | 0
1857 1857 | 1 files changed, 0 insertions(+), 0 deletions(-)
1858 1858 |
1859 1859
1860 1860 $ hg log -G --git --patch --follow b
1861 1861 o changeset: 1:216d4c92cf98
1862 1862 | user: test
1863 1863 | date: Thu Jan 01 00:00:00 1970 +0000
1864 1864 | summary: copy a b
1865 1865 |
1866 1866 | diff --git a/a b/b
1867 1867 | copy from a
1868 1868 | copy to b
1869 1869 |
1870 1870 o changeset: 0:f8035bb17114
1871 1871 user: test
1872 1872 date: Thu Jan 01 00:00:00 1970 +0000
1873 1873 summary: add a
1874 1874
1875 1875 diff --git a/a b/a
1876 1876 new file mode 100644
1877 1877 --- /dev/null
1878 1878 +++ b/a
1879 1879 @@ -0,0 +1,1 @@
1880 1880 +a
1881 1881
1882 1882
1883 1883 $ hg log -G --git --stat --follow b
1884 1884 o changeset: 1:216d4c92cf98
1885 1885 | user: test
1886 1886 | date: Thu Jan 01 00:00:00 1970 +0000
1887 1887 | summary: copy a b
1888 1888 |
1889 | a | 0
1889 | b | 0
1890 1890 | 1 files changed, 0 insertions(+), 0 deletions(-)
1891 1891 |
1892 1892 o changeset: 0:f8035bb17114
1893 1893 user: test
1894 1894 date: Thu Jan 01 00:00:00 1970 +0000
1895 1895 summary: add a
1896 1896
1897 1897 a | 1 +
1898 1898 1 files changed, 1 insertions(+), 0 deletions(-)
1899 1899
1900 1900
1901 1901 $ hg up -q 6
1902 1902 $ hg log -G --git --patch --follow-first e
1903 1903 @ changeset: 6:fc281d8ff18d
1904 1904 |\ tag: tip
1905 1905 | | parent: 5:99b31f1c2782
1906 1906 | | parent: 4:17d952250a9d
1907 1907 | | user: test
1908 1908 | | date: Thu Jan 01 00:00:00 1970 +0000
1909 1909 | | summary: merge 5 and 4
1910 1910 | |
1911 1911 | | diff --git a/e b/e
1912 1912 | | --- a/e
1913 1913 | | +++ b/e
1914 1914 | | @@ -1,1 +1,1 @@
1915 1915 | | -ee
1916 1916 | | +merge
1917 1917 | |
1918 1918 o | changeset: 5:99b31f1c2782
1919 1919 | | parent: 3:5918b8d165d1
1920 1920 | | user: test
1921 1921 | | date: Thu Jan 01 00:00:00 1970 +0000
1922 1922 | | summary: add another e
1923 1923 | |
1924 1924 | | diff --git a/e b/e
1925 1925 | | new file mode 100644
1926 1926 | | --- /dev/null
1927 1927 | | +++ b/e
1928 1928 | | @@ -0,0 +1,1 @@
1929 1929 | | +ee
1930 1930 | |
1931 1931
1932 1932 Test old-style --rev
1933 1933
1934 1934 $ hg tag 'foo-bar'
1935 1935 $ testlog -r 'foo-bar'
1936 1936 ['foo-bar']
1937 1937 []
1938 1938
1939 1939 Test --follow and forward --rev
1940 1940
1941 1941 $ hg up -q 6
1942 1942 $ echo g > g
1943 1943 $ hg ci -Am 'add g' g
1944 1944 created new head
1945 1945 $ hg up -q 2
1946 1946 $ hg log -G --template "{rev} {desc|firstline}\n"
1947 1947 o 8 add g
1948 1948 |
1949 1949 | o 7 Added tag foo-bar for changeset fc281d8ff18d
1950 1950 |/
1951 1951 o 6 merge 5 and 4
1952 1952 |\
1953 1953 | o 5 add another e
1954 1954 | |
1955 1955 o | 4 mv dir/b e
1956 1956 |/
1957 1957 o 3 mv a b; add d
1958 1958 |
1959 1959 @ 2 mv b dir/b
1960 1960 |
1961 1961 o 1 copy a b
1962 1962 |
1963 1963 o 0 add a
1964 1964
1965 1965 $ testlog --follow -r6 -r8 -r5 -r7 -r4
1966 1966 ['6', '8', '5', '7', '4']
1967 1967 (group
1968 1968 (func
1969 1969 ('symbol', 'descendants')
1970 1970 ('symbol', '6')))
1971 1971 --- log.nodes * (glob)
1972 1972 +++ glog.nodes * (glob)
1973 1973 @@ -1,3 +1,3 @@
1974 1974 -nodetag 6
1975 1975 nodetag 8
1976 1976 nodetag 7
1977 1977 +nodetag 6
1978 1978
1979 1979 Test --follow-first and forward --rev
1980 1980
1981 1981 $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
1982 1982 ['6', '8', '5', '7', '4']
1983 1983 (group
1984 1984 (func
1985 1985 ('symbol', '_firstdescendants')
1986 1986 ('symbol', '6')))
1987 1987 --- log.nodes * (glob)
1988 1988 +++ glog.nodes * (glob)
1989 1989 @@ -1,3 +1,3 @@
1990 1990 -nodetag 6
1991 1991 nodetag 8
1992 1992 nodetag 7
1993 1993 +nodetag 6
1994 1994
1995 1995 Test --follow and backward --rev
1996 1996
1997 1997 $ testlog --follow -r6 -r5 -r7 -r8 -r4
1998 1998 ['6', '5', '7', '8', '4']
1999 1999 (group
2000 2000 (func
2001 2001 ('symbol', 'ancestors')
2002 2002 ('symbol', '6')))
2003 2003
2004 2004 Test --follow-first and backward --rev
2005 2005
2006 2006 $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
2007 2007 ['6', '5', '7', '8', '4']
2008 2008 (group
2009 2009 (func
2010 2010 ('symbol', '_firstancestors')
2011 2011 ('symbol', '6')))
2012 2012
2013 2013 Test subdir
2014 2014
2015 2015 $ hg up -q 3
2016 2016 $ cd dir
2017 2017 $ testlog .
2018 2018 []
2019 2019 (group
2020 2020 (func
2021 2021 ('symbol', '_matchfiles')
2022 2022 (list
2023 2023 (list
2024 2024 ('string', 'r:')
2025 2025 ('string', 'd:relpath'))
2026 2026 ('string', 'p:.'))))
2027 2027 $ testlog ../b
2028 2028 []
2029 2029 (group
2030 2030 (group
2031 2031 (func
2032 2032 ('symbol', 'filelog')
2033 2033 ('string', '../b'))))
2034 2034 $ testlog -f ../b
2035 2035 []
2036 2036 (group
2037 2037 (group
2038 2038 (func
2039 2039 ('symbol', 'follow')
2040 2040 ('string', 'b'))))
2041 2041 $ cd ..
2042 2042
2043 2043 Test --hidden
2044 2044 (enable obsolete)
2045 2045
2046 2046 $ cat > ${TESTTMP}/obs.py << EOF
2047 2047 > import mercurial.obsolete
2048 2048 > mercurial.obsolete._enabled = True
2049 2049 > EOF
2050 2050 $ echo '[extensions]' >> $HGRCPATH
2051 2051 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
2052 2052
2053 2053 $ hg debugobsolete `hg id --debug -i -r 8`
2054 2054 $ testlog
2055 2055 []
2056 2056 []
2057 2057 $ testlog --hidden
2058 2058 []
2059 2059 []
2060 2060 $ hg log -G --template '{rev} {desc}\n'
2061 2061 o 7 Added tag foo-bar for changeset fc281d8ff18d
2062 2062 |
2063 2063 o 6 merge 5 and 4
2064 2064 |\
2065 2065 | o 5 add another e
2066 2066 | |
2067 2067 o | 4 mv dir/b e
2068 2068 |/
2069 2069 @ 3 mv a b; add d
2070 2070 |
2071 2071 o 2 mv b dir/b
2072 2072 |
2073 2073 o 1 copy a b
2074 2074 |
2075 2075 o 0 add a
2076 2076
2077 2077
2078 2078 A template without trailing newline should do something sane
2079 2079
2080 2080 $ hg log -G -r ::2 --template '{rev} {desc}'
2081 2081 o 2 mv b dir/b
2082 2082 |
2083 2083 o 1 copy a b
2084 2084 |
2085 2085 o 0 add a
2086 2086
2087 2087
2088 2088 Extra newlines must be preserved
2089 2089
2090 2090 $ hg log -G -r ::2 --template '\n{rev} {desc}\n\n'
2091 2091 o
2092 2092 | 2 mv b dir/b
2093 2093 |
2094 2094 o
2095 2095 | 1 copy a b
2096 2096 |
2097 2097 o
2098 2098 0 add a
2099 2099
2100 2100
2101 2101 The almost-empty template should do something sane too ...
2102 2102
2103 2103 $ hg log -G -r ::2 --template '\n'
2104 2104 o
2105 2105 |
2106 2106 o
2107 2107 |
2108 2108 o
2109 2109
2110 2110
2111 2111 issue3772
2112 2112
2113 2113 $ hg log -G -r :null
2114 2114 o changeset: 0:f8035bb17114
2115 2115 | user: test
2116 2116 | date: Thu Jan 01 00:00:00 1970 +0000
2117 2117 | summary: add a
2118 2118 |
2119 2119 o changeset: -1:000000000000
2120 2120 user:
2121 2121 date: Thu Jan 01 00:00:00 1970 +0000
2122 2122
2123 2123 $ hg log -G -r null:null
2124 2124 o changeset: -1:000000000000
2125 2125 user:
2126 2126 date: Thu Jan 01 00:00:00 1970 +0000
2127 2127
2128 2128
2129 2129 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now