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