chiark / gitweb /
44cf98467997182a70c998cac1e7a20652d46aca
[stgit] / stgit / commands / pick.py
1 __copyright__ = """
2 Copyright (C) 2005, 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 from optparse import OptionParser, make_option
20
21 from stgit.commands.common import *
22 from stgit.utils import *
23 from stgit.out import *
24 from stgit import stack, git
25 from stgit.stack import Series
26
27
28 help = 'import a patch from a different branch or a commit object'
29 usage = """%prog [options] ([<patch1>] [<patch2>] [<patch3>..<patch4>])|<commit>
30
31 Import one or more patches from a different branch or a commit object
32 into the current series. By default, the name of the imported patch is
33 used as the name of the current patch. It can be overridden with the
34 '--name' option. A commit object can be reverted with the '--reverse'
35 option. The log and author information are those of the commit
36 object."""
37
38 directory = DirectoryGotoToplevel()
39 options = [make_option('-n', '--name',
40                        help = 'use NAME as the patch name'),
41            make_option('-B', '--ref-branch',
42                        help = 'pick patches from BRANCH'),
43            make_option('-r', '--reverse',
44                        help = 'reverse the commit object before importing',
45                        action = 'store_true'),
46            make_option('-p', '--parent', metavar = 'COMMITID',
47                        help = 'use COMMITID as parent'),
48            make_option('-x', '--expose',
49                        help = 'append the imported commit id to the patch log',
50                        action = 'store_true'),
51            make_option('--fold',
52                        help = 'fold the commit object into the current patch',
53                        action = 'store_true'),
54            make_option('--update',
55                        help = 'like fold but only update the current patch files',
56                        action = 'store_true'),
57            make_option('--unapplied',
58                        help = 'keep the patch unapplied',
59                        action = 'store_true')]
60
61 def __pick_commit(commit_id, patchname, options):
62     """Pick a commit id.
63     """
64     commit = git.Commit(commit_id)
65
66     if options.name:
67         patchname = options.name
68
69     if options.parent:
70         parent = git_id(crt_series, options.parent)
71     else:
72         parent = commit.get_parent()
73
74     if not options.reverse:
75         bottom = parent
76         top = commit_id
77     else:
78         bottom = commit_id
79         top = parent
80
81     if options.fold:
82         out.start('Folding commit %s' % commit_id)
83
84         # try a direct git-apply first
85         if not git.apply_diff(bottom, top):
86             git.merge(bottom, git.get_head(), top, recursive = True)
87
88         out.done()
89     elif options.update:
90         rev1 = git_id(crt_series, '//bottom')
91         rev2 = git_id(crt_series, '//top')
92         files = git.barefiles(rev1, rev2).split('\n')
93
94         out.start('Updating with commit %s' % commit_id)
95
96         if not git.apply_diff(bottom, top, files = files):
97             raise CmdException, 'Patch updating failed'
98
99         out.done()
100     else:
101         message = commit.get_log()
102         if options.expose:
103             message += '(imported from commit %s)\n' % commit.get_id_hash()
104         author_name, author_email, author_date = \
105                      name_email_date(commit.get_author())
106
107         out.start('Importing commit %s' % commit_id)
108
109         newpatch = crt_series.new_patch(patchname, message = message, can_edit = False,
110                                         unapplied = True, bottom = bottom, top = top,
111                                         author_name = author_name,
112                                         author_email = author_email,
113                                         author_date = author_date)
114         # in case the patch name was automatically generated
115         patchname = newpatch.get_name()
116
117         # find a patchlog to fork from
118         (refpatchname, refbranchname, refpatchid) = parse_rev(patchname)
119         if refpatchname and not refpatchid and \
120                (not refpatchid or refpatchid == 'top'):
121             # FIXME: should also support picking //top.old
122             if refbranchname:
123                 # assume the refseries is OK, since we already resolved
124                 # commit_str to a git_id
125                 refseries = Series(refbranchname)
126             else:
127                 refseries = crt_series
128             patch = refseries.get_patch(refpatchname)
129             if patch.get_log():
130                 out.info("Log was %s" % newpatch.get_log())
131                 out.info("Setting log to %s\n" %  patch.get_log())
132                 newpatch.set_log(patch.get_log())
133                 out.info("Log is now %s" % newpatch.get_log())
134             else:
135                 out.info("No log for %s\n" % patchname)
136
137         if not options.unapplied:
138             modified = crt_series.push_patch(patchname)
139         else:
140             modified = False
141
142         if crt_series.empty_patch(patchname):
143             out.done('empty patch')
144         elif modified:
145             out.done('modified')
146         else:
147             out.done()
148
149
150 def func(parser, options, args):
151     """Import a commit object as a new patch
152     """
153     if not args:
154         parser.error('incorrect number of arguments')
155
156     if not options.unapplied:
157         check_local_changes()
158         check_conflicts()
159         check_head_top_equal(crt_series)
160
161     if options.ref_branch:
162         remote_series = Series(options.ref_branch)
163     else:
164         remote_series = crt_series
165
166     applied = remote_series.get_applied()
167     unapplied = remote_series.get_unapplied()
168     try:
169         patches = parse_patches(args, applied + unapplied, len(applied))
170         commit_id = None
171     except CmdException:
172         if len(args) >= 1:
173             raise
174         # no patches found, try a commit id
175         commit_id = git_id(remote_series, args[0])
176
177     if len(patches) > 1:
178         if options.name:
179             raise CmdException, '--name can only be specified with one patch'
180         if options.parent:
181             raise CmdException, '--parent can only be specified with one patch'
182
183     if (options.fold or options.update) and not crt_series.get_current():
184         raise CmdException, 'No patches applied'
185
186     if commit_id:
187         __pick_commit(commit_id, None, options)
188     else:
189         if options.unapplied:
190             patches.reverse()
191         for patch in patches:
192             __pick_commit(git_id(remote_series, patch), patch, options)
193
194     print_crt_patch(crt_series)