~ubuntu-branches/ubuntu/wily/mercurial/wily

« back to all changes in this revision

Viewing changes to hgext/strip.py

  • Committer: Package Import Robot
  • Author(s): Javi Merino
  • Date: 2013-11-01 23:19:57 UTC
  • mfrom: (1.2.38) (9.1.16 experimental)
  • Revision ID: package-import@ubuntu.com-20131101231957-hs70pwpinavlz3t6
Tags: 2.8-1
* New upstream release
* Fix mercurial-git and hgsubversion autopkgtest by loading the
  appropriate extension
* Bump standards-version to 3.9.5 (no change needed)

Show diffs side-by-side

added added

removed removed

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