28 remove patch from applied stack qpop |
28 remove patch from applied stack qpop |
29 refresh contents of top applied patch qrefresh |
29 refresh contents of top applied patch qrefresh |
30 ''' |
30 ''' |
31 |
31 |
32 from mercurial.i18n import _ |
32 from mercurial.i18n import _ |
33 from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup |
33 from mercurial import commands, cmdutil, hg, patch, revlog, util |
|
34 from mercurial import repair |
34 import os, sys, re, errno |
35 import os, sys, re, errno |
35 |
36 |
36 commands.norepo += " qclone qversion" |
37 commands.norepo += " qclone qversion" |
37 |
38 |
38 # Patch names looks like unix-file names. |
39 # Patch names looks like unix-file names. |
627 if commitfiles: |
628 if commitfiles: |
628 self.refresh(repo, short=True) |
629 self.refresh(repo, short=True) |
629 self.removeundo(repo) |
630 self.removeundo(repo) |
630 |
631 |
631 def strip(self, repo, rev, update=True, backup="all", wlock=None): |
632 def strip(self, repo, rev, update=True, backup="all", wlock=None): |
632 def limitheads(chlog, stop): |
|
633 """return the list of all nodes that have no children""" |
|
634 p = {} |
|
635 h = [] |
|
636 stoprev = 0 |
|
637 if stop in chlog.nodemap: |
|
638 stoprev = chlog.rev(stop) |
|
639 |
|
640 for r in xrange(chlog.count() - 1, -1, -1): |
|
641 n = chlog.node(r) |
|
642 if n not in p: |
|
643 h.append(n) |
|
644 if n == stop: |
|
645 break |
|
646 if r < stoprev: |
|
647 break |
|
648 for pn in chlog.parents(n): |
|
649 p[pn] = 1 |
|
650 return h |
|
651 |
|
652 def bundle(cg): |
|
653 backupdir = repo.join("strip-backup") |
|
654 if not os.path.isdir(backupdir): |
|
655 os.mkdir(backupdir) |
|
656 name = os.path.join(backupdir, "%s" % revlog.short(rev)) |
|
657 name = savename(name) |
|
658 self.ui.warn("saving bundle to %s\n" % name) |
|
659 return changegroup.writebundle(cg, name, "HG10BZ") |
|
660 |
|
661 def stripall(revnum): |
|
662 mm = repo.changectx(rev).manifest() |
|
663 seen = {} |
|
664 |
|
665 for x in xrange(revnum, repo.changelog.count()): |
|
666 for f in repo.changectx(x).files(): |
|
667 if f in seen: |
|
668 continue |
|
669 seen[f] = 1 |
|
670 if f in mm: |
|
671 filerev = mm[f] |
|
672 else: |
|
673 filerev = 0 |
|
674 seen[f] = filerev |
|
675 # we go in two steps here so the strip loop happens in a |
|
676 # sensible order. When stripping many files, this helps keep |
|
677 # our disk access patterns under control. |
|
678 seen_list = seen.keys() |
|
679 seen_list.sort() |
|
680 for f in seen_list: |
|
681 ff = repo.file(f) |
|
682 filerev = seen[f] |
|
683 if filerev != 0: |
|
684 if filerev in ff.nodemap: |
|
685 filerev = ff.rev(filerev) |
|
686 else: |
|
687 filerev = 0 |
|
688 ff.strip(filerev, revnum) |
|
689 |
|
690 if not wlock: |
633 if not wlock: |
691 wlock = repo.wlock() |
634 wlock = repo.wlock() |
692 lock = repo.lock() |
635 lock = repo.lock() |
693 chlog = repo.changelog |
|
694 # TODO delete the undo files, and handle undo of merge sets |
|
695 pp = chlog.parents(rev) |
|
696 revnum = chlog.rev(rev) |
|
697 |
636 |
698 if update: |
637 if update: |
699 self.check_localchanges(repo, refresh=False) |
638 self.check_localchanges(repo, refresh=False) |
700 urev = self.qparents(repo, rev) |
639 urev = self.qparents(repo, rev) |
701 hg.clean(repo, urev, wlock=wlock) |
640 hg.clean(repo, urev, wlock=wlock) |
702 repo.dirstate.write() |
641 repo.dirstate.write() |
703 |
642 |
704 # save is a list of all the branches we are truncating away |
|
705 # that we actually want to keep. changegroup will be used |
|
706 # to preserve them and add them back after the truncate |
|
707 saveheads = [] |
|
708 savebases = {} |
|
709 |
|
710 heads = limitheads(chlog, rev) |
|
711 seen = {} |
|
712 |
|
713 # search through all the heads, finding those where the revision |
|
714 # we want to strip away is an ancestor. Also look for merges |
|
715 # that might be turned into new heads by the strip. |
|
716 while heads: |
|
717 h = heads.pop() |
|
718 n = h |
|
719 while True: |
|
720 seen[n] = 1 |
|
721 pp = chlog.parents(n) |
|
722 if pp[1] != revlog.nullid: |
|
723 for p in pp: |
|
724 if chlog.rev(p) > revnum and p not in seen: |
|
725 heads.append(p) |
|
726 if pp[0] == revlog.nullid: |
|
727 break |
|
728 if chlog.rev(pp[0]) < revnum: |
|
729 break |
|
730 n = pp[0] |
|
731 if n == rev: |
|
732 break |
|
733 r = chlog.reachable(h, rev) |
|
734 if rev not in r: |
|
735 saveheads.append(h) |
|
736 for x in r: |
|
737 if chlog.rev(x) > revnum: |
|
738 savebases[x] = 1 |
|
739 |
|
740 # create a changegroup for all the branches we need to keep |
|
741 if backup == "all": |
|
742 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip') |
|
743 bundle(backupch) |
|
744 if saveheads: |
|
745 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip') |
|
746 chgrpfile = bundle(backupch) |
|
747 |
|
748 stripall(revnum) |
|
749 |
|
750 change = chlog.read(rev) |
|
751 chlog.strip(revnum, revnum) |
|
752 repo.manifest.strip(repo.manifest.rev(change[0]), revnum) |
|
753 self.removeundo(repo) |
643 self.removeundo(repo) |
754 if saveheads: |
644 repair.strip(self.ui, repo, rev, backup) |
755 self.ui.status("adding branch\n") |
|
756 commands.unbundle(self.ui, repo, "file:%s" % chgrpfile, |
|
757 update=False) |
|
758 if backup != "strip": |
|
759 os.unlink(chgrpfile) |
|
760 |
645 |
761 def isapplied(self, patch): |
646 def isapplied(self, patch): |
762 """returns (index, rev, patch)""" |
647 """returns (index, rev, patch)""" |
763 for i in xrange(len(self.applied)): |
648 for i in xrange(len(self.applied)): |
764 a = self.applied[i] |
649 a = self.applied[i] |