chiark / gitweb /
dfdcea1f5a14218656dfebdcb42358c3d511fdae
[stgit] / stgit / commands / export.py
1 """Export command
2 """
3
4 __copyright__ = """
5 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 """
20
21 import os
22 from stgit.argparse import opt
23 from stgit.commands import common
24 from stgit import argparse, git, templates
25 from stgit.out import out
26 from stgit.lib import git as gitlib
27
28 help = 'Export patches to a directory'
29 kind = 'patch'
30 usage = ['[options] [<patch1>] [<patch2>] [<patch3>..<patch4>]']
31 description = """
32 Export a range of applied patches to a given directory (defaults to
33 'patches-<branch>') in a standard unified GNU diff format. A template
34 file (defaulting to '.git/patchexport.tmpl' or
35 '~/.stgit/templates/patchexport.tmpl' or
36 '/usr/share/stgit/templates/patchexport.tmpl') can be used for the
37 patch format. The following variables are supported in the template
38 file:
39
40   %(description)s - patch description
41   %(shortdescr)s  - the first line of the patch description
42   %(longdescr)s   - the rest of the patch description, after the first line
43   %(diffstat)s    - the diff statistics
44   %(authname)s    - author's name
45   %(authemail)s   - author's e-mail
46   %(authdate)s    - patch creation date
47   %(commname)s    - committer's name
48   %(commemail)s   - committer's e-mail"""
49
50 args = [argparse.patch_range(argparse.applied_patches,
51                              argparse.unapplied_patches,
52                              argparse.hidden_patches)]
53 options = [
54     opt('-d', '--dir', args = [argparse.dir],
55         short = 'Export patches to DIR instead of the default'),
56     opt('-p', '--patch', action = 'store_true',
57         short = 'Append .patch to the patch names'),
58     opt('-e', '--extension',
59         short = 'Append .EXTENSION to the patch names'),
60     opt('-n', '--numbered', action = 'store_true',
61         short = 'Prefix the patch names with order numbers'),
62     opt('-t', '--template', metavar = 'FILE', args = [argparse.files],
63         short = 'Use FILE as a template'),
64     opt('-b', '--branch', args = [argparse.stg_branches],
65         short = 'Use BRANCH instead of the default branch'),
66     opt('-s', '--stdout', action = 'store_true',
67         short = 'Dump the patches to the standard output'),
68     ] + argparse.diff_opts_option()
69
70 directory = common.DirectoryHasRepositoryLib()
71
72 def func(parser, options, args):
73     """Export a range of patches.
74     """
75     stack = directory.repository.get_stack(options.branch)
76
77     if options.dir:
78         dirname = options.dir
79     else:
80         dirname = 'patches-%s' % stack.name
81         directory.cd_to_topdir()
82
83     if not options.branch and git.local_changes():
84         out.warn('Local changes in the tree;'
85                  ' you might want to commit them first')
86
87     if not options.stdout:
88         if not os.path.isdir(dirname):
89             os.makedirs(dirname)
90         series = file(os.path.join(dirname, 'series'), 'w+')
91
92     applied = stack.patchorder.applied
93     unapplied = stack.patchorder.unapplied
94     if len(args) != 0:
95         patches = common.parse_patches(args, applied + unapplied, len(applied))
96     else:
97         patches = applied
98
99     num = len(patches)
100     if num == 0:
101         raise common.CmdException, 'No patches applied'
102
103     zpadding = len(str(num))
104     if zpadding < 2:
105         zpadding = 2
106
107     # get the template
108     if options.template:
109         tmpl = file(options.template).read()
110     else:
111         tmpl = templates.get_template('patchexport.tmpl')
112         if not tmpl:
113             tmpl = ''
114
115     # note the base commit for this series
116     if not options.stdout:
117         base_commit = stack.patches.get(patches[0]).commit.sha1
118         print >> series, '# This series applies on GIT commit %s' % base_commit
119
120     patch_no = 1;
121     for p in patches:
122         pname = p
123         if options.patch:
124             pname = '%s.patch' % pname
125         elif options.extension:
126             pname = '%s.%s' % (pname, options.extension)
127         if options.numbered:
128             pname = '%s-%s' % (str(patch_no).zfill(zpadding), pname)
129         pfile = os.path.join(dirname, pname)
130         if not options.stdout:
131             print >> series, pname
132
133         # get the patch description
134         patch = stack.patches.get(p)
135         cd = patch.commit.data
136
137         descr = cd.message.strip()
138         descr_lines = descr.split('\n')
139
140         short_descr = descr_lines[0].rstrip()
141         long_descr = reduce(lambda x, y: x + '\n' + y,
142                             descr_lines[1:], '').strip()
143
144         diff = stack.repository.diff_tree(cd.parent.data.tree, cd.tree, options.diff_flags)
145
146         tmpl_dict = {'description': descr,
147                      'shortdescr': short_descr,
148                      'longdescr': long_descr,
149                      'diffstat': gitlib.diffstat(diff).rstrip(),
150                      'authname': cd.author.name,
151                      'authemail': cd.author.email,
152                      'authdate': cd.author.date.isoformat(),
153                      'commname': cd.committer.name,
154                      'commemail': cd.committer.email}
155         for key in tmpl_dict:
156             if not tmpl_dict[key]:
157                 tmpl_dict[key] = ''
158
159         try:
160             descr = tmpl % tmpl_dict
161         except KeyError, err:
162             raise common.CmdException, 'Unknown patch template variable: %s' \
163                   % err
164         except TypeError:
165             raise common.CmdException, 'Only "%(name)s" variables are ' \
166                   'supported in the patch template'
167
168         if options.stdout:
169             f = sys.stdout
170         else:
171             f = open(pfile, 'w+')
172
173         if options.stdout and num > 1:
174             print '-'*79
175             print patch.name
176             print '-'*79
177
178         f.write(descr)
179         f.write(diff)
180         if not options.stdout:
181             f.close()
182         patch_no += 1
183
184     if not options.stdout:
185         series.close()