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