##// END OF EJS Templates
merge with stable
Matt Mackall -
r20101:80d8bd69 merge default
parent child Browse files
Show More
@@ -1,219 +1,225
1 1 """strip changesets and their descendents from history
2 2
3 3 This extension allows you to strip changesets and all their descendants from the
4 4 repository. See the command help for details.
5 5 """
6 6 from mercurial.i18n import _
7 7 from mercurial.node import nullid
8 8 from mercurial.lock import release
9 9 from mercurial import cmdutil, hg, scmutil, util
10 10 from mercurial import repair, bookmarks
11 11
12 12 cmdtable = {}
13 13 command = cmdutil.command(cmdtable)
14 14 testedwith = 'internal'
15 15
16 16 def checksubstate(repo, baserev=None):
17 17 '''return list of subrepos at a different revision than substate.
18 18 Abort if any subrepos have uncommitted changes.'''
19 19 inclsubs = []
20 20 wctx = repo[None]
21 21 if baserev:
22 22 bctx = repo[baserev]
23 23 else:
24 24 bctx = wctx.parents()[0]
25 25 for s in sorted(wctx.substate):
26 26 if wctx.sub(s).dirty(True):
27 27 raise util.Abort(
28 28 _("uncommitted changes in subrepository %s") % s)
29 29 elif s not in bctx.substate or bctx.sub(s).dirty():
30 30 inclsubs.append(s)
31 31 return inclsubs
32 32
33 33 def checklocalchanges(repo, force=False, excsuffix=''):
34 34 cmdutil.checkunfinished(repo)
35 35 m, a, r, d = repo.status()[:4]
36 36 if not force:
37 37 if (m or a or r or d):
38 38 _("local changes found") # i18n tool detection
39 39 raise util.Abort(_("local changes found" + excsuffix))
40 40 if checksubstate(repo):
41 41 _("local changed subrepos found") # i18n tool detection
42 42 raise util.Abort(_("local changed subrepos found" + excsuffix))
43 43 return m, a, r, d
44 44
45 45 def strip(ui, repo, revs, update=True, backup="all", force=None):
46 46 wlock = lock = None
47 47 try:
48 48 wlock = repo.wlock()
49 49 lock = repo.lock()
50 50
51 51 if update:
52 52 checklocalchanges(repo, force=force)
53 53 urev, p2 = repo.changelog.parents(revs[0])
54 54 if (util.safehasattr(repo, 'mq') and
55 55 p2 != nullid
56 56 and p2 in [x.node for x in repo.mq.applied]):
57 57 urev = p2
58 58 hg.clean(repo, urev)
59 59 repo.dirstate.write()
60 60
61 61 repair.strip(ui, repo, revs, backup)
62 62 finally:
63 63 release(lock, wlock)
64 64
65 65
66 66 @command("strip",
67 67 [
68 68 ('r', 'rev', [], _('strip specified revision (optional, '
69 69 'can specify revisions without this '
70 70 'option)'), _('REV')),
71 71 ('f', 'force', None, _('force removal of changesets, discard '
72 72 'uncommitted changes (no backup)')),
73 73 ('b', 'backup', None, _('bundle only changesets with local revision'
74 74 ' number greater than REV which are not'
75 75 ' descendants of REV (DEPRECATED)')),
76 76 ('', 'no-backup', None, _('no backups')),
77 77 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
78 78 ('n', '', None, _('ignored (DEPRECATED)')),
79 79 ('k', 'keep', None, _("do not modify working copy during strip")),
80 80 ('B', 'bookmark', '', _("remove revs only reachable from given"
81 81 " bookmark"))],
82 82 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
83 83 def stripcmd(ui, repo, *revs, **opts):
84 84 """strip changesets and all their descendants from the repository
85 85
86 86 The strip command removes the specified changesets and all their
87 87 descendants. If the working directory has uncommitted changes, the
88 88 operation is aborted unless the --force flag is supplied, in which
89 89 case changes will be discarded.
90 90
91 91 If a parent of the working directory is stripped, then the working
92 92 directory will automatically be updated to the most recent
93 93 available ancestor of the stripped parent after the operation
94 94 completes.
95 95
96 96 Any stripped changesets are stored in ``.hg/strip-backup`` as a
97 97 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
98 98 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
99 99 where BUNDLE is the bundle file created by the strip. Note that
100 100 the local revision numbers will in general be different after the
101 101 restore.
102 102
103 103 Use the --no-backup option to discard the backup bundle once the
104 104 operation completes.
105 105
106 106 Strip is not a history-rewriting operation and can be used on
107 107 changesets in the public phase. But if the stripped changesets have
108 108 been pushed to a remote repository you will likely pull them again.
109 109
110 110 Return 0 on success.
111 111 """
112 112 backup = 'all'
113 113 if opts.get('backup'):
114 114 backup = 'strip'
115 115 elif opts.get('no_backup') or opts.get('nobackup'):
116 116 backup = 'none'
117 117
118 118 cl = repo.changelog
119 119 revs = list(revs) + opts.get('rev')
120 120 revs = set(scmutil.revrange(repo, revs))
121 121
122 wlock = repo.wlock()
123 try:
122 124 if opts.get('bookmark'):
123 125 mark = opts.get('bookmark')
124 126 marks = repo._bookmarks
125 127 if mark not in marks:
126 128 raise util.Abort(_("bookmark '%s' not found") % mark)
127 129
128 130 # If the requested bookmark is not the only one pointing to a
129 131 # a revision we have to only delete the bookmark and not strip
130 132 # anything. revsets cannot detect that case.
131 133 uniquebm = True
132 134 for m, n in marks.iteritems():
133 135 if m != mark and n == repo[mark].node():
134 136 uniquebm = False
135 137 break
136 138 if uniquebm:
137 139 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
138 140 "ancestors(head() and not bookmark(%s)) - "
139 141 "ancestors(bookmark() and not bookmark(%s))",
140 142 mark, mark, mark)
141 143 revs.update(set(rsrevs))
142 144 if not revs:
143 145 del marks[mark]
144 146 marks.write()
145 147 ui.write(_("bookmark '%s' deleted\n") % mark)
146 148
147 149 if not revs:
148 150 raise util.Abort(_('empty revision set'))
149 151
150 152 descendants = set(cl.descendants(revs))
151 153 strippedrevs = revs.union(descendants)
152 154 roots = revs.difference(descendants)
153 155
154 156 update = False
155 157 # if one of the wdir parent is stripped we'll need
156 158 # to update away to an earlier revision
157 159 for p in repo.dirstate.parents():
158 160 if p != nullid and cl.rev(p) in strippedrevs:
159 161 update = True
160 162 break
161 163
162 164 rootnodes = set(cl.node(r) for r in roots)
163 165
164 166 q = getattr(repo, 'mq', None)
165 167 if q is not None and q.applied:
166 168 # refresh queue state if we're about to strip
167 169 # applied patches
168 170 if cl.rev(repo.lookup('qtip')) in strippedrevs:
169 171 q.applieddirty = True
170 172 start = 0
171 173 end = len(q.applied)
172 174 for i, statusentry in enumerate(q.applied):
173 175 if statusentry.node in rootnodes:
174 176 # if one of the stripped roots is an applied
175 177 # patch, only part of the queue is stripped
176 178 start = i
177 179 break
178 180 del q.applied[start:end]
179 181 q.savedirty()
180 182
181 183 revs = sorted(rootnodes)
182 184 if update and opts.get('keep'):
183 185 wlock = repo.wlock()
184 186 try:
185 187 urev, p2 = repo.changelog.parents(revs[0])
186 188 if (util.safehasattr(repo, 'mq') and p2 != nullid
187 189 and p2 in [x.node for x in repo.mq.applied]):
188 190 urev = p2
189 191 uctx = repo[urev]
190 192
191 193 # only reset the dirstate for files that would actually change
192 194 # between the working context and uctx
193 195 descendantrevs = repo.revs("%s::." % uctx.rev())
194 196 changedfiles = []
195 197 for rev in descendantrevs:
196 # blindly reset the files, regardless of what actually changed
198 # blindly reset the files, regardless of what actually
199 # changed
197 200 changedfiles.extend(repo[rev].files())
198 201
199 202 # reset files that only changed in the dirstate too
200 203 dirstate = repo.dirstate
201 204 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
202 205 changedfiles.extend(dirchanges)
203 206
204 207 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
205 208 repo.dirstate.write()
206 209 update = False
207 210 finally:
208 211 wlock.release()
209 212
210 213 if opts.get('bookmark'):
211 214 if mark == repo._bookmarkcurrent:
212 215 bookmarks.unsetcurrent(repo)
213 216 del marks[mark]
214 217 marks.write()
215 218 ui.write(_("bookmark '%s' deleted\n") % mark)
216 219
217 strip(ui, repo, revs, backup=backup, update=update, force=opts.get('force'))
220 strip(ui, repo, revs, backup=backup, update=update,
221 force=opts.get('force'))
222 finally:
223 wlock.release()
218 224
219 225 return 0
General Comments 0
You need to be logged in to leave comments. Login now