Add --file option to pick
[stgit] / stgit / commands / pick.py
CommitLineData
0618ea9c
CM
1__copyright__ = """
2Copyright (C) 2005, 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
575bbdae 19from stgit.argparse import opt
0618ea9c
CM
20from stgit.commands.common import *
21from stgit.utils import *
5e888f30 22from stgit.out import *
6c8a90e1 23from stgit import argparse, stack, git
8fce9909 24from stgit.stack import Series
0618ea9c 25
575bbdae 26help = 'Import a patch from a different branch or a commit object'
33ff9cdd 27kind = 'patch'
575bbdae
KH
28usage = ['[options] ([<patch1>] [<patch2>] [<patch3>..<patch4>])|<commit>']
29description = """
074f71bb
CM
30Import one or more patches from a different branch or a commit object
31into the current series. By default, the name of the imported patch is
32used as the name of the current patch. It can be overridden with the
33'--name' option. A commit object can be reverted with the '--reverse'
34option. The log and author information are those of the commit
35object."""
0618ea9c 36
6c8a90e1
KH
37args = [argparse.patch_range(argparse.applied_patches,
38 argparse.unapplied_patches,
39 argparse.hidden_patches)]
575bbdae
KH
40options = [
41 opt('-n', '--name',
42 short = 'Use NAME as the patch name'),
6c8a90e1 43 opt('-B', '--ref-branch', args = [argparse.stg_branches],
575bbdae
KH
44 short = 'Pick patches from BRANCH'),
45 opt('-r', '--reverse', action = 'store_true',
46 short = 'Reverse the commit object before importing'),
6c8a90e1 47 opt('-p', '--parent', metavar = 'COMMITID', args = [argparse.commit],
575bbdae
KH
48 short = 'Use COMMITID as parent'),
49 opt('-x', '--expose', action = 'store_true',
50 short = 'Append the imported commit id to the patch log'),
51 opt('--fold', action = 'store_true',
52 short = 'Fold the commit object into the current patch'),
53 opt('--update', action = 'store_true',
54 short = 'Like fold but only update the current patch files'),
0584ad1d
CM
55 opt('-f', '--file', action = 'append',
56 short = 'Only fold the given file (can be used multiple times)'),
575bbdae
KH
57 opt('--unapplied', action = 'store_true',
58 short = 'Keep the patch unapplied')]
59
117ed129 60directory = DirectoryGotoToplevel(log = True)
0618ea9c 61
074f71bb
CM
62def __pick_commit(commit_id, patchname, options):
63 """Pick a commit id.
0618ea9c 64 """
fff9bce5 65 commit = git.Commit(commit_id)
0618ea9c 66
074f71bb
CM
67 if options.name:
68 patchname = options.name
214a8453
CM
69 if patchname:
70 patchname = find_patch_name(patchname, crt_series.patch_exists)
0618ea9c 71
89e3bc51 72 if options.parent:
6972fd6b 73 parent = git_id(crt_series, options.parent)
89e3bc51
YD
74 else:
75 parent = commit.get_parent()
76
0618ea9c 77 if not options.reverse:
89e3bc51 78 bottom = parent
0618ea9c
CM
79 top = commit_id
80 else:
81 bottom = commit_id
89e3bc51 82 top = parent
0618ea9c 83
ba524d4c 84 if options.fold:
27ac2b7e 85 out.start('Folding commit %s' % commit_id)
0618ea9c 86
04b44217 87 # try a direct git apply first
0584ad1d
CM
88 if not git.apply_diff(bottom, top, files = options.file):
89 if options.file:
90 raise CmdException('Patch folding failed')
91 else:
92 git.merge_recursive(bottom, git.get_head(), top)
5f9ce587 93
27ac2b7e 94 out.done()
d0bfda1a 95 elif options.update:
e4560d7e
CM
96 rev1 = git_id(crt_series, 'HEAD^')
97 rev2 = git_id(crt_series, 'HEAD')
d0bfda1a
CM
98 files = git.barefiles(rev1, rev2).split('\n')
99
27ac2b7e 100 out.start('Updating with commit %s' % commit_id)
d0bfda1a
CM
101
102 if not git.apply_diff(bottom, top, files = files):
103 raise CmdException, 'Patch updating failed'
104
27ac2b7e 105 out.done()
ba524d4c
CM
106 else:
107 message = commit.get_log()
2ff25eb8
CM
108 if options.expose:
109 message += '(imported from commit %s)\n' % commit.get_id_hash()
ba524d4c
CM
110 author_name, author_email, author_date = \
111 name_email_date(commit.get_author())
112
27ac2b7e 113 out.start('Importing commit %s' % commit_id)
0618ea9c 114
8fce9909
YD
115 newpatch = crt_series.new_patch(patchname, message = message, can_edit = False,
116 unapplied = True, bottom = bottom, top = top,
117 author_name = author_name,
118 author_email = author_email,
119 author_date = author_date)
e2b1102c
CM
120 # in case the patch name was automatically generated
121 patchname = newpatch.get_name()
122
8fce9909 123 # find a patchlog to fork from
e4560d7e
CM
124 refbranchname, refpatchname = parse_rev(patchname)
125 if refpatchname:
8fce9909
YD
126 if refbranchname:
127 # assume the refseries is OK, since we already resolved
128 # commit_str to a git_id
129 refseries = Series(refbranchname)
130 else:
131 refseries = crt_series
132 patch = refseries.get_patch(refpatchname)
133 if patch.get_log():
27ac2b7e
KH
134 out.info("Log was %s" % newpatch.get_log())
135 out.info("Setting log to %s\n" % patch.get_log())
8fce9909 136 newpatch.set_log(patch.get_log())
27ac2b7e 137 out.info("Log is now %s" % newpatch.get_log())
8fce9909 138 else:
27ac2b7e
KH
139 out.info("No log for %s\n" % patchname)
140
7829d19d 141 if not options.unapplied:
2e88afce 142 modified = crt_series.push_patch(patchname)
7829d19d
CM
143 else:
144 modified = False
0618ea9c 145
2e88afce 146 if crt_series.empty_patch(patchname):
27ac2b7e 147 out.done('empty patch')
5f9ce587 148 elif modified:
27ac2b7e 149 out.done('modified')
5f9ce587 150 else:
27ac2b7e
KH
151 out.done()
152
074f71bb
CM
153
154def func(parser, options, args):
155 """Import a commit object as a new patch
156 """
157 if not args:
158 parser.error('incorrect number of arguments')
159
0584ad1d
CM
160 if options.file and not options.fold:
161 parser.error('--file can only be specified with --fold')
162
074f71bb
CM
163 if not options.unapplied:
164 check_local_changes()
165 check_conflicts()
166 check_head_top_equal(crt_series)
167
168 if options.ref_branch:
169 remote_series = Series(options.ref_branch)
170 else:
171 remote_series = crt_series
172
173 applied = remote_series.get_applied()
174 unapplied = remote_series.get_unapplied()
175 try:
d3cb28ca 176 patches = parse_patches(args, applied + unapplied, len(applied))
074f71bb
CM
177 commit_id = None
178 except CmdException:
d714caa1 179 if len(args) > 1:
074f71bb
CM
180 raise
181 # no patches found, try a commit id
182 commit_id = git_id(remote_series, args[0])
183
d714caa1 184 if not commit_id and len(patches) > 1:
074f71bb
CM
185 if options.name:
186 raise CmdException, '--name can only be specified with one patch'
187 if options.parent:
188 raise CmdException, '--parent can only be specified with one patch'
189
d37b109f 190 if options.update and not crt_series.get_current():
074f71bb
CM
191 raise CmdException, 'No patches applied'
192
193 if commit_id:
214a8453
CM
194 # Try to guess a patch name if the argument was <branch>:<patch>
195 try:
196 patchname = args[0].split(':')[1]
197 except IndexError:
198 patchname = None
199 __pick_commit(commit_id, patchname, options)
074f71bb
CM
200 else:
201 if options.unapplied:
202 patches.reverse()
203 for patch in patches:
204 __pick_commit(git_id(remote_series, patch), patch, options)
205
6972fd6b 206 print_crt_patch(crt_series)