##// END OF EJS Templates
merge with stable
Matt Mackall -
r20101:80d8bd69 merge default
parent child Browse files
Show More
@@ -1,219 +1,225 b''
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 if opts.get('bookmark'):
123 mark = opts.get('bookmark')
124 marks = repo._bookmarks
125 if mark not in marks:
126 raise util.Abort(_("bookmark '%s' not found") % mark)
122 wlock = repo.wlock()
123 try:
124 if opts.get('bookmark'):
125 mark = opts.get('bookmark')
126 marks = repo._bookmarks
127 if mark not in marks:
128 raise util.Abort(_("bookmark '%s' not found") % mark)
129
130 # If the requested bookmark is not the only one pointing to a
131 # a revision we have to only delete the bookmark and not strip
132 # anything. revsets cannot detect that case.
133 uniquebm = True
134 for m, n in marks.iteritems():
135 if m != mark and n == repo[mark].node():
136 uniquebm = False
137 break
138 if uniquebm:
139 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
140 "ancestors(head() and not bookmark(%s)) - "
141 "ancestors(bookmark() and not bookmark(%s))",
142 mark, mark, mark)
143 revs.update(set(rsrevs))
144 if not revs:
145 del marks[mark]
146 marks.write()
147 ui.write(_("bookmark '%s' deleted\n") % mark)
148
149 if not revs:
150 raise util.Abort(_('empty revision set'))
151
152 descendants = set(cl.descendants(revs))
153 strippedrevs = revs.union(descendants)
154 roots = revs.difference(descendants)
155
156 update = False
157 # if one of the wdir parent is stripped we'll need
158 # to update away to an earlier revision
159 for p in repo.dirstate.parents():
160 if p != nullid and cl.rev(p) in strippedrevs:
161 update = True
162 break
163
164 rootnodes = set(cl.node(r) for r in roots)
127 165
128 # If the requested bookmark is not the only one pointing to a
129 # a revision we have to only delete the bookmark and not strip
130 # anything. revsets cannot detect that case.
131 uniquebm = True
132 for m, n in marks.iteritems():
133 if m != mark and n == repo[mark].node():
134 uniquebm = False
135 break
136 if uniquebm:
137 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
138 "ancestors(head() and not bookmark(%s)) - "
139 "ancestors(bookmark() and not bookmark(%s))",
140 mark, mark, mark)
141 revs.update(set(rsrevs))
142 if not revs:
166 q = getattr(repo, 'mq', None)
167 if q is not None and q.applied:
168 # refresh queue state if we're about to strip
169 # applied patches
170 if cl.rev(repo.lookup('qtip')) in strippedrevs:
171 q.applieddirty = True
172 start = 0
173 end = len(q.applied)
174 for i, statusentry in enumerate(q.applied):
175 if statusentry.node in rootnodes:
176 # if one of the stripped roots is an applied
177 # patch, only part of the queue is stripped
178 start = i
179 break
180 del q.applied[start:end]
181 q.savedirty()
182
183 revs = sorted(rootnodes)
184 if update and opts.get('keep'):
185 wlock = repo.wlock()
186 try:
187 urev, p2 = repo.changelog.parents(revs[0])
188 if (util.safehasattr(repo, 'mq') and p2 != nullid
189 and p2 in [x.node for x in repo.mq.applied]):
190 urev = p2
191 uctx = repo[urev]
192
193 # only reset the dirstate for files that would actually change
194 # between the working context and uctx
195 descendantrevs = repo.revs("%s::." % uctx.rev())
196 changedfiles = []
197 for rev in descendantrevs:
198 # blindly reset the files, regardless of what actually
199 # changed
200 changedfiles.extend(repo[rev].files())
201
202 # reset files that only changed in the dirstate too
203 dirstate = repo.dirstate
204 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
205 changedfiles.extend(dirchanges)
206
207 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
208 repo.dirstate.write()
209 update = False
210 finally:
211 wlock.release()
212
213 if opts.get('bookmark'):
214 if mark == repo._bookmarkcurrent:
215 bookmarks.unsetcurrent(repo)
143 216 del marks[mark]
144 217 marks.write()
145 218 ui.write(_("bookmark '%s' deleted\n") % mark)
146 219
147 if not revs:
148 raise util.Abort(_('empty revision set'))
149
150 descendants = set(cl.descendants(revs))
151 strippedrevs = revs.union(descendants)
152 roots = revs.difference(descendants)
153
154 update = False
155 # if one of the wdir parent is stripped we'll need
156 # to update away to an earlier revision
157 for p in repo.dirstate.parents():
158 if p != nullid and cl.rev(p) in strippedrevs:
159 update = True
160 break
161
162 rootnodes = set(cl.node(r) for r in roots)
163
164 q = getattr(repo, 'mq', None)
165 if q is not None and q.applied:
166 # refresh queue state if we're about to strip
167 # applied patches
168 if cl.rev(repo.lookup('qtip')) in strippedrevs:
169 q.applieddirty = True
170 start = 0
171 end = len(q.applied)
172 for i, statusentry in enumerate(q.applied):
173 if statusentry.node in rootnodes:
174 # if one of the stripped roots is an applied
175 # patch, only part of the queue is stripped
176 start = i
177 break
178 del q.applied[start:end]
179 q.savedirty()
180
181 revs = sorted(rootnodes)
182 if update and opts.get('keep'):
183 wlock = repo.wlock()
184 try:
185 urev, p2 = repo.changelog.parents(revs[0])
186 if (util.safehasattr(repo, 'mq') and p2 != nullid
187 and p2 in [x.node for x in repo.mq.applied]):
188 urev = p2
189 uctx = repo[urev]
190
191 # only reset the dirstate for files that would actually change
192 # between the working context and uctx
193 descendantrevs = repo.revs("%s::." % uctx.rev())
194 changedfiles = []
195 for rev in descendantrevs:
196 # blindly reset the files, regardless of what actually changed
197 changedfiles.extend(repo[rev].files())
198
199 # reset files that only changed in the dirstate too
200 dirstate = repo.dirstate
201 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
202 changedfiles.extend(dirchanges)
203
204 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
205 repo.dirstate.write()
206 update = False
207 finally:
208 wlock.release()
209
210 if opts.get('bookmark'):
211 if mark == repo._bookmarkcurrent:
212 bookmarks.unsetcurrent(repo)
213 del marks[mark]
214 marks.write()
215 ui.write(_("bookmark '%s' deleted\n") % mark)
216
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