36 commands.norepo += " qclone qversion" |
36 commands.norepo += " qclone qversion" |
37 |
37 |
38 # Patch names looks like unix-file names. |
38 # Patch names looks like unix-file names. |
39 # They must be joinable with queue directory and result in the patch path. |
39 # They must be joinable with queue directory and result in the patch path. |
40 normname = util.normpath |
40 normname = util.normpath |
|
41 |
|
42 def _strip(ui, repo, rev, backup="all"): |
|
43 def limitheads(chlog, stop): |
|
44 """return the list of all nodes that have no children""" |
|
45 p = {} |
|
46 h = [] |
|
47 stoprev = 0 |
|
48 if stop in chlog.nodemap: |
|
49 stoprev = chlog.rev(stop) |
|
50 |
|
51 for r in xrange(chlog.count() - 1, -1, -1): |
|
52 n = chlog.node(r) |
|
53 if n not in p: |
|
54 h.append(n) |
|
55 if n == stop: |
|
56 break |
|
57 if r < stoprev: |
|
58 break |
|
59 for pn in chlog.parents(n): |
|
60 p[pn] = 1 |
|
61 return h |
|
62 |
|
63 def bundle(cg): |
|
64 backupdir = repo.join("strip-backup") |
|
65 if not os.path.isdir(backupdir): |
|
66 os.mkdir(backupdir) |
|
67 name = os.path.join(backupdir, "%s" % revlog.short(rev)) |
|
68 name = savename(name) |
|
69 ui.warn("saving bundle to %s\n" % name) |
|
70 return changegroup.writebundle(cg, name, "HG10BZ") |
|
71 |
|
72 def stripall(revnum): |
|
73 mm = repo.changectx(rev).manifest() |
|
74 seen = {} |
|
75 |
|
76 for x in xrange(revnum, repo.changelog.count()): |
|
77 for f in repo.changectx(x).files(): |
|
78 if f in seen: |
|
79 continue |
|
80 seen[f] = 1 |
|
81 if f in mm: |
|
82 filerev = mm[f] |
|
83 else: |
|
84 filerev = 0 |
|
85 seen[f] = filerev |
|
86 # we go in two steps here so the strip loop happens in a |
|
87 # sensible order. When stripping many files, this helps keep |
|
88 # our disk access patterns under control. |
|
89 seen_list = seen.keys() |
|
90 seen_list.sort() |
|
91 for f in seen_list: |
|
92 ff = repo.file(f) |
|
93 filerev = seen[f] |
|
94 if filerev != 0: |
|
95 if filerev in ff.nodemap: |
|
96 filerev = ff.rev(filerev) |
|
97 else: |
|
98 filerev = 0 |
|
99 ff.strip(filerev, revnum) |
|
100 |
|
101 chlog = repo.changelog |
|
102 # TODO delete the undo files, and handle undo of merge sets |
|
103 pp = chlog.parents(rev) |
|
104 revnum = chlog.rev(rev) |
|
105 |
|
106 # save is a list of all the branches we are truncating away |
|
107 # that we actually want to keep. changegroup will be used |
|
108 # to preserve them and add them back after the truncate |
|
109 saveheads = [] |
|
110 savebases = {} |
|
111 |
|
112 heads = limitheads(chlog, rev) |
|
113 seen = {} |
|
114 |
|
115 # search through all the heads, finding those where the revision |
|
116 # we want to strip away is an ancestor. Also look for merges |
|
117 # that might be turned into new heads by the strip. |
|
118 while heads: |
|
119 h = heads.pop() |
|
120 n = h |
|
121 while True: |
|
122 seen[n] = 1 |
|
123 pp = chlog.parents(n) |
|
124 if pp[1] != revlog.nullid: |
|
125 for p in pp: |
|
126 if chlog.rev(p) > revnum and p not in seen: |
|
127 heads.append(p) |
|
128 if pp[0] == revlog.nullid: |
|
129 break |
|
130 if chlog.rev(pp[0]) < revnum: |
|
131 break |
|
132 n = pp[0] |
|
133 if n == rev: |
|
134 break |
|
135 r = chlog.reachable(h, rev) |
|
136 if rev not in r: |
|
137 saveheads.append(h) |
|
138 for x in r: |
|
139 if chlog.rev(x) > revnum: |
|
140 savebases[x] = 1 |
|
141 |
|
142 # create a changegroup for all the branches we need to keep |
|
143 if backup == "all": |
|
144 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip') |
|
145 bundle(backupch) |
|
146 if saveheads: |
|
147 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip') |
|
148 chgrpfile = bundle(backupch) |
|
149 |
|
150 stripall(revnum) |
|
151 |
|
152 change = chlog.read(rev) |
|
153 chlog.strip(revnum, revnum) |
|
154 repo.manifest.strip(repo.manifest.rev(change[0]), revnum) |
|
155 if saveheads: |
|
156 ui.status("adding branch\n") |
|
157 commands.unbundle(ui, repo, "file:%s" % chgrpfile, update=False) |
|
158 if backup != "strip": |
|
159 os.unlink(chgrpfile) |
41 |
160 |
42 class statusentry: |
161 class statusentry: |
43 def __init__(self, rev, name=None): |
162 def __init__(self, rev, name=None): |
44 if not name: |
163 if not name: |
45 fields = rev.split(':', 1) |
164 fields = rev.split(':', 1) |
627 if commitfiles: |
746 if commitfiles: |
628 self.refresh(repo, short=True) |
747 self.refresh(repo, short=True) |
629 self.removeundo(repo) |
748 self.removeundo(repo) |
630 |
749 |
631 def strip(self, repo, rev, update=True, backup="all", wlock=None): |
750 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: |
751 if not wlock: |
691 wlock = repo.wlock() |
752 wlock = repo.wlock() |
692 lock = repo.lock() |
753 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 |
754 |
698 if update: |
755 if update: |
699 self.check_localchanges(repo, refresh=False) |
756 self.check_localchanges(repo, refresh=False) |
700 urev = self.qparents(repo, rev) |
757 urev = self.qparents(repo, rev) |
701 hg.clean(repo, urev, wlock=wlock) |
758 hg.clean(repo, urev, wlock=wlock) |
702 repo.dirstate.write() |
759 repo.dirstate.write() |
703 |
760 |
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) |
761 self.removeundo(repo) |
754 if saveheads: |
762 _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 |
763 |
761 def isapplied(self, patch): |
764 def isapplied(self, patch): |
762 """returns (index, rev, patch)""" |
765 """returns (index, rev, patch)""" |
763 for i in xrange(len(self.applied)): |
766 for i in xrange(len(self.applied)): |
764 a = self.applied[i] |
767 a = self.applied[i] |