aboutsummaryrefslogtreecommitdiffhomepage
path: root/gbp/scripts
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2011-10-30 20:03:33 +0100
committerGuido Günther <agx@sigxcpu.org>2011-11-01 18:12:48 +0100
commite28ea0740a7b4eb2ef4c1bd3079d77a40c6072b8 (patch)
tree779145e71a996c471a1d1a6b1acde1bc90deecc4 /gbp/scripts
parentabf90abcba15beb51196cf503f35695acdcd91c1 (diff)
Get rid of the symlink
by moving the commands to gbp/scripts/
Diffstat (limited to 'gbp/scripts')
-rw-r--r--gbp/scripts/__init__.py0
-rw-r--r--gbp/scripts/buildpackage.py571
-rw-r--r--gbp/scripts/clone.py113
-rw-r--r--gbp/scripts/create_remote_repo.py248
-rw-r--r--gbp/scripts/dch.py500
-rw-r--r--gbp/scripts/import_dsc.py344
-rw-r--r--gbp/scripts/import_dscs.py156
-rw-r--r--gbp/scripts/import_orig.py452
-rw-r--r--gbp/scripts/pq.py427
-rw-r--r--gbp/scripts/pull.py134
10 files changed, 2945 insertions, 0 deletions
diff --git a/gbp/scripts/__init__.py b/gbp/scripts/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/gbp/scripts/__init__.py
diff --git a/gbp/scripts/buildpackage.py b/gbp/scripts/buildpackage.py
new file mode 100644
index 00000000..d0ce3c17
--- /dev/null
+++ b/gbp/scripts/buildpackage.py
@@ -0,0 +1,571 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006-2011 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""run commands to build a debian package out of a git repository"""
+
+import ConfigParser
+import errno
+import os, os.path
+import pipes
+import sys
+import time
+import tempfile
+import shutil
+import gbp.deb as du
+from gbp.git import (GitRepositoryError, GitRepository, build_tag)
+from gbp.command_wrappers import (Command,
+ RunAtCommand, CommandExecFailed, PristineTar,
+ RemoveTree, CatenateTarArchive)
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.errors import GbpError
+from glob import glob
+import gbp.log
+import gbp.notifications
+
+# when we want to reference the index in a treeish context we call it:
+index_name = "INDEX"
+# when we want to reference the working copy in treeish context we call it:
+wc_name = "WC"
+# index file name used to export working copy
+wc_index = ".git/gbp_index"
+
+
+def git_archive_submodules(repo, treeish, output, prefix, comp_type, comp_level, comp_opts):
+ """
+ Create tar.gz of an archive with submodules
+
+ since git-archive always writes an end of tarfile trailer we concatenate
+ the generated archives using tar and compress the result.
+
+ Exception handling is left to the caller.
+ """
+
+ tarfile = output.rsplit('.', 1)[0]
+ tempdir = tempfile.mkdtemp()
+ submodule_tarfile = os.path.join(tempdir, "submodule.tar")
+ try:
+ # generate main tarfile
+ repo.archive(format='tar', prefix='%s/' % (prefix),
+ output=tarfile, treeish=treeish)
+
+ # generate each submodule's tarfile and append it to the main archive
+ for (subdir, commit) in repo.get_submodules(treeish):
+ tarpath = [subdir, subdir[2:]][subdir.startswith("./")]
+
+ gbp.log.debug("Processing submodule %s (%s)" % (subdir, commit[0:8]))
+ repo.archive(format='tar', prefix='%s/%s/' % (prefix, tarpath),
+ output=submodule_tarfile, treeish=commit, cwd=subdir)
+ CatenateTarArchive(tarfile)(submodule_tarfile)
+
+ # compress the output
+ ret = os.system("%s -%s %s %s" % (comp_type, comp_level, comp_opts, tarfile))
+ if ret:
+ raise GbpError("Error creating %s: %d" % (output, ret))
+ finally:
+ shutil.rmtree(tempdir)
+
+
+def git_archive_single(treeish, output, prefix, comp_type, comp_level, comp_opts):
+ """
+ Create tar.gz of an archive without submodules
+
+ Exception handling is left to the caller.
+ """
+ pipe = pipes.Template()
+ pipe.prepend("git archive --format=tar --prefix=%s/ %s" % (prefix, treeish), '.-')
+ pipe.append('%s -c -%s %s' % (comp_type, comp_level, comp_opts), '--')
+ ret = pipe.copy('', output)
+ if ret:
+ raise GbpError("Error creating %s: %d" % (output, ret))
+
+
+def git_archive(repo, cp, output_dir, treeish, comp_type, comp_level, with_submodules):
+ "create a compressed orig tarball in output_dir using git_archive"
+ try:
+ comp_opts = du.compressor_opts[comp_type][0]
+ except KeyError:
+ raise GbpError, "Unsupported compression type '%s'" % comp_type
+
+ output = os.path.join(output_dir, du.orig_file(cp, comp_type))
+ prefix = "%s-%s" % (cp['Source'], cp['Upstream-Version'])
+
+ try:
+ if repo.has_submodules() and with_submodules:
+ repo.update_submodules()
+ git_archive_submodules(repo, treeish, output, prefix,
+ comp_type, comp_level, comp_opts)
+
+ else:
+ git_archive_single(treeish, output, prefix,
+ comp_type, comp_level, comp_opts)
+ except CommandExecFailed:
+ gbp.log.err("Error generating submodules' archives")
+ return False
+ except OSError, err:
+ gbp.log.err("Error creating %s: %s" % (output, err[0]))
+ return False
+ except GbpError:
+ raise
+ except Exception as e:
+ gbp.log.err("Error creating %s: %s" % (output, e))
+ return False
+ return True
+
+
+def dump_tree(repo, export_dir, treeish, with_submodules):
+ "dump a tree to output_dir"
+ output_dir = os.path.dirname(export_dir)
+ prefix = os.path.basename(export_dir)
+
+ pipe = pipes.Template()
+ pipe.prepend('git archive --format=tar --prefix=%s/ %s' % (prefix, treeish), '.-')
+ pipe.append('tar -C %s -xf -' % output_dir, '-.')
+ top = os.path.abspath(os.path.curdir)
+ try:
+ ret = pipe.copy('', '')
+ if ret:
+ raise GbpError, "Error in dump_tree archive pipe"
+
+ if with_submodules:
+ if repo.has_submodules():
+ repo.update_submodules()
+ for (subdir, commit) in repo.get_submodules(treeish):
+ gbp.log.info("Processing submodule %s (%s)" % (subdir, commit[0:8]))
+ tarpath = [subdir, subdir[2:]][subdir.startswith("./")]
+ os.chdir(subdir)
+ pipe = pipes.Template()
+ pipe.prepend('git archive --format=tar --prefix=%s/%s/ %s' %
+ (prefix, tarpath, commit), '.-')
+ pipe.append('tar -C %s -xf -' % output_dir, '-.')
+ ret = pipe.copy('', '')
+ os.chdir(top)
+ if ret:
+ raise GbpError, "Error in dump_tree archive pipe in submodule %s" % subdir
+ except OSError, err:
+ gbp.log.err("Error dumping tree to %s: %s" % (output_dir, err[0]))
+ return False
+ except GbpError, err:
+ gbp.log.err(err)
+ return False
+ except Exception as e:
+ gbp.log.err("Error dumping tree to %s: %s" % (output_dir, e))
+ return False
+ finally:
+ os.chdir(top)
+ return True
+
+
+def move_old_export(target):
+ """move a build tree away if it exists"""
+ try:
+ os.mkdir(target)
+ except OSError, (e, msg):
+ if e == errno.EEXIST:
+ os.rename(target, "%s.obsolete.%s" % (target, time.time()))
+
+
+def prepare_output_dir(dir):
+ output_dir = dir
+ if not dir:
+ output_dir = '..'
+ output_dir = os.path.abspath(output_dir)
+
+ try:
+ os.mkdir(output_dir)
+ except OSError, (e, msg):
+ if e != errno.EEXIST:
+ raise GbpError, "Cannot create output dir %s" % output_dir
+ return output_dir
+
+def pristine_tar_build_orig(repo, cp, output_dir, options):
+ """
+ build orig using pristine-tar
+ @return: True: orig.tar.gz build, False: noop
+ """
+ if options.pristine_tar:
+ pt = PristineTar()
+ if not repo.has_branch(pt.branch):
+ gbp.log.warn('Pristine-tar branch "%s" not found' % pt.branch)
+ pt.checkout(os.path.join(output_dir, du.orig_file(cp, options.comp_type)))
+ return True
+ else:
+ return False
+
+
+def git_archive_build_orig(repo, cp, output_dir, options):
+ """build orig using git-archive"""
+ if options.upstream_tree == 'tag':
+ upstream_tree = build_tag(options.upstream_tag, cp['Upstream-Version'])
+ elif options.upstream_tree == 'branch':
+ upstream_tree = options.upstream_branch
+ else:
+ raise GbpError, "Unknown value %s" % options.upstream_tree
+ gbp.log.info("%s does not exist, creating from '%s'" % (du.orig_file(cp,
+ options.comp_type),
+ upstream_tree))
+ if not repo.has_treeish(upstream_tree):
+ raise GbpError # git-ls-tree printed an error message already
+ gbp.log.debug("Building upstream tarball with compression '%s -%s'" % (options.comp_type,
+ options.comp_level))
+ if not git_archive(repo, cp, output_dir, upstream_tree,
+ options.comp_type, options.comp_level, options.with_submodules):
+ raise GbpError, "Cannot create upstream tarball at '%s'" % output_dir
+
+
+def write_wc(repo):
+ """write out the current working copy as a treeish object"""
+ repo.add_files(repo.path, force=True, index_file=wc_index)
+ tree = repo.write_tree(index_file=wc_index)
+ return tree
+
+def drop_index():
+ """drop our custom index"""
+ if os.path.exists(wc_index):
+ os.unlink(wc_index)
+
+def extract_orig(orig_tarball, dest_dir):
+ """extract orig tarball to export dir before exporting from git"""
+ gbp.log.info("Extracting %s to '%s'" % (os.path.basename(orig_tarball), dest_dir))
+
+ move_old_export(dest_dir)
+ upstream = gbp.deb.UpstreamSource(orig_tarball)
+ upstream.unpack(dest_dir)
+
+ # Check if tarball extracts into a single folder or not:
+ if upstream.unpacked != dest_dir:
+ # If it extracts a single folder, move all of its contents to dest_dir:
+ r = glob("%s/*" % upstream.unpacked)
+ r.extend(glob("%s/.*" % upstream.unpacked)) # include hidden files and folders
+ for f in r:
+ os.rename(f, os.path.join(dest_dir, os.path.basename(f)))
+
+ # Remove that single folder:
+ os.rmdir(upstream.unpacked)
+
+
+def guess_comp_type(repo, comp_type, cp, tarball_dir):
+ """Guess compression type"""
+
+ srcpkg = cp['Source']
+ upstream_version = cp['Upstream-Version']
+
+ if comp_type != 'auto':
+ comp_type = du.compressor_aliases.get(comp_type, comp_type)
+ try:
+ dummy = du.compressor_opts[comp_type]
+ except KeyError:
+ gbp.log.warn("Unknown compression type - guessing.")
+ comp_type = 'auto'
+
+ if comp_type == 'auto':
+ if not repo.has_branch(PristineTar.branch):
+ if not tarball_dir:
+ tarball_dir = '..'
+ detected = None
+ for comp in du.compressor_opts.keys():
+ if du.has_orig(cp, comp, tarball_dir):
+ if detected is not None:
+ raise GbpError, "Multiple orig tarballs found."
+ detected = comp
+ if detected is not None:
+ comp_type = detected
+ else:
+ comp_type = 'gzip'
+ else:
+ regex = 'pristine-tar .* %s_%s\.orig.tar\.' % (srcpkg, upstream_version)
+ commits = repo.grep_log(regex, PristineTar.branch)
+ if commits:
+ commit = commits[-1]
+ gbp.log.debug("Found pristine-tar commit at '%s'" % commit)
+ else:
+ commit = PristineTar.branch
+ tarball = repo.get_subject(commit)
+ comp_type = du.get_compression(tarball)
+ gbp.log.debug("Determined compression type '%s'" % comp_type)
+ if not comp_type:
+ comp_type = 'gzip'
+ gbp.log.warn("Unknown compression type of %s, assuming %s" % (tarball, comp_type))
+ return comp_type
+
+
+def setup_pbuilder(options):
+ """setup everything to use git-pbuilder"""
+ if options.use_pbuilder or options.use_qemubuilder:
+ options.builder = 'git-pbuilder'
+ options.cleaner = '/bin/true'
+ os.environ['DIST'] = options.pbuilder_dist
+ if options.pbuilder_arch:
+ os.environ['ARCH'] = options.pbuilder_arch
+ if options.use_qemubuilder:
+ os.environ['BUILDER'] = "qemubuilder"
+
+
+def parse_args(argv, prefix):
+ args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == 0 ]
+ dpkg_args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == -1 ]
+
+ # We handle these although they don't have a --git- prefix
+ for arg in [ "--help", "-h", "--version" ]:
+ if arg in dpkg_args:
+ args.append(arg)
+
+ try:
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix=prefix)
+ except ConfigParser.ParsingError, err:
+ gbp.log.err(err)
+ return None, None, None
+
+ tag_group = GbpOptionGroup(parser, "tag options", "options related to git tag creation")
+ branch_group = GbpOptionGroup(parser, "branch options", "branch layout options")
+ cmd_group = GbpOptionGroup(parser, "external command options", "how and when to invoke external commands and hooks")
+ orig_group = GbpOptionGroup(parser, "orig tarball options", "options related to the creation of the orig tarball")
+ export_group = GbpOptionGroup(parser, "export build-tree options", "alternative build tree related options")
+ parser.add_option_group(tag_group)
+ parser.add_option_group(orig_group)
+ parser.add_option_group(branch_group)
+ parser.add_option_group(cmd_group)
+ parser.add_option_group(export_group)
+
+ parser.add_boolean_config_file_option(option_name = "ignore-new", dest="ignore_new")
+ parser.add_option("--git-verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+ parser.add_config_file_option(option_name="notify", dest="notify", type='tristate')
+ tag_group.add_option("--git-tag", action="store_true", dest="tag", default=False,
+ help="create a tag after a successful build")
+ tag_group.add_option("--git-tag-only", action="store_true", dest="tag_only", default=False,
+ help="don't build, only tag and run the posttag hook")
+ tag_group.add_option("--git-retag", action="store_true", dest="retag", default=False,
+ help="don't fail if the tag already exists")
+ tag_group.add_boolean_config_file_option(option_name="sign-tags", dest="sign_tags")
+ tag_group.add_config_file_option(option_name="keyid", dest="keyid")
+ tag_group.add_config_file_option(option_name="debian-tag", dest="debian_tag")
+ tag_group.add_config_file_option(option_name="upstream-tag", dest="upstream_tag")
+ orig_group.add_config_file_option(option_name="upstream-tree", dest="upstream_tree")
+ orig_group.add_boolean_config_file_option(option_name="pristine-tar", dest="pristine_tar")
+ orig_group.add_config_file_option(option_name="force-create", dest="force_create",
+ help="force creation of orig.tar.gz", action="store_true")
+ orig_group.add_config_file_option(option_name="no-create-orig", dest="no_create_orig",
+ help="don't create orig.tar.gz", action="store_true")
+ orig_group.add_config_file_option(option_name="tarball-dir", dest="tarball_dir", type="path",
+ help="location to look for external tarballs")
+ orig_group.add_config_file_option(option_name="compression", dest="comp_type",
+ help="Compression type, default is '%(compression)s'")
+ orig_group.add_config_file_option(option_name="compression-level", dest="comp_level",
+ help="Compression level, default is '%(compression-level)s'")
+ branch_group.add_config_file_option(option_name="upstream-branch", dest="upstream_branch")
+ branch_group.add_config_file_option(option_name="debian-branch", dest="debian_branch")
+ branch_group.add_boolean_config_file_option(option_name = "ignore-branch", dest="ignore_branch")
+ branch_group.add_boolean_config_file_option(option_name = "submodules", dest="with_submodules")
+ cmd_group.add_config_file_option(option_name="builder", dest="builder",
+ help="command to build the Debian package, default is '%(builder)s'")
+ cmd_group.add_config_file_option(option_name="cleaner", dest="cleaner",
+ help="command to clean the working copy, default is '%(cleaner)s'")
+ cmd_group.add_config_file_option(option_name="prebuild", dest="prebuild",
+ help="command to run before a build, default is '%(prebuild)s'")
+ cmd_group.add_config_file_option(option_name="postbuild", dest="postbuild",
+ help="hook run after a successful build, default is '%(postbuild)s'")
+ cmd_group.add_config_file_option(option_name="posttag", dest="posttag",
+ help="hook run after a successful tag operation, default is '%(posttag)s'")
+ cmd_group.add_boolean_config_file_option(option_name="pbuilder", dest="use_pbuilder")
+ cmd_group.add_boolean_config_file_option(option_name="qemubuilder", dest="use_qemubuilder")
+ cmd_group.add_config_file_option(option_name="dist", dest="pbuilder_dist")
+ cmd_group.add_config_file_option(option_name="arch", dest="pbuilder_arch")
+ export_group.add_config_file_option(option_name="export-dir", dest="export_dir", type="path",
+ help="before building the package export the source into EXPORT_DIR, default is '%(export-dir)s'")
+ export_group.add_config_file_option("export", dest="export",
+ help="export treeish object TREEISH, default is '%(export)s'", metavar="TREEISH")
+ export_group.add_option("--git-dont-purge", action="store_false", dest="purge", default=True,
+ help="retain exported package build directory")
+ export_group.add_boolean_config_file_option(option_name="overlay", dest="overlay")
+ options, args = parser.parse_args(args)
+
+ gbp.log.setup(options.color, options.verbose)
+ if options.retag:
+ if not options.tag and not options.tag_only:
+ gbp.log.err("'--%sretag' needs either '--%stag' or '--%stag-only'" % (prefix, prefix, prefix))
+ return None, None, None
+
+ if options.overlay and not options.export_dir:
+ gbp.log.err("Overlay must be used with --git-export-dir")
+ return None, None, None
+
+ return options, args, dpkg_args
+
+
+def main(argv):
+ retval = 0
+ changelog = 'debian/changelog'
+ prefix = "git-"
+ cp = None
+
+ options, gbp_args, dpkg_args = parse_args(argv, prefix)
+ if not options:
+ return 1
+
+ try:
+ repo = GitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+ else:
+ repo_dir = os.path.abspath(os.path.curdir)
+
+ try:
+ branch = repo.get_branch()
+ Command(options.cleaner, shell=True)()
+ if not options.ignore_new:
+ (ret, out) = repo.is_clean()
+ if not ret:
+ gbp.log.err("You have uncommitted changes in your source tree:")
+ gbp.log.err(out)
+ raise GbpError, "Use --git-ignore-new to ignore."
+
+ if not options.ignore_new and not options.ignore_branch:
+ if branch != options.debian_branch:
+ gbp.log.err("You are not on branch '%s' but on '%s'" % (options.debian_branch, branch))
+ raise GbpError, "Use --git-ignore-branch to ignore or --git-debian-branch to set the branch name."
+
+ try:
+ cp = du.parse_changelog(filename=changelog)
+ version = cp['Version']
+ version_no_epoch = cp['NoEpoch-Version']
+ if du.is_native(cp):
+ major = cp['Debian-Version']
+ else:
+ major = cp['Upstream-Version']
+ except du.NoChangelogError:
+ raise GbpError, "'%s' does not exist, not a debian package" % changelog
+ except du.ParseChangeLogError, err:
+ raise GbpError, "Error parsing Changelog: %s" % err
+ except KeyError:
+ raise GbpError, "Can't parse version from changelog"
+
+ if not options.tag_only:
+ output_dir = prepare_output_dir(options.export_dir)
+ if options.tarball_dir:
+ tarball_dir = options.tarball_dir
+ else:
+ tarball_dir = output_dir
+
+ # Get/build the orig.tar.gz if necessary:
+ if not du.is_native(cp):
+ options.comp_type = guess_comp_type(
+ repo, options.comp_type, cp, options.tarball_dir)
+ orig_file = du.orig_file(cp, options.comp_type)
+
+ # look in tarball_dir first, if found force a symlink to it
+ if options.tarball_dir:
+ gbp.log.debug("Looking for orig tarball '%s' at '%s'" % (orig_file, tarball_dir))
+ if not du.symlink_orig(cp, options.comp_type, tarball_dir, output_dir, force=True):
+ gbp.log.info("Orig tarball '%s' not found at '%s'" % (orig_file, tarball_dir))
+ else:
+ gbp.log.info("Orig tarball '%s' found at '%s'" % (orig_file, tarball_dir))
+ # build an orig unless the user forbids it, always build (and overwrite pre-existing) if user forces it
+ if options.force_create or (not options.no_create_orig and not du.has_orig(cp, options.comp_type, output_dir)):
+ if not pristine_tar_build_orig(repo, cp, output_dir, options):
+ git_archive_build_orig(repo, cp, output_dir, options)
+
+ # Export to another build dir if requested:
+ if options.export_dir:
+ # write a tree of the index if necessary:
+ if options.export == index_name:
+ tree = repo.write_tree()
+ elif options.export == wc_name:
+ tree = write_wc(repo)
+ else:
+ tree = options.export
+ if not repo.has_treeish(tree):
+ raise GbpError # git-ls-tree printed an error message already
+ tmp_dir = os.path.join(output_dir, "%s-tmp" % cp['Source'])
+
+ # Extract orig tarball if git-overlay option is selected:
+ if options.overlay:
+ if du.is_native(cp):
+ raise GbpError, "Cannot overlay Debian native package"
+ extract_orig(os.path.join(output_dir, du.orig_file(cp, options.comp_type)), tmp_dir)
+
+ gbp.log.info("Exporting '%s' to '%s'" % (options.export, tmp_dir))
+ if not dump_tree(repo, tmp_dir, tree, options.with_submodules):
+ raise GbpError
+ cp = du.parse_changelog(filename=os.path.join(tmp_dir, 'debian', 'changelog'))
+ export_dir = os.path.join(output_dir, "%s-%s" % (cp['Source'], major))
+ gbp.log.info("Moving '%s' to '%s'" % (tmp_dir, export_dir))
+ move_old_export(export_dir)
+ os.rename(tmp_dir, export_dir)
+
+ if options.export_dir:
+ build_dir = export_dir
+ else:
+ build_dir = repo_dir
+
+ if options.prebuild:
+ RunAtCommand(options.prebuild, shell=True,
+ extra_env={'GBP_GIT_DIR': repo.git_dir,
+ 'GBP_BUILD_DIR': build_dir})(dir=build_dir)
+
+ setup_pbuilder(options)
+ # Finally build the package:
+ RunAtCommand(options.builder, dpkg_args, shell=True,
+ extra_env={'GBP_BUILD_DIR': build_dir})(dir=build_dir)
+ if options.postbuild:
+ arch = os.getenv('ARCH', None) or du.get_arch()
+ changes = os.path.abspath("%s/../%s_%s_%s.changes" %
+ (build_dir, cp['Source'], version_no_epoch, arch))
+ gbp.log.debug("Looking for changes file %s" % changes)
+ if not os.path.exists(changes):
+ changes = os.path.abspath("%s/../%s_%s_source.changes" %
+ (build_dir, cp['Source'], version_no_epoch))
+ Command(options.postbuild, shell=True,
+ extra_env={'GBP_CHANGES_FILE': changes,
+ 'GBP_BUILD_DIR': build_dir})()
+ if options.tag or options.tag_only:
+ gbp.log.info("Tagging %s" % version)
+ tag = build_tag(options.debian_tag, version)
+ if options.retag and repo.has_tag(tag):
+ repo.delete_tag(tag)
+ repo.create_tag(name=tag, msg="Debian release %s" % version,
+ sign=options.sign_tags, keyid=options.keyid)
+ if options.posttag:
+ sha = repo.rev_parse("%s^{}" % tag)
+ Command(options.posttag, shell=True,
+ extra_env={'GBP_TAG': tag,
+ 'GBP_BRANCH': branch,
+ 'GBP_SHA1': sha})()
+ except CommandExecFailed:
+ retval = 1
+ except GbpError, err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ retval = 1
+ finally:
+ drop_index()
+
+ if not options.tag_only:
+ if options.export_dir and options.purge and not retval:
+ RemoveTree(export_dir)()
+
+ if cp and not gbp.notifications.notify(cp, not retval, options.notify):
+ gbp.log.err("Failed to send notification")
+ retval = 1
+
+ return retval
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/clone.py b/gbp/scripts/clone.py
new file mode 100644
index 00000000..665b548e
--- /dev/null
+++ b/gbp/scripts/clone.py
@@ -0,0 +1,113 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2009,2010 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# inspired by dom-git-checkout
+#
+"""clone a repo and set it up for gbp"""
+
+import sys
+import os, os.path
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.git import (GitRepositoryError, GitRepository)
+from gbp.command_wrappers import (Command, CommandExecFailed,
+ PristineTar)
+from gbp.errors import GbpError
+import gbp.log
+
+
+def parse_args (argv):
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ usage='%prog [options] repository - clone a remote repository')
+ branch_group = GbpOptionGroup(parser, "branch options", "branch tracking and layout options")
+ parser.add_option_group(branch_group)
+
+ branch_group.add_option("--all", action="store_true", dest="all", default=False,
+ help="track all branches, not only debian and upstream")
+ branch_group.add_config_file_option(option_name="upstream-branch", dest="upstream_branch")
+ branch_group.add_config_file_option(option_name="debian-branch", dest="debian_branch")
+ branch_group.add_boolean_config_file_option(option_name="pristine-tar", dest="pristine_tar")
+
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+
+ (options, args) = parser.parse_args(argv)
+ gbp.log.setup(options.color, options.verbose)
+
+ return (options, args)
+
+
+def main(argv):
+ retval = 0
+
+ (options, args) = parse_args(argv)
+
+ if len(args) != 2:
+ gbp.log.err("Need a repository to clone.")
+ return 1
+ else:
+ source = args[1]
+
+ try:
+ GitRepository(os.path.curdir)
+ gbp.log.err("Can't run inside a git repository.")
+ return 1
+ except GitRepositoryError:
+ pass
+
+ try:
+ repo = GitRepository.clone(os.path.curdir, source)
+ os.chdir(repo.path)
+
+ # Reparse the config files of the cloned repository so we pick up the
+ # branch information from there:
+ (options, args) = parse_args(argv)
+
+ # Track all branches:
+ if options.all:
+ remotes = repo.get_remote_branches()
+ for remote in remotes:
+ local = remote.replace("origin/", "", 1)
+ if not repo.has_branch(local) and \
+ local != "HEAD":
+ repo.create_branch(local, remote)
+ else: # only track gbp's default branches
+ branches = [ options.debian_branch, options.upstream_branch ]
+ if options.pristine_tar:
+ branches += [ PristineTar.branch ]
+ gbp.log.debug('Will track branches: %s' % branches)
+ for branch in branches:
+ remote = 'origin/%s' % branch
+ if repo.has_branch(remote, remote=True) and \
+ not repo.has_branch(branch):
+ repo.create_branch(branch, remote)
+
+ repo.set_branch(options.debian_branch)
+
+ except CommandExecFailed:
+ retval = 1
+ except GbpError, err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ retval = 1
+
+ return retval
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/create_remote_repo.py b/gbp/scripts/create_remote_repo.py
new file mode 100644
index 00000000..00f3410d
--- /dev/null
+++ b/gbp/scripts/create_remote_repo.py
@@ -0,0 +1,248 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Based on the aa-create-git-repo and dom-new-git-repo shell scripts
+
+"""Create a remote repo based on the current one"""
+# TODO: allow to add hooks by default
+
+import sys
+import os, os.path
+import urlparse
+import subprocess
+import tty, termios
+import re
+import gbp.deb as du
+from gbp.command_wrappers import (CommandExecFailed, PristineTar, GitCommand)
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.errors import GbpError
+from gbp.git import (GitRepositoryError, GitRepository)
+import gbp.log
+
+def print_config(remote, branches):
+ print """[remote "%(name)s"]
+ url = %(url)s
+ fetch = +refs/heads/*:refs/remotes/%(name)s/*""" % remote
+
+ for branch in branches:
+ print " push = %s" % branch
+
+ for branch in branches:
+ print """[branch "%s"]
+ remote = %s
+ merge = refs/heads/%s""" % (branch, remote['name'], branch)
+
+
+def parse_remote(remote_url, name, pkg):
+ """
+ Sanity check our remote URL
+
+ >>> parse_remote("ssh://host/path/%(pkg)s", "origin", "package")
+ {'name': 'origin', 'url': 'ssh://host/path/package', 'host': 'host', 'base': '', 'pkg': 'package', 'port': None, 'dir': '/path/package'}
+
+ >>> parse_remote("ssh://host:22/path/repo.git", "origin", "package")
+ {'name': 'origin', 'url': 'ssh://host:22/path/repo.git', 'host': 'host', 'base': '', 'pkg': 'package', 'port': '22', 'dir': '/path/repo.git'}
+
+ >>> parse_remote("ssh://host:22/~/path/%(pkg)s.git", "origin", "package")
+ {'name': 'origin', 'url': 'ssh://host:22/~/path/package.git', 'host': 'host', 'base': '~/', 'pkg': 'package', 'port': '22', 'dir': 'path/package.git'}
+
+ >>> parse_remote("ssh://host:22/~user/path/%(pkg)s.git", "origin", "package")
+ {'name': 'origin', 'url': 'ssh://host:22/~user/path/package.git', 'host': 'host', 'base': '~user/', 'pkg': 'package', 'port': '22', 'dir': 'path/package.git'}
+
+ >>> parse_remote("git://host/repo.git", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ GbpError: Remote URL must use ssh protocol.
+ >>> parse_remote("ssh://host/path/repo", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ GbpError: Remote URL needs to contain either a repository name or '%(pkg)s'
+ >>> parse_remote("ssh://host:asdf/path/%(pkg)s.git", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ GbpError: Remote URL contains invalid port.
+ >>> parse_remote("ssh://host/~us er/path/%(pkg)s.git", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ GbpError: Remote URL contains invalid ~username expansion.
+ """
+ frags = urlparse.urlparse(remote_url)
+ if frags.scheme not in ['ssh', 'git+ssh']:
+ raise GbpError, "Remote URL must use ssh protocol."
+ if not '%(pkg)s' in remote_url and not remote_url.endswith(".git"):
+ raise GbpError, "Remote URL needs to contain either a repository name or '%(pkg)s'"
+
+ if ":" in frags.netloc:
+ (host, port) = frags.netloc.split(":", 1)
+ if not re.match(r"^[0-9]+$", port):
+ raise GbpError, "Remote URL contains invalid port."
+ else:
+ host = frags.netloc
+ port = None
+
+ if frags.path.startswith("/~"):
+ m = re.match(r"/(~[a-zA-Z0-9_-]*/)(.*)", frags.path)
+ if not m:
+ raise GbpError, "Remote URL contains invalid ~username expansion."
+ base = m.group(1)
+ path = m.group(2)
+ else:
+ base = ""
+ path = frags.path
+
+ remote = { 'pkg' : pkg,
+ 'url' : remote_url % { 'pkg': pkg },
+ 'dir' : path % { 'pkg': pkg },
+ 'base': base,
+ 'host': host,
+ 'port': port,
+ 'name': name}
+ return remote
+
+
+def read_yn():
+ fd = sys.stdin.fileno()
+ old_settings = termios.tcgetattr(fd)
+ try:
+ tty.setraw(sys.stdin.fileno())
+ ch = sys.stdin.read(1)
+ finally:
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
+
+ if ch in ( 'y', 'Y' ):
+ return True
+ else:
+ return False
+
+
+def setup_branch_tracking(repo, remote, branches):
+ repo.add_remote_repo(name=remote['name'], url=remote['url'], fetch=True)
+ gitTrackRemote = GitCommand('branch', ['--set-upstream'])
+ for branch in branches:
+ gitTrackRemote(['%s' % branch, '%s/%s' % (remote['name'], branch)])
+
+
+def push_branches(remote, branches):
+ gitPush = GitCommand('push')
+ gitPush([remote['url']] + branches)
+ gitPush([remote['url'], '--tags'])
+
+
+def main(argv):
+ retval = 0
+ changelog = 'debian/changelog'
+
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ usage='%prog [options] - create a remote repository')
+ branch_group = GbpOptionGroup(parser, "branch options", "branch layout and tracking options")
+ branch_group.add_config_file_option(option_name="remote-url-pattern", dest="remote_url")
+ parser.add_option_group(branch_group)
+ branch_group.add_config_file_option(option_name="upstream-branch", dest="upstream_branch")
+ branch_group.add_config_file_option(option_name="debian-branch", dest="debian_branch")
+ branch_group.add_boolean_config_file_option(option_name="pristine-tar", dest="pristine_tar")
+ branch_group.add_boolean_config_file_option(option_name="track", dest='track')
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+ parser.add_option("--remote-name", dest="name", default="origin",
+ help="The name of the remote, default is 'origin'")
+
+ (options, args) = parser.parse_args(argv)
+ gbp.log.setup(options.color, options.verbose)
+
+ try:
+ repo = GitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+
+ try:
+ branches = []
+
+ for branch in [ options.debian_branch, options.upstream_branch ]:
+ if repo.has_branch(branch):
+ branches += [ branch ]
+
+ if repo.has_branch(PristineTar.branch) and options.pristine_tar:
+ branches += [ PristineTar.branch ]
+
+ try:
+ cp = du.parse_changelog(filename=changelog)
+ pkg = cp['Source']
+ except gbp.deb.NoChangelogError:
+ pkg = None
+
+ if not pkg:
+ gbp.log.warn("Couldn't parse changelog, will use directory name.")
+ pkg = os.path.basename(os.path.abspath(os.path.curdir))
+ pkg = os.path.splitext(pkg)[0]
+
+ remote = parse_remote(options.remote_url, options.name, pkg)
+ if repo.has_remote_repo(options.name):
+ raise GbpError, "You already have a remote name '%s' defined for this repository." % options.name
+ gbp.log.info("Shall I create a repository for '%(pkg)s' at '%(url)s' now? (y/n)?" % remote)
+ if not read_yn():
+ raise GbpError, "Aborted."
+
+ # Create and run the remote script
+ remote_script = """
+set -e
+umask 002
+if [ -d %(base)s"%(dir)s" ]; then
+ echo "Repository at \"%(base)s%(dir)s\" already exists - giving up."
+ exit 1
+fi
+mkdir -p %(base)s"%(dir)s"
+cd %(base)s"%(dir)s"
+git init --bare --shared
+echo "%(pkg)s packaging" > description
+""" % remote
+
+ if options.verbose:
+ print remote_script
+
+ ssh = ["ssh"]
+ if remote["port"]:
+ ssh.extend(["-p", remote["port"]])
+ ssh.extend([remote["host"], "sh"])
+
+ proc = subprocess.Popen(ssh, stdin=subprocess.PIPE)
+ proc.communicate(remote_script)
+ if proc.returncode:
+ raise GbpError, "Error creating remote repository"
+
+ push_branches(remote, branches)
+ if options.track:
+ setup_branch_tracking(repo, remote, branches)
+ else:
+ gbp.log.info("You can now add:")
+ print_config(remote, branches)
+ gbp.log.info("to your .git/config to 'gbp-pull' and 'git push' in the future.")
+
+ except CommandExecFailed:
+ retval = 1
+ except GbpError, err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ retval = 1
+
+ return retval
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/dch.py b/gbp/scripts/dch.py
new file mode 100644
index 00000000..a82c1bdc
--- /dev/null
+++ b/gbp/scripts/dch.py
@@ -0,0 +1,500 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2007, 2008, 2009, 2010 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""Generate Debian changelog entries from git commit messages"""
+
+import ConfigParser
+import os.path
+import re
+import sys
+import shutil
+import subprocess
+import gbp.command_wrappers as gbpc
+import gbp.dch as dch
+import gbp.log
+from gbp.git import (GitRepositoryError, GitRepository, build_tag, tag_to_version)
+from gbp.config import GbpOptionParser, GbpOptionGroup
+from gbp.errors import GbpError
+from gbp.deb import parse_changelog, NoChangelogError, is_native, compare_versions
+
+user_customizations = {}
+snapshot_re = re.compile("\s*\*\* SNAPSHOT build @(?P<commit>[a-z0-9]+)\s+\*\*")
+
+
+def system(cmd):
+ try:
+ gbpc.Command(cmd, shell=True)()
+ except gbpc.CommandExecFailed:
+ raise GbpError
+
+
+def spawn_dch(msg=[], author=None, email=None, newversion=False, version=None,
+ release=False, distribution=None, dch_options=''):
+ """
+ Spawn dch
+ param author: committers name
+ param email: committers email
+ param newversion: start a new version
+ version: the verion to use
+ release: finalize changelog for releaze
+ distribution: distribution to use
+ dch_options: options passed verbatim to dch
+ """
+ distopt = ""
+ versionopt = ""
+ env = ""
+
+ if newversion:
+ if version:
+ try:
+ versionopt = version['increment']
+ except KeyError:
+ versionopt = '--newversion=%s' % version['version']
+ else:
+ versionopt = '-i'
+ elif release:
+ versionopt = "--release --no-force-save-on-release"
+ msg = None
+
+ if author and email:
+ env = """DEBFULLNAME="%s" DEBEMAIL="%s" """ % (author, email)
+
+ if distribution:
+ distopt = "--distribution=%s" % distribution
+
+ cmd = '%(env)s dch --no-auto-nmu %(distopt)s %(versionopt)s %(dch_options)s ' % locals()
+ if msg:
+ cmd += '-- "[[[insert-git-dch-commit-message-here]]]"'
+ else:
+ cmd += '-- ""'
+ system(cmd)
+ if msg:
+ old_cl = open("debian/changelog", "r")
+ new_cl = open("debian/changelog.bak", "w")
+ for line in old_cl:
+ if line == " * [[[insert-git-dch-commit-message-here]]]\n":
+ print >> new_cl, " * " + msg[0]
+ for line in msg[1:]:
+ print >> new_cl, " " + line
+ else:
+ print >> new_cl, line,
+ os.rename("debian/changelog.bak", "debian/changelog")
+
+
+def add_changelog_entry(msg, author, email, dch_options):
+ "add a single changelog entry"
+ spawn_dch(msg=msg, author=author, email=email, dch_options=dch_options)
+
+
+def add_changelog_section(msg, distribution, repo, options, cp,
+ author=None, email=None, version=None, dch_options=''):
+ "add a new changelog section"
+ # If no version(change) was specified guess the new version based on the
+ # latest upstream version on the upstream branch
+ if not version and not is_native(cp):
+ pattern = options.upstream_tag.replace('%(version)s', '*')
+ try:
+ tag = repo.find_tag('HEAD', pattern=pattern)
+ upstream = tag_to_version(tag, options.upstream_tag)
+ if upstream:
+ gbp.log.debug("Found %s." % upstream)
+ new_version = "%s-1" % upstream
+ if compare_versions(upstream, cp['Version']) > 0:
+ version['version'] = new_version
+ except GitRepositoryError:
+ gbp.log.debug("No tag found matching pattern %s." % pattern)
+ spawn_dch(msg=msg, newversion=True, version=version, author=author, email=email,
+ distribution=distribution, dch_options=dch_options)
+
+
+def get_author_email(repo, use_git_config):
+ """Get author and email from git configuration"""
+ author = email = None
+
+ if use_git_config:
+ try: author = repo.get_config('user.name')
+ except KeyError: pass
+
+ try: email = repo.get_config('user.email')
+ except KeyError: pass
+ return author, email
+
+
+def fixup_trailer(repo, git_author, dch_options):
+ """fixup the changelog trailer's comitter and email address - it might
+ otherwise point to the last git committer instead of the person creating
+ the changelog"""
+ author, email = get_author_email(repo, git_author)
+ spawn_dch(msg='', author=author, email=email, dch_options=dch_options)
+
+
+def snapshot_version(version):
+ """
+ get the current release and snapshot version
+ Format is <debian-version>~<release>.gbp<short-commit-id>
+ """
+ try:
+ (release, suffix) = version.rsplit('~', 1)
+ (snapshot, commit) = suffix.split('.', 1)
+ if not commit.startswith('gbp'):
+ raise ValueError
+ else:
+ snapshot = int(snapshot)
+ except ValueError: # not a snapshot release
+ release = version
+ snapshot = 0
+ return release, snapshot
+
+
+def mangle_changelog(changelog, cp, snapshot=''):
+ """
+ Mangle changelog to either add or remove snapshot markers
+
+ @param snapshot: SHA1 if snapshot header should be added/maintained, empty if it should be removed
+ @type snapshot: str
+ """
+ try:
+ tmpfile = '%s.%s' % (changelog, snapshot)
+ cw = file(tmpfile, 'w')
+ cr = file(changelog, 'r')
+
+ cr.readline() # skip version and empty line
+ cr.readline()
+ print >>cw, "%(Source)s (%(MangledVersion)s) %(Distribution)s; urgency=%(urgency)s\n" % cp
+
+ line = cr.readline()
+ if snapshot_re.match(line):
+ cr.readline() # consume the empty line after the snapshot header
+ line = ''
+
+ if snapshot:
+ print >>cw, " ** SNAPSHOT build @%s **\n" % snapshot
+
+ if line:
+ print >>cw, line.rstrip()
+ shutil.copyfileobj(cr, cw)
+ cw.close()
+ cr.close()
+ os.unlink(changelog)
+ os.rename(tmpfile, changelog)
+ except OSError, e:
+ raise GbpError, "Error mangling changelog %s" % e
+
+
+def do_release(changelog, repo, cp, git_author, dch_options):
+ "remove the snapshot header and set the distribution"
+ author, email = get_author_email(repo, git_author)
+ (release, snapshot) = snapshot_version(cp['Version'])
+ if snapshot:
+ cp['MangledVersion'] = release
+ mangle_changelog(changelog, cp)
+ spawn_dch(release=True, author=author, email=email, dch_options=dch_options)
+
+
+def do_snapshot(changelog, repo, next_snapshot):
+ """
+ Add new snapshot banner to most recent changelog section. The next snapshot
+ number is calculated by eval()'ing next_snapshot
+ """
+ commit = repo.head
+
+ cp = parse_changelog(filename=changelog)
+ (release, snapshot) = snapshot_version(cp['Version'])
+ snapshot = int(eval(next_snapshot))
+
+ suffix = "%d.gbp%s" % (snapshot, "".join(commit[0:6]))
+ cp['MangledVersion'] = "%s~%s" % (release, suffix)
+
+ mangle_changelog(changelog, cp, commit)
+ return snapshot, commit
+
+
+def parse_commit(repo, commitid, opts, last_commit=False):
+ """parse a commit and return message, author, and author email"""
+ commit_info = repo.get_commit_info(commitid)
+ author = commit_info['author']
+ email = commit_info['email']
+ format_entry = user_customizations.get('format_changelog_entry')
+ if not format_entry:
+ format_entry = dch.format_changelog_entry
+ entry = format_entry(commit_info, opts, last_commit=last_commit)
+ return entry, (author, email)
+
+
+def guess_snapshot_commit(cp, repo, options):
+ """
+ guess the last commit documented in the changelog from the snapshot banner
+ or the last point the changelog was touched.
+ """
+ sr = re.search(snapshot_re, cp['Changes'])
+ if sr:
+ return sr.group('commit')
+ # If the current topmost changelog entry has already been tagged rely on
+ # the version information only. The upper level relies then on the version
+ # info anyway:
+ if repo.find_version(options.debian_tag, cp['Version']):
+ return None
+ # If we didn't find a snapshot header we look at the point the changelog
+ # was last touched.
+ last = repo.get_commits(paths="debian/changelog", options=["-1"])
+ if last:
+ gbp.log.info("Changelog last touched at '%s'" % last[0])
+ return last[0]
+ return None
+
+
+def get_customizations(customization_file):
+ if customization_file:
+ execfile(customization_file,
+ user_customizations,
+ user_customizations)
+
+
+def process_options(options, parser):
+ if options.snapshot and options.release:
+ parser.error("'--snapshot' and '--release' are incompatible options")
+
+ if options.since and options.auto:
+ parser.error("'--since' and '--auto' are incompatible options")
+
+ if options.multimaint_merge:
+ dch_options = "--multimaint-merge"
+ else:
+ dch_options = "--nomultimaint-merge"
+
+ if options.multimaint:
+ dch_options += " --multimaint"
+ else:
+ dch_options += " --nomultimaint"
+
+ get_customizations(options.customization_file)
+ return dch_options
+
+
+def process_editor_option(options):
+ # Determine Editor and check if we need it
+ states = ['always']
+
+ if options.snapshot:
+ states.append("snapshot")
+ elif options.release:
+ states.append("release")
+
+ if options.spawn_editor in states:
+ return "sensible-editor"
+ else:
+ return None
+
+
+def main(argv):
+ ret = 0
+ changelog = 'debian/changelog'
+ until = 'HEAD'
+ found_snapshot_header = False
+ version_change = {}
+
+ try:
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ usage='%prog [options] paths')
+ except ConfigParser.ParsingError, err:
+ gbp.log.errror(err)
+ return 1
+ range_group = GbpOptionGroup(parser, "commit range options", "which commits to add to the changelog")
+ version_group = GbpOptionGroup(parser, "release & version number options", "what version number and release to use")
+ commit_group = GbpOptionGroup(parser, "commit message formatting", "howto format the changelog entries")
+ naming_group = GbpOptionGroup(parser, "branch and tag naming", "branch names and tag formats")
+ custom_group = GbpOptionGroup(parser, "customization", "options for customization")
+ parser.add_option_group(range_group)
+ parser.add_option_group(version_group)
+ parser.add_option_group(commit_group)
+ parser.add_option_group(naming_group)
+ parser.add_option_group(custom_group)
+
+ parser.add_boolean_config_file_option(option_name = "ignore-branch", dest="ignore_branch")
+ naming_group.add_config_file_option(option_name="debian-branch", dest="debian_branch")
+ naming_group.add_config_file_option(option_name="upstream-tag", dest="upstream_tag")
+ naming_group.add_config_file_option(option_name="debian-tag", dest="debian_tag")
+ naming_group.add_config_file_option(option_name="snapshot-number", dest="snapshot_number",
+ help="expression to determine the next snapshot number, default is '%(snapshot-number)s'")
+ parser.add_config_file_option(option_name="git-log", dest="git_log",
+ help="options to pass to git-log, default is '%(git-log)s'")
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+ range_group.add_option("-s", "--since", dest="since", help="commit to start from (e.g. HEAD^^^, debian/0.4.3)")
+ range_group.add_option("-a", "--auto", action="store_true", dest="auto", default=False,
+ help="autocomplete changelog from last snapshot or tag")
+ version_group.add_option("-R", "--release", action="store_true", dest="release", default=False,
+ help="mark as release")
+ version_group.add_option("-S", "--snapshot", action="store_true", dest="snapshot", default=False,
+ help="mark as snapshot build")
+ version_group.add_option("-N", "--new-version", dest="new_version",
+ help="use this as base for the new version number")
+ version_group.add_option("--bpo", dest="bpo", action="store_true", default=False,
+ help="Increment the Debian release number for an upload to lenny-backports, and add a backport upload changelog comment.")
+ version_group.add_option("--nmu", dest="nmu", action="store_true", default=False,
+ help="Increment the Debian release number for a non-maintainer upload")
+ version_group.add_option("--qa", dest="qa", action="store_true", default=False,
+ help="Increment the Debian release number for a Debian QA Team upload, and add a QA upload changelog comment.")
+ version_group.add_boolean_config_file_option(option_name="git-author", dest="git_author")
+ commit_group.add_boolean_config_file_option(option_name="meta", dest="meta")
+ commit_group.add_config_file_option(option_name="meta-closes", dest="meta_closes",
+ help="Meta tags for the bts close commands, default is '%(meta-closes)s'")
+ commit_group.add_boolean_config_file_option(option_name="full", dest="full")
+ commit_group.add_config_file_option(option_name="id-length", dest="idlen",
+ help="include N digits of the commit id in the changelog entry, default is '%(id-length)s'",
+ type="int", metavar="N")
+ commit_group.add_config_file_option(option_name="ignore-regex", dest="ignore_regex",
+ help="Ignore commit lines matching regex, default is '%(ignore-regex)s'")
+ commit_group.add_boolean_config_file_option(option_name="multimaint", dest="multimaint")
+ commit_group.add_boolean_config_file_option(option_name="multimaint-merge", dest="multimaint_merge")
+ commit_group.add_config_file_option(option_name="spawn-editor", dest="spawn_editor")
+
+ help_msg = 'Load Python code from CUSTOMIZATION_FILE. At the moment,' \
+ + ' the only useful thing the code can do is define a custom' \
+ + ' format_changelog_entry() function.'
+ custom_group.add_config_file_option(option_name="customizations",
+ dest="customization_file",
+ help=help_msg)
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose)
+ dch_options = process_options(options, parser)
+ editor_cmd = process_editor_option(options)
+
+ try:
+ try:
+ repo = GitRepository('.')
+ except GitRepositoryError:
+ raise GbpError, "%s is not a git repository" % (os.path.abspath('.'))
+
+ branch = repo.get_branch()
+ if options.debian_branch != branch and not options.ignore_branch:
+ gbp.log.err("You are not on branch '%s' but on '%s'" % (options.debian_branch, branch))
+ raise GbpError, "Use --ignore-branch to ignore or --debian-branch to set the branch name."
+
+ cp = parse_changelog(filename=changelog)
+
+ if options.since:
+ since = options.since
+ else:
+ since = ''
+ if options.auto:
+ since = guess_snapshot_commit(cp, repo, options)
+ if since:
+ gbp.log.info("Continuing from commit '%s'" % since)
+ found_snapshot_header = True
+ else:
+ gbp.log.info("Couldn't find snapshot header, using version info")
+ if not since:
+ since = repo.find_version(options.debian_tag, cp['Version'])
+ if not since:
+ raise GbpError, "Version %s not found" % cp['Version']
+
+ if args:
+ gbp.log.info("Only looking for changes on '%s'" % " ".join(args))
+ commits = repo.get_commits(since=since, until=until,
+ paths=" ".join(args),
+ options=options.git_log.split(" "))
+ commits.reverse()
+
+ # add a new changelog section if:
+ if options.new_version or options.bpo or options.nmu or options.qa:
+ if options.bpo:
+ version_change['increment'] = '--bpo'
+ elif options.nmu:
+ version_change['increment'] = '--nmu'
+ elif options.qa:
+ version_change['increment'] = '--qa'
+ else:
+ version_change['version'] = options.new_version
+ # the user wants to force a new version
+ add_section = True
+ elif cp['Distribution'] != "UNRELEASED" and not found_snapshot_header and commits:
+ # the last version was a release and we have pending commits
+ add_section = True
+ elif options.snapshot and not found_snapshot_header:
+ # the user want to switch to snapshot mode
+ add_section = True
+ else:
+ add_section = False
+
+ i = 0
+ for c in commits:
+ i += 1
+ parsed = parse_commit(repo, c, options,
+ last_commit = i == len(commits))
+ commit_msg, (commit_author, commit_email) = parsed
+ if not commit_msg:
+ # Some commits can be ignored
+ continue
+
+ if add_section:
+ # Add a section containing just this message (we can't
+ # add an empty section with dch)
+ add_changelog_section(distribution="UNRELEASED", msg=commit_msg,
+ version=version_change,
+ author=commit_author,
+ email=commit_email,
+ dch_options=dch_options,
+ repo=repo,
+ options=options,
+ cp=cp)
+ # Adding a section only needs to happen once.
+ add_section = False
+ else:
+ add_changelog_entry(commit_msg, commit_author, commit_email, dch_options)
+
+
+ # Show a message if there were no commits (not even ignored
+ # commits).
+ if not commits:
+ gbp.log.info("No changes detected from %s to %s." % (since, until))
+
+ if add_section:
+ # If we end up here, then there were no commits to include,
+ # so we put a dummy message in the new section.
+ add_changelog_section(distribution="UNRELEASED", msg=["UNRELEASED"],
+ version=version_change,
+ dch_options=dch_options,
+ repo=repo,
+ options=options,
+ cp=cp)
+
+ fixup_trailer(repo, git_author=options.git_author,
+ dch_options=dch_options)
+
+ if options.release:
+ do_release(changelog, repo, cp, git_author=options.git_author,
+ dch_options=dch_options)
+ elif options.snapshot:
+ (snap, version) = do_snapshot(changelog, repo, options.snapshot_number)
+ gbp.log.info("Changelog has been prepared for snapshot #%d at %s" % (snap, version))
+
+ if editor_cmd:
+ gbpc.Command(editor_cmd, ["debian/changelog"])()
+
+ except (GbpError, GitRepositoryError, NoChangelogError), err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ ret = 1
+ return ret
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/import_dsc.py b/gbp/scripts/import_dsc.py
new file mode 100644
index 00000000..94013e25
--- /dev/null
+++ b/gbp/scripts/import_dsc.py
@@ -0,0 +1,344 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,2011 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""Import a Debian source package into a git repository"""
+
+import ConfigParser
+import sys
+import re
+import os
+import tempfile
+import glob
+import pipes
+import time
+from email.Utils import parseaddr
+import gbp.command_wrappers as gbpc
+from gbp.deb import (debian_version_chars, parse_changelog,
+ parse_dsc, DscFile, UpstreamSource)
+from gbp.git import (build_tag, GitRepository,
+ GitRepositoryError, rfc822_date_to_git)
+from gbp.config import GbpOptionParser, GbpOptionGroup, no_upstream_branch_msg
+from gbp.errors import GbpError
+import gbp.log
+
+class SkipImport(Exception):
+ pass
+
+
+def download_source(pkg, dirs):
+ if re.match(r'[a-z]{1,5}://', pkg):
+ mode='dget'
+ else:
+ mode='apt-get'
+
+ dirs['download'] = os.path.abspath(tempfile.mkdtemp())
+ gbp.log.info("Downloading '%s' using '%s'..." % (pkg, mode))
+ if mode == 'apt-get':
+ gbpc.RunAtCommand('apt-get',
+ ['-qq', '--download-only', 'source', pkg],
+ shell=False)(dir=dirs['download'])
+ else:
+ gbpc.RunAtCommand('dget',
+ ['-q', '--download-only', pkg],
+ shell=False)(dir=dirs['download'])
+ dsc = glob.glob(os.path.join(dirs['download'], '*.dsc'))[0]
+ return dsc
+
+
+def apply_patch(diff):
+ "Apply patch to a source tree"
+ pipe = pipes.Template()
+ pipe.prepend('gunzip -c %s' % diff, '.-')
+ pipe.append('patch -p1 --quiet', '-.')
+ try:
+ ret = pipe.copy('', '')
+ if ret:
+ gbp.log.err("Error import %s: %d" % (diff, ret))
+ return False
+ except OSError, err:
+ gbp.log.err("Error importing %s: %s" % (diff, err[0]))
+ return False
+ return True
+
+
+def apply_deb_tgz(deb_tgz):
+ """Apply .debian.tar.gz (V3 source format)"""
+ gbpc.UnpackTarArchive(deb_tgz, ".")()
+ return True
+
+
+def apply_debian_patch(repo, unpack_dir, src, options, parents):
+ """apply the debian patch and tag appropriately"""
+ try:
+ os.chdir(unpack_dir)
+
+ if src.diff and not apply_patch(src.diff):
+ raise GbpError
+
+ if src.deb_tgz and not apply_deb_tgz(src.deb_tgz):
+ raise GbpError
+
+ if os.path.exists('debian/rules'):
+ os.chmod('debian/rules', 0755)
+ os.chdir(repo.path)
+
+ dch = parse_changelog(filename=os.path.join(unpack_dir, 'debian/changelog'))
+ date= rfc822_date_to_git(dch['Date'])
+ author, email = parseaddr(dch['Maintainer'])
+ if not (author and email):
+ gbp.log.warn("Failed to parse maintainer")
+ commit = repo.commit_dir(unpack_dir,
+ "Imported Debian patch %s" % src.version,
+ branch = options.debian_branch,
+ other_parents = parents,
+ author=dict(name=author, email = email, date = date),
+ committer=dict(name=[None, author][options.author_committer],
+ email=[None, email][options.author_committer],
+ date=[None, date][options.author_committer_date]))
+ repo.create_tag(build_tag(options.debian_tag, src.version),
+ msg="Debian release %s" % src.version,
+ commit=commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+ except gbpc.CommandExecFailed:
+ gbp.log.err("Failed to import Debian package")
+ raise GbpError
+ finally:
+ os.chdir(repo.path)
+
+
+def print_dsc(dsc):
+ if dsc.native:
+ gbp.log.debug("Debian Native Package %s")
+ gbp.log.debug("Version: %s" % dsc.upstream_version)
+ gbp.log.debug("Debian tarball: %s" % dsc.tgz)
+ else:
+ gbp.log.debug("Upstream version: %s" % dsc.upstream_version)
+ gbp.log.debug("Debian version: %s" % dsc.debian_version)
+ gbp.log.debug("Upstream tarball: %s" % dsc.tgz)
+ if dsc.diff:
+ gbp.log.debug("Debian patch: %s" % dsc.diff)
+ if dsc.deb_tgz:
+ gbp.log.debug("Debian patch: %s" % dsc.deb_tgz)
+ if dsc.epoch:
+ gbp.log.debug("Epoch: %s" % dsc.epoch)
+
+
+def move_tag_stamp(repo, format, version):
+ "Move tag out of the way appending the current timestamp"
+ old = build_tag(format, version)
+ timestamped = "%s~%s" % (version, int(time.time()))
+ new = build_tag(format, timestamped)
+ repo.move_tag(old, new)
+
+
+def set_bare_repo_options(options):
+ """Modify options for import into a bare repository"""
+ if options.pristine_tar:
+ gbp.log.info("Bare repository: setting %s option"
+ % (["", " '--no-pristine-tar'"][options.pristine_tar], ))
+ options.pristine_tar = False
+
+
+def parse_args(argv):
+ try:
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ usage='%prog [options] /path/to/package.dsc')
+ except ConfigParser.ParsingError, err:
+ gbp.log.err(err)
+ return None, None
+
+ import_group = GbpOptionGroup(parser, "import options",
+ "pristine-tar and filtering")
+ tag_group = GbpOptionGroup(parser, "tag options",
+ "options related to git tag creation")
+ branch_group = GbpOptionGroup(parser, "version and branch naming options",
+ "version number and branch layout options")
+
+ for group in [import_group, branch_group, tag_group ]:
+ parser.add_option_group(group)
+
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+ parser.add_option("--download", action="store_true", dest="download", default=False,
+ help="download source package")
+ branch_group.add_config_file_option(option_name="debian-branch",
+ dest="debian_branch")
+ branch_group.add_config_file_option(option_name="upstream-branch",
+ dest="upstream_branch")
+ branch_group.add_boolean_config_file_option(option_name="create-missing-branches",
+ dest="create_missing_branches")
+
+ tag_group.add_boolean_config_file_option(option_name="sign-tags",
+ dest="sign_tags")
+ tag_group.add_config_file_option(option_name="keyid",
+ dest="keyid")
+ tag_group.add_config_file_option(option_name="debian-tag",
+ dest="debian_tag")
+ tag_group.add_config_file_option(option_name="upstream-tag",
+ dest="upstream_tag")
+
+ import_group.add_config_file_option(option_name="filter",
+ dest="filters", action="append")
+ import_group.add_boolean_config_file_option(option_name="pristine-tar",
+ dest="pristine_tar")
+ import_group.add_option("--allow-same-version", action="store_true",
+ dest="allow_same_version", default=False,
+ help="allow to import already imported version")
+ import_group.add_boolean_config_file_option(option_name="author-is-committer",
+ dest="author_committer")
+ import_group.add_boolean_config_file_option(option_name="author-date-is-committer-date",
+ dest="author_committer_date")
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose)
+ return options, args
+
+
+def main(argv):
+ dirs = dict(top=os.path.abspath(os.curdir))
+ needs_repo = False
+ ret = 0
+ skipped = False
+ parents = None
+
+ options, args = parse_args(argv)
+
+ try:
+ if len(args) != 1:
+ gbp.log.err("Need to give exactly one package to import. Try --help.")
+ raise GbpError
+ else:
+ pkg = args[0]
+ if options.download:
+ dsc = download_source(pkg, dirs=dirs)
+ else:
+ dsc = pkg
+
+ src = parse_dsc(dsc)
+ if src.pkgformat not in [ '1.0', '3.0' ]:
+ raise GbpError, "Importing %s source format not yet supported." % src.pkgformat
+ if options.verbose:
+ print_dsc(src)
+
+ try:
+ repo = GitRepository('.')
+ is_empty = repo.is_empty()
+
+ (clean, out) = repo.is_clean()
+ if not clean and not is_empty:
+ gbp.log.err("Repository has uncommitted changes, commit these first: ")
+ raise GbpError, out
+
+ except GitRepositoryError:
+ # no repo found, create one
+ needs_repo = True
+ is_empty = True
+
+ if needs_repo:
+ gbp.log.info("No git repository found, creating one.")
+ repo = GitRepository.create(src.pkg)
+ os.chdir(repo.path)
+
+ if repo.bare:
+ set_bare_repo_options(options)
+
+ dirs['tmp'] = os.path.abspath(tempfile.mkdtemp(dir='..'))
+ upstream = UpstreamSource(src.tgz)
+ upstream.unpack(dirs['tmp'], options.filters)
+
+ format = [(options.upstream_tag, "Upstream"), (options.debian_tag, "Debian")][src.native]
+ tag = build_tag(format[0], src.upstream_version)
+ msg = "%s version %s" % (format[1], src.upstream_version)
+
+ if repo.find_version(options.debian_tag, src.version):
+ gbp.log.warn("Version %s already imported." % src.version)
+ if options.allow_same_version:
+ gbp.log.info("Moving tag of version '%s' since import forced" % src.version)
+ move_tag_stamp(repo, options.debian_tag, src.version)
+ else:
+ raise SkipImport
+
+ if not repo.find_version(format[0], src.upstream_version):
+ gbp.log.info("Tag %s not found, importing %s tarball" % (tag, format[1]))
+ if is_empty:
+ branch = None
+ else:
+ branch = [options.upstream_branch,
+ options.debian_branch][src.native]
+ if not repo.has_branch(branch):
+ if options.create_missing_branches:
+ gbp.log.info("Creating missing branch '%s'" % branch)
+ repo.create_branch(branch)
+ else:
+ gbp.log.err(no_upstream_branch_msg % branch +
+ "\nAlso check the --create-missing-branches option.")
+ raise GbpError
+
+ commit = repo.commit_dir(upstream.unpacked,
+ "Imported %s" % msg,
+ branch)
+ repo.create_tag(name=tag,
+ msg=msg,
+ commit=commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+ if not src.native:
+ if is_empty:
+ repo.create_branch(options.upstream_branch, commit)
+ if options.pristine_tar:
+ gbpc.PristineTar().commit(src.tgz, 'refs/heads/%s' % options.upstream_branch)
+ parents = [ options.upstream_branch ]
+ if not src.native:
+ if is_empty and not repo.has_branch(options.debian_branch):
+ repo.create_branch(options.debian_branch, commit)
+ if src.diff or src.deb_tgz:
+ apply_debian_patch(repo, upstream.unpacked, src, options, parents)
+ else:
+ gbp.log.warn("Didn't find a diff to apply.")
+ if repo.get_branch() == options.debian_branch or is_empty:
+ # Update HEAD if we modified the checked out branch
+ repo.force_head(options.debian_branch, hard=True)
+ except KeyboardInterrupt:
+ ret = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except gbpc.CommandExecFailed:
+ ret = 1
+ except GitRepositoryError, msg:
+ gbp.log.err("Git command failed: %s" % msg)
+ ret = 1
+ except GbpError, err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ ret = 1
+ except SkipImport:
+ skipped = True
+ finally:
+ os.chdir(dirs['top'])
+
+ for d in [ 'tmp', 'download' ]:
+ if dirs.has_key(d):
+ gbpc.RemoveTree(dirs[d])()
+
+ if not ret and not skipped:
+ gbp.log.info("Version '%s' imported under '%s'" % (src.version, src.pkg))
+ return ret
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/import_dscs.py b/gbp/scripts/import_dscs.py
new file mode 100644
index 00000000..fc39ce4a
--- /dev/null
+++ b/gbp/scripts/import_dscs.py
@@ -0,0 +1,156 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2008, 2009, 2010 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""Import multiple dsc files in one go"""
+
+import glob
+import os
+import sys
+import tempfile
+import gbp.command_wrappers as gbpc
+from gbp.deb import parse_dsc, DscFile, DpkgCompareVersions
+from gbp.errors import GbpError
+from gbp.git import GitRepository, GitRepositoryError
+import gbp.log
+
+class DscCompareVersions(DpkgCompareVersions):
+ def __init__(self):
+ DpkgCompareVersions.__init__(self)
+
+ def __call__(self, dsc1, dsc2):
+ return DpkgCompareVersions.__call__(self, dsc1.version, dsc2.version)
+
+
+class GitImportDsc(gbpc.Command):
+ # git-import-dsc should reside in the same directory as git-import-dscs
+ # so we can reuse the full path from the later
+ cmd = os.path.abspath(__file__[:-1])
+ def __init__(self, args):
+ if not os.access(self.cmd, os.X_OK):
+ raise GbpError, "%s not found - can't import packages" % self.cmd
+ gbpc.Command.__init__(self, self.cmd, args)
+
+ def importdsc(self, dsc):
+ gbpc.Command.__call__(self, [dsc.dscfile])
+
+
+def fetch_snapshots(pkg, downloaddir):
+ "Fetch snapshots using debsnap von snapshots.debian.org"
+ dscs = None
+
+ gbp.log.info("Downloading snapshots of '%s' to '%s'..." % (pkg, downloaddir))
+ debsnap = gbpc.Command("debsnap", [ '--force', '--destdir=%s' % (downloaddir), pkg])
+ try:
+ debsnap()
+ except gbpc.CommandExecFailed:
+ if debsnap.retcode == 2:
+ gbp.log.warn("Some packages failed to download. Continuing.")
+ pass
+ else:
+ raise
+
+ dscs = glob.glob(os.path.join(downloaddir, '*.dsc'))
+ if not dscs:
+ raise GbpError, 'No package downloaded'
+
+ return [os.path.join(downloaddir, dsc) for dsc in dscs]
+
+
+def print_help():
+ print """Usage:
+ git-import-dscs [git-import-dsc options] /path/to/dsc1 [/path/to/dsc2] ...
+or
+ git-import-dscs --debsnap [git-import-dsc options] package
+"""
+
+
+def main(argv):
+ dirs = dict(top=os.path.abspath(os.curdir))
+ dscs = []
+ ret = 0
+ verbose = False
+ dsc_cmp = DscCompareVersions()
+ use_debsnap = False
+
+ try:
+ import_args = argv[1:]
+
+ if '--verbose' in import_args:
+ verbose = True
+ gbp.log.setup(False, verbose)
+
+ # Not using Configparser since we want to pass all unknown options
+ # unaltered to git-import-dsc
+ if '--debsnap' in import_args:
+ use_debsnap = True
+ import_args.remove('--debsnap')
+ if import_args == []:
+ print_help()
+ raise GbpError
+ pkg = import_args[-1]
+ import_args = import_args[:-1]
+ else:
+ for arg in argv[::-1]:
+ if arg.endswith('.dsc'):
+ dscs.append(parse_dsc(arg))
+ import_args.remove(arg)
+
+ if not use_debsnap and not dscs:
+ print_help()
+ raise GbpError
+
+ if use_debsnap:
+ dirs['tmp'] = os.path.abspath(tempfile.mkdtemp())
+ dscs = [ parse_dsc(f) for f in fetch_snapshots(pkg, dirs['tmp']) ]
+
+ dscs.sort(cmp=dsc_cmp)
+ importer = GitImportDsc(import_args)
+
+ try:
+ repo = GitRepository('.')
+ (clean, out) = repo.is_clean()
+ if not clean:
+ gbp.log.err("Repository has uncommitted changes, commit these first: ")
+ raise GbpError, out
+ else:
+ dirs['pkg'] = dirs['top']
+ except GitRepositoryError:
+ # no git repository there yet
+ dirs['pkg'] = os.path.join(dirs['top'], dscs[0].pkg)
+
+ importer.importdsc(dscs[0])
+ os.chdir(dirs['pkg'])
+
+ for dsc in dscs[1:]:
+ importer.importdsc(dsc)
+
+ except (GbpError, gbpc.CommandExecFailed), err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ ret = 1
+ finally:
+ if dirs.has_key('tmp'):
+ gbpc.RemoveTree(dirs['tmp'])()
+ os.chdir(dirs['top'])
+
+ if not ret:
+ gbp.log.info('Everything imported under %s' % dirs['pkg'])
+ return ret
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/import_orig.py b/gbp/scripts/import_orig.py
new file mode 100644
index 00000000..445d1a6d
--- /dev/null
+++ b/gbp/scripts/import_orig.py
@@ -0,0 +1,452 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006, 2007, 2009, 2011 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""Import a new upstream version into a git repository"""
+
+import ConfigParser
+import glob
+import os
+import sys
+import re
+import subprocess
+import tempfile
+import gbp.command_wrappers as gbpc
+from gbp.deb import (parse_changelog, UpstreamSource,
+ NoChangelogError, has_epoch,
+ do_uscan,
+ parse_changelog_repo, is_valid_packagename,
+ packagename_msg, is_valid_upstreamversion,
+ upstreamversion_msg)
+from gbp.git import (GitRepositoryError, GitRepository, build_tag)
+from gbp.config import GbpOptionParser, GbpOptionGroup, no_upstream_branch_msg
+from gbp.errors import (GbpError, GbpNothingImported)
+import gbp.log
+
+# Try to import readline, since that will cause raw_input to get fancy
+# line editing and history capabilities. However, if readline is not
+# available, raw_input will still work.
+try:
+ import readline
+except ImportError:
+ pass
+
+
+class OrigUpstreamSource(UpstreamSource):
+ """Upstream source that will be imported"""
+
+ def needs_repack(self, options):
+ """
+ Determine if the upstream sources needs to be repacked
+
+ We repack if
+ 1. we want to filter out files and use pristine tar since we want
+ to make a filtered tarball available to pristine-tar
+ 2. when we don't have a suitable upstream tarball (e.g. zip archive or unpacked dir)
+ and want to use filters
+ 3. when we don't have a suitable upstream tarball (e.g. zip archive or unpacked dir)
+ and want to use pristine-tar
+ """
+ if ((options.pristine_tar and options.filter_pristine_tar and len(options.filters) > 0)):
+ return True
+ elif not self.is_orig:
+ if len(options.filters):
+ return True
+ elif options.pristine_tar:
+ return True
+ return False
+
+
+def cleanup_tmp_tree(tree):
+ """remove a tree of temporary files"""
+ try:
+ gbpc.RemoveTree(tree)()
+ except gbpc.CommandExecFailed:
+ gbp.log.err("Removal of tmptree %s failed." % tree)
+
+
+def is_link_target(target, link):
+ """does symlink link already point to target?"""
+ if os.path.exists(link):
+ if os.path.samefile(target, link):
+ return True
+ return False
+
+
+def symlink_orig(archive, pkg, version):
+ """
+ create a symlink <pkg>_<version>.orig.tar.<ext> so pristine-tar will see the
+ correct basename
+ @return: archive path to be used by pristine tar
+ """
+ if os.path.isdir(archive):
+ return None
+ ext = os.path.splitext(archive)[1]
+ link = "../%s_%s.orig.tar%s" % (pkg, version, ext)
+ if os.path.basename(archive) != os.path.basename(link):
+ try:
+ if not is_link_target(archive, link):
+ os.symlink(os.path.abspath(archive), link)
+ except OSError, err:
+ raise GbpError, "Cannot symlink '%s' to '%s': %s" % (archive, link, err[1])
+ return link
+ else:
+ return archive
+
+
+def upstream_import_commit_msg(options, version):
+ return options.import_msg % dict(version=version)
+
+
+def detect_name_and_version(repo, source, options):
+ # Guess defaults for the package name and version from the
+ # original tarball.
+ (guessed_package, guessed_version) = source.guess_version() or ('', '')
+
+ # Try to find the source package name
+ try:
+ cp = parse_changelog(filename='debian/changelog')
+ sourcepackage = cp['Source']
+ except NoChangelogError:
+ try:
+ # Check the changelog file from the repository, in case
+ # we're not on the debian-branch (but upstream, for
+ # example).
+ cp = parse_changelog_repo(repo, options.debian_branch, 'debian/changelog')
+ sourcepackage = cp['Source']
+ except NoChangelogError:
+ if options.interactive:
+ sourcepackage = ask_package_name(guessed_package)
+ else:
+ if guessed_package:
+ sourcepackage = guessed_package
+ else:
+ raise GbpError, "Couldn't determine upstream package name. Use --interactive."
+
+ # Try to find the version.
+ if options.version:
+ version = options.version
+ else:
+ if options.interactive:
+ version = ask_package_version(guessed_version)
+ else:
+ if guessed_version:
+ version = guessed_version
+ else:
+ raise GbpError, "Couldn't determine upstream version. Use '-u<version>' or --interactive."
+
+ return (sourcepackage, version)
+
+
+def ask_package_name(default):
+ """
+ Ask the user for the source package name.
+ @param default: The default package name to suggest to the user.
+ """
+ while True:
+ sourcepackage = raw_input("What will be the source package name? [%s] " % default)
+ if not sourcepackage: # No input, use the default.
+ sourcepackage = default
+ # Valid package name, return it.
+ if is_valid_packagename(sourcepackage):
+ return sourcepackage
+
+ # Not a valid package name. Print an extra
+ # newline before the error to make the output a
+ # bit clearer.
+ gbp.log.warn("\nNot a valid package name: '%s'.\n%s" % (sourcepackage, packagename_msg))
+
+
+def ask_package_version(default):
+ """
+ Ask the user for the upstream package version.
+ @param default: The default package version to suggest to the user.
+ """
+ while True:
+ version = raw_input("What is the upstream version? [%s] " % default)
+ if not version: # No input, use the default.
+ version = default
+ # Valid version, return it.
+ if is_valid_upstreamversion(version):
+ return version
+
+ # Not a valid upstream version. Print an extra
+ # newline before the error to make the output a
+ # bit clearer.
+ gbp.log.warn("\nNot a valid upstream version: '%s'.\n%s" % (version, upstreamversion_msg))
+
+
+def find_source(options, args):
+ """Find the tarball to import - either via uscan or via command line argument
+ @return: upstream source filename or None if nothing to import
+ @rtype: string
+ @raise GbpError: raised on all detected errors
+ """
+ if options.uscan: # uscan mode
+ if args:
+ raise GbpError, "you can't pass both --uscan and a filename."
+
+ gbp.log.info("Launching uscan...")
+ try:
+ status, source = do_uscan()
+ except KeyError:
+ raise GbpError, "error running uscan - debug by running uscan --verbose"
+
+ if status:
+ if source:
+ gbp.log.info("using %s" % source)
+ args.append(source)
+ else:
+ raise GbpError, "uscan didn't download anything, and no source was found in ../"
+ else:
+ gbp.log.info("package is up to date, nothing to do.")
+ return None
+ if len(args) > 1: # source specified
+ raise GbpError, "More than one archive specified. Try --help."
+ elif len(args) == 0:
+ raise GbpError, "No archive to import specified. Try --help."
+ else:
+ archive = OrigUpstreamSource(args[0])
+ return archive
+
+
+def repacked_tarball_name(source, name, version):
+ if source.is_orig:
+ # Repacked orig tarballs get need a different name since there's already
+ # one with that name
+ name = os.path.join(
+ os.path.dirname(source.path),
+ os.path.basename(source.path).replace(".tar", ".gbp.tar"))
+ else:
+ # Repacked sources or other archives get canonical name
+ name = os.path.join(
+ os.path.dirname(source.path),
+ "%s_%s.orig.tar.bz2" % (name, version))
+ return name
+
+
+def repack_source(source, name, version, tmpdir, filters):
+ """Repack the source tree"""
+ name = repacked_tarball_name(source, name, version)
+ repacked = source.pack(name, filters)
+ if source.is_orig: # the tarball was filtered on unpack
+ repacked.unpacked = source.unpacked
+ else: # otherwise unpack the generated tarball get a filtered tree
+ if tmpdir:
+ cleanup_tmp_tree(tmpdir)
+ tmpdir = tempfile.mkdtemp(dir='../')
+ repacked.unpack(tmpdir, filters)
+ return (repacked, tmpdir)
+
+
+def set_bare_repo_options(options):
+ """Modify options for import into a bare repository"""
+ if options.pristine_tar or options.merge:
+ gbp.log.info("Bare repository: setting %s%s options"
+ % (["", " '--no-pristine-tar'"][options.pristine_tar],
+ ["", " '--no-merge'"][options.merge]))
+ options.pristine_tar = False
+ options.merge = False
+
+
+def parse_args(argv):
+ try:
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ usage='%prog [options] /path/to/upstream-version.tar.gz | --uscan')
+ except ConfigParser.ParsingError, err:
+ gbp.log.err(err)
+ return None, None
+
+ import_group = GbpOptionGroup(parser, "import options",
+ "pristine-tar and filtering")
+ tag_group = GbpOptionGroup(parser, "tag options",
+ "options related to git tag creation")
+ branch_group = GbpOptionGroup(parser, "version and branch naming options",
+ "version number and branch layout options")
+ cmd_group = GbpOptionGroup(parser, "external command options", "how and when to invoke external commands and hooks")
+
+ for group in [import_group, branch_group, tag_group, cmd_group ]:
+ parser.add_option_group(group)
+
+ branch_group.add_option("-u", "--upstream-version", dest="version",
+ help="Upstream Version")
+ branch_group.add_config_file_option(option_name="debian-branch",
+ dest="debian_branch")
+ branch_group.add_config_file_option(option_name="upstream-branch",
+ dest="upstream_branch")
+ branch_group.add_boolean_config_file_option(option_name="merge", dest="merge")
+
+ tag_group.add_boolean_config_file_option(option_name="sign-tags",
+ dest="sign_tags")
+ tag_group.add_config_file_option(option_name="keyid",
+ dest="keyid")
+ tag_group.add_config_file_option(option_name="upstream-tag",
+ dest="upstream_tag")
+ import_group.add_config_file_option(option_name="filter",
+ dest="filters", action="append")
+ import_group.add_boolean_config_file_option(option_name="pristine-tar",
+ dest="pristine_tar")
+ import_group.add_boolean_config_file_option(option_name="filter-pristine-tar",
+ dest="filter_pristine_tar")
+ import_group.add_config_file_option(option_name="import-msg",
+ dest="import_msg")
+ cmd_group.add_config_file_option(option_name="postimport", dest="postimport")
+
+ parser.add_boolean_config_file_option(option_name="interactive",
+ dest='interactive')
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+
+ # Accepted for compatibility
+ parser.add_option("--no-dch", dest='no_dch', action="store_true",
+ default=False, help="deprecated - don't use.")
+ parser.add_option("--uscan", dest='uscan', action="store_true",
+ default=False, help="use uscan(1) to download the new tarball.")
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose)
+
+ if options.no_dch:
+ gbp.log.warn("'--no-dch' passed. This is now the default, please remove this option.")
+
+ return options, args
+
+
+def main(argv):
+ ret = 0
+ tmpdir = ''
+ pristine_orig = None
+
+ (options, args) = parse_args(argv)
+ try:
+ source = find_source(options, args)
+ if not source:
+ return ret
+
+ try:
+ repo = GitRepository('.')
+ except GitRepositoryError:
+ raise GbpError, "%s is not a git repository" % (os.path.abspath('.'))
+
+ # an empty repo has now branches:
+ initial_branch = repo.get_branch()
+ is_empty = False if initial_branch else True
+
+ if not repo.has_branch(options.upstream_branch) and not is_empty:
+ gbp.log.err(no_upstream_branch_msg % options.upstream_branch)
+ raise GbpError
+
+ (sourcepackage, version) = detect_name_and_version(repo, source, options)
+
+ (clean, out) = repo.is_clean()
+ if not clean and not is_empty:
+ gbp.log.err("Repository has uncommitted changes, commit these first: ")
+ raise GbpError, out
+
+ if repo.bare:
+ set_bare_repo_options(options)
+
+ if not source.is_dir:
+ tmpdir = tempfile.mkdtemp(dir='../')
+ source.unpack(tmpdir, options.filters)
+ gbp.log.debug("Unpacked '%s' to '%s'" % (source.path, source.unpacked))
+
+ if source.needs_repack(options):
+ gbp.log.debug("Filter pristine-tar: repacking '%s' from '%s'" % (source.path, source.unpacked))
+ (source, tmpdir) = repack_source(source, sourcepackage, version, tmpdir, options.filters)
+
+ pristine_orig = symlink_orig(source.path, sourcepackage, version)
+
+ # Don't mess up our repo with git metadata from an upstream tarball
+ try:
+ if os.path.isdir(os.path.join(source.unpacked, '.git/')):
+ raise GbpError, "The orig tarball contains .git metadata - giving up."
+ except OSError:
+ pass
+
+ try:
+ upstream_branch = [ options.upstream_branch, 'master' ][is_empty]
+ filter_msg = ["", " (filtering out %s)"
+ % options.filters][len(options.filters) > 0]
+ gbp.log.info("Importing '%s' to branch '%s'%s..." % (source.path,
+ upstream_branch,
+ filter_msg))
+ gbp.log.info("Source package is %s" % sourcepackage)
+ gbp.log.info("Upstream version is %s" % version)
+
+ import_branch = [ options.upstream_branch, None ][is_empty]
+ msg = upstream_import_commit_msg(options, version)
+ commit = repo.commit_dir(source.unpacked, msg=msg, branch=import_branch)
+ if not commit:
+ raise GbpError, "Import of upstream version %s failed." % version
+
+ if options.pristine_tar:
+ if pristine_orig:
+ gbpc.PristineTar().commit(pristine_orig, 'refs/heads/%s' % upstream_branch)
+ else:
+ gbp.log.warn("'%s' not an archive, skipping pristine-tar" % source.path)
+
+ tag = build_tag(options.upstream_tag, version)
+ repo.create_tag(name=tag,
+ msg="Upstream version %s" % version,
+ commit=commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+ if is_empty:
+ repo.create_branch(options.upstream_branch, rev=commit)
+ repo.force_head(options.upstream_branch, hard=True)
+ elif options.merge:
+ gbp.log.info("Merging to '%s'" % options.debian_branch)
+ repo.set_branch(options.debian_branch)
+ try:
+ repo.merge(tag)
+ except gbpc.CommandExecFailed:
+ raise GbpError, """Merge failed, please resolve."""
+ if options.postimport:
+ epoch = ''
+ if os.access('debian/changelog', os.R_OK):
+ # No need to check the changelog file from the
+ # repository, since we're certain that we're on
+ # the debian-branch
+ cp = parse_changelog(filename='debian/changelog')
+ if has_epoch(cp):
+ epoch = '%s:' % cp['Epoch']
+ info = { 'version': "%s%s-1" % (epoch, version) }
+ env = { 'GBP_BRANCH': options.debian_branch }
+ gbpc.Command(options.postimport % info, extra_env=env, shell=True)()
+ except gbpc.CommandExecFailed:
+ raise GbpError, "Import of %s failed" % source.path
+ except GbpNothingImported, err:
+ gbp.log.err(err)
+ repo.set_branch(initial_branch)
+ ret = 1
+ except GbpError, err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ ret = 1
+
+ if tmpdir:
+ cleanup_tmp_tree(tmpdir)
+
+ if not ret:
+ gbp.log.info("Successfully imported version %s of %s" % (version, source.path))
+ return ret
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/pq.py b/gbp/scripts/pq.py
new file mode 100644
index 00000000..2ad7b259
--- /dev/null
+++ b/gbp/scripts/pq.py
@@ -0,0 +1,427 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011 Guido Günther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+"""manage patches in a patch queue"""
+
+import errno
+import re
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.git import (GitRepositoryError, GitRepository)
+from gbp.command_wrappers import (Command, GitCommand, RunAtCommand,
+ CommandExecFailed)
+from gbp.errors import GbpError
+import gbp.log
+from gbp.pq import PatchQueue
+
+PQ_BRANCH_PREFIX = "patch-queue/"
+PATCH_DIR = "debian/patches/"
+SERIES_FILE = os.path.join(PATCH_DIR,"series")
+
+
+def is_pq_branch(branch):
+ """
+ is branch a patch-queue branch?
+
+ >>> is_pq_branch("foo")
+ False
+ >>> is_pq_branch("patch-queue/foo")
+ True
+ """
+ return [False, True][branch.startswith(PQ_BRANCH_PREFIX)]
+
+
+def pq_branch_name(branch):
+ """
+ get the patch queue branch corresponding to branch
+
+ >>> pq_branch_name("patch-queue/master")
+ >>> pq_branch_name("foo")
+ 'patch-queue/foo'
+ """
+ if not is_pq_branch(branch):
+ return PQ_BRANCH_PREFIX + branch
+
+
+def pq_branch_base(pq_branch):
+ """
+ get the branch corresponding to the given patch queue branch
+
+ >>> pq_branch_base("patch-queue/master")
+ 'master'
+ >>> pq_branch_base("foo")
+ """
+ if is_pq_branch(pq_branch):
+ return pq_branch[len(PQ_BRANCH_PREFIX):]
+
+def write_patch(patch, options):
+ """Write the patch exported by 'git-format-patch' to it's final location
+ (as specified in the commit)"""
+ oldname = patch[len(PATCH_DIR):]
+ newname = oldname
+ tmpname = patch + ".gbp"
+ old = file(patch, 'r')
+ tmp = file(tmpname, 'w')
+ in_patch = False
+ topic = None
+
+ # Skip first line (From <sha1>)
+ old.readline()
+ for line in old:
+ if in_patch:
+ if line == '-- \n':
+ # Found final signature, we're done:
+ tmp.write(line)
+ break
+ else:
+ if line.lower().startswith("gbp-pq-topic: "):
+ topic = line.split(" ",1)[1].strip()
+ gbp.log.debug("Topic %s found for %s" % (topic, patch))
+ continue
+ elif (line.startswith("diff --git a/") or
+ line.startswith("---")):
+ in_patch = True
+ tmp.write(line)
+
+ tmp.close()
+ old.close()
+
+ if not options.patch_numbers:
+ patch_re = re.compile("[0-9]+-(?P<name>.+)")
+ m = patch_re.match(oldname)
+ if m:
+ newname = m.group('name')
+
+ if topic:
+ topicdir = os.path.join(PATCH_DIR, topic)
+ else:
+ topicdir = PATCH_DIR
+
+ if not os.path.isdir(topicdir):
+ os.makedirs(topicdir, 0755)
+
+ os.unlink(patch)
+ dstname = os.path.join(topicdir, newname)
+ gbp.log.debug("Moving %s to %s" % (tmpname, dstname))
+ shutil.move(tmpname, dstname)
+
+ return dstname
+
+
+def export_patches(repo, branch, options):
+ """Export patches from the pq branch into a patch series"""
+ if is_pq_branch(branch):
+ base = pq_branch_base(branch)
+ gbp.log.info("On '%s', switching to '%s'" % (branch, base))
+ branch = base
+ repo.set_branch(branch)
+
+ pq_branch = pq_branch_name(branch)
+ try:
+ shutil.rmtree(PATCH_DIR)
+ except OSError, (e, msg):
+ if e != errno.ENOENT:
+ raise GbpError, "Failed to remove patch dir: %s" % msg
+ else:
+ gbp.log.debug("%s does not exist." % PATCH_DIR)
+
+ patches = repo.format_patches(branch, pq_branch, PATCH_DIR)
+ if patches:
+ f = file(SERIES_FILE, 'w')
+ gbp.log.info("Regenerating patch queue in '%s'." % PATCH_DIR)
+ for patch in patches:
+ filename = write_patch(patch, options)
+ f.write(filename[len(PATCH_DIR):] + '\n')
+
+ f.close()
+ GitCommand('status')(['--', PATCH_DIR])
+ else:
+ gbp.log.info("No patches on '%s' - nothing to do." % pq_branch)
+
+
+def get_maintainer_from_control():
+ """Get the maintainer from the control file"""
+ cmd = 'sed -n -e \"s/Maintainer: \\+\\(.*\\)/\\1/p\" debian/control'
+ maintainer = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.readlines()[0].strip()
+
+ m = re.match('(?P<name>.*[^ ]) *<(?P<email>.*)>', maintainer)
+ if m:
+ return m.group('name'), m.group('email')
+ else:
+ return None, None
+
+
+def safe_patches(series):
+ """
+ Safe the current patches in a temporary directory
+ below .git/
+
+ @param series: path to series file
+ @return: tmpdir and path to safed series file
+ @rtype: tuple
+ """
+
+ src = os.path.dirname(series)
+ name = os.path.basename(series)
+
+ tmpdir = tempfile.mkdtemp(dir='.git/', prefix='gbp-pq')
+ patches = os.path.join(tmpdir, 'patches')
+ series = os.path.join(patches, name)
+
+ gbp.log.debug("Safeing patches '%s' in '%s'" % (src, tmpdir))
+ shutil.copytree(src, patches)
+
+ return (tmpdir, series)
+
+
+def import_quilt_patches(repo, branch, series, tries):
+ """
+ apply a series of quilt patches in the series file 'series' to branch
+ the patch-queue branch for 'branch'
+
+ @param repo: git repository to work on
+ @param branch: branch to base pqtch queue on
+ @param series; series file to read patches from
+ @param tries: try that many times to apply the patches going back one
+ commit in the branches history after each failure.
+ """
+ tmpdir = None
+
+ if is_pq_branch(branch):
+ gbp.log.err("Already on a patch-queue branch '%s' - doing nothing." % branch)
+ raise GbpError
+ else:
+ pq_branch = pq_branch_name(branch)
+
+ if repo.has_branch(pq_branch):
+ raise GbpError, ("Patch queue branch '%s'. already exists. Try 'rebase' instead."
+ % pq_branch)
+
+ commits = repo.get_commits(options=['-%d' % tries], first_parent=True)
+ # If we go back in history we have to safe our pq so we always try to apply
+ # the latest one
+ if len(commits) > 1:
+ tmpdir, series = safe_patches(series)
+
+ queue = PatchQueue.read_series_file(series)
+ for commit in commits:
+ try:
+ gbp.log.info("Trying to apply patches at '%s'" % commit)
+ repo.create_branch(pq_branch, commit)
+ except CommandExecFailed:
+ raise GbpError, ("Cannot create patch-queue branch '%s'." % pq_branch)
+
+ repo.set_branch(pq_branch)
+ for patch in queue:
+ gbp.log.debug("Applying %s" % patch.path)
+ try:
+ apply_and_commit_patch(repo, patch.path, patch.topic)
+ except (GbpError, GitRepositoryError, CommandExecFailed):
+ repo.set_branch(branch)
+ repo.delete_branch(pq_branch)
+ break
+ else:
+ # All patches applied successfully
+ break
+ else:
+ raise GbpError, "Couldn't apply patches"
+
+ if tmpdir:
+ gbp.log.debug("Remove temporary patch safe '%s'" % tmpdir)
+ shutil.rmtree(tmpdir)
+
+
+def get_mailinfo(patch):
+ """Read patch information into a structured form"""
+
+ info = {}
+ body = os.path.join('.git', 'gbp_patchinfo')
+ pipe = subprocess.Popen("git mailinfo %s /dev/null < %s" % (body, patch),
+ shell=True, stdout=subprocess.PIPE).stdout
+ for line in pipe:
+ if ':' in line:
+ rfc_header, value = line.split(" ",1)
+ header = rfc_header[:-1].lower()
+ info[header] = value.strip()
+
+ try:
+ f = file(body)
+ commit_msg = "".join([ line for line in f ])
+ f.close()
+ os.unlink(body)
+ except IOError, msg:
+ raise GbpError, "Failed to read patch header of '%s': %s" % (patch, msg)
+
+ return info, commit_msg
+
+
+def switch_to_pq_branch(repo, branch):
+ """Switch to patch-queue branch if not already there, create it if it
+ doesn't exist yet"""
+ if is_pq_branch (branch):
+ return
+
+ pq_branch = pq_branch_name(branch)
+ if not repo.has_branch(pq_branch):
+ try:
+ repo.create_branch(pq_branch)
+ except CommandExecFailed:
+ raise GbpError, ("Cannot create patch-queue branch '%s'. Try 'rebase' instead."
+ % pq_branch)
+
+ gbp.log.info("Switching to '%s'" % pq_branch)
+ repo.set_branch(pq_branch)
+
+
+def apply_single_patch(repo, branch, patch, topic=None):
+ switch_to_pq_branch(repo, branch)
+ apply_and_commit_patch(repo, patch, topic)
+
+
+def apply_and_commit_patch(repo, patch, topic=None):
+ """apply a single patch 'patch', add topic 'topic' and commit it"""
+ header, body = get_mailinfo(patch)
+
+ # If we don't find a subject use the patch's name
+ if not header.has_key('subject'):
+ header['subject'] = os.path.basename(patch)
+ # Strip of .diff or .patch from patch name
+ base, ext = header['subject'].rsplit('.', 1)
+ if ext in [ 'diff', 'patch' ]:
+ header['subject'] = base
+
+ if header.has_key('author') and header.has_key('email'):
+ header['name'] = header['author']
+ else:
+ name, email = get_maintainer_from_control()
+ if name:
+ gbp.log.warn("Patch '%s' has no authorship information, using '%s <%s>'"
+ % (patch, name, email))
+ header['name'] = name
+ header['email'] = email
+ else:
+ gbp.log.warn("Patch %s has no authorship information")
+
+ repo.apply_patch(patch)
+ tree = repo.write_tree()
+ msg = "%s\n\n%s" % (header['subject'], body)
+ if topic:
+ msg += "\nGbp-Pq-Topic: %s" % topic
+ commit = repo.commit_tree(tree, msg, [repo.head], author=header)
+ repo.update_ref('HEAD', commit, msg="gbp-pq import %s" % patch)
+
+
+def drop_pq(repo, branch):
+ if is_pq_branch(branch):
+ gbp.log.err("On a patch-queue branch, can't drop it.")
+ raise GbpError
+ else:
+ pq_branch = pq_branch_name(branch)
+
+ if repo.has_branch(pq_branch):
+ repo.delete_branch(pq_branch)
+ gbp.log.info("Dropped branch '%s'." % pq_branch)
+ else:
+ gbp.log.info("No patch queue branch found - doing nothing.")
+
+
+def rebase_pq(repo, branch):
+ switch_to_pq_branch(repo, branch)
+ GitCommand("rebase")([branch])
+
+
+def main(argv):
+ retval = 0
+
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ usage="%prog [options] action - maintain patches on a patch queue branch\n"
+ "Actions:\n"
+ " export export the patch queue associated to the current branch\n"
+ " into a quilt patch series in debian/patches/ and update the\n"
+ " series file.\n"
+ " import create a patch queue branch from quilt patches in debian/patches.\n"
+ " rebase switch to patch queue branch associated to the current\n"
+ " branch and rebase against current branch.\n"
+ " drop drop (delete) the patch queue associated to the current branch.\n"
+ " apply apply a patch\n")
+ parser.add_boolean_config_file_option(option_name="patch-numbers", dest="patch_numbers")
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_option("--topic", dest="topic", help="in case of 'apply' topic (subdir) to put patch into")
+ parser.add_config_file_option(option_name="time-machine", dest="time_machine", type="int")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+
+ (options, args) = parser.parse_args(argv)
+ gbp.log.setup(options.color, options.verbose)
+
+ if len(args) < 2:
+ gbp.log.err("No action given.")
+ return 1
+ else:
+ action = args[1]
+
+ if args[1] in ["export", "import", "rebase", "drop"]:
+ pass
+ elif args[1] in ["apply"]:
+ if len(args) != 3:
+ gbp.log.err("No patch name given.")
+ return 1
+ else:
+ patch = args[2]
+ else:
+ gbp.log.err("Unknown action '%s'." % args[1])
+ return 1
+
+ try:
+ repo = GitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+
+ try:
+ current = repo.get_branch()
+ if action == "export":
+ export_patches(repo, current, options)
+ elif action == "import":
+ series = SERIES_FILE
+ tries = options.time_machine if (options.time_machine > 0) else 1
+ import_quilt_patches(repo, current, series, tries)
+ current = repo.get_branch()
+ gbp.log.info("Patches listed in '%s' imported on '%s'" %
+ (series, current))
+ elif action == "drop":
+ drop_pq(repo, current)
+ elif action == "rebase":
+ rebase_pq(repo, current)
+ elif action == "apply":
+ apply_single_patch(repo, current, patch, options.topic)
+ except CommandExecFailed:
+ retval = 1
+ except GbpError, err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ retval = 1
+
+ return retval
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
diff --git a/gbp/scripts/pull.py b/gbp/scripts/pull.py
new file mode 100644
index 00000000..69ad1a6d
--- /dev/null
+++ b/gbp/scripts/pull.py
@@ -0,0 +1,134 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2009 Guido Guenther <agx@sigxcpu.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# heavily inspired by dom-safe-pull which is © 2009 Stéphane Glondu <steph@glondu.net>
+#
+"""fast forward debian, upstream and pristine-tar branch"""
+
+import sys
+import os, os.path
+from gbp.command_wrappers import (Command,
+ CommandExecFailed, PristineTar)
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.errors import GbpError
+from gbp.git import (GitRepositoryError, GitRepository)
+import gbp.log
+
+def fast_forward_branch(branch, repo, options):
+ """
+ update branch to its remote branch, fail on non fast forward updates
+ unless --force is given
+ @return: branch updated or already up to date
+ @rtype: boolean
+ """
+ update = False
+
+ remote = repo.get_merge_branch(branch)
+ if not remote:
+ gbp.log.warn("No branch tracking '%s' found - skipping." % branch)
+ return False
+
+ can_fast_forward, up_to_date = repo.is_fast_forward(branch, remote)
+
+ if up_to_date: # Great, we're done
+ gbp.log.info("Branch '%s' is already up to date." % branch)
+ return True
+
+ if can_fast_forward:
+ update = True
+ else:
+ if options.force:
+ gbp.log.info("Non-fast forwarding '%s' due to --force" % branch)
+ update = True
+ else:
+ gbp.log.warn("Skipping non-fast forward of '%s' - use --force" % branch)
+
+ if update:
+ gbp.log.info("Updating '%s'" % branch)
+ repo.set_branch(branch)
+ repo.merge(remote)
+ return update
+
+def main(argv):
+ retval = 0
+
+ parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
+ usage='%prog [options] - safely update a repository from remote')
+ branch_group = GbpOptionGroup(parser, "branch options", "branch update and layout options")
+ parser.add_option_group(branch_group)
+ branch_group.add_option("--force", action="store_true", dest="force", default=False,
+ help="force a branch update even if can't be fast forwarded")
+ branch_group.add_option("--redo-pq", action="store_true", dest="redo_pq", default=False,
+ help="redo the patch queue branch after a pull. Warning: this drops the old patch-queue branch")
+ branch_group.add_config_file_option(option_name="upstream-branch", dest="upstream_branch")
+ branch_group.add_config_file_option(option_name="debian-branch", dest="debian_branch")
+ branch_group.add_boolean_config_file_option(option_name="pristine-tar", dest="pristine_tar")
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+
+
+ (options, args) = parser.parse_args(argv)
+ gbp.log.setup(options.color, options.verbose)
+
+ try:
+ repo = GitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+
+ try:
+ branches = []
+ current = repo.get_branch()
+
+ for branch in [ options.debian_branch, options.upstream_branch ]:
+ if repo.has_branch(branch):
+ branches += [ branch ]
+
+ if repo.has_branch(PristineTar.branch) and options.pristine_tar:
+ branches += [ PristineTar.branch ]
+
+ (ret, out) = repo.is_clean()
+ if not ret:
+ gbp.log.err("You have uncommitted changes in your source tree:")
+ gbp.log.err(out)
+ raise GbpError
+
+ repo.fetch()
+ for branch in branches:
+ if not fast_forward_branch(branch, repo, options):
+ retval = 2
+
+ if options.redo_pq:
+ repo.set_branch(options.debian_branch)
+ Command("gbp-pq")(["drop"])
+ Command("gbp-pq")(["import"])
+
+ repo.set_branch(current)
+ except CommandExecFailed:
+ retval = 1
+ except GbpError, err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ retval = 1
+
+ return retval
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: