~ddormer/renamer/1125067-add-prepare-method

« back to all changes in this revision

Viewing changes to renamer/plugins/undo.py

  • Committer: Jonathan Jacobs
  • Date: 2010-10-24 17:41:36 UTC
  • mfrom: (83.2.23 undo-command)
  • Revision ID: korpse@slipgate.za.net-20101024174136-geqsnh247drgj5hu
Merge lp:~renamer-developers/renamer/undo-command.

Implement persistent history of Renamer actions and undoing them.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from twisted.python import usage
 
2
 
 
3
from renamer import logging
 
4
from renamer.history import Action, Changeset
 
5
from renamer.plugin import Command, SubCommand
 
6
 
 
7
 
 
8
 
 
9
def getItem(store, storeID, acceptableTypes):
 
10
    """
 
11
    Get an Axiom Item from a store by ID and verify that it is an acceptable
 
12
    type.
 
13
    """
 
14
    try:
 
15
        storeID = int(storeID)
 
16
    except (ValueError, TypeError):
 
17
        raise usage.UsageError(
 
18
            'Identifier %r is not an integer' % (storeID,))
 
19
    else:
 
20
        item = store.getItemByID(storeID, default=None)
 
21
        if not isinstance(item, acceptableTypes):
 
22
            raise usage.UsageError(
 
23
                'Invalid identifier %r' % (storeID,))
 
24
        return item
 
25
 
 
26
 
 
27
 
 
28
class _UndoMixin(object):
 
29
    optFlags = [
 
30
        ('ignore-errors', None, 'Do not stop the process when encountering OS errors')]
 
31
 
 
32
 
 
33
    def undoActions(self, renamer, changeset, actions):
 
34
        """
 
35
        Undo specific actions from a changeset.
 
36
        """
 
37
        for action in actions:
 
38
            msg = 'Simulating undo'
 
39
            if not renamer.options['no-act']:
 
40
                msg = 'Undo'
 
41
 
 
42
            logging.msg('%s: %s' % (msg, action.asHumanly()), verbosity=3)
 
43
            if not renamer.options['no-act']:
 
44
                try:
 
45
                    changeset.undo(action, renamer.options)
 
46
                except OSError, e:
 
47
                    if not self['ignore-errors']:
 
48
                        raise e
 
49
                    logging.msg('Ignoring %r' % (e,), verbosity=3)
 
50
 
 
51
 
 
52
 
 
53
class UndoAction(SubCommand, _UndoMixin):
 
54
    name = 'action'
 
55
 
 
56
 
 
57
    synopsis = '[options] <actionID>'
 
58
 
 
59
 
 
60
    longdesc = """
 
61
    Undo a single action from a changeset. Consult "undo list" for action
 
62
    identifiers.
 
63
    """
 
64
 
 
65
 
 
66
    def parseArgs(self,  action):
 
67
        self['action'] = action
 
68
 
 
69
 
 
70
    def process(self, renamer):
 
71
        action = getItem(renamer.store, self['action'], Action)
 
72
        self.undoActions(
 
73
            renamer, action.changeset, [action])
 
74
 
 
75
 
 
76
 
 
77
class UndoChangeset(SubCommand, _UndoMixin):
 
78
    name = 'changeset'
 
79
 
 
80
 
 
81
    synopsis = '[options] <changesetID>'
 
82
 
 
83
 
 
84
    longdesc = """
 
85
    Undo an entire changeset. Consult "undo list" for changeset identifiers.
 
86
    """
 
87
 
 
88
 
 
89
    def parseArgs(self, changeset):
 
90
        self['changeset'] = changeset
 
91
 
 
92
 
 
93
    def process(self, renamer):
 
94
        changeset = getItem(renamer.store, self['changeset'], Changeset)
 
95
        logging.msg('Undoing: %s' % (changeset.asHumanly(),),
 
96
                    verbosity=3)
 
97
        actions = list(changeset.getActions())
 
98
        self.undoActions(
 
99
            renamer, changeset, reversed(actions))
 
100
 
 
101
 
 
102
 
 
103
class UndoList(SubCommand):
 
104
    name = 'list'
 
105
 
 
106
 
 
107
    longdesc = """
 
108
    List undoable changesets and actions.
 
109
    """
 
110
 
 
111
 
 
112
    def process(self, renamer):
 
113
        changesets = list(renamer.history.getChangesets())
 
114
        for cs in changesets:
 
115
            print 'Changeset ID=%d:  %s' % (cs.storeID, cs.asHumanly())
 
116
            for a in cs.getActions():
 
117
                print '   Action ID=%d:  %s' % (a.storeID, a.asHumanly())
 
118
            print
 
119
 
 
120
        if not changesets:
 
121
            print 'No changesets!'
 
122
 
 
123
 
 
124
 
 
125
class UndoForget(SubCommand):
 
126
    name = 'forget'
 
127
 
 
128
 
 
129
    synopsis = '<identifier>'
 
130
 
 
131
 
 
132
    longdesc = """
 
133
    Forget (permanently remove) an undo history action or entire changeset.
 
134
    Consult "undo list" for identifiers.
 
135
    """
 
136
 
 
137
 
 
138
    def parseArgs(self, identifier):
 
139
        self['identifier'] = identifier
 
140
 
 
141
 
 
142
    def process(self, renamer):
 
143
        item = getItem(renamer.store, self['identifier'], (Action, Changeset))
 
144
        if not renamer.options['no-act']:
 
145
            logging.msg('Forgetting: %s' % (item.asHumanly(),), verbosity=2)
 
146
            item.deleteFromStore()
 
147
 
 
148
 
 
149
 
 
150
class Undo(Command):
 
151
    name = 'undo'
 
152
 
 
153
 
 
154
    description = 'Undo previous Renamer actions'
 
155
 
 
156
 
 
157
    longdesc = """
 
158
    Every invocation of Renamer stores the actions taken as a changeset, this
 
159
    allows Renamer to undo entire changesets or previously performed individual
 
160
    actions.
 
161
 
 
162
    Undo actions are communicated by identifiers, which can be discovered by
 
163
    consulting "undo list".
 
164
    """
 
165
 
 
166
 
 
167
    subCommands = [
 
168
        ('action',    None, UndoAction,    'Undo a single action from a changeset'),
 
169
        ('changeset', None, UndoChangeset, 'Undo a whole changeset'),
 
170
        ('forget',    None, UndoForget,    'Forget an undo history item'),
 
171
        ('list',      None, UndoList,      'List changesets')]
 
172
 
 
173
 
 
174
    def parseArgs(self, *args):
 
175
        raise usage.UsageError('Issue an undo subcommand to perform')