chiark / gitweb /
966ac553635fece01886db34f230da86437717a5
[stgit] / stgit / commands / sync.py
1 __copyright__ = """
2 Copyright (C) 2006, Catalin Marinas <catalin.marinas@gmail.com>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License version 2 as
6 published by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 """
17
18 import sys, os
19 import stgit.commands.common
20 from stgit.argparse import opt
21 from stgit.commands.common import *
22 from stgit.utils import *
23 from stgit.out import *
24 from stgit import stack, git
25
26 help = 'Synchronise patches with a branch or a series'
27 kind = 'patch'
28 usage = ['[options] [<patch1>] [<patch2>] [<patch3>..<patch4>]']
29 description = """
30 For each of the specified patches perform a three-way merge with the
31 same patch in the specified branch or series. The command can be used
32 for keeping patches on several branches in sync. Note that the
33 operation may fail for some patches because of conflicts. The patches
34 in the series must apply cleanly."""
35
36 options = [
37     opt('-a', '--all', action = 'store_true',
38         short = 'Synchronise all the applied patches'),
39     opt('-B', '--ref-branch',
40         short = 'Syncronise patches with BRANCH'),
41     opt('-s', '--series',
42         short = 'Syncronise patches with SERIES')]
43
44 directory = DirectoryGotoToplevel(log = True)
45
46 def __check_all():
47     check_local_changes()
48     check_conflicts()
49     check_head_top_equal(crt_series)
50
51 def __branch_merge_patch(remote_series, pname):
52     """Merge a patch from a remote branch into the current tree.
53     """
54     patch = remote_series.get_patch(pname)
55     git.merge_recursive(patch.get_bottom(), git.get_head(), patch.get_top())
56
57 def __series_merge_patch(base, patchdir, pname):
58     """Merge a patch file with the given StGIT patch.
59     """
60     patchfile = os.path.join(patchdir, pname)
61     git.apply_patch(filename = patchfile, base = base)
62
63 def func(parser, options, args):
64     """Synchronise a range of patches
65     """
66     if options.ref_branch:
67         remote_series = stack.Series(options.ref_branch)
68         if options.ref_branch == crt_series.get_name():
69             raise CmdException, 'Cannot synchronise with the current branch'
70         remote_patches = remote_series.get_applied()
71
72         # the merge function merge_patch(patch, pname)
73         merge_patch = lambda patch, pname: \
74                       __branch_merge_patch(remote_series, pname)
75     elif options.series:
76         patchdir = os.path.dirname(options.series)
77
78         remote_patches = []
79         f = file(options.series)
80         for line in f:
81             p = re.sub('#.*$', '', line).strip()
82             if not p:
83                 continue
84             remote_patches.append(p)
85         f.close()
86
87         # the merge function merge_patch(patch, pname)
88         merge_patch = lambda patch, pname: \
89                       __series_merge_patch(patch.get_bottom(), patchdir, pname)
90     else:
91         raise CmdException, 'No remote branch or series specified'
92
93     applied = crt_series.get_applied()
94     unapplied = crt_series.get_unapplied()
95     
96     if options.all:
97         patches = applied
98     elif len(args) != 0:
99         patches = parse_patches(args, applied + unapplied, len(applied),
100                                 ordered = True)
101     elif applied:
102         patches = [crt_series.get_current()]
103     else:
104         parser.error('no patches applied')
105
106     if not patches:
107         raise CmdException, 'No patches to synchronise'
108
109     __check_all()
110
111     # only keep the patches to be synchronised
112     sync_patches = [p for p in patches if p in remote_patches]
113     if not sync_patches:
114         raise CmdException, 'No common patches to be synchronised'
115
116     # pop to the one before the first patch to be synchronised
117     first_patch = sync_patches[0]
118     if first_patch in applied:
119         to_pop = applied[applied.index(first_patch) + 1:]
120         if to_pop:
121             pop_patches(crt_series, to_pop[::-1])
122         pushed = [first_patch]
123     else:
124         to_pop = []
125         pushed = []
126     popped = to_pop + [p for p in patches if p in unapplied]
127
128     for p in pushed + popped:
129         if p in popped:
130             # push this patch
131             push_patches(crt_series, [p])
132         if p not in sync_patches:
133             # nothing to synchronise
134             continue
135
136         # the actual sync
137         out.start('Synchronising "%s"' % p)
138
139         patch = crt_series.get_patch(p)
140         bottom = patch.get_bottom()
141         top = patch.get_top()
142
143         # reset the patch backup information.
144         patch.set_top(top, backup = True)
145
146         # the actual merging (either from a branch or an external file)
147         merge_patch(patch, p)
148
149         if git.local_changes(verbose = False):
150             # index (cache) already updated by the git merge. The
151             # backup information was already reset above
152             crt_series.refresh_patch(cache_update = False, backup = False,
153                                      log = 'sync')
154             out.done('updated')
155         else:
156             out.done()