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