#!/usr/bin/python -u # vim: set fileencoding=utf-8 : # # (C) 2006, 2007, 2009 Guido Guenther # 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 sys import os import tempfile import re import glob import subprocess import tarfile import time import gbp.command_wrappers as gbpc from gbp.deb_utils import parse_changelog, unpack_orig, repack_orig, NoChangelogError, has_epoch, tar_toplevel, guess_upstream_version from gbp.git_utils import (GitRepositoryError, GitRepository, build_tag) from gbp.config import GbpOptionParser from gbp.errors import (GbpError, GbpNothingImported) class FastImport(object): """Invoke git-fast-import""" _bufsize = 1024 m_regular = 644 m_exec = 755 m_symlink = 120000 def __init__(self): try: self._fi = subprocess.Popen([ 'git', 'fast-import', '--quiet'], stdin=subprocess.PIPE) self._out = self._fi.stdin except OSError, err: raise GbpError, "Error spawning git fast-import: %s", err except ValueError, err: raise GbpError, "Invalid argument when spawning git fast-import: %s", err def _do_data(self, fd, size): self._out.write("data %s\n" % size) while True: data = fd.read(self._bufsize) self._out.write(data) if len(data) != self._bufsize: break self._out.write("\n") def _do_file(self, filename, mode, fd, size): name = "/".join(filename.split('/')[1:]) self._out.write("M %d inline %s\n" % (mode, name)) self._do_data(fd, size) def add_file(self, filename, fd, size): self._do_file(filename, self.m_regular, fd, size) def add_executable(self, filename, fd, size): self._do_file(filename, self.m_exec, fd, size) def add_symlink(self, filename, linkname): name = "/".join(filename.split('/')[1:]) self._out.write("M %d inline %s\n" % (self.m_symlink, name)) self._out.write("data %s\n" % len(linkname)) self._out.write("%s\n" % linkname) def start_commit(self, branch, committer, email, time, msg): length = len(msg) self._out.write("""commit refs/heads/%(branch)s committer %(committer)s <%(email)s> %(time)s data %(length)s %(msg)s from refs/heads/%(branch)s^0 """ % locals()) def do_deleteall(self): self._out.write("deleteall\n") def close(self): if self._out: self._out.close() if self._fi: self._fi.wait() def __del__(self): self.close() def cleanup_tmp_tree(tree): """remove a tree of temporary files""" try: gbpc.RemoveTree(tree)() except gbpc.CommandExecFailed: print >>sys.stderr, "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 _.orig.tar.gz 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(version): return "Imported Upstream version %s" % version def import_upstream_tree(repo, src_dir, version, filters, verbose): """import the upstream tree to the current branch""" try: if repo.replace_tree(src_dir, filters, verbose=True): gbpc.GitCommitAll(verbose=verbose)(msg=upstream_import_commit_msg(version)) else: raise GbpNothingImported except gbpc.CommandExecFailed: raise GbpError, "Import of upstream version %s failed." % version def fast_import_upstream_tree(repo, tarball, version, options): """import the upstream tree to the current branch using git fast-import""" try: compr = tarball.split('.')[-1] if not tarfile.is_tarfile(tarball): raise GbpError, "'%s' not a tarball" % tarball tar = tarfile.open(tarball, "r:%s" % compr) now = "%d %s" % (time.time(), time.strftime("%z")) fastimport = FastImport() name, email = repo.get_author_info() if options.verbose: print "Starting fastimport of %s" % tarball fastimport.start_commit(options.upstream_branch, name, email, now, upstream_import_commit_msg(version)) fastimport.do_deleteall() for item in tar: if item.isfile(): if item.mode & 0100: fastimport.add_executable(item.name, tar.extractfile(item.name), item.size) else: fastimport.add_file(item.name, tar.extractfile(item.name), item.size) elif item.isdir(): continue # handled by git transparently elif item.issym(): fastimport.add_symlink(item.name, item.linkname) # if tarinfo.isextended() not implemented: elif item.type in ( "x", "g", "X" ): if options.verbose: print "Skipping %s of type '%s'" % (item.name, item.type) continue else: raise GbpError, "'%s' is not a regular file (%s) - don't use fastimport." % (item.name, item.type) except gbpc.CommandExecFailed: raise GbpError, "Fastimport of upstream version %s failed." % version finally: tar.close() fastimport.close() if options.verbose: print "FastImport done." def turn_off_fastimport(options, msg): if options.fast_import: print >>sys.stderr, msg print >>sys.stderr, "Turning off fastimport." options.fast_import = False def main(argv): ret = 0 tmpdir = '' pristine_orig = None parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='', usage='%prog [-u version] /path/to/upstream-version.tar.gz') parser.add_option("-u", "--upstream-version", dest="version", help="Upstream Version") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="verbose command execution") parser.add_option("--fast-import", action="store_true", dest="fast_import", default=False, help="use 'git fastimport' (experimental)") parser.add_config_file_option(option_name="debian-branch", dest="debian_branch") parser.add_config_file_option(option_name="upstream-branch", dest="upstream_branch") parser.add_option("--no-merge", dest='merge', action="store_false", default=True, help="after import dont do any merging to another branch") parser.add_config_file_option(option_name="no-dch", dest='no_dch', help="don't call dch after the import", action="store_true") parser.add_boolean_config_file_option(option_name="sign-tags", dest="sign_tags") parser.add_config_file_option(option_name="keyid", dest="keyid") parser.add_config_file_option(option_name="upstream-tag", dest="upstream_tag") parser.add_config_file_option(option_name="filter", dest="filters", action="append") parser.add_boolean_config_file_option(option_name="pristine-tar", dest="pristine_tar") parser.add_boolean_config_file_option(option_name="filter-pristine-tar", dest="filter_pristine_tar") (options, args) = parser.parse_args(argv[1:]) if options.verbose: gbpc.Command.verbose = True if options.filters: turn_off_fastimport(options, "Import filters currently not supported with fastimport.") try: if len(args) != 1: parser.print_help() raise GbpError else: archive = args[0] 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() if initial_branch: is_empty = False else: is_empty = True turn_off_fastimport(options, "Fast importing into empty archives not yet supported.") if not repo.has_branch(options.upstream_branch) and not is_empty: print >>sys.stderr, """ Repository does not have branch '%s' for upstream sources. If there is none see file:///usr/share/doc/git-buildpackage/manual-html/gbp.import.html#GBP.IMPORT.CONVERT on howto create it otherwise use --upstream-branch to specify it. """ % options.upstream_branch raise GbpError if options.version: version = options.version else: version = guess_upstream_version(archive) if version: print "Upstream version is %s" % version else: print >>sys.stderr, "Cannot determine upstream version from %s - use -u " % archive parser.print_help() raise GbpError (clean, out) = repo.is_clean() if not clean and not is_empty: print >>sys.stderr, "Repository has uncommitted changes, commit these first: " raise GbpError, out if os.path.isdir(archive): orig_dir = archive turn_off_fastimport(options, "Fastimport only supported for tar achives.") else: if not options.fast_import: tmpdir = tempfile.mkdtemp(dir='../') unpack_orig(archive, tmpdir, options.filters) if options.verbose: print "Unpacked %s to '%s'" % (archive , tmpdir) orig_dir = tar_toplevel(tmpdir) if options.pristine_tar and options.filter_pristine_tar and len(options.filters) > 0: if options.verbose: print "Filter pristine-tar: repacking %s from '%s'" % (archive, tmpdir) archive = os.path.join( os.path.dirname(archive), os.path.basename(archive).replace(".tar", ".gbp.tar") ) repack_orig(archive, tmpdir, os.path.basename(orig_dir)) try: cp = parse_changelog('debian/changelog') pristine_orig = symlink_orig(archive, cp['Source'], version) except NoChangelogError: print "Warning: Can't symlink orig.tar.gz due to missing debian/changelog" pristine_orig = archive try: filter_msg = ["", " (filtering out %s)" % options.filters][len(options.filters) > 0] if is_empty: print "Initial import of '%s' %s..." % (archive, filter_msg) else: print "Importing '%s' to branch '%s'%s..." % (archive, options.upstream_branch, filter_msg) if not options.fast_import: repo.set_branch(options.upstream_branch) if options.fast_import: fast_import_upstream_tree(repo, pristine_orig, version, options) else: import_upstream_tree(repo, orig_dir, version, options.filters, verbose=not is_empty) if options.pristine_tar: upstream_branch = [ options.upstream_branch, 'master' ][is_empty] if pristine_orig: gbpc.PristineTar().commit(pristine_orig, 'refs/heads/%s' % upstream_branch) else: print >>sys.stderr, "Warning: '%s' not an archive, skipping pristine-tar" % archive tag = build_tag(options.upstream_tag, version) gbpc.GitTag(options.sign_tags, options.keyid)(tag, msg="Upstream version %s" % version, commit=[None, options.upstream_branch][options.fast_import]) if is_empty: gbpc.GitBranch()(options.upstream_branch) elif options.merge: print "Merging to '%s'" % options.debian_branch repo.set_branch(options.debian_branch) try: gbpc.GitMerge(tag)() except gbpc.CommandExecFailed: raise GbpError, """Merge failed, please resolve and run "dch -v %s-1".""" % version if not options.no_dch: epoch = '' if os.access('debian/changelog', os.R_OK): cp = parse_changelog('debian/changelog') if has_epoch(cp): epoch = '%s:' % cp['Epoch'] gbpc.Dch("%s%s-1" % (epoch, version), 'New Upstream Version')() except gbpc.CommandExecFailed: raise GbpError, "Import of %s failed" % archive except GbpNothingImported, err: print >>sys.stderr, err repo.set_branch(initial_branch) ret = 1 except GbpError, err: if len(err.__str__()): print >>sys.stderr, err ret = 1 if tmpdir: cleanup_tmp_tree(tmpdir) if not ret: print "Succesfully merged version %s of %s into ." % (version, archive) 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\:·: