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