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