From cbacdfb40ca35633da06e9e05497ac0fb56cc4f9 Mon Sep 17 00:00:00 2001 From: Guido Günther Date: Thu, 3 Aug 2017 22:56:57 -0300 Subject: push: new command to push changes in one go Closes: #733639 --- debian/git-buildpackage.install | 1 + debian/git-buildpackage.manpages | 1 + docs/Makefile | 1 + docs/common.ent | 1 + docs/man.gbp-push.sgml | 11 +++ docs/manpages/gbp-push.sgml | 174 +++++++++++++++++++++++++++++++++++++++ docs/manpages/gbp.sgml | 1 + docs/manpages/manpages.ent | 1 + docs/manual.sgml | 1 + gbp/deb/git.py | 9 ++ gbp/scripts/push.py | 169 +++++++++++++++++++++++++++++++++++++ packaging/git-buildpackage.spec | 2 + tests/component/deb/test_push.py | 109 ++++++++++++++++++++++++ 13 files changed, 481 insertions(+) create mode 100644 docs/man.gbp-push.sgml create mode 100644 docs/manpages/gbp-push.sgml create mode 100755 gbp/scripts/push.py create mode 100644 tests/component/deb/test_push.py diff --git a/debian/git-buildpackage.install b/debian/git-buildpackage.install index e99dcec8..0989806c 100644 --- a/debian/git-buildpackage.install +++ b/debian/git-buildpackage.install @@ -28,6 +28,7 @@ usr/lib/python3.?/dist-packages/gbp/scripts/__init__.py usr/lib/python3/dist-pac usr/lib/python3.?/dist-packages/gbp/scripts/pq.py usr/lib/python3/dist-packages/gbp/scripts/ usr/lib/python3.?/dist-packages/gbp/scripts/pristine_tar.py usr/lib/python3/dist-packages/gbp/scripts/ usr/lib/python3.?/dist-packages/gbp/scripts/pull.py usr/lib/python3/dist-packages/gbp/scripts/ +usr/lib/python3.?/dist-packages/gbp/scripts/push.py usr/lib/python3/dist-packages/gbp/scripts/ usr/lib/python3.?/dist-packages/gbp/scripts/supercommand.py usr/lib/python3/dist-packages/gbp/scripts/ usr/lib/python3.?/dist-packages/gbp/tmpfile.py usr/lib/python3/dist-packages/gbp/ usr/lib/python3.?/dist-packages/gbp/tristate.py usr/lib/python3/dist-packages/gbp/ diff --git a/debian/git-buildpackage.manpages b/debian/git-buildpackage.manpages index 4f58db69..73ea6b1f 100644 --- a/debian/git-buildpackage.manpages +++ b/debian/git-buildpackage.manpages @@ -12,4 +12,5 @@ docs/gbp-import-orig.1 docs/gbp-pq.1 docs/gbp-pristine-tar.1 docs/gbp-pull.1 +docs/gbp-push.1 docs/git-pbuilder.1 diff --git a/docs/Makefile b/docs/Makefile index 28a05393..6ad326a3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -14,6 +14,7 @@ MAN1S = \ gbp-pq \ gbp-pristine-tar \ gbp-pull \ + gbp-push \ gbp-buildpackage-rpm \ gbp-import-srpm \ gbp-pq-rpm \ diff --git a/docs/common.ent b/docs/common.ent index 7ff2ef94..ad882b54 100644 --- a/docs/common.ent +++ b/docs/common.ent @@ -19,6 +19,7 @@ gbp pq "> gbp pq "> gbp pq "> + gbp push"> gbp pristine-tar"> gbp create-remote-repo"> git-pbuilder"> diff --git a/docs/man.gbp-push.sgml b/docs/man.gbp-push.sgml new file mode 100644 index 00000000..430f722f --- /dev/null +++ b/docs/man.gbp-push.sgml @@ -0,0 +1,11 @@ + + %COMMON; + + %MANPAGES; +]> + + +git-buildpackage Manual +&man.gbp.push; + diff --git a/docs/manpages/gbp-push.sgml b/docs/manpages/gbp-push.sgml new file mode 100644 index 00000000..dfddcb3d --- /dev/null +++ b/docs/manpages/gbp-push.sgml @@ -0,0 +1,174 @@ + + +
+ &dhemail; +
+ + &dhfirstname; + &dhsurname; + +
+ + gbp-push + &dhsection; + + + gbp-push + + Push &debian; packaging changes to a &git; remote + + + + &gbp-push; + + &man.common.options.synopsis; + + branch_name + branch_name + tag-format + tag-format + + + repository + + + + DESCRIPTION + + &gbp-push; pushes your local changes to a remote repository. It + is best run after uploading a &debian; package to the archive to + update you &debian-branch;, &upstream-branch;, &pristine-tar; + branch and corresponding tags. It will in order + + + + + Verify that it is being executed from the &debian-branch;. + + + + + Determine the debian tag from debian/changelog + and add it the list of things to push if the changelog does not indicate + an unreleased package. + + + + + Determine the upstream tag from debian/changelog + and add it to the list of things to push if it's not a native package. + + + + + Determine if the tags correspond to the branch tips of the corresponding + upstream and debian branches. If so, these branches will be added to the list + of things to push. If not the changes will only be pushed up to the commit + that is referenced by the corresponding tag. + + + + + Determine if the pristine-tar branch needs to be pushed and if so adds it + to the list of things to push. + + + + + Finally, if not in dry-run mode, pushes the above changes to the remote side. + + + + + If a remote is given on the command line + the changes are pushed to the given remote repository. By + default it will push to the current branchs remote and fall + back to origin. + + + + OPTIONS + + &man.common.options.description; + + + + + Don't fail if the &debian-branch; does not match the currently checked out + branach and push the current branch instead. + + + + + =branch_name + + + The branch in the Git repository the Debian package is being + developed on. + + + + + TAG-FORMAT + + + Use this tag format when looking for tags corresponding to a &debian; + version. Default is debian/%(version)s. + + + + + =branch_name + + + The branch in the &git; repository the upstream sources are put + onto. + + + + + TAG-FORMAT + + + Use this tag format when looking for tags of upstream + versions. Default + is upstream/%(version)s. + + + + + + + Whether to update the pristine-tar branch too. + + + + + + + Pass the to &gitcmd; . So don't + push anything, only check if things are pushable. + + + + + + + &man.gbp.config-files; + + + SEE ALSO + + + + + AUTHOR + + &dhusername; &dhemail; + + +
diff --git a/docs/manpages/gbp.sgml b/docs/manpages/gbp.sgml index 5425475d..eff89f25 100644 --- a/docs/manpages/gbp.sgml +++ b/docs/manpages/gbp.sgml @@ -164,6 +164,7 @@ git-pbuilder diff --git a/docs/manpages/manpages.ent b/docs/manpages/manpages.ent index f7cf17c5..9415946a 100644 --- a/docs/manpages/manpages.ent +++ b/docs/manpages/manpages.ent @@ -8,6 +8,7 @@ + diff --git a/docs/manual.sgml b/docs/manual.sgml index 4b851fed..35809ada 100644 --- a/docs/manual.sgml +++ b/docs/manual.sgml @@ -37,6 +37,7 @@ &man.gbp.clone; &man.gbp.config; &man.gbp.pull; + &man.gbp.push; &man.gbp.pristine.tar; &man.gbp.exportorig; &man.gbp.pq; diff --git a/gbp/deb/git.py b/gbp/deb/git.py index 9bada975..1f12f46f 100644 --- a/gbp/deb/git.py +++ b/gbp/deb/git.py @@ -312,6 +312,15 @@ class DebianGitRepository(PkgGitRepository): except CommandExecFailed as e: raise GitRepositoryError(str(e)) + def get_pristine_tar_commit(self, source, component=None): + """ + Get the pristine-tar commit for the given source package's latest version. + """ + comp = '-%s' % component if component else '' + return self.pristine_tar.get_commit('%s_%s.orig%s.tar.*' % (source.sourcepkg, + source.upstream_version, + comp)) + def create_upstream_tarball_via_pristine_tar(self, source, output_dir, comp, component=None): output = source.upstream_tarball_name(comp.type, component=component) try: diff --git a/gbp/scripts/push.py b/gbp/scripts/push.py new file mode 100755 index 00000000..d74417d1 --- /dev/null +++ b/gbp/scripts/push.py @@ -0,0 +1,169 @@ +#!/usr/bin/python3 +# vim: set fileencoding=utf-8 : +# +# (C) 2017 Guido Günther +# 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, please see +# +"""Push your changes to a remote""" + +import os +import sys + +import gbp.log +from gbp.config import GbpOptionParserDebian +from gbp.deb.git import DebianGitRepository, GitRepositoryError +from gbp.deb.source import DebianSource +from gbp.errors import GbpError +from gbp.scripts.common import ExitCodes + + +def build_parser(name): + try: + parser = GbpOptionParserDebian(command=os.path.basename(name), + usage='%prog [options]') + except GbpError as err: + gbp.log.err(err) + return None + + parser.add_option("-d", "--dry-run", dest="dryrun", default=False, + action="store_true", help="dry run, don't push.") + parser.add_config_file_option(option_name="upstream-branch", + dest="upstream_branch") + parser.add_config_file_option(option_name="upstream-tag", + dest="upstream_tag") + parser.add_config_file_option(option_name="debian-branch", + dest="debian_branch") + parser.add_config_file_option(option_name="debian-tag", + dest="debian_tag") + parser.add_boolean_config_file_option(option_name="pristine-tar", + dest="pristine_tar") + parser.add_boolean_config_file_option(option_name="ignore-branch", dest="ignore_branch") + parser.add_config_file_option(option_name="color", dest="color", type='tristate') + parser.add_config_file_option(option_name="color-scheme", + dest="color_scheme") + parser.add_option("--verbose", action="store_true", dest="verbose", + default=False, help="verbose command execution") + return parser + + +def parse_args(argv): + parser = build_parser(argv[0]) + if not parser: + return None, None + return parser.parse_args(argv) + + +def do_push(repo, dests, to_push, dry_run): + verb = "Dry-run: Pushing" if dry_run else "Pushing" + for dest in dests: + for tag in to_push['tags']: + gbp.log.info("%s %s to %s" % (verb, tag, dest)) + repo.push_tag(dest, tag, dry_run=dry_run) + for k, v in to_push['refs'].items(): + gbp.log.info("%s %s to %s:%s" % (verb, v, dest, k)) + repo.push(dest, v, k, dry_run=dry_run) + + +def get_push_src(repo, ref, tag): + """ + Determine wether we can push the ref + + If the ref is further ahead than the tag + we only want to push up to this tag. + """ + commit = repo.rev_parse("%s^{commit}" % tag) + if repo.rev_parse(ref) == commit: + return ref + else: + return commit + + +def get_remote(repo, branch): + remote_branch = repo.get_merge_branch(branch) + return remote_branch.split('/')[0] if remote_branch else 'origin' + + +def main(argv): + retval = 1 + branch = None + dest = None + to_push = { + 'refs': {}, + 'tags': [], + } + + (options, args) = parse_args(argv) + if not options: + return ExitCodes.parse_error + + if len(args) > 2: + gbp.log.err("Only a single remote repository can be given") + elif len(args) == 2: + dest = args[1] + + gbp.log.setup(options.color, options.verbose, options.color_scheme) + try: + repo = DebianGitRepository(os.path.curdir, toplevel=False) + except GitRepositoryError: + gbp.log.err("%s is not inside a git repository" % (os.path.abspath('.'))) + return 1 + + try: + source = DebianSource(repo.path) + branch = repo.get_branch() + if 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.") + + if not dest: + dest = get_remote(repo, branch) + + dtag = repo.version_to_tag(options.debian_tag, source.version) + if repo.has_tag(dtag): + to_push['tags'].append(dtag) + if source.is_releasable() and branch: + ref = 'refs/heads/%s' % branch + to_push['refs'][ref] = get_push_src(repo, ref, dtag) + + if not source.is_native(): + utag = repo.version_to_tag(options.upstream_tag, + source.upstream_version) + if repo.has_tag(utag): + to_push['tags'].append(utag) + ref = 'refs/heads/%s' % options.upstream_branch + to_push['refs'][ref] = get_push_src(repo, ref, utag) + + if options.pristine_tar: + commit = repo.get_pristine_tar_commit(source) + if commit: + ref = 'refs/heads/pristine-tar' + to_push['refs'][ref] = get_push_src(repo, ref, commit) + + do_push(repo, + [dest], + to_push, + dry_run=options.dryrun) + retval = 0 + except (GbpError, GitRepositoryError) as err: + if str(err): + gbp.log.err(err) + + 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/packaging/git-buildpackage.spec b/packaging/git-buildpackage.spec index 751e558d..045688c5 100644 --- a/packaging/git-buildpackage.spec +++ b/packaging/git-buildpackage.spec @@ -225,6 +225,7 @@ done %{python_sitelib}/gbp/scripts/config.py* %{python_sitelib}/gbp/scripts/pristine_tar.py* %{python_sitelib}/gbp/scripts/pull.py* +%{python_sitelib}/gbp/scripts/push.py* %{python_sitelib}/gbp/scripts/supercommand.py* %{python_sitelib}/gbp/scripts/common/*.py* %{python_sitelib}/gbp/git/*.py* @@ -236,6 +237,7 @@ done %{_mandir}/man1/gbp-config.1* %{_mandir}/man1/gbp-pristine-tar.1* %{_mandir}/man1/gbp-pull.1* +%{_mandir}/man1/gbp-push.1* %{_mandir}/man5/*.5* %endif diff --git a/tests/component/deb/test_push.py b/tests/component/deb/test_push.py new file mode 100644 index 00000000..641f63c6 --- /dev/null +++ b/tests/component/deb/test_push.py @@ -0,0 +1,109 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2017 Guido Günther +# +# 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, please see +# + +import subprocess + +from tests.component import ComponentTestBase + +from tests.component.deb.fixtures import RepoFixtures + +from gbp.git import GitRepository +from gbp.scripts.push import main as push + + +class TestPush(ComponentTestBase): + """Test pushing to remote repos""" + + def setUp(self): + ComponentTestBase.setUp(self) + self.target = GitRepository.create('target', bare=True) + + @RepoFixtures.native() + def test_push_native(self, repo): + repo.add_remote_repo('origin', self.target.path) + self.assertEquals(push(['argv0']), 0) + self._check_repo_state(self.target, 'master', + ['master'], + tags=['debian/0.4.14']) + self.assertEquals(repo.head, self.target.head) + + @RepoFixtures.quilt30() + def test_push_upstream(self, repo): + repo.add_remote_repo('origin', self.target.path) + self.assertEquals(push(['argv0']), 0) + self._check_repo_state(self.target, 'master', + ['master', 'upstream'], + tags=['debian/2.8-1', 'upstream/2.8']) + self.assertEquals(repo.head, self.target.head) + + @RepoFixtures.quilt30(opts=['--pristine-tar']) + def test_push_pristine_tar(self, repo): + repo.add_remote_repo('origin', self.target.path) + self.assertEquals(push(['argv0', '--pristine-tar']), 0) + self._check_repo_state(self.target, 'master', + ['master', 'upstream', 'pristine-tar'], + tags=['debian/2.8-1', 'upstream/2.8']) + self.assertEquals(repo.head, self.target.head) + + @RepoFixtures.native() + def test_push_tag_ne_branch(self, repo): + repo.add_remote_repo('origin', self.target.path) + self.add_file(repo, "foo.txt", "foo") + self.assertEquals(push(['argv0']), 0) + self._check_repo_state(self.target, 'master', + ['master'], + tags=['debian/0.4.14']) + self.assertEquals(repo.rev_parse("HEAD^"), + self.target.head) + + @RepoFixtures.quilt30() + def test_not_debian_branch(self, repo): + repo.add_remote_repo('origin', self.target.path) + repo.create_branch("foo") + repo.set_branch("foo") + self.assertEquals(push(['argv0']), 1) + self._check_log(-2, ".*You are not on branch 'master' but on 'foo'") + + @RepoFixtures.quilt30() + def test_dont_push_unreleased(self, repo): + repo.add_remote_repo('origin', self.target.path) + subprocess.check_call(["debchange", "-i", "foo"]) + self.assertEquals(push(['argv0']), 0) + self._check_repo_state(self.target, None, + ['upstream'], + tags=['upstream/2.8']) + + @RepoFixtures.quilt30() + def test_push_not_origin(self, repo): + repo.add_remote_repo('notorigin', self.target.path) + self.assertEquals(push(['argv0', 'notorigin']), 0) + self._check_repo_state(self.target, 'master', + ['master', 'upstream'], + tags=['debian/2.8-1', 'upstream/2.8']) + self.assertEquals(repo.head, self.target.head) + + @RepoFixtures.quilt30() + def test_push_not_origin_detect(self, repo): + repo.add_remote_repo('notorigin', self.target.path) + repo.set_config("branch.master.remote", "notorigin") + repo.set_config("branch.master.merge", "refs/heads/master") + self.assertEquals(push(['argv0']), 0) + self._check_repo_state(self.target, 'master', + ['master', 'upstream'], + tags=['debian/2.8-1', 'upstream/2.8']) + self.assertEquals(repo.head, self.target.head) -- cgit v1.2.3