~garyvdm/bzr-pipeline/decoratemerge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# Copyright (C) 2009 Aaron Bentley
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


from bzrlib import builtins, errors, trace
from bzrlib.branch import Branch
from bzrlib.bzrdir import BzrDir
from bzrlib.commands import Command
from bzrlib.option import Option
from bzrlib.switch import switch
from bzrlib.workingtree import WorkingTree
from bzrlib.plugins.pipeline.pipeline import (
    NoSuchPipe,
    PipeManager,
    tree_to_pipeline,
)


def is_light_checkout(tree):
    return (tree.branch.bzrdir.root_transport.base !=
            tree.bzrdir.root_transport.base)


def require_light_checkout(tree):
    if not is_light_checkout(tree):
        raise errors.BzrCommandError('Directory is not a lightweight'
                                     ' checkout.')


class PipeCommand(Command):

    def _get_manager(self, branch, after, before):
        manager = PipeManager(branch)
        if after is not None:
            if before is not None:
                raise errors.BzrCommandError('Cannot specify --before and'
                                             ' --after.')
            manager = PipeManager(manager.find_pipe(after))
        if before is not None:
            manager = PipeManager(manager.find_pipe(before))
        return manager

    def _get_location_manager(self, location='.'):
        branch = Branch.open_containing(location)[0]
        return PipeManager(branch)

    @staticmethod
    def _get_checkout_manager(location='.', checkout_optional=False,
                              pipe=None, allow_tree=False):
        if checkout_optional:
            tree, branch = BzrDir.open_containing_tree_or_branch(location)[:2]
        else:
            tree = WorkingTree.open_containing(location)[0]
            branch = tree.branch
        if tree is not None and not allow_tree:
            require_light_checkout(tree)
        manager = PipeManager(branch)
        if pipe is not None:
            manager = PipeManager(manager.find_pipe(pipe))
        return tree, manager

    def _get_revision_id(self, branch, revision):
        if revision is None:
            return None
        if len(revision) > 1:
            raise errors.BzrCommandError('Only one revision may be supplied.')
        return revision[0].as_revision_id(branch)


class cmd_add_pipe(PipeCommand):
    """Add a pipe to the pipeline.

    By default, the pipe is added after the current active pipe, at the
    current revision.

    This command can be used to start a new pipeline.
    """

    takes_args = ['pipe']
    takes_options = [Option('after', type=unicode,
                            help='Insert after this pipe.'),
                     Option('before', type=unicode,
                            help='Insert before this pipe.'),
                     Option('interactive', short_name='i',
                             help='Interactively decide which changes to place'
                             ' in the new pipe.'),
                     'revision',]

    def run(self, pipe, after=None, before=None, revision=None,
            interactive=False):
        tree = WorkingTree.open_containing('.')[0]
        if not is_light_checkout(tree):
            raise errors.BzrCommandError('add-pipe should be run in a '
                'lightweight checkout.  See bzr help pipeline for details.')
        revision_id = self._get_revision_id(tree.branch, revision)
        manager = self._get_manager(tree.branch, after, before)
        insert_before = (before is not None)
        new_br = manager.insert_pipe(pipe, revision_id, before=insert_before)
        if revision_id is None:
            PipeManager(new_br).store_uncommitted(tree, interactive)


class cmd_merge(builtins.cmd_merge):
    #Support merge --uncommitted PIPE
    __doc__ = builtins.cmd_merge.__doc__

    def run(self, location=None, revision=None, force=False,
            merge_type=None, show_base=False, reprocess=None, remember=False,
            uncommitted=False, pull=False,
            directory=None,
            preview=False,
            *args,
            **kwargs
            ):
        if uncommitted:
            if directory is None:
                directory = '.'
            try:
                tree, manager = PipeCommand._get_checkout_manager(
                    directory, allow_tree=True, pipe=location)
            except NoSuchPipe:
                pass
            else:
                manager.restore_uncommitted(tree, delete=False)
                return 0
        return builtins.cmd_merge.run(self, location, revision, force,
            merge_type, show_base, reprocess, remember, uncommitted, pull,
            directory, preview, *args, **kwargs)


class cmd_reconfigure_pipeline(builtins.cmd_reconfigure):
    """Reconfigure a tree with branch into a lightweight checkout."""

    def run(self):
        tree = WorkingTree.open_containing('.')[0]
        tree_to_pipeline(tree)


class cmd_remove_pipe(PipeCommand):
    """Remove a pipe from the pipeline.

    By default, the current pipe is removed, but a pipe may be specified as
    the first parameter.  By default, only the association of the pipe with
    its pipeline is removed, but if --branch is specified, the branch is
    also deleted.
    """

    takes_args = ['pipe?']
    takes_options = [Option('branch', help="Remove pipe's branch.")]

    def run(self, pipe=None, branch=False):
        tree, manager = self._get_checkout_manager(pipe=pipe,
                                                   checkout_optional=True,
                                                   allow_tree=True)
        target_branch = manager.get_next_pipe()
        if target_branch is None:
            target_branch = manager.get_prev_pipe()
        if target_branch is None:
            raise errors.BzrCommandError('Branch is not connected to a'
                                         ' pipeline.')
        if (tree is not None and
            is_light_checkout(tree) and
            tree.branch.base == manager.storage.branch.base):
            switch(tree.bzrdir, target_branch)
        manager.storage.disconnect()
        if branch:
            manager.storage.branch.bzrdir.root_transport.delete_tree('.')


class cmd_switch_pipe(PipeCommand):
    """Switch from one pipe to another.

    Any uncommitted changes are stored.  Any stored changes in the target
    pipe are restored.
    """

    takes_args = ['pipe']

    def run(self, pipe):
        checkout, manager = self._get_checkout_manager()
        manager.switch_to_pipe(checkout, pipe)


class cmd_show_pipeline(PipeCommand):
    """Show the current pipeline.

    All pipes are listed with the beginning of the pipeline at the top and the
    end of the pipeline at the bottom.

    * - The current pipe.
    U - A pipe holding uncommitted changes.

    Uncommitted changes are automatically restored by the 'switch-pipe'
    command.
    """

    takes_args = ['location?']

    def run(self, location='.'):
        manager = self._get_location_manager(location)
        for pipe in manager.list_pipes():
            if pipe is manager.storage.branch:
                selected = '*'
            else:
                selected = ' '
            if PipeManager(pipe).has_stored_changes():
                uncommitted = 'U'
            else:
                uncommitted = ' '
            self.outf.write('%s%s %s\n' % (selected, uncommitted, pipe.nick))


class cmd_pump(PipeCommand):
    """From this pipe onward, merge all pipes into their next pipe and commit.

    If the merge is successful, the changes are automatically committed, and
    the process repeats for the next pipe.  Eventually, the last pipe will
    have all the changes from all of the affected pipes.  On success, the
    checkout's initial state is restored.

    If the merge produces conflicts, the process aborts and no commit is
    performed.  You should resolve the conflicts, commit, and re-run pump.
    """

    def run(self):
        tree, manager = self._get_checkout_manager()
        if not manager.pipeline_merge(tree):
            trace.note('Please resolve conflicts, commit, and re-run pump.')


class cmd_sync_pipeline(PipeCommand):
    """Synchronise the contents of this pipeline with another copy.

    The first argument is the location of one of the pipes in the remote
    pipeline.  It defaults to the push location.  If it does not exist, the
    whole remote pipeline will be created.  If any remote pipes are missing,
    they will be created.

    The pipelines are then synchronized by pulling and pushing between
    pipes, depending on which is newer.

    If pipes have diverged, the process will abort.  You should then merge the
    remote pipe into the local pipe and re-run sync-pipeline.
    """

    takes_args = ['pipe?']

    def run(self, pipe=None):
        checkout, manager = self._get_checkout_manager(checkout_optional=True,
                                                       allow_tree=True)
        if pipe is None:
            pipe = manager.storage.branch.get_push_location()
        manager.sync_pipeline(pipe)