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. |
39 # They must be joinable with queue directory and result in the patch path. |
40 # They must be joinable with queue directory and result in the patch path. |
40 normname = util.normpath |
41 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(repo, bases, heads, rev, suffix): |
|
64 cg = repo.changegroupsubset(bases, heads, 'strip') |
|
65 backupdir = repo.join("strip-backup") |
|
66 if not os.path.isdir(backupdir): |
|
67 os.mkdir(backupdir) |
|
68 name = os.path.join(backupdir, "%s-%s" % (revlog.short(rev), suffix)) |
|
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 bundle(repo, [rev], chlog.heads(), rev, 'backup') |
|
145 if saveheads: |
|
146 chgrpfile = bundle(repo, savebases.keys(), saveheads, rev, 'temp') |
|
147 |
|
148 stripall(revnum) |
|
149 |
|
150 change = chlog.read(rev) |
|
151 chlog.strip(revnum, revnum) |
|
152 repo.manifest.strip(repo.manifest.rev(change[0]), revnum) |
|
153 if saveheads: |
|
154 ui.status("adding branch\n") |
|
155 commands.unbundle(ui, repo, "file:%s" % chgrpfile, update=False) |
|
156 if backup != "strip": |
|
157 os.unlink(chgrpfile) |
|
158 |
42 |
159 class statusentry: |
43 class statusentry: |
160 def __init__(self, rev, name=None): |
44 def __init__(self, rev, name=None): |
161 if not name: |
45 if not name: |
162 fields = rev.split(':', 1) |
46 fields = rev.split(':', 1) |
755 urev = self.qparents(repo, rev) |
639 urev = self.qparents(repo, rev) |
756 hg.clean(repo, urev, wlock=wlock) |
640 hg.clean(repo, urev, wlock=wlock) |
757 repo.dirstate.write() |
641 repo.dirstate.write() |
758 |
642 |
759 self.removeundo(repo) |
643 self.removeundo(repo) |
760 _strip(self.ui, repo, rev, backup) |
644 repair.strip(self.ui, repo, rev, backup) |
761 |
645 |
762 def isapplied(self, patch): |
646 def isapplied(self, patch): |
763 """returns (index, rev, patch)""" |
647 """returns (index, rev, patch)""" |
764 for i in xrange(len(self.applied)): |
648 for i in xrange(len(self.applied)): |
765 a = self.applied[i] |
649 a = self.applied[i] |