5 # This software may be used and distributed according to the terms |
5 # This software may be used and distributed according to the terms |
6 # of the GNU General Public License, incorporated herein by reference. |
6 # of the GNU General Public License, incorporated herein by reference. |
7 |
7 |
8 from i18n import _ |
8 from i18n import _ |
9 from node import * |
9 from node import * |
10 import base85, cmdutil, mdiff, util |
10 import base85, cmdutil, mdiff, util, context, revlog |
11 import cStringIO, email.Parser, os, popen2, re, sha |
11 import cStringIO, email.Parser, os, popen2, re, sha |
12 import sys, tempfile, zlib |
12 import sys, tempfile, zlib |
13 |
13 |
14 # helper functions |
14 # helper functions |
15 |
15 |
434 fp = repo.ui |
434 fp = repo.ui |
435 |
435 |
436 if not node1: |
436 if not node1: |
437 node1 = repo.dirstate.parents()[0] |
437 node1 = repo.dirstate.parents()[0] |
438 |
438 |
439 clcache = {} |
439 ccache = {} |
440 def getchangelog(n): |
440 def getctx(r): |
441 if n not in clcache: |
441 if r not in ccache: |
442 clcache[n] = repo.changelog.read(n) |
442 ccache[r] = context.changectx(repo, r) |
443 return clcache[n] |
443 return ccache[r] |
444 mcache = {} |
444 |
445 def getmanifest(n): |
445 flcache = {} |
446 if n not in mcache: |
446 def getfilectx(f, ctx): |
447 mcache[n] = repo.manifest.read(n) |
447 flctx = ctx.filectx(f, filelog=flcache.get(f)) |
448 return mcache[n] |
448 if f not in flcache: |
449 fcache = {} |
449 flcache[f] = flctx._filelog |
450 def getfile(f): |
450 return flctx |
451 if f not in fcache: |
|
452 fcache[f] = repo.file(f) |
|
453 return fcache[f] |
|
454 |
451 |
455 # reading the data for node1 early allows it to play nicely |
452 # reading the data for node1 early allows it to play nicely |
456 # with repo.status and the revlog cache. |
453 # with repo.status and the revlog cache. |
457 change = getchangelog(node1) |
454 ctx1 = context.changectx(repo, node1) |
458 mmap = getmanifest(change[0]) |
455 # force manifest reading |
459 date1 = util.datestr(change[2]) |
456 man1 = ctx1.manifest() |
|
457 date1 = util.datestr(ctx1.date()) |
460 |
458 |
461 if not changes: |
459 if not changes: |
462 changes = repo.status(node1, node2, files, match=match)[:5] |
460 changes = repo.status(node1, node2, files, match=match)[:5] |
463 modified, added, removed, deleted, unknown = changes |
461 modified, added, removed, deleted, unknown = changes |
464 if files: |
462 if files: |
474 modified, added, removed = map(filterfiles, (modified, added, removed)) |
472 modified, added, removed = map(filterfiles, (modified, added, removed)) |
475 |
473 |
476 if not modified and not added and not removed: |
474 if not modified and not added and not removed: |
477 return |
475 return |
478 |
476 |
479 # returns False if there was no rename between n1 and n2 |
477 if node2: |
480 # returns None if the file was created between n1 and n2 |
478 ctx2 = context.changectx(repo, node2) |
481 # returns the (file, node) present in n1 that was renamed to f in n2 |
479 else: |
482 def renamedbetween(f, n1, n2): |
480 ctx2 = context.workingctx(repo) |
483 r1, r2 = map(repo.changelog.rev, (n1, n2)) |
481 man2 = ctx2.manifest() |
|
482 |
|
483 # returns False if there was no rename between ctx1 and ctx2 |
|
484 # returns None if the file was created between ctx1 and ctx2 |
|
485 # returns the (file, node) present in ctx1 that was renamed to f in ctx2 |
|
486 def renamed(f): |
|
487 startrev = ctx1.rev() |
|
488 c = ctx2 |
|
489 crev = c.rev() |
|
490 if crev is None: |
|
491 crev = repo.changelog.count() |
484 orig = f |
492 orig = f |
485 src = None |
493 while crev > startrev: |
486 while r2 > r1: |
494 if f in c.files(): |
487 cl = getchangelog(n2) |
|
488 if f in cl[3]: |
|
489 m = getmanifest(cl[0]) |
|
490 try: |
495 try: |
491 src = getfile(f).renamed(m[f]) |
496 src = getfilectx(f, c).renamed() |
492 except KeyError: |
497 except revlog.LookupError: |
493 return None |
498 return None |
494 if src: |
499 if src: |
495 f = src[0] |
500 f = src[0] |
496 n2 = repo.changelog.parents(n2)[0] |
501 crev = c.parents()[0].rev() |
497 r2 = repo.changelog.rev(n2) |
502 # try to reuse |
498 cl = getchangelog(n1) |
503 c = getctx(crev) |
499 m = getmanifest(cl[0]) |
504 if f not in man1: |
500 if f not in m: |
|
501 return None |
505 return None |
502 if f == orig: |
506 if f == orig: |
503 return False |
507 return False |
504 return f, m[f] |
508 return f |
505 |
|
506 if node2: |
|
507 change = getchangelog(node2) |
|
508 mmap2 = getmanifest(change[0]) |
|
509 _date2 = util.datestr(change[2]) |
|
510 def date2(f): |
|
511 return _date2 |
|
512 def read(f): |
|
513 return getfile(f).read(mmap2[f]) |
|
514 def renamed(f): |
|
515 return renamedbetween(f, node1, node2) |
|
516 else: |
|
517 tz = util.makedate()[1] |
|
518 _date2 = util.datestr() |
|
519 def date2(f): |
|
520 try: |
|
521 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz)) |
|
522 except OSError, err: |
|
523 if err.errno != errno.ENOENT: raise |
|
524 return _date2 |
|
525 def read(f): |
|
526 return repo.wread(f) |
|
527 def renamed(f): |
|
528 src = repo.dirstate.copied(f) |
|
529 parent = repo.dirstate.parents()[0] |
|
530 if src: |
|
531 f = src |
|
532 of = renamedbetween(f, node1, parent) |
|
533 if of or of is None: |
|
534 return of |
|
535 elif src: |
|
536 cl = getchangelog(parent)[0] |
|
537 return (src, getmanifest(cl)[src]) |
|
538 else: |
|
539 return None |
|
540 |
509 |
541 if repo.ui.quiet: |
510 if repo.ui.quiet: |
542 r = None |
511 r = None |
543 else: |
512 else: |
544 hexfunc = repo.ui.debugflag and hex or short |
513 hexfunc = repo.ui.debugflag and hex or short |
548 copied = {} |
517 copied = {} |
549 for f in added: |
518 for f in added: |
550 src = renamed(f) |
519 src = renamed(f) |
551 if src: |
520 if src: |
552 copied[f] = src |
521 copied[f] = src |
553 srcs = [x[1][0] for x in copied.items()] |
522 srcs = [x[1] for x in copied.items()] |
554 |
523 |
555 all = modified + added + removed |
524 all = modified + added + removed |
556 all.sort() |
525 all.sort() |
557 gone = {} |
526 gone = {} |
558 for f in all: |
527 for f in all: |
559 to = None |
528 to = None |
560 tn = None |
529 tn = None |
561 dodiff = True |
530 dodiff = True |
562 header = [] |
531 header = [] |
563 if f in mmap: |
532 if f in man1: |
564 to = getfile(f).read(mmap[f]) |
533 to = getfilectx(f, ctx1).data() |
565 if f not in removed: |
534 if f not in removed: |
566 tn = read(f) |
535 tn = getfilectx(f, ctx2).data() |
567 if opts.git: |
536 if opts.git: |
568 def gitmode(x): |
537 def gitmode(x): |
569 return x and '100755' or '100644' |
538 return x and '100755' or '100644' |
570 def addmodehdr(header, omode, nmode): |
539 def addmodehdr(header, omode, nmode): |
571 if omode != nmode: |
540 if omode != nmode: |
572 header.append('old mode %s\n' % omode) |
541 header.append('old mode %s\n' % omode) |
573 header.append('new mode %s\n' % nmode) |
542 header.append('new mode %s\n' % nmode) |
574 |
543 |
575 a, b = f, f |
544 a, b = f, f |
576 if f in added: |
545 if f in added: |
577 if node2: |
546 mode = gitmode(man2.execf(f)) |
578 mode = gitmode(mmap2.execf(f)) |
|
579 else: |
|
580 mode = gitmode(util.is_exec(repo.wjoin(f), None)) |
|
581 if f in copied: |
547 if f in copied: |
582 a, arev = copied[f] |
548 a = copied[f] |
583 omode = gitmode(mmap.execf(a)) |
549 omode = gitmode(man1.execf(a)) |
584 addmodehdr(header, omode, mode) |
550 addmodehdr(header, omode, mode) |
585 if a in removed and a not in gone: |
551 if a in removed and a not in gone: |
586 op = 'rename' |
552 op = 'rename' |
587 gone[a] = 1 |
553 gone[a] = 1 |
588 else: |
554 else: |
589 op = 'copy' |
555 op = 'copy' |
590 header.append('%s from %s\n' % (op, a)) |
556 header.append('%s from %s\n' % (op, a)) |
591 header.append('%s to %s\n' % (op, f)) |
557 header.append('%s to %s\n' % (op, f)) |
592 to = getfile(a).read(arev) |
558 to = getfilectx(a, ctx1).data() |
593 else: |
559 else: |
594 header.append('new file mode %s\n' % mode) |
560 header.append('new file mode %s\n' % mode) |
595 if util.binary(tn): |
561 if util.binary(tn): |
596 dodiff = 'binary' |
562 dodiff = 'binary' |
597 elif f in removed: |
563 elif f in removed: |
598 if f in srcs: |
564 if f in srcs: |
599 dodiff = False |
565 dodiff = False |
600 else: |
566 else: |
601 mode = gitmode(mmap.execf(f)) |
567 mode = gitmode(man1.execf(f)) |
602 header.append('deleted file mode %s\n' % mode) |
568 header.append('deleted file mode %s\n' % mode) |
603 else: |
569 else: |
604 omode = gitmode(mmap.execf(f)) |
570 omode = gitmode(man1.execf(f)) |
605 if node2: |
571 nmode = gitmode(man2.execf(f)) |
606 nmode = gitmode(mmap2.execf(f)) |
|
607 else: |
|
608 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f))) |
|
609 addmodehdr(header, omode, nmode) |
572 addmodehdr(header, omode, nmode) |
610 if util.binary(to) or util.binary(tn): |
573 if util.binary(to) or util.binary(tn): |
611 dodiff = 'binary' |
574 dodiff = 'binary' |
612 r = None |
575 r = None |
613 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b)) |
576 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b)) |
614 if dodiff == 'binary': |
577 if dodiff == 'binary': |
615 fp.write(''.join(header)) |
578 fp.write(''.join(header)) |
616 b85diff(fp, to, tn) |
579 b85diff(fp, to, tn) |
617 elif dodiff: |
580 elif dodiff: |
618 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts) |
581 text = mdiff.unidiff(to, date1, |
|
582 # ctx2 date may be dynamic |
|
583 tn, util.datestr(ctx2.date()), |
|
584 f, r, opts=opts) |
619 if text or len(header) > 1: |
585 if text or len(header) > 1: |
620 fp.write(''.join(header)) |
586 fp.write(''.join(header)) |
621 fp.write(text) |
587 fp.write(text) |
622 |
588 |
623 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False, |
589 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False, |