~abentley/bzrtools/bzrtools.dev

0.1.1 by Michael Ellerman
Initial import
1
#!/usr/bin/python
2
3
import os
4
import sys
0.2.3 by Michael Ellerman
Factor out bzrisms.
5
from errors import CommandError
0.1.56 by Michael Ellerman
Make HunkSelector agnostic as to whether it's selecting for shelving or
6
from hunk_selector import ShelveHunkSelector, UnshelveHunkSelector
0.1.35 by Michael Ellerman
Use DiffStat rather than calling out to /bin/diffstat
7
from diffstat import DiffStat
0.2.1 by Michael Ellerman
Factor out patch generation into PatchSource classes.
8
from patchsource import PatchSource, FilePatchSource
0.1.23 by Michael Ellerman
Incorporate Aaron's changes from bzrtools.
9
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
10
class Shelf(object):
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
11
    MESSAGE_PREFIX = "# Shelved patch: "
12
0.2.22 by Michael Ellerman
Infrastructure to allow for multiple interchangeable shelves.
13
    _paths = {
14
        'base'          : '.shelf',
15
        'shelves'       : '.shelf/shelves',
16
        'current-shelf' : '.shelf/current-shelf',
17
    }
18
19
    def __init__(self, base, name=None):
20
        self.base = base
21
        self.__setup()
22
23
        if name is None:
24
            current = os.path.join(self.base, self._paths['current-shelf'])
25
            name = open(current).read().strip()
26
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
27
        assert '\n' not in name
0.2.5 by Michael Ellerman
Make some of the messages a bit more readable.
28
        self.name = name
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
29
0.2.22 by Michael Ellerman
Infrastructure to allow for multiple interchangeable shelves.
30
        self.dir = os.path.join(self.base, self._paths['shelves'], name)
31
        if not os.path.isdir(self.dir):
32
            os.mkdir(self.dir)
0.1.43 by Michael Ellerman
New shelf layout. Shelves now sit under .bzr/x-shelf/default/
33
0.2.22 by Michael Ellerman
Infrastructure to allow for multiple interchangeable shelves.
34
    def __setup(self):
35
        # Create required directories etc.
36
        for dir in [self._paths['base'], self._paths['shelves']]:
37
            dir = os.path.join(self.base, dir)
0.2.2 by Michael Ellerman
For the moment at least storing scads of stuff under .bzr isn't really
38
            if not os.path.isdir(dir):
39
                os.mkdir(dir)
0.1.43 by Michael Ellerman
New shelf layout. Shelves now sit under .bzr/x-shelf/default/
40
0.2.22 by Michael Ellerman
Infrastructure to allow for multiple interchangeable shelves.
41
        current = os.path.join(self.base, self._paths['current-shelf'])
42
        if not os.path.exists(current):
43
            f = open(current, 'w')
44
            f.write('default')
45
            f.close()
46
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
47
    def make_default(self):
48
        f = open(os.path.join(self.base, self._paths['current-shelf']), 'w')
49
        f.write(self.name)
50
        f.close()
51
        self.log("Default shelf is now '%s'\n" % self.name)
52
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
53
    def log(self, msg):
0.2.20 by Michael Ellerman
Make list output look nicer when the shelf is empty.
54
        sys.stderr.write(msg)
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
55
0.2.19 by Michael Ellerman
Add shelf delete subcommand, make list barf if it gets an arg.
56
    def delete(self, patch):
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
57
        path = self.__path_from_user(patch)
0.2.19 by Michael Ellerman
Add shelf delete subcommand, make list barf if it gets an arg.
58
        os.remove(path)
59
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
60
    def display(self, patch):
61
        path = self.__path_from_user(patch)
62
        sys.stdout.write(open(path).read())
63
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
64
    def list(self):
65
        self.log("Patches on shelf '%s':" % self.name)
0.2.20 by Michael Ellerman
Make list output look nicer when the shelf is empty.
66
        indexes = self.__list()
67
        if len(indexes) == 0:
68
            self.log(' None\n')
69
            return
70
        self.log('\n')
71
        for index in indexes:
0.2.18 by Michael Ellerman
Make next_patch() cope with holes, eg. 00, 02 etc.
72
            msg = self.get_patch_message(self.__path(index))
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
73
            if msg is None:
74
                msg = "No message saved with patch."
0.2.20 by Michael Ellerman
Make list output look nicer when the shelf is empty.
75
            self.log(' %.2d: %s\n' % (index, msg))
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
76
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
77
    def __path_from_user(self, patch_id):
78
        try:
0.2.29 by Michael Ellerman
Better error message when trying to show an non-existant patch
79
            patch_index = int(patch_id)
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
80
        except TypeError:
81
            raise CommandError("Invalid patch name '%s'" % patch_id)
82
0.2.29 by Michael Ellerman
Better error message when trying to show an non-existant patch
83
        path = self.__path(patch_index)
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
84
85
        if not os.path.exists(path):
0.2.29 by Michael Ellerman
Better error message when trying to show an non-existant patch
86
            raise CommandError("Patch '%s' doesn't exist on shelf %s!" % \
87
                        (patch_id, self.name))
0.2.24 by Michael Ellerman
Add switch command to switch between multiple shelves.
88
89
        return path
90
0.2.18 by Michael Ellerman
Make next_patch() cope with holes, eg. 00, 02 etc.
91
    def __path(self, index):
0.2.22 by Michael Ellerman
Infrastructure to allow for multiple interchangeable shelves.
92
        return os.path.join(self.dir, '%.2d' % index)
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
93
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
94
    def next_patch(self):
0.2.18 by Michael Ellerman
Make next_patch() cope with holes, eg. 00, 02 etc.
95
        indexes = self.__list()
96
97
        if len(indexes) == 0:
98
            next = 0
99
        else:
100
            next = indexes[-1] + 1
101
        return self.__path(next)
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
102
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
103
    def __list(self):
0.2.22 by Michael Ellerman
Infrastructure to allow for multiple interchangeable shelves.
104
        patches = os.listdir(self.dir)
0.2.18 by Michael Ellerman
Make next_patch() cope with holes, eg. 00, 02 etc.
105
        indexes = [int(f) for f in patches]
106
        indexes.sort()
107
        return indexes
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
108
109
    def last_patch(self):
0.2.18 by Michael Ellerman
Make next_patch() cope with holes, eg. 00, 02 etc.
110
        indexes = self.__list()
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
111
0.2.18 by Michael Ellerman
Make next_patch() cope with holes, eg. 00, 02 etc.
112
        if len(indexes) == 0:
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
113
            return None
114
0.2.18 by Michael Ellerman
Make next_patch() cope with holes, eg. 00, 02 etc.
115
        return self.__path(indexes[-1])
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
116
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
117
    def get_patch_message(self, patch_path):
118
        patch = open(patch_path, 'r').read()
119
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
120
        if not patch.startswith(self.MESSAGE_PREFIX):
121
            return None
122
        return patch[len(self.MESSAGE_PREFIX):patch.index('\n')]
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
123
0.2.1 by Michael Ellerman
Factor out patch generation into PatchSource classes.
124
    def __show_status(self, source):
125
        if source.can_live_update():
0.2.5 by Michael Ellerman
Make some of the messages a bit more readable.
126
            diff_stat = str(DiffStat(source.readlines()))
127
            if len(diff_stat) > 0:
0.2.20 by Michael Ellerman
Make list output look nicer when the shelf is empty.
128
                self.log('Diff status is now:\n' + diff_stat + '\n')
0.2.5 by Michael Ellerman
Make some of the messages a bit more readable.
129
            else:
0.2.20 by Michael Ellerman
Make list output look nicer when the shelf is empty.
130
                self.log('No changes left in working tree.\n')
0.2.1 by Michael Ellerman
Factor out patch generation into PatchSource classes.
131
132
    def unshelve(self, patch_source, pick_hunks=False):
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
133
        patch_name = self.last_patch()
134
135
        if patch_name is None:
136
            raise CommandError("No patch found on shelf %s" % self.name)
137
138
        hunks = FilePatchSource(patch_name).readhunks()
0.1.55 by Michael Ellerman
Add support for 'unshelve --pick'. This works but the UI is broken, as the
139
        if pick_hunks:
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
140
            to_unshelve, to_remain = UnshelveHunkSelector(hunks).select()
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
141
        else:
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
142
            to_unshelve = hunks
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
143
            to_remain = []
0.1.55 by Michael Ellerman
Add support for 'unshelve --pick'. This works but the UI is broken, as the
144
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
145
        if len(to_unshelve) == 0:
0.2.3 by Michael Ellerman
Factor out bzrisms.
146
            raise CommandError('Nothing to unshelve')
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
147
0.2.13 by Michael Ellerman
Add shelf command with subcommand "list" which lists the shelf contents.
148
        message = self.get_patch_message(patch_name)
0.2.14 by Michael Ellerman
Use new log function.
149
        if message is None:
150
            message = ""
0.2.20 by Michael Ellerman
Make list output look nicer when the shelf is empty.
151
        self.log('Reapplying shelved patches "%s"\n' % message)
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
152
0.2.2 by Michael Ellerman
For the moment at least storing scads of stuff under .bzr isn't really
153
        pipe = os.popen('patch -d %s -s -p0' % self.base, 'w')
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
154
        for hunk in to_unshelve:
155
            pipe.write(str(hunk))
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
156
        pipe.flush()
157
158
        if pipe.close() is not None:
0.2.3 by Michael Ellerman
Factor out bzrisms.
159
            raise CommandError("Failed running patch!")
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
160
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
161
        if len(to_remain) == 0:
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
162
            os.remove(patch_name)
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
163
        else:
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
164
            f = open(patch_name, 'w')
165
            for hunk in to_remain:
166
                f.write(str(hunk))
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
167
            f.close()
0.1.35 by Michael Ellerman
Use DiffStat rather than calling out to /bin/diffstat
168
0.2.1 by Michael Ellerman
Factor out patch generation into PatchSource classes.
169
        self.__show_status(patch_source)
170
171
    def shelve(self, patch_source, pick_hunks=False, message=None):
0.2.8 by Michael Ellerman
Always set shelf message and print it when shelving.
172
        from datetime import datetime
173
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
174
        hunks = patch_source.readhunks()
0.1.31 by Michael Ellerman
- Keep our branch around, and use it directly instead of bzr_root.
175
0.1.46 by Michael Ellerman
Shelve everything by default, use --pick to select hunks individually.
176
        if pick_hunks:
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
177
            to_shelve = ShelveHunkSelector(hunks).select()[0]
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
178
        else:
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
179
            to_shelve = hunks
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
180
0.1.58 by Michael Ellerman
Unshelve --pick was broken, because we deleted the whole patch, even when only
181
        if len(to_shelve) == 0:
0.2.3 by Michael Ellerman
Factor out bzrisms.
182
            raise CommandError('Nothing to shelve')
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
183
0.2.8 by Michael Ellerman
Always set shelf message and print it when shelving.
184
        if message is None:
185
            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
186
            message = "Changes shelved on %s" % timestamp
187
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
188
        patch_name = self.next_patch()
0.2.25 by Michael Ellerman
Fixup shelving message
189
        self.log('Shelving to %s/%s: "%s"\n' % \
0.2.14 by Michael Ellerman
Use new log function.
190
                (self.name, os.path.basename(patch_name), message))
0.2.8 by Michael Ellerman
Always set shelf message and print it when shelving.
191
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
192
        patch = open(patch_name, 'a')
0.2.8 by Michael Ellerman
Always set shelf message and print it when shelving.
193
194
        assert '\n' not in message
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
195
        patch.write("%s%s\n" % (self.MESSAGE_PREFIX, message))
196
197
        for hunk in to_shelve:
198
            patch.write(str(hunk))
199
200
        patch.flush()
201
        os.fsync(patch.fileno())
202
        patch.close()
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
203
0.2.2 by Michael Ellerman
For the moment at least storing scads of stuff under .bzr isn't really
204
        pipe = os.popen('patch -d %s -sR -p0' % self.base, 'w')
0.2.12 by Michael Ellerman
Try to clear up terminology confusion. A shelf contains multiple patches, each
205
        for hunk in to_shelve:
206
            pipe.write(str(hunk))
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
207
        pipe.flush()
208
209
        if pipe.close() is not None:
0.2.3 by Michael Ellerman
Factor out bzrisms.
210
            raise CommandError("Failed running patch!")
0.1.27 by Michael Ellerman
Move all shelf functions into a class. Only logic change is we save the
211
0.2.1 by Michael Ellerman
Factor out patch generation into PatchSource classes.
212
        self.__show_status(patch_source)