diff options
author | Guido Günther <agx@sigxcpu.org> | 2011-10-30 20:03:33 +0100 |
---|---|---|
committer | Guido Günther <agx@sigxcpu.org> | 2011-11-01 18:12:48 +0100 |
commit | e28ea0740a7b4eb2ef4c1bd3079d77a40c6072b8 (patch) | |
tree | 779145e71a996c471a1d1a6b1acde1bc90deecc4 /gbp/scripts | |
parent | abf90abcba15beb51196cf503f35695acdcd91c1 (diff) |
Get rid of the symlink
by moving the commands to gbp/scripts/
Diffstat (limited to 'gbp/scripts')
-rw-r--r-- | gbp/scripts/__init__.py | 0 | ||||
-rw-r--r-- | gbp/scripts/buildpackage.py | 571 | ||||
-rw-r--r-- | gbp/scripts/clone.py | 113 | ||||
-rw-r--r-- | gbp/scripts/create_remote_repo.py | 248 | ||||
-rw-r--r-- | gbp/scripts/dch.py | 500 | ||||
-rw-r--r-- | gbp/scripts/import_dsc.py | 344 | ||||
-rw-r--r-- | gbp/scripts/import_dscs.py | 156 | ||||
-rw-r--r-- | gbp/scripts/import_orig.py | 452 | ||||
-rw-r--r-- | gbp/scripts/pq.py | 427 | ||||
-rw-r--r-- | gbp/scripts/pull.py | 134 |
10 files changed, 2945 insertions, 0 deletions
diff --git a/gbp/scripts/__init__.py b/gbp/scripts/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/gbp/scripts/__init__.py diff --git a/gbp/scripts/buildpackage.py b/gbp/scripts/buildpackage.py new file mode 100644 index 0000000..d0ce3c1 --- /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 0000000..665b548 --- /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 0000000..00f3410 --- /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 0000000..a82c1bd --- /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 0000000..94013e2 --- /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 0000000..fc39ce4 --- /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 0000000..445d1a6 --- /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 0000000..2ad7b25 --- /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 0000000..69ad1a6 --- /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\:·: |