summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2018-08-19 07:29:47 +0100
committerGuido Günther <agx@sigxcpu.org>2018-08-19 07:29:47 +0100
commit717bbf7a3597366690d62184259ebf79aa890927 (patch)
tree6910f7897f3d3de2e953edafcfabae63bf3239ef
git-buildpackage (0.9.10) unstable; urgency=medium
[ Ken Dreyer ] * [f0b1bbe] deb.git: fix duplicate "tarball" in docstring. Fix the docstring for create_pristine_tar_commits() * [29b9f2c] deb.git: fix spelling of "described" in docstring. Fix the docstring for _sanitize_version() and _unsanitize_version() [ Guido Günther ] * [62e0102] push: Allow to skip upstream and debian branch and tag push. Push of tag and or branch can be skipped by setting --{upstream,debian}-{branch,tag}='' . (Closes: #899234) * [28a950a] tests: Check help output of tag and push too * [a287bf6] config: allow to override default values via add_config_file_option * [b8221b8] pull: Check that repo is clean before fetching anything * [6dda2da] pull: allow to set up branch tracking for missing branches. If the remote branch does not exist at all that's currently not fatal. (Closes: #882187) * [d69006d] Depend on sensible-utils. gbp-dch uses sensible-editor. [ Chris Lamb ] * [6c30ac9] import-{dsc,orig}: Make --download deprecation text more useful. Point to the manpages for usage examples. (Closes: #900606) [ Guus Sliepen ] * [48ef0ec] changelog: try iso8859-1 when utf-8 fails. Fall back to iso8859-1 when opening the changelog. Helps when importing old versions. (Closes: #900841) [ Carsten Schoenert ] * [50b9223] create_remote_repo: import urllib.parse urllib.parse did not get imported in packaegs without changelog. [ Iain Lane ] * [5fedb2b] Ignore merge commits when looking at the pristine-tar branch. (Closes: #906331) [dgit import package git-buildpackage 0.9.10]
-rw-r--r--.coverage1
-rw-r--r--.coveragerc2
-rw-r--r--.pydoctor.cfg4
-rw-r--r--.travis.yml21
-rw-r--r--COPYING339
-rw-r--r--HACKING71
-rw-r--r--Makefile31
-rw-r--r--README.md28
-rw-r--r--TODO1
-rwxr-xr-xbin/gbp-builder-mock86
-rwxr-xr-xbin/git-pbuilder611
-rw-r--r--coverage.xml9029
-rw-r--r--debian/NEWS82
-rw-r--r--debian/bug-presubj3
-rw-r--r--debian/changelog4767
-rw-r--r--debian/compat1
-rw-r--r--debian/control93
-rw-r--r--debian/copyright30
-rw-r--r--debian/doc-base11
-rw-r--r--debian/docs3
-rw-r--r--debian/examples1
-rw-r--r--debian/gbp.completion189
-rw-r--r--debian/gbp.conf6
-rw-r--r--debian/git-buildpackage-rpm.install6
-rw-r--r--debian/git-buildpackage-rpm.manpages4
-rw-r--r--debian/git-buildpackage.bash-completion1
-rw-r--r--debian/git-buildpackage.install37
-rw-r--r--debian/git-buildpackage.manpages17
-rw-r--r--debian/git-buildpackage.postinst42
-rw-r--r--debian/git-buildpackage.postrm43
-rw-r--r--debian/git-buildpackage.preinst52
-rw-r--r--debian/git-buildpackage.zsh-completion488
-rw-r--r--debian/links2
-rw-r--r--debian/pk412
-rwxr-xr-xdebian/rules50
-rw-r--r--debian/source/format1
-rw-r--r--debian/tests/control4
-rwxr-xr-xdebian/tests/smoke-rpm23
-rw-r--r--dev_requirements.txt6
-rw-r--r--docs/Makefile87
-rw-r--r--docs/chapters/building.xml271
-rw-r--r--docs/chapters/cfgfile.xml137
-rw-r--r--docs/chapters/chapters.ent8
-rw-r--r--docs/chapters/import.xml479
-rw-r--r--docs/chapters/intro.xml134
-rw-r--r--docs/chapters/patches.xml387
-rw-r--r--docs/chapters/releases.xml224
-rw-r--r--docs/chapters/special.xml250
-rw-r--r--docs/common.ent79
-rw-r--r--docs/copyright.xml22
-rw-r--r--docs/gbp.css132
-rw-r--r--docs/gbp.svg433
-rw-r--r--docs/images/pq-applied.pngbin0 -> 150493 bytes
-rw-r--r--docs/images/pq-export.pngbin0 -> 144857 bytes
-rw-r--r--docs/images/pq-rebase.pngbin0 -> 142065 bytes
-rw-r--r--docs/images/pq-time-machine.pngbin0 -> 157733 bytes
-rw-r--r--docs/images/pq-unapplied.pngbin0 -> 139525 bytes
-rw-r--r--docs/man.gbp.xml33
-rw-r--r--docs/manpages/gbp-buildpackage-rpm.xml678
-rw-r--r--docs/manpages/gbp-buildpackage.xml838
-rw-r--r--docs/manpages/gbp-clone.xml202
-rw-r--r--docs/manpages/gbp-config.xml100
-rw-r--r--docs/manpages/gbp-create-remote-repo.xml162
-rw-r--r--docs/manpages/gbp-dch.xml650
-rw-r--r--docs/manpages/gbp-export-orig.xml244
-rw-r--r--docs/manpages/gbp-import-dsc.xml310
-rw-r--r--docs/manpages/gbp-import-dscs.xml96
-rw-r--r--docs/manpages/gbp-import-orig.xml403
-rw-r--r--docs/manpages/gbp-import-srpm.xml286
-rw-r--r--docs/manpages/gbp-pq-rpm.xml275
-rw-r--r--docs/manpages/gbp-pq.xml357
-rw-r--r--docs/manpages/gbp-pristine-tar.xml112
-rw-r--r--docs/manpages/gbp-pull.xml178
-rw-r--r--docs/manpages/gbp-push.xml176
-rw-r--r--docs/manpages/gbp-rpm-ch.xml355
-rw-r--r--docs/manpages/gbp-tag.xml197
-rw-r--r--docs/manpages/gbp.conf.xml327
-rw-r--r--docs/manpages/gbp.xml211
-rw-r--r--docs/manpages/man.common-options.ent66
-rw-r--r--docs/manpages/man.conffiles.xml4
-rw-r--r--docs/manpages/man.seealso.xml14
-rw-r--r--docs/manpages/manpages.ent25
-rw-r--r--docs/manual.xml58
-rw-r--r--examples/README.source29
-rwxr-xr-xexamples/gbp-add-patch120
-rwxr-xr-xexamples/gbp-configure-unpatched-source54
-rwxr-xr-xexamples/gbp-svn-tag19
-rwxr-xr-xexamples/gbp-try-ff49
-rwxr-xr-xexamples/jenkins-scratchbuilder41
-rw-r--r--examples/wrap_cl.py15
-rwxr-xr-xexamples/zeitgeist-git.py104
-rw-r--r--gbp.conf141
-rw-r--r--gbp.egg-info/PKG-INFO44
-rw-r--r--gbp.egg-info/SOURCES.txt85
-rw-r--r--gbp.egg-info/dependency_links.txt1
-rw-r--r--gbp.egg-info/entry_points.txt3
-rw-r--r--gbp.egg-info/requires.txt1
-rw-r--r--gbp.egg-info/top_level.txt1
-rwxr-xr-xgbp/__init__.py19
-rw-r--r--gbp/command_wrappers.py366
-rw-r--r--gbp/config.py872
-rw-r--r--gbp/dch.py141
-rw-r--r--gbp/deb/__init__.py119
-rw-r--r--gbp/deb/changelog.py368
-rw-r--r--gbp/deb/control.py81
-rw-r--r--gbp/deb/dscfile.py146
-rw-r--r--gbp/deb/format.py118
-rw-r--r--gbp/deb/git.py379
-rw-r--r--gbp/deb/policy.py98
-rw-r--r--gbp/deb/pristinetar.py67
-rw-r--r--gbp/deb/rollbackgit.py134
-rw-r--r--gbp/deb/source.py163
-rw-r--r--gbp/deb/upstreamsource.py56
-rw-r--r--gbp/deb/uscan.py199
-rw-r--r--gbp/errors.py24
-rw-r--r--gbp/format.py66
-rw-r--r--gbp/git/__init__.py53
-rw-r--r--gbp/git/args.py108
-rw-r--r--gbp/git/commit.py45
-rw-r--r--gbp/git/errors.py22
-rw-r--r--gbp/git/fastimport.py144
-rw-r--r--gbp/git/modifier.py174
-rw-r--r--gbp/git/repository.py2098
-rw-r--r--gbp/git/vfs.py78
-rw-r--r--gbp/log.py178
-rw-r--r--gbp/notifications.py73
-rw-r--r--gbp/patch_series.py378
-rw-r--r--gbp/paths.py26
-rw-r--r--gbp/pkg/__init__.py24
-rw-r--r--gbp/pkg/archive.py80
-rw-r--r--gbp/pkg/compressor.py75
-rw-r--r--gbp/pkg/git.py122
-rw-r--r--gbp/pkg/pkgpolicy.py167
-rw-r--r--gbp/pkg/pristinetar.py109
-rw-r--r--gbp/pkg/upstreamsource.py193
-rw-r--r--gbp/rpm/__init__.py1009
-rw-r--r--gbp/rpm/changelog.py261
-rw-r--r--gbp/rpm/git.py108
-rw-r--r--gbp/rpm/lib_rpm.py46
-rw-r--r--gbp/rpm/linkedlist.py216
-rw-r--r--gbp/rpm/policy.py200
-rw-r--r--gbp/scripts/__init__.py17
-rwxr-xr-xgbp/scripts/buildpackage.py598
-rw-r--r--gbp/scripts/buildpackage_rpm.py657
-rwxr-xr-xgbp/scripts/clone.py229
-rw-r--r--gbp/scripts/common/__init__.py69
-rw-r--r--gbp/scripts/common/buildpackage.py102
-rw-r--r--gbp/scripts/common/hook.py39
-rw-r--r--gbp/scripts/common/import_orig.py179
-rw-r--r--gbp/scripts/common/pq.py353
-rw-r--r--gbp/scripts/common/repo_setup.py30
-rwxr-xr-xgbp/scripts/config.py142
-rw-r--r--gbp/scripts/create_remote_repo.py406
-rw-r--r--gbp/scripts/dch.py613
-rwxr-xr-xgbp/scripts/export_orig.py366
-rw-r--r--gbp/scripts/import_dsc.py557
-rw-r--r--gbp/scripts/import_dscs.py207
-rw-r--r--gbp/scripts/import_orig.py538
-rwxr-xr-xgbp/scripts/import_srpm.py511
-rwxr-xr-xgbp/scripts/pq.py513
-rwxr-xr-xgbp/scripts/pq_rpm.py490
-rw-r--r--gbp/scripts/pristine_tar.py122
-rwxr-xr-xgbp/scripts/pull.py240
-rwxr-xr-xgbp/scripts/push.py189
-rw-r--r--gbp/scripts/rpm_ch.py458
-rw-r--r--gbp/scripts/supercommand.py151
-rwxr-xr-xgbp/scripts/tag.py163
-rw-r--r--gbp/tmpfile.py55
-rw-r--r--gbp/tristate.py107
-rw-r--r--nosetests.xml2312
-rw-r--r--packaging/git-buildpackage.spec265
-rwxr-xr-xpackaging/run-in-docker128
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg13
-rwxr-xr-xsetup.py85
-rw-r--r--tests/01_test_help.py37
-rw-r--r--tests/02_test_upstream_source_tar_unpack.py81
-rw-r--r--tests/03_test_dch_guess_version.py136
-rw-r--r--tests/04_test_submodules.py193
-rw-r--r--tests/05_test_detection.py138
-rw-r--r--tests/06_test_upstream_source.py96
-rw-r--r--tests/07_test_fastimport.py66
-rw-r--r--tests/08_test_patch.py54
-rw-r--r--tests/08_test_patch_data/dep3-iso8859-1.patch15
-rw-r--r--tests/08_test_patch_data/patch1.diff7
-rw-r--r--tests/09_test_git_repository.py81
-rw-r--r--tests/10_test_get_upstream_tree.py76
-rw-r--r--tests/11_test_dch_main.py413
-rw-r--r--tests/12_test_deb.py177
-rw-r--r--tests/13_test_gbp_pq.py437
-rw-r--r--tests/14_test_gbp_import_dscs.py102
-rw-r--r--tests/15_test_DebianSource.py143
-rw-r--r--tests/16_test_supercommand.py59
-rw-r--r--tests/17_test_dch_guess_documented_commit.py73
-rw-r--r--tests/18_test_Config.py134
-rw-r--r--tests/19_test_gbp_scripts_config.py111
-rw-r--r--tests/20_test_rpm.py422
-rw-r--r--tests/21_test_command_wrappers.py80
-rw-r--r--tests/22_test_gbp_buildpackage.py126
-rw-r--r--tests/23_test_dch_extract_bts_cmds.py55
-rw-r--r--tests/24_test_gbp_import_orig.py90
-rw-r--r--tests/25_test_broken_gbp_conf.py58
-rw-r--r--tests/26_test_dch_extract_thanks.py40
-rw-r--r--tests/27_test_create_remote_repo.py47
-rw-r--r--tests/28_test_gbp_git_repository_commit_dir.py42
-rw-r--r--tests/29_test_gbp_clone.py34
-rw-r--r--tests/30_test_deb_changelog.py67
-rw-r--r--tests/30_test_rpm_changelog.py226
-rw-r--r--tests/31_test_uscan.py44
-rw-r--r--tests/__init__.py20
-rw-r--r--tests/component/__init__.py270
-rw-r--r--tests/component/deb/__init__.py31
-rw-r--r--tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.diff.gzbin0 -> 14104 bytes
-rw-r--r--tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.dsc20
-rw-r--r--tests/component/deb/data/dsc-1.0/hello-debhelper_2.6.orig.tar.gzbin0 -> 594257 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.debian.tar.gzbin0 -> 5980 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.dsc38
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig-foo.tar.gzbin0 -> 153 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig.tar.gzbin0 -> 697483 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.debian.tar.gzbin0 -> 5980 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.dsc38
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig-foo.tar.gzbin0 -> 177 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig.tar.gzbin0 -> 697483 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.debian.tar.gzbin0 -> 6074 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.dsc33
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.debian.tar.gzbin0 -> 5996 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.dsc33
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.6.orig.tar.gzbin0 -> 594257 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.debian.tar.gzbin0 -> 5980 bytes
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.dsc35
-rw-r--r--tests/component/deb/data/dsc-3.0/hello-debhelper_2.8.orig.tar.gzbin0 -> 697483 bytes
-rw-r--r--tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.dsc12
-rw-r--r--tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.tar.gzbin0 -> 33598 bytes
-rw-r--r--tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.dsc23
-rw-r--r--tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.tar.gzbin0 -> 34151 bytes
-rw-r--r--tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.dsc23
-rw-r--r--tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.tar.gzbin0 -> 34720 bytes
-rw-r--r--tests/component/deb/fixtures.py146
-rw-r--r--tests/component/deb/test_buildpackage.py305
-rw-r--r--tests/component/deb/test_clone.py84
-rw-r--r--tests/component/deb/test_dch.py72
-rw-r--r--tests/component/deb/test_export_orig.py113
-rw-r--r--tests/component/deb/test_import_dsc.py369
-rw-r--r--tests/component/deb/test_import_dscs.py46
-rw-r--r--tests/component/deb/test_import_orig.py355
-rw-r--r--tests/component/deb/test_pq.py188
-rw-r--r--tests/component/deb/test_pristine_tar.py71
-rw-r--r--tests/component/deb/test_pull.py106
-rw-r--r--tests/component/deb/test_push.py152
-rw-r--r--tests/component/deb/test_tag.py77
-rw-r--r--tests/component/rpm/__init__.py64
-rw-r--r--tests/component/rpm/data/README2
-rw-r--r--tests/component/rpm/data/gbp-test-1.0-1.other.src.rpmbin0 -> 3274 bytes
-rw-r--r--tests/component/rpm/data/gbp-test-1.0-1.src.rpmbin0 -> 3436 bytes
-rw-r--r--tests/component/rpm/data/gbp-test-1.1-1.src.rpmbin0 -> 3450 bytes
-rw-r--r--tests/component/rpm/data/gbp-test-1.1-2.src.rpmbin0 -> 4043 bytes
-rw-r--r--tests/component/rpm/data/gbp-test-native-1.0-1.src.rpmbin0 -> 2749 bytes
-rw-r--r--tests/component/rpm/data/gbp-test-native.data/Add-changelog-file.patch17
-rw-r--r--tests/component/rpm/data/gbp-test-native.data/Add-dummy-Makefile.patch14
-rw-r--r--tests/component/rpm/data/gbp-test-native.data/Add-dummy-files.patch22
-rw-r--r--tests/component/rpm/data/gbp-test-native.data/Add-gbp.conf.patch17
-rw-r--r--tests/component/rpm/data/gbp-test-native.data/Add-packaging-files.patch46
-rw-r--r--tests/component/rpm/data/gbp-test-native.data/Repository-initialization.patch0
-rw-r--r--tests/component/rpm/data/gbp-test-native.data/manifest.json81
-rw-r--r--tests/component/rpm/data/gbp-test-native2-2.0-0.src.rpmbin0 -> 2002 bytes
-rw-r--r--tests/component/rpm/data/gbp-test-native2.data/Add-gbp.conf.patch17
-rw-r--r--tests/component/rpm/data/gbp-test-native2.data/Add-packaging-files.patch56
-rw-r--r--tests/component/rpm/data/gbp-test-native2.data/Repository-initialization.patch0
-rw-r--r--tests/component/rpm/data/gbp-test-native2.data/manifest.json48
-rw-r--r--tests/component/rpm/data/gbp-test.data/Add-.gitignore.patch16
-rw-r--r--tests/component/rpm/data/gbp-test.data/Add-changelog.patch21
-rw-r--r--tests/component/rpm/data/gbp-test.data/Add-compressed-patches.patch64
-rw-r--r--tests/component/rpm/data/gbp-test.data/Add-dummy-Makefile.patch14
-rw-r--r--tests/component/rpm/data/gbp-test.data/Add-dummy-files.patch22
-rw-r--r--tests/component/rpm/data/gbp-test.data/Add-gbp.conf.patch19
-rw-r--r--tests/component/rpm/data/gbp-test.data/Add-initial-packaging-files.patch117
-rw-r--r--tests/component/rpm/data/gbp-test.data/Auto-import-file-s-from-branch-srcdata-gbp-test-master.patch21
-rw-r--r--tests/component/rpm/data/gbp-test.data/Create-a-forked-version.patch50
-rw-r--r--tests/component/rpm/data/gbp-test.data/Initial-commit.patch0
-rw-r--r--tests/component/rpm/data/gbp-test.data/Modify-README.patch14
-rw-r--r--tests/component/rpm/data/gbp-test.data/Repository-initialization.patch0
-rw-r--r--tests/component/rpm/data/gbp-test.data/Update-gbp.conf-to-rename-the-generated-source-package.patch15
-rw-r--r--tests/component/rpm/data/gbp-test.data/Version-bump-to-1.1.patch18
-rw-r--r--tests/component/rpm/data/gbp-test.data/manifest.json262
-rw-r--r--tests/component/rpm/data/gbp-test.data/my-bzip2.patch12
-rw-r--r--tests/component/rpm/data/gbp-test.data/my-gz.patch12
-rw-r--r--tests/component/rpm/data/gbp-test.data/my2.patch12
-rw-r--r--tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.0.tar.bz2.patch38
-rw-r--r--tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.1.tar.bz2.patch38
-rw-r--r--tests/component/rpm/data/gbp-test2-2.0-0.src.rpmbin0 -> 4035 bytes
-rw-r--r--tests/component/rpm/data/gbp-test2-2.0-1.src.rpmbin0 -> 3935 bytes
-rw-r--r--tests/component/rpm/data/gbp-test2.data/Add-dummy-Makefile.patch14
-rw-r--r--tests/component/rpm/data/gbp-test2.data/Add-dummy-files.patch23
-rw-r--r--tests/component/rpm/data/gbp-test2.data/Add-gbp.conf.patch22
-rw-r--r--tests/component/rpm/data/gbp-test2.data/Add-packaging-files.patch123
-rw-r--r--tests/component/rpm/data/gbp-test2.data/My-addition.patch15
-rw-r--r--tests/component/rpm/data/gbp-test2.data/Remove-imported-patches.patch85
-rw-r--r--tests/component/rpm/data/gbp-test2.data/Repository-initialization.patch0
-rw-r--r--tests/component/rpm/data/gbp-test2.data/Update-.gbp.conf.patch20
-rw-r--r--tests/component/rpm/data/gbp-test2.data/manifest.json163
-rw-r--r--tests/component/rpm/data/gbp-test2.data/packaging-add-alternative-spec-file-and-change-patch-p.patch91
-rw-r--r--tests/component/rpm/data/gbp-test2.data/packaging-change-my2.patch-to-git-email-format.patch26
-rw-r--r--tests/component/rpm/data/gbp-test2.data/packaging-reflect-the-corrected-patch-number-handling.patch35
-rw-r--r--tests/component/rpm/data/gbp-test2.data/pristine-tar-data-for-gbp-test2-2.0.tar.gz.patch39
-rwxr-xr-xtests/component/rpm/data/manage.py516
-rw-r--r--tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2bin0 -> 386 bytes
-rw-r--r--tests/component/rpm/data/orig/gbp-test-1.1.tar.bz2bin0 -> 389 bytes
-rw-r--r--tests/component/rpm/data/orig/gbp-test-1.1.with_dotgit.tar.bz2bin0 -> 5543 bytes
-rw-r--r--tests/component/rpm/data/orig/gbp-test-native-1.0.zipbin0 -> 1652 bytes
-rw-r--r--tests/component/rpm/data/orig/gbp-test2-2.0.tar.gzbin0 -> 334 bytes
-rw-r--r--tests/component/rpm/data/rpm/gbp-test-1.0-1.noarch.rpmbin0 -> 3391 bytes
-rw-r--r--tests/component/rpm/data/rpm/gbp-test-1.1-1.noarch.rpmbin0 -> 3387 bytes
-rw-r--r--tests/component/rpm/data/rpm/gbp-test-1.1-2.noarch.rpmbin0 -> 3657 bytes
-rw-r--r--tests/component/rpm/data/rpm/gbp-test-native-1.0-1.noarch.rpmbin0 -> 3028 bytes
-rw-r--r--tests/component/rpm/data/rpm/gbp-test-native2-2.0-0.noarch.rpmbin0 -> 2166 bytes
-rw-r--r--tests/component/rpm/data/rpm/gbp-test2-2.0-0.noarch.rpmbin0 -> 2965 bytes
-rw-r--r--tests/component/rpm/data/rpm/gbp-test2-2.0-1.noarch.rpmbin0 -> 2965 bytes
-rw-r--r--tests/component/rpm/test_buildpackage_rpm.py625
-rw-r--r--tests/component/rpm/test_import_orig_rpm.py311
-rw-r--r--tests/component/rpm/test_import_srpm.py430
-rw-r--r--tests/component/rpm/test_pq_rpm.py367
-rw-r--r--tests/component/rpm/test_rpm_ch.py334
-rw-r--r--tests/context.py61
-rw-r--r--tests/data/brackets-in-subject.patch12
-rw-r--r--tests/data/foo.patch12
-rw-r--r--tests/data/gbp_config.conf4
-rw-r--r--tests/data/gbp_create_remote_repo.conf8
-rw-r--r--tests/data/pristine_tar/testfile10
-rw-r--r--tests/data/pristine_tar/testfile20
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/bar.tar.gzbin0 -> 177 bytes
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/foo.txt3
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2bin0 -> 383 bytes
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zipbin0 -> 656 bytes
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gzbin0 -> 328 bytes
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/my.patch9
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/my2.patch7
-rw-r--r--tests/data/rpm/rpmbuild/SOURCES/my3.patch7
-rw-r--r--tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec34
-rw-r--r--tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec35
-rw-r--r--tests/data/rpm/rpmbuild/SPECS/gbp-test.spec42
-rw-r--r--tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec60
l---------tests/data/rpm/specs/gbp-test-native.spec1
l---------tests/data/rpm/specs/gbp-test-native2.spec1
-rw-r--r--tests/data/rpm/specs/gbp-test-quirks.spec30
-rw-r--r--tests/data/rpm/specs/gbp-test-reference.spec43
-rw-r--r--tests/data/rpm/specs/gbp-test-reference2.spec47
-rw-r--r--tests/data/rpm/specs/gbp-test-tags.spec78
-rw-r--r--tests/data/rpm/specs/gbp-test-updates-reference.spec43
-rw-r--r--tests/data/rpm/specs/gbp-test-updates.spec48
l---------tests/data/rpm/specs/gbp-test.spec1
-rw-r--r--tests/data/rpm/specs/gbp-test2-reference.spec61
-rw-r--r--tests/data/rpm/specs/gbp-test2-reference2.spec68
l---------tests/data/rpm/specs/gbp-test2.spec1
-rw-r--r--tests/data/rpm/specs/gbp-test3.spec32
-rw-r--r--tests/data/rpm/srpms/gbp-test-1.0-1.src.rpmbin0 -> 3427 bytes
-rw-r--r--tests/data/rpm/srpms/gbp-test2-3.0-0.src.rpmbin0 -> 3507 bytes
-rw-r--r--tests/data/test1.conf35
-rw-r--r--tests/doctests/__init__.py0
-rw-r--r--tests/doctests/test_Changelog.py344
-rw-r--r--tests/doctests/test_Config.py112
-rw-r--r--tests/doctests/test_Control.py93
-rw-r--r--tests/doctests/test_GitModifier.py87
-rw-r--r--tests/doctests/test_GitRepository.py1087
-rw-r--r--tests/doctests/test_GitVfs.py116
-rw-r--r--tests/doctests/test_PristineTar.py174
-rw-r--r--tests/doctests/test_create_remote_repo.py122
-rw-r--r--tests/test_RollbackDebianGitRepository.py51
-rw-r--r--tests/testutils/__init__.py143
-rw-r--r--tests/testutils/capture.py53
-rw-r--r--tests/testutils/data.py35
-rw-r--r--tests/testutils/debiangittestrepo.py47
-rw-r--r--tests/testutils/gbplogtester.py108
-rw-r--r--tests/testutils/popen.py18
-rw-r--r--tox.ini23
374 files changed, 60617 insertions, 0 deletions
diff --git a/.coverage b/.coverage
new file mode 100644
index 0000000..a76a6a3
--- /dev/null
+++ b/.coverage
@@ -0,0 +1 @@
+!coverage.py: This is a private format, don't read it directly!{"lines":{"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/__init__.py":[17],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/command_wrappers.py":[20,22,23,24,25,26,27,29,32,33,34,37,68,77,78,79,80,96,118,122,166,170,184,218,265,266,267,276,277,278,291,292,293,306,307,308,312,316,317,318,324,328,329,332,337,338,339,347,348,349,353,359,360,361,1,81,82,83,84,114,116,115,85,86,87,88,89,93,94,119,120,209,210,127,131,132,133,134,137,45,46,47,48,49,51,52,53,54,55,57,58,138,139,141,142,143,144,145,146,147,148,149,150,152,60,61,62,63,64,65,159,160,162,164,213,154,155,156,157,211,212,214,216,176,177,178,179,180,181,182,254,255,256,260,262,257,258,2,90,91,151,163,279,280,281,283,284,286,287,288,319,320,321,136,268,269,270,271,273,153,294,295,296,298,299,301,302,303,362,363,309,310,313,350,351,354,355,356,215,168,340,341,343,344],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/log.py":[18,20,21,22,23,24,27,28,29,30,31,32,33,36,37,38,41,48,49,51,52,54,61,65,70,74,84,94,95,97,107,112,117,123,128,133,138,143,165,176,178,98,99,55,56,57,58,59,100,101,39,102,103,104,105,140,43,44,86,87,76,78,79,81,90,91,45,167,109,110,63,168,145,146,147,149,153,154,155,156,157,158,159,160,161,162,114,115,67,68,169,172,170,135,82,125,130],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/tristate.py":[20,23,24,25,26,27,30,32,52,70,81,86,89,92,95,33,40,41,43,44,90,87,1,42,79,46,63,64,34,35,36,39,65,68,66,47,48,84,93],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/config.py":[17,19,20,21,22,23,24,26,28,29,32,33,34,40,43,48,58,80,81,82,83,84,87,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,194,197,199,202,205,208,211,214,216,218,221,224,227,230,232,234,236,238,240,242,244,246,249,252,255,257,260,263,266,269,272,274,276,281,284,287,290,294,297,300,303,306,309,312,315,318,321,324,326,330,333,336,339,342,345,349,351,354,356,358,360,362,365,367,371,377,380,381,382,383,384,386,388,389,424,442,447,454,459,485,503,555,585,595,620,628,636,637,60,77,657,662,673,691,710,711,732,733,734,754,760,763,764,765,766,769,774,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,804,805,808,811,813,817,820,823,826,829,833,836,839,842,845,848,851,853,855,859,861,864,868,1,474,483,475,481,476,477,478,479,704,705,708,706,417,418,419,420,422,421,567,568,569,570,571,572,573,575,576,508,510,511,512,513,514,515,517,518,426,427,431,432,433,435,521,525,526,533,534,535,536,541,546,553,492,493,494,495,496,497,499,580,581,582,583,61,62,63,68,74,75,76,647,648,649,629,630,634,650,651,652,622,587,588,589,591,592,593,625,626,653,654,655,69,72,744,745,746,747,748,749,750,751,752,755,590,623,600,604,605,607,608,612,614,615,618,756,757,601,602,49,50,55,428,429,430,436,437,438,440,658,659,660,609,610,64,613,44,45,631,633,439,871,770,577,578,717,719,723,724,725,726,727,728,729,544,678,679,681,682,683,684,686,527,528,529,537,538,443,444,445,530,531,671,632,457],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/errors.py":[17,20,21,22],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/version.py":[1,2],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/__init__.py":[17,19,20,22,23,24,25,27,28,29,32,1,48,49,50,51],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/modifier.py":[21,23,24,26,29,30,31,34,35,36,40,43,47,48,49,62,83,98,106,110,112,117,122,137,152,158,164,168,1,58,59,60,63,64,37,38,66,72,74,79,135,85,86,89,90,91,92,93,94,100,104,96,150,67,68,69,70,71,41,44,169,170,166,171,159,162,172,173,160,101,102,174,115,108,75,76,87,73,78,120,153,154,156],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/errors.py":[17,20,21,22],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/commit.py":[17,19,22,23,24,26,1,45],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/repository.py":[17,19,20,21,22,23,25,26,27,28,29,30,31,32,35,36,37,40,41,42,50,53,58,63,69,80,82,91,103,120,133,142,174,197,198,223,247,291,296,301,306,311,319,325,335,350,371,396,416,432,447,470,491,497,500,531,545,554,563,583,607,636,658,683,693,697,710,754,767,782,794,812,832,849,877,898,931,942,961,962,993,1002,1015,1036,1058,1072,1106,1119,1126,1134,1142,1164,1199,1211,1225,1244,1249,1275,1295,1332,1349,1375,1390,1416,1439,1453,1457,1472,1484,1499,1500,1562,1598,1636,1648,1673,1685,1735,1758,1773,1820,1848,1871,1890,1908,1917,1941,1983,1984,2026,2028,1,982,983,985,987,990,988,989,128,129,104,105,106,107,193,195,202,203,204,207,208,136,137,140,209,210,212,213,214,215,216,217,218,219,220,221,108,109,111,113,118,130,84,85,194,294,86,89,131,92,93,94,97,98,101,110,114,115,1995,1996,1998,1999,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2013,2016,2020,1132,1123,1124,235,236,237,238,239,240,243,304,706,159,161,162,164,165,166,167,168,169,170,171,707,1522,1523,1524,1525,1526,1527,1528,1360,1362,1363,1365,1367,1368,1370,1371,1373,138,139,1529,1024,1025,1029,1030,1031,1032,1034,1531,1541,1542,1545,1546,1548,1554,1555,1574,1575,1576,1577,1578,1582,1583,1585,1586,1587,1588,1589,1590,1591,1556,1558,1559,577,578,579,580,581,1560,674,675,676,679,680,681,406,408,409,410,411,414,380,381,382,386,389,390,391,394,820,821,953,954,955,956,957,959,823,827,828,829,830,314,315,862,865,866,867,833,834,835,837,838,842,843,844,845,847,868,875,552,540,541,542,543,1616,1617,1618,1619,1621,1623,1624,1625,1627,1629,1630,1634,299,345,346,347,348,594,595,596,597,599,600,602,603,604,1532,1533,1547,1584,244,245,412,413,1011,1012,1013,1084,1085,1086,1087,1088,1090,1091,1094,1095,1096,1097,1099,1100,1101,1102,1043,1044,1046,1047,1048,1050,1051,1052,1053,1054,1056,1900,1906,1642,1643,1644,1646,1495,1496,1497,1454,1455,1878,1879,1882,1883,1884,1885,1657,1658,1659,1660,1661,1663,1664,1665,1668,1669,1694,1695,1696,1697,1698,1699,1703,1705,1706,1707,1708,1709,1710,1712,1713,1716,1718,1719,1720,1721,1723,1724,1725,1726,1727,1728,1729,1000,385,316,317,322,690,691,423,426,430,458,459,460,461,462,463,464,465,466,116,117,2052,2053,2056,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2070,2071,2072,2073,2074,2075,2076,2079,2082,2092,424,2054,2080,2093,1620,1480,1481,1482,779,780,732,733,734,737,739,740,741,742,743,744,746,747,748,749,752,359,362,363,364,366,367,1671,940,605,1549,1550,1551,1552,1717,1645,332,333,392,393,309,791,792,1683,1622,1140,1114,1115,1117,1915,1929,1931,1932,1933,1934,1935,1936,1937,1939,99,958,1534,1535,1536,1537,1760,1761,1763,1764,1765,1767,1769,1770,1027,1794,1795,1796,1798,1799,1800,1801,1802,1803,1804,1807,1808,1809,1810,1811,1812,1813,1814,1815,205,1816,1818,1443,1444,1445,1446,1447,561,1264,1265,1266,1267,1270,1271,1273,439,440,441,444,445,508,509,510,511,512,513,515,518,519,521,522,524,526,528,529,481,482,483,258,259,260,261,262,263,268,269,270,271,272,273,286,287,288,274,275,276,277,278,282,283,284,484,488,489,516,1238,1239,1240,1241,1242,1116,442,443,1343,1344,1345,1315,1316,1317,1318,1319,1322,1323,1324,1325,1326,1328,1330,863,1901,1902,1903,1904,870,871,872,873,750,751,1905,1930,1863,1864,1865,1866,1867,1868,1951,1952,1953,1955,1956,1957,1959,1960,1961,1962,1963,1966,1967,1969,1972,1973,1974,1975,1976,1977,1978,1968,1979,1964,1965,824,825,427,428,1579,1580,1626,694,695,1749,1750,1751,1752,1753,1755,1756,1150,1151,1154,1155,1156,1157,1158,1159,1160,1067,1068,1070,1831,1832,1834,1835,1837,1838,1839,1841,1843,1845,1399,1400,1401,1403,1404,1405,1408,1409,1411,1412,1768,907,908,912,913,914,917,918,920,921,922,923,925,927,929,1766,1152,1153,112,839,840,1448,888,889,890,891,893,894,895,369,618,619,622,623,627,628,629,630,631,646,647,648,652,654,620,650,803,805,806,807,808,765,1414,1468,1469,1470,1407,2068,2083,2084,2087,2088,2089,2090,1208,1209,1220,1171,1172,1173,1174,1178,1179,1180,1181,1182,1183,1186,1187,1188,1189,1190,1192,1193,1191,1194,1195,43,44,45,48,1197,1221,1223,56,61,66,498,1286,1287,1288,1291,1292,1289,1268,1245,1246,2017,2018,2019,2021,2022,2023,1880,1666,1667,520,527,1427,1428,1429,1431,1432,1433,1434,1435,468,909,910,926,279,280,281,289,264,265,1593,1033,241,242,360,495],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/format.py":[17,19,22,47,38,39,1,64,65,66,40,42,43,44,41],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/args.py":[19,21,24,44,46,50,54,69,81,92,1,47,48,58,59,61,62,63,67,52,60,104,107,108,77,78,79,89,90,105],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/paths.py":[17,20,26],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/fastimport.py":[18,20,21,22,23,24,27,28,29,31,32,33,35,51,60,65,80,95,128,134,143,40,41,42,43,44,106,107,108,109,111,114,119,120,121,122,123,124,125,126,132,78,61,62,63,52,53,54,55,56,57,58,89,90,91,92,93,138,139,140,141],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/git/vfs.py":[17,19,20,23,25,30,31,41,44,47,50,53,56,59,67,64,65,68,70,71,73,74,75,76,32,33,36,37,54,48,57,51,34,77,78,42,45],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/dch.py":[17,19,21,24,40,50,73,87,91,103,108,109,110,111,29,30,31,37,113,115,118,119,120,121,56,57,58,59,60,70,122,76,77,78,79,84,123,43,47,125,128,132,140,94,95,141,32,36,61,62,63,64,65,66,67,133,134,137,138,69,80,83,81],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/__init__.py":[17,19,20,22,23,24,27,28,47,50,51,53,58,84,103,109,114,104,105,106,111,54,56,65,66,67,71,72,91,96,97,98,73,74,75,79,80,115,116,117,68,69,70,81],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/changelog.py":[17,19,20,21,22,25,26,27,30,31,32,35,36,37,41,45,49,65,66,68,92,104,130,138,141,144,149,154,159,163,168,173,178,183,192,200,207,214,228,235,236,237,303,317,318,337,340,356,357,75,76,77,80,83,86,89,131,132,147,133,90,106,93,94,95,96,97,98,99,102,109,110,111,114,115,118,122,123,124,128,212,198,347,350,353,348,354,351,205,338,87,157,139,171,152,181,116,334,335,256,257,258,259,260,261,262,263,270,272,275,276,278,280,283,284,285,288,289,290,291,292,293,294,295,300,296,297,301,166,266,287,176,265,271,273,142,84,190,161,112,233,219,220,221,222,224,225,59,60,61,62,38,39,226,43,47,315,361,362,363,364,365,367,281,368,267,268],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/policy.py":[21,23,24,26,27,28,31,39,46,49,58,61,64,66,67,1,90,91,94,95,96,98,97],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/pkg/__init__.py":[18,20,21,22,23,24],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/pkg/pkgpolicy.py":[19,20,22,25,28,29,30,31,32,34,48,62,63,125,133,137,138,165,166,58,60,1,104,105,107,108,115,116,119,120,121,122,123,44,45,59,135,128,129,130,131,144,145,146,148,151,152,153,154,157,158,160,163,167,155,156,46],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/pkg/archive.py":[18,21,23,26,27,28,29,31,1,60,63,64,65,68,72,73,74,75,76,77,78,80,66,67,69,70],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/pkg/compressor.py":[19,21,22,24,25,26,27,29,30,31,32,34,38,41,45,49,53,57,67,1,35,36,74,47,75,43,64,51,55,65,39],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/pkg/upstreamsource.py":[18,19,21,23,24,26,29,41,42,52,76,84,92,96,114,127,133,143,155,187,191,189,43,44,45,46,48,61,90,65,66,67,69,73,74,49,94,70,71,72,102,105,106,108,111,119,120,125,148,149,150,112,135,136,138,139,62,63,50,192,193,82,166,169,172,175,176,177,178,179,180,181,185,121,123,128,129,141,170],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/pkg/pristinetar.py":[17,19,20,21,22,25,26,27,29,35,51,54,63,81,91,105,30,31,32,33,61,70,71,101,102,103,88,89,52,44,45,46,47,48,108,109,73,74,75,76,77,78,79],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/control.py":[17,19,20,23,24,25,28,29,30,33,34,36,62,65,68,73,78,47,48,56,59,60,71,63,76,81,50,51,57],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/dscfile.py":[17,19,20,22,23,24,27,28,29,30,31,33,34,35,36,37,38,40,41,42,43,44,46,123,135,138,140,141,47,48,49,50,51,52,53,54,55,56,57,59,60,61,62,63,76,77,80,81,84,85,89,90,93,94,97,98,101,102,103,104,78,79,64,69,70,71,74,75,91,92,105,109,111,114,116,118,120,121,144,125,126,127,133,65,66,67,82,83,129,130,86,87,88,99,100,110,132,95,96],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/upstreamsource.py":[18,20,21,22,24,25,26,29,30,31,37,32,33,34,42,43,44,45,46,47,49,50,52,54,55,56],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/format.py":[17,20,21,24,44,45,47,58,63,68,73,76,99,100,116,1,59,60,61,48,50,51,52,53,71,66,55,56,2,96,97,74,110,111,112,113],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/git.py":[17,19,20,22,23,24,25,26,27,29,32,33,35,40,44,59,70,108,135,148,176,192,203,215,237,250,272,280,290,315,324,334,367,1,143,144,145,146,184,185,186,187,188,234,235,248,264,197,198,201,265,266,267,268,269,208,209,213,199,210,211,270,172,190,173,174,41,42,88,89,90,93,104,288,278,49,50,51,53,54,55,56,57,353,354,355,356,358,359,362,365,63,64,65,66,67,300,301,303,304,305,306,309,310,311,325,326,327,328,331,92,124,125,126,128,131,133,68,329,330,369,376,319,320,321,322,370,371,372,132,360,361],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/pristinetar.py":[17,19,20,21,24,25,26,48,38,41,43,45,62,63,64,65,66,67,39],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/pkg/git.py":[17,19,20,21,22,24,25,26,29,32,33,35,39,55,65,107,1,51,53,52,36,37,57,60,63,113,114,115,116,117,118,119,120,61,74,75,76,77,78,80,81,84,85,87,88,89,90,91,96,99,100,105,92,93,103],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/rollbackgit.py":[4,7,8,9,12,15,16,21,25,29,30,35,38,61,70,73,76,109,115,120,126,31,32,33,116,117,62,63,64,46,48,49,50,59,118,110,111,112,71,53,54,113,66,121,122,123,68,124,36,82,83,84,85,86,88,89,87,105,92,93,94,127,128,129,74,55,56,95,96,100,97,98,58],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/source.py":[17,19,20,21,22,23,26,27,36,41,42,45,51,52,66,83,91,104,117,124,128,132,136,140,153,34,58,59,61,64,70,71,37,38,75,76,78,79,96,97,98,99,102,130,122,138,72,73,74,134,126,146,148,149,150,151,160,161,162,163,62,89,100,101,109,110,111,112,115,80],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/deb/uscan.py":[17,19,20,21,24,25,28,29,31,36,40,44,110,136,177,1,32,33,34,62,63,66,68,69,70,71,108,42,38,72,84,86,87,88,90,91,92,94,105,106,107,130,131,134,133,165,167,168,169,170,171,173,175,174,183,184,185,186,187,190,193,196,197],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/notifications.py":[18,20,23,41,49,60,42,43,44,46,66,69,26,28,29,30,31,35,36,37,38,70,71,67],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/patch_series.py":[17,19,20,21,22,23,24,26,29,42,43,45,52,61,64,92,126,146,153,158,163,169,170,175,187,227,257,260,261,262,264,281,312,327,341,365,1,46,47,48,49,50,116,118,119,120,121,124,122,123,322,323,325,324,375,339,376,377,353,354,356,357,358,359,360,361,363,378,53,54,55,56,57,58,59,2,3,302,303,304,305,309,310,306,307,308,267,269,270,272,273,277,278,279,156,138,139,171,62,70,71,72,73,74,75,76,77,78,79,80,81,82,83,88,89,172,141,142,161,166,144,151,173,231,237,238,239,240,241,244,247,248,249,246,242,254,193,202,211,212,176,177,185,214,215,216,194,178,183,184,195,196,197,198,199,200,217,218,203,179,180,181,204,205,207,208,209,224,225,219,222,223,253],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/rpm/__init__.py":[18,20,21,22,23,24,26,27,28,29,30,31,32,33,34,37,42,43,44,47,48,49,52,53,54,61,62,63,79,88,93,98,103,115,116,117,118,119,120,121,122,128,130,193,212,221,226,234,240,246,251,260,273,281,330,346,364,393,413,451,472,514,531,551,575,601,605,618,720,755,794,821,833,854,869,884,895,924,958,990,978,979,980,981,982,983,985,986,984,1,987,1006,943,945,946,947,951,952,955,953,949,950,913,914,915,916,918,921,919,873,874,875,876,880,835,836,837,839,843,844,845,846,848,851,881,886,887,132,135,137,140,151,152,153,154,158,159,166,167,195,196,197,198,199,200,203,204,170,171,172,173,176,177,38,178,179,180,183,417,418,419,420,421,284,286,287,290,291,293,296,300,301,302,307,309,311,317,318,319,320,324,325,326,328,422,294,310,303,304,288,423,367,368,369,370,424,428,396,397,398,411,372,374,375,385,386,387,388,389,390,391,425,426,427,432,236,237,433,242,244,434,436,437,438,188,189,191,799,800,248,249,801,802,803,805,806,807,809,810,815,816,758,759,760,761,766,767,768,350,351,352,353,354,355,356,357,358,359,360,361,362,769,770,771,773,777,792,818,888,889,890,224,142,143,144,145,146,147,516,517,518,519,521,522,523,524,526,277,278,279,215,216,217,219,812,321,322,399,400,403,404,407,408,409,297,376,334,335,336,337,338,339,340,341,342,343,344,377,378,238,441,444,446,447,776,778,780,781,782,270,271,252,253,254,256,257,791,39,308,379,382,406,445,774,779,218,847,891,892,849,850,525,474,475,476,477,481,482,483,486,489,490,491,493,494,495,504,505,506,507,508,511,512,823,824,65,66,69,70,72,73,74,75,76,77,830,108,109,110,111,112,91,856,857,859,860,861,864,866,840,841,842,55,56,57,58,825,826,380,722,724,725,727,733,734,735,736,739,740,741,742,743,744,746,747,748,749,750,751,752,753,858,865,229,230,231,621,622,623,625,626,627,453,454,455,459,460,461,462,466,463,464,467,468,470,629,630,634,635,636,533,536,537,538,539,540,541,545,542,543,546,547,549,638,639,646,647,650,654,655,656,670,673,676,677,679,695,696,698,699,700,701,702,703,484,485,492,496,497,498,509,706,707,708,709,710,553,554,555,558,559,560,561,567,568,569,570,571,572,573,711,631,648,674,675,148,149,607,608,616,603,577,580,592,593,594,595,597,598,599,609,610,611,612,613,615,581,584,585,586,587,588,590,614,589,401,402,313,314,316,469,499,500,501,502,243,456,457,478,529,548,562,563,564,565,566,534,556,578,582,583,136,138,232,657,660,661,663,680,684,685,687,729,730,731,671,672,813,763,712,713,714,715,716,718,520,640,641,642,527,82,83,84,86,96,101,85,862],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/rpm/policy.py":[17,19,21,22,25,26,29,31,33,35,38,39,40,43,46,47,48,50,52,76,77,81,82,83,84,85,88,89,90,92,93,96,98,100,102,135,153,1,71,72,73,74,168,169,170,171,175,115,116,117,118,119,120,121,122,132,133,178,147,151,181,182,183,186,189,194,200,191,184,148,149],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/__init__.py":[17],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/common/__init__.py":[17,19,20,21,22,23,26,27,28,29,30,31,34,39,53,1,48,49,50,35,58,59,60,69,61,62,63,64,65,66,67],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/common/pq.py":[19,21,22,23,24,25,26,27,28,30,31,32,33,35,38,50,65,80,123,139,192,195,196,197,252,275,288,305,311,346,1,47,74,75,77,59,62,60,277,279,280,281,282,283,284,293,294,347,348,349,350,351,313,314,315,317,318,329,330,334,335,336,338,339,340,341,342,343,96,97,98,99,100,101,102,118,119,120,103,104,105,106,115,201,202,203,205,207,215,216,222,223,229,230,232,239,127,134,136,242,243,244,245,246,141,144,145,146,147,148,149,152,153,155,157,158,159,162,163,164,165,166,167,168,169,172,174,181,182,185,186,189,247,248,249,156,176,177,178,296,297,301,302,225,226,227,306,307,319,327,308,255,256,257,258,259,260,261,262,263,264,266,267,268,269,270,271,331,332,333,110,111,116,320,321,322,323,324,325,337,285,219,233,234,235,236],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/rpm/linkedlist.py":[17,19,21,24,25,27,32,35,43,62,71,72,74,77,85,89,90,92,96,99,104,109,129,146,165,184,1,93,94,141,142,123,124,28,29,30,127,100,97,75,107,78,79,80,83,101,82,102,144,176,177,180,181,182,33,38,41,157,158,159,162,163,208,209,211,213,64,65,66,67,68,214,39,40,210,212,178,161,126,57,60,58,59],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/rpm/lib_rpm.py":[17,19,21,22,24,26,34,35,36,37,40],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/rpm/changelog.py":[17,19,20,21,23,25,28,41,42,43,46,47,49,54,57,62,29,38,30,37,74,75,77,96,104,105,107,113,121,125,131,132,134,138,144,148,155,156,158,166,183,191,214,218,245,159,160,161,162,163,164,185,186,187,168,135,136,169,170,171,172,174,175,176,179,180,181,248,249,253,32,33,34,195,196,198,199,200,206,207,208,211,212,108,109,50,51,52,110,111,35,36,254,256,55,58,59,257,258,220,221,222,223,224,225,230,231,233,235,239,240,241,216,86,87,88,91,93,94,243,259,127,128,261,146,150,151,152,139,140,141,114,64,65,66,67,68,115,116,98,99,100,101,118,119,142,173,123,89,60,69,70,71,226,228,227,232,178,197,250,251,201,202,203,209,210],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/rpm/git.py":[19,20,21,22,23,26,27,29,33,54,75,90,98,1,88,70,71,72,73,30,31,96,106,45,46,49,52,51,47,48],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/buildpackage.py":[18,20,21,22,23,24,25,26,27,28,31,32,33,34,35,36,37,38,39,40,43,44,46,47,51,74,97,106,147,158,170,188,196,228,269,278,286,308,316,337,437,472,595,1,299,300,302,303,301,305,473,474,475,476,478,438,439,442,443,446,338,339,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,403,404,406,407,409,410,412,413,415,416,418,419,420,421,422,423,424,425,426,427,429,430,431,432,433,434,447,449,451,452,454,460,464,468,273,469,480,483,484,489,490,179,180,181,182,491,322,323,324,330,331,334,492,60,70,71,493,149,150,151,152,155,495,190,191,497,498,160,162,163,164,165,167,499,500,501,240,242,265,266,502,504,505,506,309,310,311,312,313,513,521,540,548,549,550,551,552,553,554,555,562,579,581,582,585,586,587,588,592,503,514,517,518,565,568,570,571,572,573,541,542,543,544,545,556,557,558,559,560,61,63,66,67,522,87,92,93,525,532,533,99,100,534,537,583,88,90,109,110,111,112,113,114,116,117,118,121,123,124,125,126,127,130,131,132,140,141,142,143,144,274,275,64,455,325,327,563,192,444,200,201,202,203,204,207,208,209,210,211,221,225,215,216,217,218,219,222,213,224,243,244,245,247,248,249,250,255,256,259,261,263,251,252,253,340,341,342,448,481],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/common/buildpackage.py":[19,21,22,23,24,25,26,27,30,32,36,85,90,98,100,87,101,102,38,39,40,41,46,47,48,49,50,51,52,53,56,81,82,92,93,94,95,43,44,57,58,59,60,61,62,63,64,65,66,67,68,69],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/common/hook.py":[17,19,20,23,24,25,30,34,37,38,39,26,27,28,31,32],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/export_orig.py":[18,20,21,22,23,24,25,26,27,28,29,30,31,34,76,94,134,154,169,194,224,264,306,316,363,45,48,49,50,52,53,54,55,230,236,237,252,253,254,255,256,260,261,56,59,69,70,100,101,71,205,206,175,176,178,179,189,191,207,208,209,210,211,212,213,214,217,218,219,220,221,72,155,156,73,140,141,257,259,60,61,62,65,66,231,232,158,159,160,163,164,165,166,238,239,240,241,242,245,246,247,248,103,107,108,83,84,85,86,87,88,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,143,148,149,150,151,180,184,185,186,317,318,320,307,265,266,271,272,273,274,275,276,277,278,279,280,281,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,308,310,312,313,322,325,326,331,332,333,334,338,340,344,345,360,215,216,346,349,351,352,353,354,244,249,250,125,126,130,258,190,188,181,182,183],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/tag.py":[18,20,21,23,24,25,26,27,28,29,31,32,34,37,58,81,111,118,160,64,65,68,70,43,44,45,46,47,48,49,50,51,52,53,54,55,71,72,73,74,75,76,77,66,119,121,112,82,83,84,89,90,91,92,93,94,95,96,97,98,99,101,102,103,104,105,106,107,108,113,115,122,125,126,127,132,133,134,135,142,143,144,149,150,157],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/buildpackage_rpm.py":[19,21,22,23,24,26,27,28,29,30,31,32,33,34,35,36,37,38,40,43,44,47,57,77,110,129,147,169,178,195,229,246,257,271,286,297,305,437,464,656,466,467,468,470,439,440,443,444,447,307,308,309,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,381,382,383,384,385,386,387,388,389,390,392,393,395,396,398,399,400,402,403,405,406,408,409,410,411,412,413,414,415,416,417,418,419,421,422,423,424,425,426,427,429,430,431,432,433,434,448,450,452,453,455,461,472,475,476,482,483,152,153,156,160,163,166,489,491,171,172,175,493,494,496,497,499,500,501,502,507,508,515,516,517,518,521,523,525,248,249,251,252,253,254,526,530,49,50,51,54,531,532,533,536,537,538,539,540,541,544,545,543,548,551,231,232,235,241,552,554,555,556,557,562,563,564,565,59,62,63,64,65,68,69,70,74,573,579,580,587,588,589,592,593,594,595,596,597,606,618,180,181,182,183,184,185,192,621,622,640,641,643,644,645,646,647,648,649,653,591,546,547,623,626,628,631,635,636,637,638,71,72,73,566,567,607,608,609,288,259,262,263,265,266,267,268,290,292,293,294,610,611,612,613,614,615,616,574,575,576,577,581,582,583,584,598,599,600,601,602,603,454,299,300,301,302,243,569,570,80,81,94,95,96,112,126,97,98,210,211,212,213,131,132,133,134,135,142,143,224,225,632,633,634,233,144,214,215,216,217,218,219,220,221,226,99,236,237,238,239,240,503,504,505,155,186,188,189,190,191,158,164,484,485,486,558,559,560,561,650,651,136,141,66,67,222,223,137,139,138,477,478,479,509,510,511,173,174,113,116,117,118,114,115,119,120,124,125,627,100,104,105,106,107,121,101,102,629,630,456,457,458,459,473,291,82,83,84,85,86,87,89,90,445,310,311,312,449],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/tmpfile.py":[18,20,21,22,24,27,30,47,32,33,38,42,43,44,49,50,51,52,53,34,35,36],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/pq_rpm.py":[19,21,22,23,24,25,26,28,29,30,31,32,33,34,35,36,37,39,40,46,49,60,127,144,158,187,198,221,261,271,340,355,365,380,412,420,489,165,166,172,173,176,177,180,182,183,184,167,170,178,179,422,424,414,382,383,384,377,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,415,417,425,428,430,434,436,438,439,440,441,443,448,449,454,456,457,458,460,462,464,466,467,468,484,486,437,469,470,357,358,359,360,463,459,200,201,202,203,204,205,209,174,181,210,189,190,191,192,195,211,213,149,133,134,135,136,151,152,64,65,66,67,68,71,72,73,78,80,82,51,52,53,54,57,86,87,89,90,91,92,93,94,95,96,97,100,101,102,103,104,105,106,107,108,109,110,111,116,124,153,154,155,215,217,461,279,281,282,283,287,288,471,474,476,479,480,481,482,284,285,293,294,263,268,295,298,301,302,303,311,312,313,314,315,316,317,318,230,231,233,234,235,236,237,238,239,240,241,242,250,251,252,253,255,256,243,247,248,258,320,321,322,324,326,327,328,329,330,336,337,290,291,292,299,300,305,207,208,137,138,141,168,264,265,266,267,218,450,451,452,331,332,333,334,431,432,445,446,113,193,194,465,342,343,344,345,349,351,352,347,348,362,386,387,388,416,426],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/clone.py":[20,22,23,24,25,26,27,28,29,30,31,32,33,34,36,39,48,80,101,140,150,226,1,87,88,91,93,95,98,94,151,153,141,102,103,104,109,110,111,112,114,115,116,117,118,119,120,121,122,123,124,126,128,129,130,131,132,133,134,135,136,137,142,145,146,147,154,157,161,89,162,165,166,167,170,171,173,174,175,176,177,181,182,185,193,194,196,197,198,199,200,203,205,207,208,209,223,212,215,216,217,186,187,188,189,190,191,105,106,107,143,155,49,51,52,53,54,56,57,58,59,62,63,66,67,68,70,64,65,60,61,69,72,76,77],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/common/repo_setup.py":[18,20,23,24,25,26,28,29,30],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/common/import_orig.py":[19,20,21,22,23,24,26,27,32,33,38,60,68,76,95,114,129,143,50,51,131,115,123,124,125,126,132,133,136,137,62,63,138,139,140,118,119,120,134,70,73,71,52,57,81,82,83,84,86,87,100,101,102,103,105,106,72,53,55],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/config.py":[18,20,21,22,23,24,25,26,29,45,52,69,78,87,112,116,139,117,119,46,30,31,32,37,38,39,40,41,42,47,49,121,124,126,129,133,135,95,98,99,104,56,59,60,66,106,107,70,71,74,136,72,113,75,100,101,102,61,64,65,109,79,80,81,82,83,84,33,34,35,48,122],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/supercommand.py":[18,20,21,22,23,27,30,38,54,62,74,82,91,108,148,1,79,109,111,115,116,118,119,39,51,120,66,35,67,68,71,69,121,126,129,132,136,137,138,139,140,141,143,142,133,92,93,94,96,97,83,84,85,87,86,88,98,99,100,101,102,103,104,105,134,112,113],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/create_remote_repo.py":[19,21,22,23,24,25,26,27,28,30,31,32,33,34,35,36,38,41,72,117,152,171,192,199,205,213,259,281,359,367,378,403,1,161,162,167,168,163,164,165,166,59,61,63,64,66,67,69,121,122,123,124,125,126,127,131,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,132,133,129,130,77,78,79,83,86,91,92,94,101,102,104,105,106,107,108,109,110,111,112,113,114,87,88,95,96,98,99,81,84,89,97,379,381,266,269,270,274,214,215,216,210,217,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,275,278,218,219,220,276,382,383,360,361,362,363,364],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/dch.py":[18,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,36,37,40,60,77,110,136,173,183,201,213,247,253,262,289,304,308,318,328,433,445,610,1,123,124,130,131,132,133,125,126,127,129,446,447,448,449,450,451,453,434,329,330,331,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,352,353,354,355,356,357,358,360,361,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,379,380,381,382,383,385,386,387,388,390,391,393,394,396,397,398,399,400,401,402,404,405,406,408,409,410,411,412,413,414,415,416,417,419,421,424,425,426,427,428,430,435,438,439,440,263,266,269,270,272,273,276,278,279,283,286,441,291,293,295,298,299,442,455,458,459,460,461,462,466,254,467,468,474,478,479,322,323,481,484,226,227,232,233,234,235,485,486,490,249,250,492,494,495,496,498,500,502,504,506,508,510,513,515,524,531,532,556,557,559,562,563,564,566,567,86,62,64,74,87,88,89,93,94,95,96,97,98,102,103,106,107,569,572,576,579,580,581,582,584,606,607,255,256,258,259,593,596,597,598,599,600,601,602,603,604,294,257,501,516,517,519,520,522,526,527,45,46,47,48,49,50,51,52,53,57,528,533,534,535,203,204,205,206,207,209,210,536,537,541,544,545,546,547,548,550,100,573,188,190,191,192,194,195,197,144,145,146,147,149,150,152,153,154,155,159,160,162,163,164,165,166,167,168,198,574,54,324,325,309,310,313,314,315,238,239,240,241,208,552,529,65,66,70,71,296,570,571,175,176,177,180,228,156,157,588,590,305,591,592,284,264,244,332,333,334,436,456],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/import_dsc.py":[17,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,37,38,39,40,43,44,47,70,88,96,110,122,135,156,200,210,242,276,295,303,310,374,388,412,433,554,1,403,405,407,408,406,409,404,434,435,436,437,439,413,375,311,312,313,318,319,320,321,322,323,324,325,327,328,329,330,331,332,333,334,335,336,337,339,340,341,342,343,344,345,346,347,348,349,351,352,353,354,355,356,357,358,359,360,361,362,363,365,366,367,368,369,370,371,376,379,380,382,385,414,417,418,419,427,429,430,440,443,444,445,452,454,456,461,463,464,466,469,470,471,475,476,477,478,479,481,485,486,487,488,492,501,502,211,212,214,215,216,221,114,115,116,119,222,126,127,130,132,223,224,225,97,98,106,107,226,227,228,229,230,231,232,233,234,235,236,237,239,527,529,530,544,545,546,547,549,550,551,504,505,506,507,243,244,246,247,252,253,255,256,257,258,259,263,264,266,268,269,270,271,272,273,508,510,517,518,159,160,162,164,165,91,93,169,170,171,173,140,141,143,144,148,153,174,175,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,197,522,489,490,523,524,525,467,277,282,283,284,285,287,289,290,291,447,449,450,249,250,201,149,150,151,100,101,102,103,202,203,204,511,512,104,105,265,472,474,531,533,535,537,538,539,163,72,73,74,76,77,78,80,81,82,286,218,219,420,421,422,314,315,316,377,415,441],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/import_dscs.py":[17,19,20,21,22,23,24,25,26,27,28,29,30,33,34,37,41,67,68,71,75,100,111,122,204,123,124,125,126,127,35,43,44,47,50,53,56,59,62,64,128,130,131,133,135,137,142,151,152,153,154,156,160,164,45,48,38,165,69,167,168,176,178,180,72,182,184,185,195,197,199,200,201,169,170,175,181,187,190,191,192,193,186],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/import_orig.py":[18,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,41,42,45,88,92,136,183,191,213,222,231,257,268,284,294,307,375,394,535,1,157,175,176,177,178,158,159,180,395,396,397,398,399,401,380,308,309,310,315,316,317,318,319,320,321,322,323,324,326,327,328,329,330,331,332,333,334,335,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,357,358,359,360,361,362,363,364,365,366,367,370,371,372,381,384,385,387,390,391,402,405,406,407,411,413,416,417,422,425,426,430,95,98,99,100,120,123,128,129,133,432,433,434,435,437,438,441,444,269,270,274,275,276,278,279,281,446,447,448,62,63,66,67,70,72,85,451,452,457,458,459,460,461,462,463,464,465,467,89,469,470,471,472,473,476,477,478,479,480,481,485,486,487,488,489,491,501,502,503,184,185,188,223,214,215,216,219,224,225,232,234,235,236,239,240,241,242,243,244,248,249,250,251,252,253,254,507,508,509,512,192,524,527,528,530,531,532,271,272,280,73,74,75,79,80,83,76,77,78,101,102,106,108,109,114,115,492,493,496,497,498,513,514,515,518,519,520,521,522,295,296,297,298,300,439,193,194,198,199,201,202,203,204,205,206,208,209,210,110,111,112,124,125,126,442,286,287,288,289,290,291,510,453,454,217,218,227,228,258,259,260,261,262,265,64,408,409,311,312,313,382,403,121,499,500,117],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/import_ref.py":[19,21,22,23,24,25,26,27,28,29,31,37,59,111,126,204,127,128,130,116,60,61,62,67,68,69,70,71,72,73,74,75,76,78,79,80,81,82,83,84,85,86,87,88,89,91,92,93,94,95,96,97,98,99,101,102,103,104,105,106,107,108,117,120,121,123,131,138,139,140,144,42,43,48,50,52,56,146,148,149,153,156,157,158,159,160,161,162,163,164,166,177,178,182,183,184,187,199,200,201,55,45,47],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/import_srpm.py":[18,20,21,22,23,24,25,26,27,28,30,31,32,34,35,36,38,39,40,41,42,47,50,51,52,55,70,89,98,106,113,122,204,220,508,222,224,225,227,206,124,125,126,127,133,134,135,136,137,138,140,141,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,161,162,163,164,165,166,167,168,169,172,173,174,175,176,177,178,179,180,181,182,184,185,186,187,188,189,190,191,192,193,194,195,197,198,199,200,201,207,210,211,213,216,217,228,231,232,233,240,241,245,246,250,251,252,253,254,255,256,257,258,265,266,267,268,276,277,278,280,281,294,295,108,298,299,300,301,302,303,304,305,308,310,319,320,322,323,324,325,331,332,333,334,338,339,343,344,345,347,348,358,359,363,364,369,370,371,372,91,92,95,375,376,377,378,380,381,382,383,384,385,390,391,392,393,397,398,399,400,401,402,403,404,405,406,407,408,409,410,412,413,427,428,429,430,431,432,439,441,450,451,452,453,454,455,457,458,459,460,461,462,463,464,465,466,469,115,117,119,472,473,474,475,476,477,478,480,500,501,503,504,505,482,485,487,490,491,492,493,247,72,73,77,78,79,84,57,58,59,63,64,60,61,62,67,85,86,286,287,288,289,290,291,292,365,366,367,368,442,443,444,445,446,447,340,341,336,423,282,284,387,389,434,437,234,238,239,93,94,394,395,349,351,356,494,497,498,352,353,354,100,101,102,103,235,236,259,260,313,314,315,316,317,495,496,262,270,271,272,273,327,328,414,415,416,417,418,420,421,129,130,131,208,229],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/pq.py":[18,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,40,41,43,44,47,68,113,127,152,179,192,198,246,269,356,368,379,388,399,413,443,450,512,1,122,123,124,134,144,145,146,147,148,149,451,453,444,414,415,416,410,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,445,447,454,457,459,463,465,466,477,478,483,484,485,487,488,370,371,372,373,374,286,287,289,297,299,306,307,195,310,314,318,320,321,322,324,325,326,330,331,344,349,353,375,376,509,489,491,492,357,381,385,359,360,363,365,486,200,201,202,203,204,205,206,208,209,210,211,212,215,217,220,222,72,73,74,75,79,80,110,224,229,231,242,490,332,333,334,335,493,497,498,390,391,392,393,300,301,81,83,50,51,52,53,55,56,57,63,64,65,84,86,87,88,89,90,92,93,94,95,96,97,98,99,101,102,103,104,105,106,225,226,227,382,383,384,395,396,232,156,157,160,161,162,164,166,167,170,171,136,137,138,139,173,174,175,176,233,234,235,236,240,158,163,237,238,243,308,183,184,185,186,189,315,218,361,100,417,418,419,446,455],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/pristine_tar.py":[18,20,21,22,23,24,25,26,27,28,31,39,59,73,119,74,75,77,64,40,41,42,36,47,48,49,50,51,52,53,54,55,56,65,68,69,70,78,81,85,87,88,89,93,94,95,96,97,98,99,100,101,102,103,110,112,113,114,115,116],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/pull.py":[20,22,23,24,25,26,27,28,29,30,31,34,81,113,124,133,144,237,145,146,147,149,114,82,83,84,89,90,91,92,93,94,95,97,98,99,100,101,102,103,104,105,106,107,108,109,110,115,117,118,121,150,153,155,159,161,162,167,168,169,170,179,180,185,186,188,125,126,127,130,189,190,192,195,196,198,206,214,215,41,43,46,48,52,54,58,59,68,69,70,71,72,73,78,55,56,218,223,234,207,208,209,210,211,212,75,76,77,156,157,44,193,134,135,136,137,138,139,141,199,200,201,203,204,85,86,87,116,151],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/push.py":[18,20,21,23,24,25,26,27,28,29,32,61,68,89,103,108,186,109,110,111,113,114,117,62,33,34,35,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,63,65,118,121,123,126,127,128,133,134,135,136,137,143,144,104,105,146,147,148,151,155,156,157,158,159,160,162,163,164,96,97,98,166,172,69,70,71,72,73,74,75,79,80,81,82,86,173,183,138,139,140,141,177,178,179,149,152,153,76,77,78,175,176,124,167,168,169,170,100],"/var/scratch/src/git-buildpackage/git-buildpackage/gbp/scripts/rpm_ch.py":[19,21,22,23,24,25,26,28,29,30,31,32,34,35,36,37,38,41,44,46,48,70,79,88,106,125,138,150,171,224,240,265,279,310,386,402,457,404,388,312,313,314,319,320,321,322,323,324,325,326,327,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,350,351,352,353,354,355,356,357,358,360,361,363,364,365,366,367,368,369,370,372,373,374,375,377,378,379,380,381,382,383,389,392,394,395,397,399,405,408,409,411,91,92,412,110,111,112,113,114,414,415,127,128,131,418,140,144,145,146,147,421,152,153,156,158,160,161,162,168,49,51,56,57,62,63,66,67,68,422,226,229,230,174,176,179,180,181,182,185,194,195,196,197,198,199,206,207,208,209,233,236,237,425,427,428,429,430,434,435,267,268,269,270,271,272,273,274,275,276,436,282,283,242,244,249,250,253,254,256,257,258,262,284,285,286,287,288,289,290,296,297,298,306,307,439,72,76,77,441,452,454,227,245,246,247,132,133,134,444,445,446,447,186,187,188,189,190,191,200,211,212,215,216,217,218,219,220,201,202,203,204,205,221,234,141,142,159,58,59,60,232,164,52,53,54,73,74,157,166,291,292,293,117,118,442,82,85,119,120,301,302,303,426,431,93,94,95,97,98,96,102,103,315,316,317,390,406]}} \ No newline at end of file
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..e8a06d9
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,2 @@
+[run]
+include = gbp/*
diff --git a/.pydoctor.cfg b/.pydoctor.cfg
new file mode 100644
index 0000000..ede8e51
--- /dev/null
+++ b/.pydoctor.cfg
@@ -0,0 +1,4 @@
+projectname: git-buildpackage
+projecturl: https://honk.sigxcpu.org/piki/projects/git-buildpackage/
+htmloutput: build/apidocs
+packages: gbp,tests/doctests/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..bf95add
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,21 @@
+---
+sudo: required
+language: python
+
+services:
+ - docker
+
+python:
+ - "3.4"
+ - "3.5"
+ - "3.6"
+
+before_install:
+ - sudo apt-get -qq update
+ - sudo apt-get install -y pristine-tar devscripts
+
+install: "pip install -r dev_requirements.txt"
+script:
+ - flake8
+ - python $(which nosetests) -v tests/doctests/ tests/component/deb/
+ - packaging/run-in-docker
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/HACKING b/HACKING
new file mode 100644
index 0000000..877d73c
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,71 @@
+Running the Tests
+-----------------
+The tests are run via
+
+ make
+
+To also run the component tests, you need to initialize the git submodules once
+via:
+
+ git submodule update --init --recursive
+
+This will fetch the necessary binary data for the DEB and RPM component tests,
+and the tests are from now on included within each regular test run.
+
+Some tests reach out to the network. To run these in addition to all
+other tests use:
+
+ make all+net
+
+You can run the tests in a `debian:sid` docker container by using
+
+ packaging/run-in-docker
+
+Building the API Docs
+---------------------
+You can build the API docs using
+
+ make apidocs
+
+
+Contributing Patches
+--------------------
+Make sure the tests pass before sending in patch. You can either send it to the
+mailing list or add it to a bug report against git-buildpackage on
+
+ http://bugs.debian.org/src:git-buildpackage
+
+If you fix a regression or add a new feature please make sure this is covered
+by either a unittest (tests/*.py) or a component test that exercises one of the
+scripts (tests/component/{deb,rpm}/*.py.
+
+Layout
+------
+
+ gbp/scripts/*.py - the actual gbp commands (buildpackage, dch, …)
+ gbp/scripts/common/ - code shared between Debian and RPM commands
+ gbp/deb/ - Debian package handling (control, dsc, …)
+ gbp/rpm/ - RPM package handling (spec files, …)
+ gbp/git/ - Git repository interaction
+ tests/*.py - unit tests
+ tests/doctests - doctests that also serve as examples
+ tests/component/ - component tests that invoke actual commands
+
+Interfaces
+----------
+A gbp command in gbp/scripts/<commmand>.py must provide these interfaces:
+
+When one invokes `gbp <command>` gbp/scripts/<commmand>.py is imported by
+
+ gbp/scripts/supercommand.py
+
+which then invokes it's *main* function with all given command line arguments.
+It is expected to return with the exit status that should be passed back to the
+shell.
+
+When one invokes `gbp config <command>` gbp/scripts/<commmand>.py is imported by
+
+ gbp/scripts/config.py
+
+which then invokes it's *build_parser* function with the command name as argument.
+It is expected to return a GbpConfigParser with all config files parsed.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e791e9f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,31 @@
+PY_EXAMPLES=$(shell grep -l /usr/bin/python examples/*)
+FLAKE_OPTS=$(shell test -w /dev/shm || echo '-j1')
+NOSE_OPTS=--with-xcoverage
+TEST_LOCALE?=C.UTF-8
+
+all: syntax-check test
+
+all+net:
+ $(MAKE) GBP_NETWORK_TESTS=1 all
+
+test:
+ export GIT_AUTHOR_NAME="Gbp Tests"; \
+ export GIT_AUTHOR_EMAIL=tests@example.com; \
+ export GIT_COMMITTER_NAME=$$GIT_AUTHOR_NAME; \
+ export GIT_COMMITTER_EMAIL=$$GIT_AUTHOR_EMAIL; \
+ PYTHONPATH=. \
+ LC_ALL=$(TEST_LOCALE) python3 setup.py nosetests $(NOSE_OPTS)
+
+syntax-check:
+ flake8 $(FLAKE_OPTS)
+ flake8 $(FLAKE_OPTS) $(PY_EXAMPLES)
+
+docs:
+ $(MAKE) -C docs
+ $(MAKE) apidocs
+
+apidocs:
+ mkdir -p build
+ pydoctor -v --config=.pydoctor.cfg
+
+.PHONY: docs
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..564eaf6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+Git-buildpackage
+----------------
+Tools to ease the development of Debian and (partially) RPM packages in git
+repositories. For documentation see the manual online at:
+
+ https://gbp.sigxcpu.org/manual/
+
+On Debian systems, the documentation can be found in
+/usr/share/doc/git-buildpackage/manual-html.
+
+The API documentation of the gbp module can be found at:
+
+ https://gbp.sigxcpu.org/apidocs/
+
+The mailing list is at:
+
+ * https://lists.sigxcpu.org/mailman/listinfo/git-buildpackage
+ * git-buildpackage at lists.sigxcpu.org
+
+See the HACKING document for details on contributing to gbp development. The
+package is also available on Pypi at:
+
+ https://pypi.python.org/pypi/gbp/
+
+![gbp logo](docs/gbp.svg)
+
+[![Build Status](https://travis-ci.org/agx/git-buildpackage.svg?branch=master)](https://travis-ci.org/agx/git-buildpackage)
+[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1311/badge)](https://bestpractices.coreinfrastructure.org/projects/1311)
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..baaa055
--- /dev/null
+++ b/TODO
@@ -0,0 +1 @@
+See https://honk.sigxcpu.org/piki/projects/git-buildpackage/
diff --git a/bin/gbp-builder-mock b/bin/gbp-builder-mock
new file mode 100755
index 0000000..a498823
--- /dev/null
+++ b/bin/gbp-builder-mock
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Helper to invoke mock from 'gbp buildpackage-rpm'
+#
+# 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
+# <http://www.gnu.org/licenses/>
+#
+# Copyright (C) 2015 Tzafrir Cohen
+# (C) 2015 Guido Gunther
+
+set -e
+
+# There must be a saner way to do that or a reason why this is not required
+fix_arch() {
+ GBP_BUILDER_MOCK_ARCH=${GBP_BUILDER_MOCK_ARCH:-`uname -m`}
+ case "$ARCH" in
+ amd64) ARCH='x86_64';;
+ esac
+}
+
+
+usage() {
+ EXIT=${1:-1}
+ echo >&2 "$0: Must be run via 'gbp buildpackage-rpm', see manpage for details"
+ exit $EXIT
+}
+
+while [ $# != 0 ]; do
+ case "$1" in
+ --help|-h|-\?) usage 0;;
+ *.spec) SPEC="$1";;
+ esac
+ shift
+done
+
+# Make sure we have the necessary tools.
+if [ ! -x /usr/bin/mock ]; then
+ echo "mock not found; you need to install the mock package" >&2
+ exit 1
+fi
+
+gbp_builder_mock() {
+ if [ -z "$GBP_BUILDER_MOCK_DIST" ]; then
+ usage
+ fi
+ local root=${GBP_BUILDER_MOCK_ROOT:-${GBP_BUILDER_MOCK_DIST}-${GBP_BUILDER_MOCK_ARCH}}
+ if [ ! -d "$GBP_BUILDER_MOCK_EXPORT_DIR" ]; then
+ echo >&2 "$0: Missing output directory (GBP_BUILDER_MOCK_EXPORT_DIR). Aborting."
+ usage
+ fi
+ if [ -z "$SPEC" ]; then
+ echo >&2 "$0: No specfile given."
+ exit 1
+ fi
+ export_dir="$PWD"
+ spec="$export_dir/SPECS/$SPEC"
+ sources="$export_dir/SOURCES"
+ srpms="$export_dir/SRPMS"
+ pat="${GBP_BUILDER_MOCK_RESULTS_PAT-results/%(dist)s/%(target_arch)s/}"
+ local resultdir="$export_dir/$pat"
+ local mock="mock -r $root --resultdir=$srpms --spec=$spec --sources=$sources"
+
+ $mock --buildsrpm
+ # Assuming that nothing was built in this directory since the previous command:
+ local srpm=`ls -t $PWD/SRPMS/*.src.rpm 2>/dev/null| head -n1`
+ if [ -z $srpm ]; then
+ echo >&2 "$0: failed to create srpm"
+ exit 1
+ fi
+ $mock --no-cleanup-after --resultdir $resultdir --rebuild "$srpm"
+}
+
+
+fix_arch
+gbp_builder_mock
diff --git a/bin/git-pbuilder b/bin/git-pbuilder
new file mode 100755
index 0000000..9161b4f
--- /dev/null
+++ b/bin/git-pbuilder
@@ -0,0 +1,611 @@
+#!/bin/bash
+# $Id: git-pbuilder,v 1.48 2017/04/30 18:27:48 eagle Exp $
+#
+# Wrapper around pbuilder for gbp buildpackage
+#
+# Note that this script requires bash, not a POSIX shell, because it uses bash
+# arrays to handle GIT_PBUILDER_OPTIONS and GIT_PBUILDER_PDEBUILDOPTIONS.
+# It's otherwise quite difficult to get the contents of that environment
+# variable to undergo the correct amount of shell expansion.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Based on the example in the git-buildpackage documentation
+# Copyright 2014, 2015, 2016 Russ Allbery <eagle@eyrie.org>
+# Copyright 2008, 2009, 2010, 2011, 2012, 2013
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted, provided
+# that the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of Stanford University not be used in
+# advertising or publicity pertaining to distribution of the software without
+# specific, written prior permission. Stanford University makes no
+# representations about the suitability of this software for any purpose. It
+# is provided "as is" without express or implied warranty.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+set -e
+
+# Helper function to quote an argument so that it's protected from the shell.
+# This is used when passing arguments through in --debbuildopts, since they'll
+# undergo another round of shell expansion.
+shell_quote () {
+ echo "$1" | sed -e "s/'/'\"'\"'/g" -e "1 s/^/'/" -e "\$ s/\$/'/"
+}
+
+# unset variables we inherited from the environment ourselfes
+# Names like DIST are very generic and can trip up other build systems
+unset_env() {
+ export -n DIST BUILDER ARCH
+}
+
+case $1 in
+ -h|--help|-\?)
+ cat <<EOF
+git-pbuilder - Wrapper around cowbuilder/qemubuilder for gbp buildpackage
+
+Usage
+
+ DIST=distribution ARCH=architecture [BUILDER=(pbuilder|qemubuilder)] \
+ git-pbuilder debbuild-options
+
+ DIST=distribution ARCH=architecture [BUILDER=(pbuilder|qemubuilder)] \
+ git-pbuilder (update | create | login) cowbuilder-options
+
+See git-pbuilder(1) for details.
+EOF
+ exit 0
+ ;;
+esac
+
+# Set default BUILDER, DIST, and ARCH based on the name we were invoked as.
+# This allows people to create symlinks like git-pbuilder-squeeze and
+# git-qemubuilder-squeeze-amd64 pointing to git-pbuilder and auto-detecting
+# the builder, distribution, and architecture from that.
+default_BUILDER=${0#*git-}
+default_DIST=${default_BUILDER#*-}
+default_BUILDER=${default_BUILDER%%-*}
+case $default_BUILDER in
+ pbuilder|cowbuilder) default_BUILDER=cowbuilder ;;
+ /*) default_BUILDER=cowbuilder ;;
+esac
+case $default_BUILDER in
+ *builder) ;;
+ *) default_BUILDER=cowbuilder ;;
+esac
+case $default_DIST in
+ *builder)
+ default_DIST=
+ ;;
+ *-*)
+ default_ARCH=${default_DIST#*-}
+ default_DIST=${default_DIST%%-*}
+ ;;
+esac
+: ${default_BUILDER:=cowbuilder}
+
+# Set BUILDER, DIST, and ARCH, allowing existing settings to override.
+: ${BUILDER:=$default_BUILDER}
+: ${DIST:=$default_DIST}
+: ${ARCH:=$default_ARCH}
+
+# If DIST ends in -backports, strip that out of DIST and add it to EXT.
+if expr "$DIST" : '.*-backports$' >/dev/null; then
+ DIST=${DIST%-backports}
+ EXT="-backports"
+
+ # The URL to the Debian backports repository to add to the chroot
+ # configuration when created via this script for a distribution ending in
+ # -backports. Backports was incorporated into the main mirrors as of
+ # wheezy.
+ case $DIST in
+ squeeze)
+ BACKPORTS='http://backports.debian.org/debian-backports'
+ ;;
+ *)
+ BACKPORTS='http://ftp.debian.org/debian'
+ ;;
+ esac
+elif expr "$DIST" : '.*-lts$' >/dev/null; then
+ DIST=${DIST%-lts}
+ EXT="-lts"
+
+ # The URL to the Debian LTS repository to add to the chroot
+ # configuration when created via this script for a distribution ending in
+ # -lts.
+ LTS='http://ftp.debian.org/debian'
+else
+ EXT=
+fi
+
+# Make sure we have the necessary tools.
+if [ ! -x /usr/sbin/"$BUILDER" ]; then
+ echo "$BUILDER not found; you need to install the $BUILDER package" >&2
+ exit 1
+fi
+
+# Default options come from the environment. Use eval to parse
+# GIT_PBUILDER_OPTIONS and GIT_PBUILDER_PDEBUILDOPTIONS into arrays, since
+# some arguments may have quoting.
+eval "OPTIONS=( $GIT_PBUILDER_OPTIONS )"
+eval "PDEBUILDOPTS=( $GIT_PBUILDER_PDEBUILDOPTIONS )"
+OUTPUT_DIR="${GIT_PBUILDER_OUTPUT_DIR:-../}"
+
+# How we handle options depends on what type of builder we're using. Ignore
+# options if $GIT_PBUILDER_AUTOCONF is set to no.
+if [ no != "$GIT_PBUILDER_AUTOCONF" ]; then
+ case $BUILDER in
+ pbuilder)
+ # The root directory where different pbuilder --basetgz files are
+ # found. git-pbuilder expects them to be named base-<dist>.tgz.
+ : ${PBUILDER_BASE:=/var/cache/pbuilder}
+
+ # If DIST is set, use base-$DIST.tgz. If DIST is not set, the sid
+ # chroot may be either base.tgz or base-sid.tgz. Try both. If
+ # ARCH is set, use base-$DIST-$ARCH.tgz.
+ : ${DIST:=sid}
+ if [ -n "$ARCH" ] ; then
+ BASE="$PBUILDER_BASE/base-$DIST$EXT-$ARCH.tgz"
+ OPTIONS+=( --architecture "$ARCH" )
+ elif [ "$DIST" = 'sid' ] ; then
+ if [ -f "$PBUILDER_BASE/base-sid.tgz" ]; then
+ BASE="$PBUILDER_BASE/base-sid.tgz"
+ else
+ BASE="$PBUILDER_BASE/base.tgz"
+ fi
+ else
+ BASE="$PBUILDER_BASE/base-$DIST$EXT.tgz"
+ fi
+ OPTIONS+=( --basetgz "$BASE" )
+
+ # Make sure the base tarball exists.
+ if [ ! -f "$BASE" ] && [ "$1" != "create" ]; then
+ echo "Base tarball $BASE does not exist" >&2
+ exit 1
+ fi
+
+ # Set --debian-etch-workaround if DIST is etch. Assume that
+ # everything else is new enough that it will be fine.
+ if [ "$DIST" = 'etch' ] || [ "$DIST" = 'ebo' ]; then
+ OPTIONS+=( --debian-etch-workaround )
+ fi
+ ;;
+
+ cowbuilder)
+ # The root directory where different cowbuilder --basepath
+ # directories are found. git-pbuilder expects them to be named
+ # base-<dist>.cow.
+ : ${COWBUILDER_BASE:=/var/cache/pbuilder}
+
+ # If --basepath is specified on the command line we need to use
+ # that and that alone, since if it's specified more than once,
+ # cowbuilder will fail.
+ bp_found=""
+ for opt in "$@" "${OPTIONS[@]}"; do
+ case $opt in
+ --basepath|--basepath=*) bp_found="yes" ;;
+ esac
+ done
+
+ # If --basepath wasn't already provided, we need to set it. If
+ # DIST is set, use base-$DIST.cow. If DIST is not set, the sid
+ # chroot may be either base.cow or base-sid.cow. Try both. If
+ # ARCH is set, use base-$DIST-$ARCH.cow.
+ if [ -z "$bp_found" ]; then
+ : ${DIST:=sid}
+ if [ -n "$ARCH" ]; then
+ BASE="$COWBUILDER_BASE/base-$DIST$EXT-$ARCH.cow"
+ OPTIONS+=( --architecture "$ARCH" )
+ elif [ "$DIST" = 'sid' ]; then
+ if [ -d "$COWBUILDER_BASE/base-sid.cow" ] ; then
+ BASE="$COWBUILDER_BASE/base-sid.cow"
+ else
+ BASE="$COWBUILDER_BASE/base.cow"
+ fi
+ else
+ BASE="$COWBUILDER_BASE/base-$DIST$EXT.cow"
+ fi
+ OPTIONS+=( --basepath "$BASE" )
+
+ # Make sure the base directory exists.
+ if [ ! -d "$BASE" ] && [ "$1" != "create" ]; then
+ echo "Base directory $BASE does not exist" >&2
+ exit 1
+ fi
+ fi
+
+ # Set --debian-etch-workaround if DIST is etch. Assume that
+ # everything else is new enough that it will be fine.
+ if [ "$DIST" = 'etch' ] || [ "$DIST" = 'ebo' ]; then
+ OPTIONS+=( --debian-etch-workaround )
+ fi
+ ;;
+
+ qemubuilder)
+ # There always has to be an architecture for qemubuilder, and it
+ # doesn't make much sense to default to the current architecture.
+ # There's probably no good default, but this one at least makes
+ # some sense.
+ : ${DIST:=sid}
+ : ${ARCH:=armel}
+
+ # There has to be a configuration file matching our distribution
+ # and architecture.
+ QEMUCONFIG="/var/cache/pbuilder/$BUILDER-$ARCH-$DIST$EXT.conf"
+ if [ ! -r "$QEMUCONFIG" ]; then
+ echo "Cannot read configuration file $QEMUCONFIG" >&2
+ exit 1
+ fi
+ OPTIONS+=( --config "$QEMUCONFIG" )
+ ;;
+
+ *)
+ echo "Unknown builder $BUILDER" >&2
+ exit 1
+ ;;
+ esac
+fi
+
+# If the first argument to the script is update, create, or login (or
+# --update, --create, or --login), set the $action.
+case $1 in
+update|create|login)
+ action="--$1"
+ shift
+ ;;
+--update|--create|--login)
+ action="$1"
+ shift
+ ;;
+*)
+ action=""
+ ;;
+esac
+
+# If $action is set, run the builder for $action under sudo rather than
+# proceeding.
+if [ -n "$action" ]; then
+ # Since we're running the builder under sudo, $HOME will change to root's
+ # home directory and the user's .pbuilderrc won't be run. sudo -E would
+ # fix this, but that requires special configuration in sudoers to allow
+ # it. Instead, check if the user has a .pbuilderrc and, if so, explicitly
+ # add it as a configuration file.
+ if [ -f "$HOME/.pbuilderrc" ] ; then
+ OPTIONS+=( --configfile "$HOME/.pbuilderrc" )
+ fi
+
+ # Check that sudo is installed and try to provide a useful error if it
+ # is not.
+ if ! which sudo >/dev/null 2>&1; then
+ cat >&2 <<EOE
+git-pbuilder: sudo not found in $PATH
+
+sudo was not found in your path. You need to install the sudo package and
+allow the current user to invoke the builder via sudo.
+EOE
+ exit 1
+ fi
+
+ # Run the builder.
+ if [ no = "$GIT_PBUILDER_AUTOCONF" ] ; then
+ [ ! "$GIT_PBUILDER_DEBUG" ] || set -x
+ sudo "$BUILDER" "$action" "${OPTIONS[@]}" "$@"
+ else
+ if [ "$EXT" = '-backports' ] ; then
+ OTHERMIRROR="deb $BACKPORTS $DIST$EXT main"
+ [ ! "$GIT_PBUILDER_DEBUG" ] || set -x
+ sudo "$BUILDER" "$action" --distribution "$DIST" \
+ --othermirror "$OTHERMIRROR" "${OPTIONS[@]}" "$@"
+ elif [ "$EXT" = '-lts' ] ; then
+ OTHERMIRROR="deb $LTS $DIST$EXT main"
+ [ ! "$GIT_PBUILDER_DEBUG" ] || set -x
+ sudo "$BUILDER" "$action" --distribution "$DIST" \
+ --othermirror "$OTHERMIRROR" "${OPTIONS[@]}" "$@"
+ else
+ [ ! "$GIT_PBUILDER_DEBUG" ] || set -x
+ sudo "$BUILDER" "$action" --distribution "$DIST" \
+ "${OPTIONS[@]}" "$@"
+ fi
+ fi
+ { set +x; } 2>/dev/null
+ exit 0
+fi
+
+# Build package: not (update | create | login)
+if [ -z "$GBP_BUILD_DIR" ]; then
+ echo "Warning: git-pbuilder should be run via 'gbp buildpackage'" >&2
+fi
+
+# Print out some information about what we're doing.
+building="Building with $BUILDER"
+if [ no = "$GIT_PBUILDER_AUTOCONF" ] ; then
+ echo "$building"
+elif [ -n "$ARCH" ] ; then
+ echo "$building for distribution $DIST$EXT, architecture $ARCH"
+else
+ echo "$building for distribution $DIST$EXT"
+fi
+
+# Source package format 1.0 doesn't automatically exclude Git files, so we
+# want to add the appropriate flags to do that. But source package format 3.0
+# does exclude by default and has many other ways of controlling those
+# exclusions that we don't want to tromp on. So we don't want to give any -i
+# or -I flags unless we're using source format 1.0.
+if [ ! -f debian/source/format ] || grep -qs '^1.0' debian/source/format ; then
+ echo 'Source format 1.0 detected, adding exclude flags'
+ DEBBUILDOPTS="-i'(?:^|/)\\.git(attributes)?(?:\$|/.*\$)' -I.git"
+else
+ DEBBUILDOPTS=''
+fi
+
+# Add all of the additional arguments we got on the command line, but quote
+# them from the shell since they'll undergo another round of shell expansion
+# when the pbuilder runs debbuild.
+for arg in "$@" ; do
+ DEBBUILDOPTS+=" $(shell_quote "$arg")"
+done
+
+# Now we can finally run pdebuild. The quoting here is tricky, but this
+# seems to pass everything through properly.
+unset_env
+if [ no = "$GIT_PBUILDER_AUTOCONF" ] ; then
+ set -e
+ [ ! "$GIT_PBUILDER_DEBUG" ] || set -x
+ pdebuild --pbuilder "$BUILDER" --debbuildopts "$DEBBUILDOPTS" \
+ "${PDEBUILDOPTS[@]}" -- "${OPTIONS[@]}"
+ { set +x; } 2>/dev/null; set +e
+else
+ set -e
+ [ ! "$GIT_PBUILDER_DEBUG" ] || set -x
+ pdebuild --buildresult "$OUTPUT_DIR" --pbuilder "$BUILDER" \
+ --debbuildopts "$DEBBUILDOPTS" "${PDEBUILDOPTS[@]}" -- "${OPTIONS[@]}"
+ { set +x; } 2>/dev/null; set +e
+fi
+exit 0
+
+# Documentation. Use a hack to hide this from the shell. Because of the
+# above exit line, this should never be executed.
+DOCS=<<__END_OF_DOCS__
+
+=head1 NAME
+
+git-pbuilder - Wrapper around cowbuilder/qemubuilder for gbp buildpackage
+
+=head1 SYNOPSIS
+
+DIST=I<distribution> ARCH=I<architecture> [BUILDER=(pbuilder|qemubuilder)] \
+ B<git-pbuilder> I<debbuild-options>
+
+DIST=I<distribution> ARCH=I<architecture> [BUILDER=(pbuilder|qemubuilder)] \
+ B<git-pbuilder> (update | create | login) I<cowbuilder-options>
+
+=head1 DESCRIPTION
+
+B<git-pbuilder> is a wrapper around B<pdebuild> intended for use by
+B<gbp buildpackage>. It configures B<pdebuild> to use B<cowbuilder> by
+default, passes appropriate options to B<debbuild>, and sets the base path
+for B<cowbuilder> based on the environment variable DIST and, if set, the
+environment variable ARCH. B<qemubuilder> can be selected instead by
+setting the environment variable BUILDER to C<qemubuilder>, and
+B<pbuilder> can be selected by setting BUILDER to C<pbuilder>.
+
+By default, B<git-pbuilder> assumes the target distribution is C<sid>, the
+same architecture as the B<cowbuilder> default, and uses
+F</var/cache/pbuilder/base-sid.cow> if it exists. If it doesn't,
+F</var/cache/pbuilder/base.cow> is tried. If DIST is set, its value is
+the target distribution and F</var/cache/pbuilder/base-I<dist>.cow> is
+used instead. If DIST is C<etch> or C<ebo>, B<--debian-etch-workaround>
+is also passed to B<cowbuilder>. If ARCH is set, its value is the target
+architecture and F</var/cache/pbuilder/base-I<dist>-I<arch>.cow> is used,
+with I<dist> being set to C<sid> if DIST was not set.
+
+If B<qemubuilder> is used as the builder, no base directory is used.
+Instead, B<qemubuilder> is invoked with the B<--config> option pointing to
+the file F</var/cache/pbuilder/qemubuilder-I<arch>-I<dist>.conf>
+
+If B<pbuilder> is used as the builder, B<git-pbuilder> instead looks for
+F</var/cache/pbuilder/base-sid.tgz> by default and
+F</var/cache/pbuilder/base.tgz> if it doesn't exist. If DIST or ARCH are
+set, they are used to form the expected name of the tgz file in the same
+way as they're used to form the expected base directory for B<cowbuilder>.
+Similar to B<cowbuilder>, B<--debian-etch-workaround> is passed to
+B<pbuilder> if from the DIST setting it looks like the target distribution
+is etch.
+
+If B<git-pbuilder> is invoked via a name that starts with C<git-*->, the
+part between the hyphens is taken to be the default name of the builder to
+use. However, C<pbuilder> is mapped to B<cowbuilder> for backward
+compatibility; if you want to use B<pbuilder>, you have to explicitly set
+BUILDER. The part after the last hyphen is taken to be the default
+distribution (if it contains no additional hyphen) or the default
+distribution followed by the default architecture (if it contains a
+hyphen). One can therefore create symlinks like C<git-pbuilder-squeeze>
+pointing to B<git-pbuilder> and use that name when wanting to use a
+distribution of C<squeeze>, or C<git-qemubuilder-sid-armel> to use
+B<qemubuilder> to build for the C<armel> architecture and the C<sid>
+distribution. Explicit settings of BUILDER, DIST, or ARCH always override
+any guesses from the command name. (But note that B<gbp buildpackage>
+does not pass on environment variables when run with B<--git-pbuilder>;
+see below.)
+
+Any arguments are passed as-is to B<dpkg-buildpackage> via the
+B<--debbuildopts> option to B<pdebuild>. To pass arguments to the builder
+instead, put them in the environment variable GIT_PBUILDER_OPTIONS.
+
+To disable all attempts to discover the base path, tarball, or
+configuration file and set up the pbuilder options and instead rely on the
+settings in .pbuilderrc, set GIT_PBUILDER_AUTOCONF to C<no>.
+
+Normally, one does not run this script directly. Instead, it's used as
+the builder script for B<gbp buildpackage> via the B<--git-pbuilder>
+command-line option. When run this way, you should use the B<--git-dist>,
+B<--git-arch>, B<--git-qemubuilder>, B<--git-pbuilder-autoconf>, and
+B<--git-pbuilder-options> flags instead of setting the DIST, ARCH, BUILDER,
+GIT_PBUILDER_AUTOCONF, and GIT_PBUILDER_OPTIONS environment variables. See
+L<gbp-buildpackage(1)> for more information.
+
+Alternately, B<git-pbuilder> may be called with an argument of C<update>,
+C<create>, or C<login>. In this case, it calls B<cowbuilder> (or the
+configured builder as described above) using B<sudo> and passes the
+corresponding command to the builder, using the same logic as above to
+determine the base directory and distribution. If the distribution (set
+in DIST) ends in C<-backports>, one of the following will be added as an
+B<--othermirror> parameter to the builder:
+
+ deb http://ftp.debian.org/debian $DIST main
+ deb http://backports.debian.org/debian-backports $DIST main
+
+The first will be used for most distributions, and the second for
+C<squeeze-backports>. If the distribution ends in C<-lts>, the following will
+be added as an B<--othermirror> parameter to the builder:
+
+ deb http://ftp.debian.org/debian $DIST main
+
+to support building for Long Term Support releases.
+
+Any additional arguments to B<git-pbuilder> are passed along to the
+builder. Due to how B<sudo> works, invoking the builder with an action
+will not read the user's F<.pbuilderrc> by default, so in this case
+B<git-pbuilder> will add an explicit B<--configfile> option pointing to
+the user's F<.pbuilderrc> if it exists.
+
+If you use B<git-pbuilder> with one of these arguments, you must have
+the C<sudo> package installed, and you must configure B<sudo> to let the
+current user run the appropriate builder command.
+
+=head1 ENVIRONMENT
+
+=over 4
+
+=item ARCH
+
+Sets the target architecture. For a B<cowbuilder> builder, this sets both
+the base path and is passed as the B<--architecture> option. With
+B<qemubuilder>, this controls the path to the configuration file. With
+B<pbuilder>, this sets the tgz path and is passed as B<--architecture>.
+
+=item BUILDER
+
+Sets the builder to use. The only supported settings are C<cowbuilder>
+(the default), C<qemubuilder>, and C<pbuilder>.
+
+=item COWBUILDER_BASE
+
+Set this environment variable to change the default location for the
+cowbuilder base directories (F</var/cache/pbuilder>).
+
+=item DIST
+
+Sets the target distribution. This is used primarily to determine the
+base path for B<cowbuilder> or B<pbuilder> or the configuration file path
+for B<qemubuilder>, but it's also used to determine whether to pass
+B<--debian-etch-workaround> to B<cowbuilder> or B<pbuilder>.
+
+=item GIT_PBUILDER_AUTOCONF
+
+If set to C<no>, disable the logic that constructs the base path, tarball,
+or configuration file and all other logic to determine the options to pass
+to the builder. Instead, just run the configured builder and assume its
+configuration is handled elsewhere (such as in F<.pbuilderrc>). This also
+suppresses setting B<--buildresult>, so the user will need to ensure that
+the configuration still puts packages where B<gbp buildpackage> expects
+them.
+
+=item GIT_PBUILDER_DEBUG
+
+Set this environment variable to get additional debugging output like how
+cowbuilder is invoked.
+
+=item GIT_PBUILDER_OPTIONS
+
+Add additional options for the builder. These options are passed as-is to
+B<cowbuilder>, B<qemubuilder>, or B<pbuilder> via B<pdebuild>. The
+contents of this variable will undergo shell expansion, so any arguments
+containing shell metacharacters or whitespace need to be quoted in the
+value of the environment variable.
+
+=item GIT_PBUILDER_OUTPUT_DIR
+
+Where to put the result of the build. The default is C<..> (the parent
+directory). This setting is ignored if GIT_PBUILDER_AUTOCONF is set to
+C<no>.
+
+=item GIT_PBUILDER_PDEBUILDOPTIONS
+
+Add additional options for B<pdebuild> itself (such as
+B<--use-pdebuild-internal>). The contents of this variable will undergo
+shell expansion, so any arguments containing shell metacharacters or
+whitespace need to be quoted in the value of the environment variable.
+
+=item PBUILDER_BASE
+
+Set this environment variable to change the default location for the
+pbuilder tgz files (F</var/cache/pbuilder>) when BUILDER is set to
+C<pbuilder>.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item /var/cache/pbuilder/base-sid.cow
+
+=item /var/cache/pbuilder/base.cow
+
+The default C<cowbuilder --basepath> directories, searched for in that
+order, if neither DIST nor ARCH is set.
+
+=item /var/cache/pbuilder/base-sid-$ARCH.cow
+
+The C<cowbuilder --basepath> directory used if ARCH is set and DIST is not
+set.
+
+=item /var/cache/pbuilder/base-$DIST.cow
+
+The C<cowbuilder --basepath> directory used if DIST is set and ARCH is
+not.
+
+=item /var/cache/pbuilder/base-$DIST-$ARCH.cow
+
+The C<cowbuilder --basepath> directory used if DIST and ARCH are both set.
+
+=item /var/cache/pbuilder/base-sid.tgz
+
+=item /var/cache/pbuilder/base.tgz
+
+=item /var/cache/pbuilder/base-sid-$ARCH.tgz
+
+=item /var/cache/pbuilder/base-$DIST.tgz
+
+=item /var/cache/pbuilder/base-$DIST-$ARCH.tgz
+
+Similar to the above, the C<pbuilder --basetgz> path used for various
+settings of DIST and ARCH if BUILDER is set to C<pbuilder>.
+
+=item /var/cache/pbuilder/qemubuilder-$ARCH-$DIST.conf
+
+The C<qemubuilder --config> file used. $ARCH defaults to C<armel> and
+$DIST defaults to C<sid> if not set.
+
+=back
+
+=head1 SEE ALSO
+
+cowbuilder(8), dpkg-buildpackage(1), gbp-buildpackage(1), pbuilder(8),
+pdebuild(1), qemubuilder(8), sudo(8)
+
+The latest version of this script is available from
+L<http://www.eyrie.org/~eagle/software/scripts/>.
+
+=head1 AUTHOR
+
+Russ Allbery <eagle@eyrie.org>
+
+=cut
+
+__END_OF_DOCS__
diff --git a/coverage.xml b/coverage.xml
new file mode 100644
index 0000000..a400a9c
--- /dev/null
+++ b/coverage.xml
@@ -0,0 +1,9029 @@
+<?xml version="1.0" ?>
+<coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.8815" lines-covered="7604" lines-valid="8626" timestamp="1534513785233" version="4.5">
+ <!-- Generated by coverage.py: https://coverage.readthedocs.io -->
+ <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
+ <sources>
+ <source>/var/scratch/src/git-buildpackage/git-buildpackage/gbp</source>
+ </sources>
+ <packages>
+ <package branch-rate="0" complexity="0" line-rate="0.9124" name=".">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="__init__.py" line-rate="1" name="__init__.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="command_wrappers.py" line-rate="0.963" name="command_wrappers.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="127"/>
+ <line hits="0" number="129"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="0" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="260"/>
+ <line hits="0" number="261"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="271"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="293"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="295"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="298"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="306"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="319"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="321"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="329"/>
+ <line hits="0" number="330"/>
+ <line hits="1" number="332"/>
+ <line hits="0" number="333"/>
+ <line hits="0" number="334"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="344"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="349"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="363"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="config.py" line-rate="0.9124" name="config.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="0" number="30"/>
+ <line hits="0" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="0" number="51"/>
+ <line hits="0" number="52"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="0" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="376"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="386"/>
+ <line hits="1" number="388"/>
+ <line hits="1" number="389"/>
+ <line hits="1" number="417"/>
+ <line hits="1" number="418"/>
+ <line hits="1" number="419"/>
+ <line hits="1" number="420"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="422"/>
+ <line hits="1" number="424"/>
+ <line hits="1" number="426"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="428"/>
+ <line hits="1" number="429"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="431"/>
+ <line hits="1" number="432"/>
+ <line hits="1" number="433"/>
+ <line hits="1" number="435"/>
+ <line hits="1" number="436"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="440"/>
+ <line hits="1" number="442"/>
+ <line hits="1" number="443"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="447"/>
+ <line hits="0" number="448"/>
+ <line hits="0" number="450"/>
+ <line hits="0" number="452"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="457"/>
+ <line hits="1" number="459"/>
+ <line hits="1" number="474"/>
+ <line hits="1" number="475"/>
+ <line hits="1" number="476"/>
+ <line hits="1" number="477"/>
+ <line hits="1" number="478"/>
+ <line hits="1" number="479"/>
+ <line hits="1" number="481"/>
+ <line hits="1" number="483"/>
+ <line hits="1" number="485"/>
+ <line hits="1" number="492"/>
+ <line hits="1" number="493"/>
+ <line hits="1" number="494"/>
+ <line hits="1" number="495"/>
+ <line hits="1" number="496"/>
+ <line hits="1" number="497"/>
+ <line hits="0" number="498"/>
+ <line hits="1" number="499"/>
+ <line hits="0" number="500"/>
+ <line hits="0" number="501"/>
+ <line hits="1" number="503"/>
+ <line hits="1" number="508"/>
+ <line hits="1" number="510"/>
+ <line hits="1" number="511"/>
+ <line hits="1" number="512"/>
+ <line hits="1" number="513"/>
+ <line hits="1" number="514"/>
+ <line hits="1" number="515"/>
+ <line hits="1" number="517"/>
+ <line hits="1" number="518"/>
+ <line hits="1" number="521"/>
+ <line hits="1" number="525"/>
+ <line hits="1" number="527"/>
+ <line hits="1" number="528"/>
+ <line hits="1" number="529"/>
+ <line hits="1" number="530"/>
+ <line hits="1" number="531"/>
+ <line hits="1" number="533"/>
+ <line hits="1" number="534"/>
+ <line hits="1" number="535"/>
+ <line hits="1" number="536"/>
+ <line hits="1" number="537"/>
+ <line hits="1" number="538"/>
+ <line hits="1" number="541"/>
+ <line hits="1" number="544"/>
+ <line hits="1" number="546"/>
+ <line hits="0" number="547"/>
+ <line hits="0" number="548"/>
+ <line hits="0" number="550"/>
+ <line hits="1" number="553"/>
+ <line hits="1" number="555"/>
+ <line hits="1" number="567"/>
+ <line hits="1" number="568"/>
+ <line hits="1" number="569"/>
+ <line hits="1" number="570"/>
+ <line hits="1" number="571"/>
+ <line hits="1" number="572"/>
+ <line hits="1" number="573"/>
+ <line hits="1" number="575"/>
+ <line hits="1" number="576"/>
+ <line hits="1" number="577"/>
+ <line hits="1" number="578"/>
+ <line hits="1" number="580"/>
+ <line hits="1" number="585"/>
+ <line hits="1" number="587"/>
+ <line hits="1" number="588"/>
+ <line hits="1" number="589"/>
+ <line hits="1" number="590"/>
+ <line hits="1" number="591"/>
+ <line hits="1" number="592"/>
+ <line hits="1" number="593"/>
+ <line hits="1" number="595"/>
+ <line hits="1" number="600"/>
+ <line hits="1" number="601"/>
+ <line hits="1" number="602"/>
+ <line hits="1" number="604"/>
+ <line hits="1" number="605"/>
+ <line hits="1" number="607"/>
+ <line hits="1" number="608"/>
+ <line hits="1" number="609"/>
+ <line hits="1" number="610"/>
+ <line hits="1" number="612"/>
+ <line hits="1" number="613"/>
+ <line hits="1" number="614"/>
+ <line hits="1" number="615"/>
+ <line hits="0" number="617"/>
+ <line hits="1" number="618"/>
+ <line hits="1" number="620"/>
+ <line hits="1" number="622"/>
+ <line hits="1" number="623"/>
+ <line hits="1" number="625"/>
+ <line hits="1" number="626"/>
+ <line hits="1" number="628"/>
+ <line hits="1" number="629"/>
+ <line hits="1" number="630"/>
+ <line hits="1" number="631"/>
+ <line hits="1" number="632"/>
+ <line hits="1" number="633"/>
+ <line hits="1" number="634"/>
+ <line hits="1" number="636"/>
+ <line hits="1" number="637"/>
+ <line hits="1" number="647"/>
+ <line hits="1" number="648"/>
+ <line hits="1" number="649"/>
+ <line hits="1" number="650"/>
+ <line hits="1" number="651"/>
+ <line hits="1" number="652"/>
+ <line hits="1" number="653"/>
+ <line hits="1" number="657"/>
+ <line hits="1" number="658"/>
+ <line hits="1" number="659"/>
+ <line hits="1" number="660"/>
+ <line hits="1" number="662"/>
+ <line hits="1" number="671"/>
+ <line hits="1" number="673"/>
+ <line hits="1" number="678"/>
+ <line hits="1" number="679"/>
+ <line hits="1" number="681"/>
+ <line hits="1" number="682"/>
+ <line hits="1" number="683"/>
+ <line hits="1" number="684"/>
+ <line hits="0" number="685"/>
+ <line hits="1" number="686"/>
+ <line hits="0" number="687"/>
+ <line hits="0" number="688"/>
+ <line hits="0" number="689"/>
+ <line hits="1" number="691"/>
+ <line hits="1" number="704"/>
+ <line hits="1" number="705"/>
+ <line hits="1" number="706"/>
+ <line hits="1" number="708"/>
+ <line hits="1" number="710"/>
+ <line hits="1" number="711"/>
+ <line hits="1" number="717"/>
+ <line hits="0" number="718"/>
+ <line hits="1" number="719"/>
+ <line hits="0" number="720"/>
+ <line hits="1" number="723"/>
+ <line hits="1" number="724"/>
+ <line hits="1" number="725"/>
+ <line hits="1" number="726"/>
+ <line hits="1" number="727"/>
+ <line hits="1" number="728"/>
+ <line hits="1" number="729"/>
+ <line hits="1" number="732"/>
+ <line hits="1" number="733"/>
+ <line hits="1" number="734"/>
+ <line hits="1" number="744"/>
+ <line hits="1" number="745"/>
+ <line hits="1" number="746"/>
+ <line hits="1" number="747"/>
+ <line hits="1" number="748"/>
+ <line hits="1" number="749"/>
+ <line hits="1" number="750"/>
+ <line hits="1" number="754"/>
+ <line hits="1" number="755"/>
+ <line hits="1" number="756"/>
+ <line hits="1" number="757"/>
+ <line hits="1" number="760"/>
+ <line hits="1" number="764"/>
+ <line hits="1" number="765"/>
+ <line hits="1" number="769"/>
+ <line hits="1" number="770"/>
+ <line hits="0" number="771"/>
+ <line hits="1" number="774"/>
+ <line hits="1" number="778"/>
+ <line hits="1" number="779"/>
+ <line hits="1" number="804"/>
+ <line hits="1" number="805"/>
+ <line hits="1" number="868"/>
+ <line hits="1" number="871"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="dch.py" line-rate="0.7975" name="dch.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="0" number="33"/>
+ <line hits="0" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="43"/>
+ <line hits="0" number="44"/>
+ <line hits="0" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="87"/>
+ <line hits="0" number="88"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="0" number="96"/>
+ <line hits="0" number="97"/>
+ <line hits="0" number="98"/>
+ <line hits="0" number="99"/>
+ <line hits="0" number="100"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="113"/>
+ <line hits="0" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="0" number="116"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="125"/>
+ <line hits="0" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="0" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="0" number="135"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="errors.py" line-rate="1" name="errors.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="format.py" line-rate="1" name="format.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="log.py" line-rate="0.9255" name="log.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="0" number="72"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="76"/>
+ <line hits="0" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="0" number="88"/>
+ <line hits="0" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="117"/>
+ <line hits="0" number="119"/>
+ <line hits="0" number="120"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="149"/>
+ <line hits="0" number="150"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="178"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="notifications.py" line-rate="0.6765" name="notifications.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="0" number="32"/>
+ <line hits="0" number="33"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="49"/>
+ <line hits="0" number="50"/>
+ <line hits="0" number="51"/>
+ <line hits="0" number="52"/>
+ <line hits="0" number="53"/>
+ <line hits="0" number="54"/>
+ <line hits="0" number="55"/>
+ <line hits="0" number="56"/>
+ <line hits="0" number="57"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="0" number="73"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="patch_series.py" line-rate="0.9593" name="patch_series.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="0" number="84"/>
+ <line hits="0" number="85"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="0" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="194"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="219"/>
+ <line hits="0" number="220"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="223"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="231"/>
+ <line hits="0" number="232"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="241"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="261"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="272"/>
+ <line hits="1" number="273"/>
+ <line hits="0" number="274"/>
+ <line hits="0" number="275"/>
+ <line hits="1" number="277"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="304"/>
+ <line hits="1" number="305"/>
+ <line hits="1" number="306"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="323"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="327"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="357"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="376"/>
+ <line hits="1" number="377"/>
+ <line hits="1" number="378"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="paths.py" line-rate="1" name="paths.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="26"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="tmpfile.py" line-rate="1" name="tmpfile.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="tristate.py" line-rate="0.8222" name="tristate.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="0" number="37"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="0" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="95"/>
+ <line hits="0" number="100"/>
+ <line hits="0" number="101"/>
+ <line hits="0" number="103"/>
+ <line hits="0" number="104"/>
+ <line hits="0" number="105"/>
+ <line hits="0" number="107"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="version.py" line-rate="1" name="version.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="2"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ <package branch-rate="0" complexity="0" line-rate="0.901" name="deb">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="deb/__init__.py" line-rate="0.898" name="__init__.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="0" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="0" number="76"/>
+ <line hits="0" number="77"/>
+ <line hits="0" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="0" number="100"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/changelog.py" line-rate="0.9515" name="changelog.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="80"/>
+ <line hits="0" number="81"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="0" number="100"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="0" number="125"/>
+ <line hits="0" number="126"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="0" number="134"/>
+ <line hits="0" number="135"/>
+ <line hits="0" number="136"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="228"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="260"/>
+ <line hits="1" number="261"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="271"/>
+ <line hits="1" number="272"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="275"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="285"/>
+ <line hits="1" number="287"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="289"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="292"/>
+ <line hits="1" number="293"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="295"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="297"/>
+ <line hits="0" number="298"/>
+ <line hits="1" number="300"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="338"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="348"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="357"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="364"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="367"/>
+ <line hits="1" number="368"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/control.py" line-rate="0.8966" name="control.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="0" number="53"/>
+ <line hits="0" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="65"/>
+ <line hits="0" number="66"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="81"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/dscfile.py" line-rate="0.92" name="dscfile.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="0" number="72"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="0" number="112"/>
+ <line hits="1" number="114"/>
+ <line hits="0" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="0" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="0" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="0" number="136"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="0" number="142"/>
+ <line hits="0" number="143"/>
+ <line hits="1" number="144"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/format.py" line-rate="0.9412" name="format.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="116"/>
+ <line hits="0" number="117"/>
+ <line hits="0" number="118"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/git.py" line-rate="0.8803" name="git.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="0" number="94"/>
+ <line hits="0" number="95"/>
+ <line hits="0" number="96"/>
+ <line hits="0" number="97"/>
+ <line hits="0" number="98"/>
+ <line hits="0" number="99"/>
+ <line hits="0" number="101"/>
+ <line hits="0" number="102"/>
+ <line hits="0" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="128"/>
+ <line hits="0" number="129"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="272"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="300"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="304"/>
+ <line hits="1" number="305"/>
+ <line hits="1" number="306"/>
+ <line hits="0" number="307"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="311"/>
+ <line hits="0" number="312"/>
+ <line hits="0" number="313"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="319"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="326"/>
+ <line hits="1" number="327"/>
+ <line hits="1" number="329"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="331"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="362"/>
+ <line hits="0" number="363"/>
+ <line hits="0" number="364"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="367"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="370"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="372"/>
+ <line hits="0" number="373"/>
+ <line hits="0" number="374"/>
+ <line hits="1" number="376"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/policy.py" line-rate="0.913" name="policy.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="0" number="92"/>
+ <line hits="0" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/pristinetar.py" line-rate="1" name="pristinetar.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="67"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/rollbackgit.py" line-rate="0.8353" name="rollbackgit.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="4"/>
+ <line hits="1" number="7"/>
+ <line hits="1" number="8"/>
+ <line hits="1" number="9"/>
+ <line hits="1" number="12"/>
+ <line hits="1" number="16"/>
+ <line hits="0" number="17"/>
+ <line hits="0" number="18"/>
+ <line hits="0" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="0" number="22"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="0" number="51"/>
+ <line hits="0" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="0" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="0" number="102"/>
+ <line hits="0" number="103"/>
+ <line hits="0" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="0" number="106"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="0" number="133"/>
+ <line hits="0" number="134"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/source.py" line-rate="0.9429" name="source.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="0" number="81"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="0" number="113"/>
+ <line hits="0" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="146"/>
+ <line hits="0" number="147"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/upstreamsource.py" line-rate="0.9583" name="upstreamsource.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="0" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="deb/uscan.py" line-rate="0.7808" name="uscan.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="0" number="73"/>
+ <line hits="0" number="74"/>
+ <line hits="0" number="75"/>
+ <line hits="0" number="76"/>
+ <line hits="0" number="77"/>
+ <line hits="0" number="79"/>
+ <line hits="0" number="80"/>
+ <line hits="0" number="81"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="0" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="0" number="96"/>
+ <line hits="0" number="100"/>
+ <line hits="0" number="101"/>
+ <line hits="0" number="102"/>
+ <line hits="0" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="190"/>
+ <line hits="0" number="191"/>
+ <line hits="1" number="193"/>
+ <line hits="0" number="194"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="197"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ <package branch-rate="0" complexity="0" line-rate="0.9443" name="git">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="git/__init__.py" line-rate="1" name="__init__.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="git/args.py" line-rate="0.9655" name="args.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="0" number="65"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="108"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="git/commit.py" line-rate="1" name="commit.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="45"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="git/errors.py" line-rate="1" name="errors.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="git/fastimport.py" line-rate="0.8966" name="fastimport.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="44"/>
+ <line hits="0" number="45"/>
+ <line hits="0" number="46"/>
+ <line hits="0" number="47"/>
+ <line hits="0" number="48"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="111"/>
+ <line hits="0" number="112"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="143"/>
+ <line hits="0" number="144"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="git/modifier.py" line-rate="0.975" name="modifier.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="0" number="80"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="0" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="git/repository.py" line-rate="0.9435" name="repository.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="0" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="0" number="51"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="0" number="87"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="94"/>
+ <line hits="0" number="95"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="194"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="223"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="241"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="245"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="271"/>
+ <line hits="1" number="272"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="274"/>
+ <line hits="1" number="275"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="277"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="282"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="287"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="289"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="304"/>
+ <line hits="1" number="306"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="311"/>
+ <line hits="1" number="314"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="319"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="335"/>
+ <line hits="1" number="345"/>
+ <line hits="1" number="346"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="348"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="364"/>
+ <line hits="1" number="366"/>
+ <line hits="1" number="367"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="385"/>
+ <line hits="1" number="386"/>
+ <line hits="1" number="389"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="391"/>
+ <line hits="1" number="392"/>
+ <line hits="1" number="393"/>
+ <line hits="1" number="394"/>
+ <line hits="1" number="396"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="408"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="410"/>
+ <line hits="1" number="411"/>
+ <line hits="1" number="412"/>
+ <line hits="1" number="413"/>
+ <line hits="1" number="414"/>
+ <line hits="1" number="416"/>
+ <line hits="1" number="423"/>
+ <line hits="1" number="424"/>
+ <line hits="1" number="426"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="432"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="440"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="442"/>
+ <line hits="1" number="443"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="447"/>
+ <line hits="1" number="458"/>
+ <line hits="1" number="459"/>
+ <line hits="1" number="460"/>
+ <line hits="1" number="461"/>
+ <line hits="1" number="465"/>
+ <line hits="1" number="466"/>
+ <line hits="1" number="468"/>
+ <line hits="1" number="470"/>
+ <line hits="1" number="481"/>
+ <line hits="1" number="482"/>
+ <line hits="1" number="483"/>
+ <line hits="1" number="484"/>
+ <line hits="0" number="486"/>
+ <line hits="1" number="488"/>
+ <line hits="1" number="489"/>
+ <line hits="1" number="491"/>
+ <line hits="1" number="495"/>
+ <line hits="1" number="497"/>
+ <line hits="1" number="498"/>
+ <line hits="1" number="500"/>
+ <line hits="1" number="508"/>
+ <line hits="1" number="509"/>
+ <line hits="1" number="510"/>
+ <line hits="1" number="515"/>
+ <line hits="1" number="516"/>
+ <line hits="1" number="518"/>
+ <line hits="1" number="519"/>
+ <line hits="1" number="520"/>
+ <line hits="1" number="521"/>
+ <line hits="1" number="522"/>
+ <line hits="1" number="524"/>
+ <line hits="0" number="525"/>
+ <line hits="1" number="526"/>
+ <line hits="1" number="527"/>
+ <line hits="1" number="528"/>
+ <line hits="1" number="529"/>
+ <line hits="1" number="531"/>
+ <line hits="1" number="540"/>
+ <line hits="1" number="541"/>
+ <line hits="1" number="542"/>
+ <line hits="1" number="543"/>
+ <line hits="1" number="545"/>
+ <line hits="1" number="552"/>
+ <line hits="1" number="554"/>
+ <line hits="1" number="561"/>
+ <line hits="1" number="563"/>
+ <line hits="1" number="577"/>
+ <line hits="1" number="578"/>
+ <line hits="1" number="579"/>
+ <line hits="1" number="580"/>
+ <line hits="1" number="581"/>
+ <line hits="1" number="583"/>
+ <line hits="1" number="594"/>
+ <line hits="1" number="595"/>
+ <line hits="1" number="596"/>
+ <line hits="1" number="597"/>
+ <line hits="1" number="599"/>
+ <line hits="1" number="600"/>
+ <line hits="1" number="602"/>
+ <line hits="1" number="603"/>
+ <line hits="1" number="604"/>
+ <line hits="1" number="605"/>
+ <line hits="1" number="607"/>
+ <line hits="1" number="618"/>
+ <line hits="1" number="619"/>
+ <line hits="1" number="620"/>
+ <line hits="1" number="622"/>
+ <line hits="1" number="623"/>
+ <line hits="0" number="625"/>
+ <line hits="1" number="627"/>
+ <line hits="1" number="631"/>
+ <line hits="0" number="632"/>
+ <line hits="1" number="636"/>
+ <line hits="1" number="646"/>
+ <line hits="1" number="647"/>
+ <line hits="1" number="648"/>
+ <line hits="1" number="650"/>
+ <line hits="1" number="652"/>
+ <line hits="1" number="654"/>
+ <line hits="1" number="658"/>
+ <line hits="1" number="674"/>
+ <line hits="1" number="675"/>
+ <line hits="1" number="676"/>
+ <line hits="0" number="677"/>
+ <line hits="0" number="678"/>
+ <line hits="1" number="679"/>
+ <line hits="1" number="680"/>
+ <line hits="1" number="681"/>
+ <line hits="1" number="683"/>
+ <line hits="1" number="690"/>
+ <line hits="1" number="691"/>
+ <line hits="1" number="693"/>
+ <line hits="1" number="694"/>
+ <line hits="1" number="695"/>
+ <line hits="1" number="697"/>
+ <line hits="1" number="706"/>
+ <line hits="1" number="707"/>
+ <line hits="1" number="709"/>
+ <line hits="1" number="732"/>
+ <line hits="1" number="733"/>
+ <line hits="1" number="734"/>
+ <line hits="1" number="737"/>
+ <line hits="0" number="738"/>
+ <line hits="1" number="739"/>
+ <line hits="1" number="740"/>
+ <line hits="1" number="741"/>
+ <line hits="1" number="742"/>
+ <line hits="1" number="743"/>
+ <line hits="1" number="744"/>
+ <line hits="1" number="746"/>
+ <line hits="1" number="749"/>
+ <line hits="1" number="750"/>
+ <line hits="1" number="752"/>
+ <line hits="1" number="754"/>
+ <line hits="1" number="765"/>
+ <line hits="1" number="767"/>
+ <line hits="1" number="779"/>
+ <line hits="1" number="780"/>
+ <line hits="1" number="782"/>
+ <line hits="1" number="791"/>
+ <line hits="1" number="792"/>
+ <line hits="1" number="794"/>
+ <line hits="1" number="803"/>
+ <line hits="1" number="805"/>
+ <line hits="1" number="806"/>
+ <line hits="1" number="807"/>
+ <line hits="1" number="808"/>
+ <line hits="0" number="809"/>
+ <line hits="1" number="812"/>
+ <line hits="1" number="820"/>
+ <line hits="1" number="821"/>
+ <line hits="1" number="823"/>
+ <line hits="1" number="824"/>
+ <line hits="1" number="825"/>
+ <line hits="1" number="827"/>
+ <line hits="1" number="828"/>
+ <line hits="1" number="829"/>
+ <line hits="1" number="830"/>
+ <line hits="1" number="832"/>
+ <line hits="1" number="833"/>
+ <line hits="1" number="834"/>
+ <line hits="1" number="835"/>
+ <line hits="1" number="837"/>
+ <line hits="1" number="838"/>
+ <line hits="1" number="839"/>
+ <line hits="1" number="840"/>
+ <line hits="1" number="842"/>
+ <line hits="1" number="845"/>
+ <line hits="0" number="846"/>
+ <line hits="1" number="847"/>
+ <line hits="1" number="849"/>
+ <line hits="1" number="862"/>
+ <line hits="1" number="863"/>
+ <line hits="1" number="865"/>
+ <line hits="1" number="868"/>
+ <line hits="1" number="870"/>
+ <line hits="1" number="873"/>
+ <line hits="1" number="875"/>
+ <line hits="1" number="877"/>
+ <line hits="1" number="888"/>
+ <line hits="1" number="889"/>
+ <line hits="1" number="890"/>
+ <line hits="1" number="891"/>
+ <line hits="1" number="893"/>
+ <line hits="1" number="895"/>
+ <line hits="0" number="896"/>
+ <line hits="1" number="898"/>
+ <line hits="1" number="907"/>
+ <line hits="1" number="908"/>
+ <line hits="1" number="909"/>
+ <line hits="1" number="910"/>
+ <line hits="1" number="912"/>
+ <line hits="1" number="914"/>
+ <line hits="0" number="915"/>
+ <line hits="1" number="917"/>
+ <line hits="1" number="918"/>
+ <line hits="1" number="920"/>
+ <line hits="1" number="921"/>
+ <line hits="1" number="922"/>
+ <line hits="1" number="923"/>
+ <line hits="1" number="925"/>
+ <line hits="1" number="926"/>
+ <line hits="1" number="927"/>
+ <line hits="1" number="929"/>
+ <line hits="1" number="931"/>
+ <line hits="1" number="940"/>
+ <line hits="1" number="942"/>
+ <line hits="1" number="953"/>
+ <line hits="1" number="954"/>
+ <line hits="1" number="955"/>
+ <line hits="1" number="956"/>
+ <line hits="1" number="957"/>
+ <line hits="1" number="958"/>
+ <line hits="1" number="959"/>
+ <line hits="1" number="961"/>
+ <line hits="1" number="962"/>
+ <line hits="1" number="982"/>
+ <line hits="1" number="983"/>
+ <line hits="1" number="985"/>
+ <line hits="1" number="987"/>
+ <line hits="1" number="988"/>
+ <line hits="1" number="990"/>
+ <line hits="1" number="993"/>
+ <line hits="1" number="1000"/>
+ <line hits="1" number="1002"/>
+ <line hits="1" number="1011"/>
+ <line hits="1" number="1013"/>
+ <line hits="1" number="1015"/>
+ <line hits="1" number="1024"/>
+ <line hits="1" number="1025"/>
+ <line hits="1" number="1027"/>
+ <line hits="1" number="1029"/>
+ <line hits="1" number="1032"/>
+ <line hits="1" number="1033"/>
+ <line hits="1" number="1034"/>
+ <line hits="1" number="1036"/>
+ <line hits="1" number="1043"/>
+ <line hits="1" number="1044"/>
+ <line hits="1" number="1046"/>
+ <line hits="1" number="1047"/>
+ <line hits="1" number="1048"/>
+ <line hits="1" number="1050"/>
+ <line hits="1" number="1054"/>
+ <line hits="0" number="1055"/>
+ <line hits="1" number="1056"/>
+ <line hits="1" number="1058"/>
+ <line hits="1" number="1067"/>
+ <line hits="1" number="1068"/>
+ <line hits="0" number="1069"/>
+ <line hits="1" number="1070"/>
+ <line hits="1" number="1072"/>
+ <line hits="1" number="1084"/>
+ <line hits="1" number="1085"/>
+ <line hits="1" number="1086"/>
+ <line hits="1" number="1087"/>
+ <line hits="1" number="1088"/>
+ <line hits="1" number="1090"/>
+ <line hits="1" number="1091"/>
+ <line hits="0" number="1092"/>
+ <line hits="1" number="1094"/>
+ <line hits="1" number="1095"/>
+ <line hits="1" number="1096"/>
+ <line hits="1" number="1097"/>
+ <line hits="1" number="1099"/>
+ <line hits="1" number="1100"/>
+ <line hits="1" number="1101"/>
+ <line hits="1" number="1102"/>
+ <line hits="1" number="1106"/>
+ <line hits="1" number="1114"/>
+ <line hits="1" number="1115"/>
+ <line hits="1" number="1116"/>
+ <line hits="1" number="1117"/>
+ <line hits="1" number="1119"/>
+ <line hits="1" number="1123"/>
+ <line hits="1" number="1124"/>
+ <line hits="1" number="1126"/>
+ <line hits="1" number="1132"/>
+ <line hits="1" number="1134"/>
+ <line hits="1" number="1140"/>
+ <line hits="1" number="1142"/>
+ <line hits="1" number="1150"/>
+ <line hits="1" number="1151"/>
+ <line hits="1" number="1152"/>
+ <line hits="1" number="1153"/>
+ <line hits="1" number="1154"/>
+ <line hits="1" number="1155"/>
+ <line hits="1" number="1156"/>
+ <line hits="1" number="1157"/>
+ <line hits="1" number="1158"/>
+ <line hits="1" number="1159"/>
+ <line hits="1" number="1160"/>
+ <line hits="1" number="1164"/>
+ <line hits="1" number="1171"/>
+ <line hits="1" number="1174"/>
+ <line hits="0" number="1175"/>
+ <line hits="1" number="1178"/>
+ <line hits="1" number="1179"/>
+ <line hits="1" number="1180"/>
+ <line hits="1" number="1183"/>
+ <line hits="0" number="1184"/>
+ <line hits="1" number="1186"/>
+ <line hits="1" number="1187"/>
+ <line hits="1" number="1188"/>
+ <line hits="1" number="1189"/>
+ <line hits="1" number="1190"/>
+ <line hits="1" number="1191"/>
+ <line hits="1" number="1192"/>
+ <line hits="1" number="1193"/>
+ <line hits="1" number="1194"/>
+ <line hits="1" number="1195"/>
+ <line hits="1" number="1197"/>
+ <line hits="1" number="1199"/>
+ <line hits="1" number="1208"/>
+ <line hits="1" number="1209"/>
+ <line hits="1" number="1211"/>
+ <line hits="1" number="1220"/>
+ <line hits="1" number="1221"/>
+ <line hits="1" number="1223"/>
+ <line hits="1" number="1225"/>
+ <line hits="1" number="1238"/>
+ <line hits="1" number="1239"/>
+ <line hits="1" number="1240"/>
+ <line hits="1" number="1241"/>
+ <line hits="1" number="1242"/>
+ <line hits="1" number="1244"/>
+ <line hits="1" number="1245"/>
+ <line hits="1" number="1246"/>
+ <line hits="1" number="1248"/>
+ <line hits="1" number="1264"/>
+ <line hits="1" number="1265"/>
+ <line hits="1" number="1266"/>
+ <line hits="1" number="1267"/>
+ <line hits="1" number="1268"/>
+ <line hits="1" number="1270"/>
+ <line hits="1" number="1271"/>
+ <line hits="1" number="1273"/>
+ <line hits="1" number="1275"/>
+ <line hits="1" number="1286"/>
+ <line hits="1" number="1287"/>
+ <line hits="1" number="1288"/>
+ <line hits="1" number="1289"/>
+ <line hits="1" number="1291"/>
+ <line hits="1" number="1292"/>
+ <line hits="1" number="1294"/>
+ <line hits="1" number="1315"/>
+ <line hits="1" number="1316"/>
+ <line hits="1" number="1317"/>
+ <line hits="1" number="1318"/>
+ <line hits="1" number="1319"/>
+ <line hits="1" number="1322"/>
+ <line hits="1" number="1323"/>
+ <line hits="1" number="1324"/>
+ <line hits="1" number="1325"/>
+ <line hits="1" number="1326"/>
+ <line hits="0" number="1327"/>
+ <line hits="1" number="1328"/>
+ <line hits="1" number="1330"/>
+ <line hits="1" number="1332"/>
+ <line hits="1" number="1343"/>
+ <line hits="1" number="1344"/>
+ <line hits="1" number="1345"/>
+ <line hits="1" number="1349"/>
+ <line hits="1" number="1360"/>
+ <line hits="1" number="1362"/>
+ <line hits="1" number="1363"/>
+ <line hits="1" number="1365"/>
+ <line hits="1" number="1367"/>
+ <line hits="1" number="1368"/>
+ <line hits="1" number="1370"/>
+ <line hits="1" number="1371"/>
+ <line hits="1" number="1373"/>
+ <line hits="1" number="1375"/>
+ <line hits="0" number="1384"/>
+ <line hits="0" number="1385"/>
+ <line hits="0" number="1387"/>
+ <line hits="0" number="1388"/>
+ <line hits="1" number="1390"/>
+ <line hits="1" number="1399"/>
+ <line hits="1" number="1401"/>
+ <line hits="1" number="1403"/>
+ <line hits="1" number="1404"/>
+ <line hits="1" number="1405"/>
+ <line hits="1" number="1407"/>
+ <line hits="1" number="1408"/>
+ <line hits="1" number="1409"/>
+ <line hits="0" number="1410"/>
+ <line hits="1" number="1411"/>
+ <line hits="1" number="1412"/>
+ <line hits="1" number="1414"/>
+ <line hits="1" number="1416"/>
+ <line hits="1" number="1427"/>
+ <line hits="1" number="1428"/>
+ <line hits="1" number="1429"/>
+ <line hits="1" number="1431"/>
+ <line hits="1" number="1434"/>
+ <line hits="1" number="1435"/>
+ <line hits="0" number="1437"/>
+ <line hits="1" number="1439"/>
+ <line hits="1" number="1443"/>
+ <line hits="1" number="1444"/>
+ <line hits="1" number="1447"/>
+ <line hits="1" number="1448"/>
+ <line hits="1" number="1453"/>
+ <line hits="1" number="1454"/>
+ <line hits="1" number="1455"/>
+ <line hits="1" number="1457"/>
+ <line hits="1" number="1468"/>
+ <line hits="1" number="1469"/>
+ <line hits="1" number="1470"/>
+ <line hits="1" number="1472"/>
+ <line hits="1" number="1480"/>
+ <line hits="1" number="1481"/>
+ <line hits="1" number="1482"/>
+ <line hits="1" number="1484"/>
+ <line hits="1" number="1495"/>
+ <line hits="1" number="1496"/>
+ <line hits="1" number="1497"/>
+ <line hits="1" number="1499"/>
+ <line hits="1" number="1522"/>
+ <line hits="1" number="1523"/>
+ <line hits="1" number="1524"/>
+ <line hits="1" number="1525"/>
+ <line hits="1" number="1526"/>
+ <line hits="1" number="1527"/>
+ <line hits="1" number="1529"/>
+ <line hits="1" number="1531"/>
+ <line hits="1" number="1532"/>
+ <line hits="1" number="1533"/>
+ <line hits="1" number="1534"/>
+ <line hits="1" number="1535"/>
+ <line hits="1" number="1536"/>
+ <line hits="1" number="1537"/>
+ <line hits="0" number="1539"/>
+ <line hits="1" number="1541"/>
+ <line hits="1" number="1542"/>
+ <line hits="1" number="1545"/>
+ <line hits="1" number="1546"/>
+ <line hits="1" number="1547"/>
+ <line hits="1" number="1548"/>
+ <line hits="1" number="1549"/>
+ <line hits="1" number="1550"/>
+ <line hits="1" number="1551"/>
+ <line hits="1" number="1552"/>
+ <line hits="1" number="1554"/>
+ <line hits="1" number="1556"/>
+ <line hits="0" number="1557"/>
+ <line hits="1" number="1558"/>
+ <line hits="1" number="1560"/>
+ <line hits="1" number="1562"/>
+ <line hits="1" number="1574"/>
+ <line hits="1" number="1575"/>
+ <line hits="1" number="1576"/>
+ <line hits="1" number="1577"/>
+ <line hits="1" number="1578"/>
+ <line hits="1" number="1579"/>
+ <line hits="1" number="1580"/>
+ <line hits="1" number="1582"/>
+ <line hits="1" number="1583"/>
+ <line hits="1" number="1584"/>
+ <line hits="1" number="1585"/>
+ <line hits="1" number="1590"/>
+ <line hits="1" number="1591"/>
+ <line hits="1" number="1593"/>
+ <line hits="1" number="1597"/>
+ <line hits="1" number="1616"/>
+ <line hits="1" number="1617"/>
+ <line hits="1" number="1618"/>
+ <line hits="1" number="1619"/>
+ <line hits="1" number="1620"/>
+ <line hits="1" number="1621"/>
+ <line hits="1" number="1622"/>
+ <line hits="1" number="1623"/>
+ <line hits="1" number="1624"/>
+ <line hits="1" number="1625"/>
+ <line hits="1" number="1626"/>
+ <line hits="1" number="1627"/>
+ <line hits="1" number="1629"/>
+ <line hits="1" number="1630"/>
+ <line hits="0" number="1631"/>
+ <line hits="0" number="1632"/>
+ <line hits="1" number="1634"/>
+ <line hits="1" number="1636"/>
+ <line hits="1" number="1642"/>
+ <line hits="1" number="1644"/>
+ <line hits="1" number="1645"/>
+ <line hits="1" number="1646"/>
+ <line hits="1" number="1648"/>
+ <line hits="1" number="1657"/>
+ <line hits="1" number="1658"/>
+ <line hits="1" number="1659"/>
+ <line hits="1" number="1660"/>
+ <line hits="1" number="1661"/>
+ <line hits="1" number="1663"/>
+ <line hits="1" number="1665"/>
+ <line hits="1" number="1666"/>
+ <line hits="1" number="1668"/>
+ <line hits="1" number="1669"/>
+ <line hits="1" number="1671"/>
+ <line hits="1" number="1673"/>
+ <line hits="1" number="1683"/>
+ <line hits="1" number="1685"/>
+ <line hits="1" number="1694"/>
+ <line hits="1" number="1695"/>
+ <line hits="1" number="1698"/>
+ <line hits="1" number="1699"/>
+ <line hits="0" number="1700"/>
+ <line hits="1" number="1703"/>
+ <line hits="1" number="1705"/>
+ <line hits="1" number="1708"/>
+ <line hits="1" number="1712"/>
+ <line hits="1" number="1713"/>
+ <line hits="1" number="1716"/>
+ <line hits="1" number="1717"/>
+ <line hits="1" number="1718"/>
+ <line hits="1" number="1719"/>
+ <line hits="1" number="1720"/>
+ <line hits="1" number="1721"/>
+ <line hits="1" number="1723"/>
+ <line hits="1" number="1732"/>
+ <line hits="1" number="1749"/>
+ <line hits="1" number="1751"/>
+ <line hits="1" number="1752"/>
+ <line hits="1" number="1753"/>
+ <line hits="1" number="1755"/>
+ <line hits="1" number="1756"/>
+ <line hits="1" number="1758"/>
+ <line hits="1" number="1760"/>
+ <line hits="1" number="1761"/>
+ <line hits="0" number="1762"/>
+ <line hits="1" number="1763"/>
+ <line hits="1" number="1764"/>
+ <line hits="1" number="1765"/>
+ <line hits="1" number="1766"/>
+ <line hits="1" number="1767"/>
+ <line hits="1" number="1768"/>
+ <line hits="1" number="1769"/>
+ <line hits="1" number="1770"/>
+ <line hits="1" number="1772"/>
+ <line hits="1" number="1794"/>
+ <line hits="1" number="1795"/>
+ <line hits="1" number="1796"/>
+ <line hits="0" number="1797"/>
+ <line hits="1" number="1798"/>
+ <line hits="1" number="1799"/>
+ <line hits="1" number="1800"/>
+ <line hits="1" number="1801"/>
+ <line hits="1" number="1802"/>
+ <line hits="1" number="1803"/>
+ <line hits="1" number="1804"/>
+ <line hits="0" number="1806"/>
+ <line hits="1" number="1807"/>
+ <line hits="1" number="1808"/>
+ <line hits="1" number="1809"/>
+ <line hits="1" number="1810"/>
+ <line hits="1" number="1811"/>
+ <line hits="1" number="1812"/>
+ <line hits="1" number="1813"/>
+ <line hits="1" number="1816"/>
+ <line hits="0" number="1817"/>
+ <line hits="1" number="1818"/>
+ <line hits="1" number="1820"/>
+ <line hits="1" number="1831"/>
+ <line hits="1" number="1832"/>
+ <line hits="1" number="1834"/>
+ <line hits="1" number="1835"/>
+ <line hits="1" number="1837"/>
+ <line hits="1" number="1838"/>
+ <line hits="1" number="1839"/>
+ <line hits="1" number="1841"/>
+ <line hits="0" number="1842"/>
+ <line hits="1" number="1843"/>
+ <line hits="1" number="1845"/>
+ <line hits="1" number="1848"/>
+ <line hits="1" number="1863"/>
+ <line hits="1" number="1867"/>
+ <line hits="1" number="1868"/>
+ <line hits="0" number="1869"/>
+ <line hits="1" number="1871"/>
+ <line hits="1" number="1878"/>
+ <line hits="1" number="1879"/>
+ <line hits="1" number="1880"/>
+ <line hits="1" number="1882"/>
+ <line hits="1" number="1883"/>
+ <line hits="1" number="1884"/>
+ <line hits="1" number="1885"/>
+ <line hits="1" number="1890"/>
+ <line hits="1" number="1900"/>
+ <line hits="1" number="1901"/>
+ <line hits="1" number="1902"/>
+ <line hits="1" number="1903"/>
+ <line hits="1" number="1904"/>
+ <line hits="1" number="1905"/>
+ <line hits="1" number="1906"/>
+ <line hits="1" number="1908"/>
+ <line hits="1" number="1915"/>
+ <line hits="1" number="1917"/>
+ <line hits="1" number="1929"/>
+ <line hits="1" number="1930"/>
+ <line hits="1" number="1931"/>
+ <line hits="1" number="1932"/>
+ <line hits="1" number="1933"/>
+ <line hits="1" number="1934"/>
+ <line hits="1" number="1935"/>
+ <line hits="1" number="1936"/>
+ <line hits="1" number="1937"/>
+ <line hits="1" number="1939"/>
+ <line hits="1" number="1941"/>
+ <line hits="1" number="1951"/>
+ <line hits="1" number="1952"/>
+ <line hits="1" number="1953"/>
+ <line hits="1" number="1955"/>
+ <line hits="1" number="1956"/>
+ <line hits="1" number="1957"/>
+ <line hits="1" number="1959"/>
+ <line hits="1" number="1963"/>
+ <line hits="1" number="1964"/>
+ <line hits="1" number="1966"/>
+ <line hits="1" number="1967"/>
+ <line hits="1" number="1968"/>
+ <line hits="1" number="1969"/>
+ <line hits="1" number="1972"/>
+ <line hits="1" number="1973"/>
+ <line hits="1" number="1974"/>
+ <line hits="1" number="1976"/>
+ <line hits="1" number="1977"/>
+ <line hits="1" number="1979"/>
+ <line hits="1" number="1983"/>
+ <line hits="1" number="1984"/>
+ <line hits="1" number="1995"/>
+ <line hits="1" number="1996"/>
+ <line hits="1" number="1998"/>
+ <line hits="1" number="1999"/>
+ <line hits="1" number="2001"/>
+ <line hits="1" number="2002"/>
+ <line hits="1" number="2003"/>
+ <line hits="1" number="2004"/>
+ <line hits="1" number="2005"/>
+ <line hits="0" number="2011"/>
+ <line hits="0" number="2012"/>
+ <line hits="1" number="2013"/>
+ <line hits="0" number="2014"/>
+ <line hits="1" number="2016"/>
+ <line hits="1" number="2017"/>
+ <line hits="1" number="2018"/>
+ <line hits="1" number="2019"/>
+ <line hits="1" number="2020"/>
+ <line hits="1" number="2021"/>
+ <line hits="1" number="2022"/>
+ <line hits="0" number="2024"/>
+ <line hits="1" number="2026"/>
+ <line hits="1" number="2027"/>
+ <line hits="1" number="2052"/>
+ <line hits="1" number="2053"/>
+ <line hits="1" number="2054"/>
+ <line hits="1" number="2056"/>
+ <line hits="1" number="2058"/>
+ <line hits="1" number="2059"/>
+ <line hits="1" number="2060"/>
+ <line hits="1" number="2061"/>
+ <line hits="1" number="2062"/>
+ <line hits="1" number="2063"/>
+ <line hits="1" number="2064"/>
+ <line hits="1" number="2065"/>
+ <line hits="1" number="2066"/>
+ <line hits="1" number="2067"/>
+ <line hits="1" number="2068"/>
+ <line hits="1" number="2070"/>
+ <line hits="1" number="2071"/>
+ <line hits="0" number="2077"/>
+ <line hits="0" number="2078"/>
+ <line hits="1" number="2079"/>
+ <line hits="1" number="2080"/>
+ <line hits="1" number="2082"/>
+ <line hits="1" number="2083"/>
+ <line hits="1" number="2084"/>
+ <line hits="0" number="2085"/>
+ <line hits="0" number="2086"/>
+ <line hits="1" number="2087"/>
+ <line hits="1" number="2088"/>
+ <line hits="1" number="2089"/>
+ <line hits="1" number="2090"/>
+ <line hits="0" number="2091"/>
+ <line hits="1" number="2092"/>
+ <line hits="1" number="2093"/>
+ <line hits="0" number="2094"/>
+ <line hits="0" number="2097"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="git/vfs.py" line-rate="0.9189" name="vfs.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="0" number="38"/>
+ <line hits="0" number="39"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="0" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ <package branch-rate="0" complexity="0" line-rate="0.9301" name="pkg">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="pkg/__init__.py" line-rate="1" name="__init__.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="pkg/archive.py" line-rate="1" name="archive.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="80"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="pkg/compressor.py" line-rate="1" name="compressor.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="pkg/git.py" line-rate="0.9623" name="git.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="57"/>
+ <line hits="0" number="58"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="0" number="101"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="pkg/pkgpolicy.py" line-rate="0.9138" name="pkgpolicy.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="116"/>
+ <line hits="0" number="117"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="148"/>
+ <line hits="0" number="149"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="0" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="0" number="161"/>
+ <line hits="0" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="pkg/pristinetar.py" line-rate="0.975" name="pristinetar.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="0" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="pkg/upstreamsource.py" line-rate="0.8588" name="upstreamsource.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="0" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="102"/>
+ <line hits="0" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="108"/>
+ <line hits="0" number="109"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="0" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="150"/>
+ <line hits="0" number="151"/>
+ <line hits="0" number="153"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="166"/>
+ <line hits="0" number="167"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="172"/>
+ <line hits="0" number="173"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="181"/>
+ <line hits="0" number="182"/>
+ <line hits="0" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="192"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ <package branch-rate="0" complexity="0" line-rate="0.9461" name="rpm">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="rpm/__init__.py" line-rate="0.9529" name="__init__.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="0" number="205"/>
+ <line hits="0" number="206"/>
+ <line hits="0" number="207"/>
+ <line hits="0" number="209"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="229"/>
+ <line hits="1" number="230"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="251"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="0" number="258"/>
+ <line hits="1" number="260"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="271"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="277"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="287"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="293"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="297"/>
+ <line hits="1" number="300"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="304"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="311"/>
+ <line hits="1" number="313"/>
+ <line hits="0" number="315"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="321"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="326"/>
+ <line hits="1" number="328"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="338"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="342"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="344"/>
+ <line hits="1" number="346"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="355"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="364"/>
+ <line hits="1" number="367"/>
+ <line hits="1" number="368"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="370"/>
+ <line hits="1" number="372"/>
+ <line hits="1" number="374"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="376"/>
+ <line hits="1" number="377"/>
+ <line hits="1" number="378"/>
+ <line hits="1" number="379"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="385"/>
+ <line hits="1" number="387"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="391"/>
+ <line hits="1" number="393"/>
+ <line hits="1" number="396"/>
+ <line hits="1" number="397"/>
+ <line hits="1" number="398"/>
+ <line hits="1" number="399"/>
+ <line hits="1" number="400"/>
+ <line hits="1" number="401"/>
+ <line hits="1" number="403"/>
+ <line hits="1" number="404"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="407"/>
+ <line hits="1" number="408"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="411"/>
+ <line hits="1" number="413"/>
+ <line hits="1" number="417"/>
+ <line hits="1" number="418"/>
+ <line hits="1" number="419"/>
+ <line hits="1" number="420"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="422"/>
+ <line hits="1" number="423"/>
+ <line hits="1" number="424"/>
+ <line hits="1" number="425"/>
+ <line hits="1" number="426"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="428"/>
+ <line hits="1" number="432"/>
+ <line hits="1" number="433"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="436"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="438"/>
+ <line hits="0" number="440"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="446"/>
+ <line hits="1" number="447"/>
+ <line hits="0" number="449"/>
+ <line hits="1" number="451"/>
+ <line hits="1" number="453"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="455"/>
+ <line hits="1" number="456"/>
+ <line hits="1" number="457"/>
+ <line hits="1" number="459"/>
+ <line hits="1" number="460"/>
+ <line hits="1" number="461"/>
+ <line hits="1" number="462"/>
+ <line hits="1" number="463"/>
+ <line hits="1" number="464"/>
+ <line hits="1" number="466"/>
+ <line hits="1" number="467"/>
+ <line hits="1" number="468"/>
+ <line hits="1" number="469"/>
+ <line hits="1" number="470"/>
+ <line hits="1" number="472"/>
+ <line hits="1" number="474"/>
+ <line hits="1" number="475"/>
+ <line hits="1" number="476"/>
+ <line hits="1" number="477"/>
+ <line hits="1" number="478"/>
+ <line hits="1" number="481"/>
+ <line hits="1" number="482"/>
+ <line hits="1" number="483"/>
+ <line hits="1" number="484"/>
+ <line hits="1" number="485"/>
+ <line hits="1" number="486"/>
+ <line hits="1" number="489"/>
+ <line hits="1" number="490"/>
+ <line hits="1" number="491"/>
+ <line hits="1" number="492"/>
+ <line hits="1" number="493"/>
+ <line hits="1" number="494"/>
+ <line hits="1" number="495"/>
+ <line hits="1" number="496"/>
+ <line hits="1" number="497"/>
+ <line hits="1" number="498"/>
+ <line hits="1" number="499"/>
+ <line hits="1" number="500"/>
+ <line hits="1" number="501"/>
+ <line hits="1" number="502"/>
+ <line hits="1" number="504"/>
+ <line hits="1" number="506"/>
+ <line hits="1" number="507"/>
+ <line hits="1" number="508"/>
+ <line hits="1" number="509"/>
+ <line hits="1" number="511"/>
+ <line hits="1" number="512"/>
+ <line hits="1" number="514"/>
+ <line hits="1" number="516"/>
+ <line hits="1" number="517"/>
+ <line hits="1" number="518"/>
+ <line hits="1" number="519"/>
+ <line hits="1" number="520"/>
+ <line hits="1" number="521"/>
+ <line hits="1" number="522"/>
+ <line hits="1" number="523"/>
+ <line hits="1" number="524"/>
+ <line hits="1" number="525"/>
+ <line hits="1" number="526"/>
+ <line hits="1" number="527"/>
+ <line hits="1" number="529"/>
+ <line hits="1" number="531"/>
+ <line hits="1" number="533"/>
+ <line hits="1" number="534"/>
+ <line hits="1" number="536"/>
+ <line hits="1" number="537"/>
+ <line hits="1" number="538"/>
+ <line hits="1" number="539"/>
+ <line hits="1" number="540"/>
+ <line hits="1" number="541"/>
+ <line hits="1" number="542"/>
+ <line hits="1" number="543"/>
+ <line hits="1" number="545"/>
+ <line hits="1" number="546"/>
+ <line hits="1" number="547"/>
+ <line hits="1" number="548"/>
+ <line hits="1" number="549"/>
+ <line hits="1" number="551"/>
+ <line hits="1" number="553"/>
+ <line hits="1" number="554"/>
+ <line hits="1" number="555"/>
+ <line hits="1" number="556"/>
+ <line hits="1" number="558"/>
+ <line hits="1" number="559"/>
+ <line hits="1" number="560"/>
+ <line hits="1" number="561"/>
+ <line hits="1" number="562"/>
+ <line hits="1" number="563"/>
+ <line hits="1" number="564"/>
+ <line hits="1" number="565"/>
+ <line hits="1" number="566"/>
+ <line hits="1" number="567"/>
+ <line hits="1" number="568"/>
+ <line hits="1" number="570"/>
+ <line hits="1" number="571"/>
+ <line hits="1" number="572"/>
+ <line hits="1" number="573"/>
+ <line hits="1" number="575"/>
+ <line hits="1" number="577"/>
+ <line hits="1" number="578"/>
+ <line hits="1" number="580"/>
+ <line hits="1" number="581"/>
+ <line hits="1" number="582"/>
+ <line hits="1" number="584"/>
+ <line hits="1" number="585"/>
+ <line hits="1" number="586"/>
+ <line hits="1" number="587"/>
+ <line hits="1" number="588"/>
+ <line hits="1" number="589"/>
+ <line hits="1" number="590"/>
+ <line hits="1" number="592"/>
+ <line hits="1" number="593"/>
+ <line hits="1" number="594"/>
+ <line hits="1" number="595"/>
+ <line hits="1" number="597"/>
+ <line hits="1" number="598"/>
+ <line hits="1" number="599"/>
+ <line hits="1" number="601"/>
+ <line hits="1" number="603"/>
+ <line hits="1" number="605"/>
+ <line hits="1" number="607"/>
+ <line hits="1" number="608"/>
+ <line hits="1" number="609"/>
+ <line hits="1" number="610"/>
+ <line hits="1" number="611"/>
+ <line hits="1" number="612"/>
+ <line hits="1" number="613"/>
+ <line hits="1" number="614"/>
+ <line hits="1" number="615"/>
+ <line hits="1" number="616"/>
+ <line hits="1" number="618"/>
+ <line hits="1" number="621"/>
+ <line hits="1" number="622"/>
+ <line hits="1" number="623"/>
+ <line hits="1" number="625"/>
+ <line hits="1" number="626"/>
+ <line hits="1" number="627"/>
+ <line hits="1" number="629"/>
+ <line hits="1" number="631"/>
+ <line hits="1" number="634"/>
+ <line hits="1" number="635"/>
+ <line hits="1" number="636"/>
+ <line hits="1" number="638"/>
+ <line hits="1" number="639"/>
+ <line hits="1" number="641"/>
+ <line hits="1" number="642"/>
+ <line hits="1" number="646"/>
+ <line hits="1" number="648"/>
+ <line hits="1" number="650"/>
+ <line hits="0" number="651"/>
+ <line hits="1" number="654"/>
+ <line hits="1" number="655"/>
+ <line hits="1" number="656"/>
+ <line hits="1" number="657"/>
+ <line hits="0" number="658"/>
+ <line hits="0" number="659"/>
+ <line hits="1" number="660"/>
+ <line hits="1" number="661"/>
+ <line hits="1" number="663"/>
+ <line hits="0" number="665"/>
+ <line hits="0" number="667"/>
+ <line hits="1" number="670"/>
+ <line hits="1" number="671"/>
+ <line hits="1" number="672"/>
+ <line hits="1" number="673"/>
+ <line hits="1" number="674"/>
+ <line hits="1" number="675"/>
+ <line hits="1" number="676"/>
+ <line hits="1" number="677"/>
+ <line hits="1" number="679"/>
+ <line hits="1" number="680"/>
+ <line hits="0" number="681"/>
+ <line hits="0" number="683"/>
+ <line hits="1" number="684"/>
+ <line hits="1" number="685"/>
+ <line hits="1" number="687"/>
+ <line hits="0" number="688"/>
+ <line hits="0" number="689"/>
+ <line hits="0" number="691"/>
+ <line hits="0" number="693"/>
+ <line hits="1" number="695"/>
+ <line hits="1" number="696"/>
+ <line hits="1" number="698"/>
+ <line hits="1" number="699"/>
+ <line hits="1" number="700"/>
+ <line hits="1" number="701"/>
+ <line hits="1" number="702"/>
+ <line hits="1" number="703"/>
+ <line hits="1" number="706"/>
+ <line hits="1" number="707"/>
+ <line hits="1" number="708"/>
+ <line hits="1" number="709"/>
+ <line hits="1" number="711"/>
+ <line hits="1" number="712"/>
+ <line hits="1" number="713"/>
+ <line hits="1" number="715"/>
+ <line hits="1" number="718"/>
+ <line hits="1" number="720"/>
+ <line hits="1" number="722"/>
+ <line hits="1" number="724"/>
+ <line hits="1" number="725"/>
+ <line hits="1" number="727"/>
+ <line hits="1" number="729"/>
+ <line hits="1" number="730"/>
+ <line hits="1" number="731"/>
+ <line hits="1" number="733"/>
+ <line hits="1" number="734"/>
+ <line hits="1" number="735"/>
+ <line hits="1" number="736"/>
+ <line hits="1" number="739"/>
+ <line hits="1" number="740"/>
+ <line hits="1" number="741"/>
+ <line hits="1" number="742"/>
+ <line hits="1" number="743"/>
+ <line hits="1" number="746"/>
+ <line hits="1" number="747"/>
+ <line hits="1" number="748"/>
+ <line hits="1" number="749"/>
+ <line hits="1" number="750"/>
+ <line hits="1" number="751"/>
+ <line hits="1" number="753"/>
+ <line hits="1" number="755"/>
+ <line hits="1" number="758"/>
+ <line hits="1" number="759"/>
+ <line hits="1" number="760"/>
+ <line hits="1" number="761"/>
+ <line hits="1" number="763"/>
+ <line hits="1" number="766"/>
+ <line hits="1" number="767"/>
+ <line hits="1" number="768"/>
+ <line hits="1" number="769"/>
+ <line hits="1" number="770"/>
+ <line hits="1" number="771"/>
+ <line hits="0" number="772"/>
+ <line hits="1" number="773"/>
+ <line hits="1" number="774"/>
+ <line hits="1" number="776"/>
+ <line hits="1" number="777"/>
+ <line hits="1" number="778"/>
+ <line hits="1" number="779"/>
+ <line hits="1" number="780"/>
+ <line hits="1" number="781"/>
+ <line hits="1" number="782"/>
+ <line hits="0" number="783"/>
+ <line hits="0" number="784"/>
+ <line hits="0" number="787"/>
+ <line hits="0" number="790"/>
+ <line hits="1" number="791"/>
+ <line hits="1" number="792"/>
+ <line hits="1" number="794"/>
+ <line hits="1" number="799"/>
+ <line hits="1" number="800"/>
+ <line hits="1" number="801"/>
+ <line hits="1" number="802"/>
+ <line hits="1" number="804"/>
+ <line hits="1" number="806"/>
+ <line hits="1" number="809"/>
+ <line hits="1" number="810"/>
+ <line hits="1" number="812"/>
+ <line hits="1" number="813"/>
+ <line hits="1" number="815"/>
+ <line hits="1" number="816"/>
+ <line hits="1" number="818"/>
+ <line hits="1" number="821"/>
+ <line hits="1" number="823"/>
+ <line hits="1" number="824"/>
+ <line hits="1" number="825"/>
+ <line hits="1" number="826"/>
+ <line hits="0" number="827"/>
+ <line hits="0" number="828"/>
+ <line hits="1" number="830"/>
+ <line hits="1" number="833"/>
+ <line hits="1" number="835"/>
+ <line hits="1" number="836"/>
+ <line hits="1" number="837"/>
+ <line hits="1" number="839"/>
+ <line hits="1" number="840"/>
+ <line hits="1" number="841"/>
+ <line hits="1" number="842"/>
+ <line hits="1" number="843"/>
+ <line hits="1" number="844"/>
+ <line hits="1" number="845"/>
+ <line hits="1" number="846"/>
+ <line hits="1" number="847"/>
+ <line hits="1" number="848"/>
+ <line hits="1" number="849"/>
+ <line hits="1" number="851"/>
+ <line hits="1" number="854"/>
+ <line hits="1" number="856"/>
+ <line hits="1" number="857"/>
+ <line hits="1" number="858"/>
+ <line hits="1" number="859"/>
+ <line hits="1" number="860"/>
+ <line hits="1" number="861"/>
+ <line hits="1" number="862"/>
+ <line hits="1" number="864"/>
+ <line hits="1" number="865"/>
+ <line hits="1" number="866"/>
+ <line hits="1" number="869"/>
+ <line hits="1" number="873"/>
+ <line hits="1" number="874"/>
+ <line hits="1" number="875"/>
+ <line hits="0" number="877"/>
+ <line hits="0" number="878"/>
+ <line hits="1" number="880"/>
+ <line hits="1" number="881"/>
+ <line hits="1" number="884"/>
+ <line hits="1" number="886"/>
+ <line hits="1" number="887"/>
+ <line hits="1" number="888"/>
+ <line hits="1" number="889"/>
+ <line hits="1" number="890"/>
+ <line hits="1" number="891"/>
+ <line hits="1" number="892"/>
+ <line hits="1" number="895"/>
+ <line hits="1" number="913"/>
+ <line hits="1" number="918"/>
+ <line hits="1" number="919"/>
+ <line hits="1" number="921"/>
+ <line hits="1" number="924"/>
+ <line hits="1" number="943"/>
+ <line hits="1" number="945"/>
+ <line hits="1" number="946"/>
+ <line hits="1" number="947"/>
+ <line hits="1" number="949"/>
+ <line hits="1" number="950"/>
+ <line hits="1" number="951"/>
+ <line hits="1" number="952"/>
+ <line hits="1" number="953"/>
+ <line hits="1" number="955"/>
+ <line hits="1" number="958"/>
+ <line hits="1" number="978"/>
+ <line hits="1" number="979"/>
+ <line hits="1" number="980"/>
+ <line hits="1" number="981"/>
+ <line hits="1" number="982"/>
+ <line hits="1" number="983"/>
+ <line hits="1" number="984"/>
+ <line hits="1" number="985"/>
+ <line hits="1" number="986"/>
+ <line hits="1" number="987"/>
+ <line hits="1" number="990"/>
+ <line hits="1" number="1006"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="rpm/changelog.py" line-rate="0.9808" name="changelog.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="187"/>
+ <line hits="0" number="188"/>
+ <line hits="0" number="189"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="223"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="228"/>
+ <line hits="1" number="230"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="235"/>
+ <line hits="0" number="236"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="241"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="245"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="261"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="rpm/git.py" line-rate="1" name="git.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="106"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="rpm/lib_rpm.py" line-rate="0.5789" name="lib_rpm.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="0" number="27"/>
+ <line hits="0" number="28"/>
+ <line hits="0" number="31"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="40"/>
+ <line hits="0" number="42"/>
+ <line hits="0" number="43"/>
+ <line hits="0" number="44"/>
+ <line hits="0" number="45"/>
+ <line hits="0" number="46"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="rpm/linkedlist.py" line-rate="0.9875" name="linkedlist.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="0" number="86"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="214"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="rpm/policy.py" line-rate="0.8472" name="policy.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="0" number="123"/>
+ <line hits="0" number="124"/>
+ <line hits="0" number="125"/>
+ <line hits="0" number="127"/>
+ <line hits="0" number="128"/>
+ <line hits="0" number="130"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="0" number="172"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="194"/>
+ <line hits="0" number="195"/>
+ <line hits="0" number="196"/>
+ <line hits="0" number="197"/>
+ <line hits="0" number="198"/>
+ <line hits="1" number="200"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ <package branch-rate="0" complexity="0" line-rate="0.8419" name="scripts">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="scripts/__init__.py" line-rate="1" name="__init__.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/buildpackage.py" line-rate="0.8418" name="buildpackage.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="0" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="0" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="0" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="0" number="94"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="0" number="101"/>
+ <line hits="0" number="102"/>
+ <line hits="0" number="103"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="0" number="133"/>
+ <line hits="0" number="134"/>
+ <line hits="0" number="135"/>
+ <line hits="0" number="137"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="152"/>
+ <line hits="0" number="153"/>
+ <line hits="0" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="0" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="0" number="183"/>
+ <line hits="0" number="184"/>
+ <line hits="0" number="185"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="192"/>
+ <line hits="0" number="193"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="0" number="205"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="228"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="245"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="251"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="0" number="257"/>
+ <line hits="0" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="0" number="260"/>
+ <line hits="1" number="261"/>
+ <line hits="0" number="262"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="274"/>
+ <line hits="1" number="275"/>
+ <line hits="1" number="278"/>
+ <line hits="0" number="280"/>
+ <line hits="0" number="281"/>
+ <line hits="0" number="282"/>
+ <line hits="0" number="283"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="300"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="305"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="323"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="327"/>
+ <line hits="0" number="328"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="331"/>
+ <line hits="0" number="332"/>
+ <line hits="0" number="333"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="338"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="342"/>
+ <line hits="1" number="344"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="366"/>
+ <line hits="1" number="368"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="373"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="376"/>
+ <line hits="1" number="377"/>
+ <line hits="1" number="378"/>
+ <line hits="1" number="379"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="381"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="384"/>
+ <line hits="1" number="386"/>
+ <line hits="1" number="388"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="392"/>
+ <line hits="1" number="394"/>
+ <line hits="1" number="396"/>
+ <line hits="1" number="397"/>
+ <line hits="1" number="398"/>
+ <line hits="1" number="399"/>
+ <line hits="1" number="400"/>
+ <line hits="1" number="403"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="412"/>
+ <line hits="1" number="415"/>
+ <line hits="1" number="418"/>
+ <line hits="1" number="419"/>
+ <line hits="1" number="420"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="422"/>
+ <line hits="1" number="424"/>
+ <line hits="1" number="425"/>
+ <line hits="1" number="426"/>
+ <line hits="1" number="429"/>
+ <line hits="1" number="432"/>
+ <line hits="1" number="433"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="438"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="442"/>
+ <line hits="1" number="443"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="446"/>
+ <line hits="1" number="447"/>
+ <line hits="1" number="448"/>
+ <line hits="1" number="449"/>
+ <line hits="1" number="451"/>
+ <line hits="1" number="452"/>
+ <line hits="0" number="453"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="455"/>
+ <line hits="0" number="456"/>
+ <line hits="0" number="458"/>
+ <line hits="1" number="460"/>
+ <line hits="0" number="461"/>
+ <line hits="0" number="462"/>
+ <line hits="1" number="464"/>
+ <line hits="0" number="465"/>
+ <line hits="0" number="466"/>
+ <line hits="1" number="468"/>
+ <line hits="1" number="469"/>
+ <line hits="1" number="472"/>
+ <line hits="1" number="473"/>
+ <line hits="1" number="474"/>
+ <line hits="1" number="475"/>
+ <line hits="1" number="476"/>
+ <line hits="1" number="478"/>
+ <line hits="1" number="480"/>
+ <line hits="1" number="481"/>
+ <line hits="1" number="483"/>
+ <line hits="1" number="484"/>
+ <line hits="0" number="485"/>
+ <line hits="0" number="486"/>
+ <line hits="0" number="487"/>
+ <line hits="1" number="489"/>
+ <line hits="1" number="490"/>
+ <line hits="1" number="491"/>
+ <line hits="1" number="492"/>
+ <line hits="1" number="493"/>
+ <line hits="1" number="495"/>
+ <line hits="1" number="497"/>
+ <line hits="1" number="498"/>
+ <line hits="1" number="499"/>
+ <line hits="1" number="500"/>
+ <line hits="1" number="501"/>
+ <line hits="1" number="502"/>
+ <line hits="1" number="504"/>
+ <line hits="1" number="505"/>
+ <line hits="1" number="506"/>
+ <line hits="1" number="513"/>
+ <line hits="1" number="514"/>
+ <line hits="0" number="515"/>
+ <line hits="1" number="517"/>
+ <line hits="1" number="521"/>
+ <line hits="1" number="522"/>
+ <line hits="1" number="525"/>
+ <line hits="0" number="526"/>
+ <line hits="1" number="532"/>
+ <line hits="1" number="533"/>
+ <line hits="1" number="534"/>
+ <line hits="1" number="537"/>
+ <line hits="0" number="538"/>
+ <line hits="1" number="540"/>
+ <line hits="1" number="541"/>
+ <line hits="1" number="548"/>
+ <line hits="1" number="549"/>
+ <line hits="1" number="555"/>
+ <line hits="1" number="556"/>
+ <line hits="1" number="557"/>
+ <line hits="1" number="562"/>
+ <line hits="1" number="563"/>
+ <line hits="1" number="565"/>
+ <line hits="0" number="566"/>
+ <line hits="0" number="567"/>
+ <line hits="1" number="568"/>
+ <line hits="0" number="569"/>
+ <line hits="1" number="570"/>
+ <line hits="1" number="571"/>
+ <line hits="1" number="572"/>
+ <line hits="1" number="573"/>
+ <line hits="0" number="574"/>
+ <line hits="0" number="575"/>
+ <line hits="0" number="576"/>
+ <line hits="0" number="577"/>
+ <line hits="1" number="579"/>
+ <line hits="1" number="581"/>
+ <line hits="1" number="582"/>
+ <line hits="1" number="583"/>
+ <line hits="1" number="585"/>
+ <line hits="1" number="586"/>
+ <line hits="1" number="588"/>
+ <line hits="0" number="589"/>
+ <line hits="0" number="590"/>
+ <line hits="1" number="592"/>
+ <line hits="1" number="595"/>
+ <line hits="0" number="596"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/buildpackage_rpm.py" line-rate="0.9437" name="buildpackage_rpm.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="0" number="52"/>
+ <line hits="0" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="160"/>
+ <line hits="0" number="161"/>
+ <line hits="0" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="229"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="241"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="249"/>
+ <line hits="0" number="250"/>
+ <line hits="1" number="251"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="271"/>
+ <line hits="0" number="273"/>
+ <line hits="0" number="274"/>
+ <line hits="0" number="275"/>
+ <line hits="0" number="276"/>
+ <line hits="0" number="277"/>
+ <line hits="0" number="278"/>
+ <line hits="0" number="279"/>
+ <line hits="0" number="280"/>
+ <line hits="0" number="281"/>
+ <line hits="0" number="282"/>
+ <line hits="0" number="283"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="292"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="297"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="300"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="305"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="311"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="314"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="326"/>
+ <line hits="1" number="327"/>
+ <line hits="1" number="328"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="335"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="345"/>
+ <line hits="1" number="348"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="355"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="364"/>
+ <line hits="1" number="366"/>
+ <line hits="1" number="368"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="374"/>
+ <line hits="1" number="377"/>
+ <line hits="1" number="381"/>
+ <line hits="1" number="383"/>
+ <line hits="1" number="385"/>
+ <line hits="1" number="387"/>
+ <line hits="1" number="389"/>
+ <line hits="1" number="392"/>
+ <line hits="1" number="395"/>
+ <line hits="1" number="398"/>
+ <line hits="1" number="402"/>
+ <line hits="1" number="405"/>
+ <line hits="1" number="408"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="410"/>
+ <line hits="1" number="411"/>
+ <line hits="1" number="412"/>
+ <line hits="1" number="413"/>
+ <line hits="1" number="414"/>
+ <line hits="1" number="417"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="423"/>
+ <line hits="1" number="425"/>
+ <line hits="1" number="429"/>
+ <line hits="1" number="431"/>
+ <line hits="1" number="433"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="440"/>
+ <line hits="1" number="443"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="447"/>
+ <line hits="1" number="448"/>
+ <line hits="1" number="449"/>
+ <line hits="1" number="450"/>
+ <line hits="1" number="452"/>
+ <line hits="1" number="453"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="455"/>
+ <line hits="1" number="456"/>
+ <line hits="1" number="457"/>
+ <line hits="1" number="459"/>
+ <line hits="1" number="461"/>
+ <line hits="1" number="464"/>
+ <line hits="1" number="466"/>
+ <line hits="1" number="467"/>
+ <line hits="1" number="468"/>
+ <line hits="1" number="470"/>
+ <line hits="1" number="472"/>
+ <line hits="1" number="473"/>
+ <line hits="1" number="475"/>
+ <line hits="1" number="476"/>
+ <line hits="1" number="477"/>
+ <line hits="1" number="478"/>
+ <line hits="1" number="479"/>
+ <line hits="1" number="482"/>
+ <line hits="1" number="483"/>
+ <line hits="1" number="484"/>
+ <line hits="1" number="485"/>
+ <line hits="1" number="486"/>
+ <line hits="1" number="489"/>
+ <line hits="1" number="491"/>
+ <line hits="1" number="493"/>
+ <line hits="1" number="494"/>
+ <line hits="1" number="496"/>
+ <line hits="1" number="497"/>
+ <line hits="1" number="499"/>
+ <line hits="1" number="500"/>
+ <line hits="1" number="501"/>
+ <line hits="1" number="502"/>
+ <line hits="1" number="503"/>
+ <line hits="1" number="504"/>
+ <line hits="1" number="505"/>
+ <line hits="1" number="507"/>
+ <line hits="1" number="508"/>
+ <line hits="1" number="509"/>
+ <line hits="1" number="511"/>
+ <line hits="1" number="515"/>
+ <line hits="1" number="516"/>
+ <line hits="1" number="517"/>
+ <line hits="1" number="518"/>
+ <line hits="0" number="519"/>
+ <line hits="1" number="521"/>
+ <line hits="1" number="523"/>
+ <line hits="1" number="525"/>
+ <line hits="1" number="526"/>
+ <line hits="0" number="527"/>
+ <line hits="1" number="530"/>
+ <line hits="1" number="531"/>
+ <line hits="1" number="533"/>
+ <line hits="1" number="536"/>
+ <line hits="1" number="538"/>
+ <line hits="1" number="539"/>
+ <line hits="1" number="540"/>
+ <line hits="1" number="541"/>
+ <line hits="1" number="543"/>
+ <line hits="1" number="544"/>
+ <line hits="1" number="545"/>
+ <line hits="1" number="546"/>
+ <line hits="1" number="547"/>
+ <line hits="1" number="548"/>
+ <line hits="1" number="551"/>
+ <line hits="1" number="552"/>
+ <line hits="1" number="554"/>
+ <line hits="1" number="556"/>
+ <line hits="1" number="557"/>
+ <line hits="1" number="558"/>
+ <line hits="1" number="560"/>
+ <line hits="1" number="562"/>
+ <line hits="1" number="563"/>
+ <line hits="1" number="566"/>
+ <line hits="1" number="569"/>
+ <line hits="1" number="570"/>
+ <line hits="1" number="573"/>
+ <line hits="1" number="574"/>
+ <line hits="1" number="579"/>
+ <line hits="1" number="580"/>
+ <line hits="1" number="581"/>
+ <line hits="1" number="587"/>
+ <line hits="1" number="588"/>
+ <line hits="1" number="591"/>
+ <line hits="1" number="592"/>
+ <line hits="1" number="597"/>
+ <line hits="1" number="598"/>
+ <line hits="1" number="600"/>
+ <line hits="1" number="601"/>
+ <line hits="1" number="606"/>
+ <line hits="1" number="607"/>
+ <line hits="1" number="608"/>
+ <line hits="1" number="610"/>
+ <line hits="1" number="611"/>
+ <line hits="1" number="612"/>
+ <line hits="1" number="613"/>
+ <line hits="1" number="618"/>
+ <line hits="1" number="621"/>
+ <line hits="1" number="622"/>
+ <line hits="1" number="623"/>
+ <line hits="0" number="624"/>
+ <line hits="0" number="625"/>
+ <line hits="1" number="626"/>
+ <line hits="1" number="627"/>
+ <line hits="1" number="628"/>
+ <line hits="1" number="629"/>
+ <line hits="1" number="630"/>
+ <line hits="1" number="631"/>
+ <line hits="1" number="632"/>
+ <line hits="1" number="633"/>
+ <line hits="1" number="634"/>
+ <line hits="1" number="635"/>
+ <line hits="1" number="636"/>
+ <line hits="1" number="637"/>
+ <line hits="1" number="638"/>
+ <line hits="1" number="640"/>
+ <line hits="1" number="641"/>
+ <line hits="1" number="643"/>
+ <line hits="1" number="644"/>
+ <line hits="1" number="645"/>
+ <line hits="1" number="646"/>
+ <line hits="1" number="649"/>
+ <line hits="1" number="650"/>
+ <line hits="1" number="651"/>
+ <line hits="1" number="653"/>
+ <line hits="1" number="656"/>
+ <line hits="0" number="657"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/clone.py" line-rate="0.8403" name="clone.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="39"/>
+ <line hits="0" number="40"/>
+ <line hits="0" number="41"/>
+ <line hits="0" number="42"/>
+ <line hits="0" number="43"/>
+ <line hits="0" number="44"/>
+ <line hits="0" number="45"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="0" number="73"/>
+ <line hits="0" number="74"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="0" number="96"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="157"/>
+ <line hits="0" number="158"/>
+ <line hits="0" number="159"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="0" number="163"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="0" number="168"/>
+ <line hits="0" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="194"/>
+ <line hits="0" number="195"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="199"/>
+ <line hits="0" number="201"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="212"/>
+ <line hits="0" number="213"/>
+ <line hits="0" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="217"/>
+ <line hits="0" number="218"/>
+ <line hits="0" number="219"/>
+ <line hits="0" number="220"/>
+ <line hits="0" number="221"/>
+ <line hits="1" number="223"/>
+ <line hits="1" number="226"/>
+ <line hits="0" number="227"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/config.py" line-rate="0.92" name="config.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="95"/>
+ <line hits="0" number="96"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="126"/>
+ <line hits="0" number="127"/>
+ <line hits="0" number="128"/>
+ <line hits="1" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="139"/>
+ <line hits="0" number="140"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/create_remote_repo.py" line-rate="0.5364" name="create_remote_repo.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="171"/>
+ <line hits="0" number="172"/>
+ <line hits="0" number="173"/>
+ <line hits="0" number="174"/>
+ <line hits="0" number="175"/>
+ <line hits="0" number="176"/>
+ <line hits="0" number="178"/>
+ <line hits="0" number="179"/>
+ <line hits="0" number="180"/>
+ <line hits="0" number="181"/>
+ <line hits="0" number="183"/>
+ <line hits="0" number="184"/>
+ <line hits="0" number="186"/>
+ <line hits="0" number="187"/>
+ <line hits="0" number="189"/>
+ <line hits="1" number="192"/>
+ <line hits="0" number="193"/>
+ <line hits="0" number="194"/>
+ <line hits="0" number="195"/>
+ <line hits="0" number="196"/>
+ <line hits="1" number="199"/>
+ <line hits="0" number="200"/>
+ <line hits="0" number="201"/>
+ <line hits="0" number="202"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="228"/>
+ <line hits="1" number="230"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="0" number="271"/>
+ <line hits="0" number="272"/>
+ <line hits="1" number="274"/>
+ <line hits="1" number="275"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="281"/>
+ <line hits="0" number="282"/>
+ <line hits="0" number="283"/>
+ <line hits="0" number="284"/>
+ <line hits="0" number="286"/>
+ <line hits="0" number="287"/>
+ <line hits="0" number="288"/>
+ <line hits="0" number="289"/>
+ <line hits="0" number="290"/>
+ <line hits="0" number="292"/>
+ <line hits="0" number="293"/>
+ <line hits="0" number="295"/>
+ <line hits="0" number="296"/>
+ <line hits="0" number="297"/>
+ <line hits="0" number="299"/>
+ <line hits="0" number="300"/>
+ <line hits="0" number="302"/>
+ <line hits="0" number="303"/>
+ <line hits="0" number="304"/>
+ <line hits="0" number="305"/>
+ <line hits="0" number="306"/>
+ <line hits="0" number="308"/>
+ <line hits="0" number="309"/>
+ <line hits="0" number="310"/>
+ <line hits="0" number="311"/>
+ <line hits="0" number="313"/>
+ <line hits="0" number="318"/>
+ <line hits="0" number="319"/>
+ <line hits="0" number="321"/>
+ <line hits="0" number="322"/>
+ <line hits="0" number="323"/>
+ <line hits="0" number="325"/>
+ <line hits="0" number="326"/>
+ <line hits="0" number="327"/>
+ <line hits="0" number="328"/>
+ <line hits="0" number="330"/>
+ <line hits="0" number="331"/>
+ <line hits="0" number="332"/>
+ <line hits="0" number="334"/>
+ <line hits="0" number="335"/>
+ <line hits="0" number="336"/>
+ <line hits="0" number="337"/>
+ <line hits="0" number="339"/>
+ <line hits="0" number="340"/>
+ <line hits="0" number="341"/>
+ <line hits="0" number="342"/>
+ <line hits="0" number="344"/>
+ <line hits="0" number="345"/>
+ <line hits="0" number="346"/>
+ <line hits="0" number="347"/>
+ <line hits="0" number="348"/>
+ <line hits="0" number="349"/>
+ <line hits="0" number="350"/>
+ <line hits="0" number="351"/>
+ <line hits="0" number="352"/>
+ <line hits="0" number="353"/>
+ <line hits="0" number="354"/>
+ <line hits="0" number="355"/>
+ <line hits="0" number="356"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="364"/>
+ <line hits="1" number="367"/>
+ <line hits="0" number="368"/>
+ <line hits="0" number="369"/>
+ <line hits="0" number="370"/>
+ <line hits="0" number="371"/>
+ <line hits="0" number="372"/>
+ <line hits="0" number="374"/>
+ <line hits="0" number="375"/>
+ <line hits="1" number="378"/>
+ <line hits="1" number="379"/>
+ <line hits="1" number="381"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="383"/>
+ <line hits="0" number="385"/>
+ <line hits="0" number="387"/>
+ <line hits="0" number="388"/>
+ <line hits="0" number="389"/>
+ <line hits="0" number="390"/>
+ <line hits="0" number="391"/>
+ <line hits="0" number="393"/>
+ <line hits="0" number="394"/>
+ <line hits="0" number="395"/>
+ <line hits="0" number="396"/>
+ <line hits="0" number="397"/>
+ <line hits="0" number="399"/>
+ <line hits="0" number="400"/>
+ <line hits="1" number="403"/>
+ <line hits="0" number="404"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/dch.py" line-rate="0.8917" name="dch.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="0" number="55"/>
+ <line hits="0" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="0" number="67"/>
+ <line hits="0" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="0" number="72"/>
+ <line hits="0" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="0" number="104"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="0" number="169"/>
+ <line hits="0" number="170"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="0" number="178"/>
+ <line hits="0" number="179"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="194"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="228"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="241"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="266"/>
+ <line hits="0" number="267"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="272"/>
+ <line hits="1" number="273"/>
+ <line hits="0" number="274"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="279"/>
+ <line hits="0" number="281"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="289"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="293"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="295"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="298"/>
+ <line hits="1" number="299"/>
+ <line hits="0" number="301"/>
+ <line hits="1" number="304"/>
+ <line hits="1" number="305"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="310"/>
+ <line hits="0" number="311"/>
+ <line hits="0" number="312"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="323"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="328"/>
+ <line hits="1" number="329"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="336"/>
+ <line hits="1" number="338"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="342"/>
+ <line hits="1" number="344"/>
+ <line hits="1" number="346"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="348"/>
+ <line hits="1" number="349"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="352"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="355"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="357"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="366"/>
+ <line hits="1" number="368"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="373"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="376"/>
+ <line hits="1" number="379"/>
+ <line hits="1" number="381"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="385"/>
+ <line hits="1" number="387"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="393"/>
+ <line hits="1" number="396"/>
+ <line hits="1" number="397"/>
+ <line hits="1" number="398"/>
+ <line hits="1" number="399"/>
+ <line hits="1" number="400"/>
+ <line hits="1" number="401"/>
+ <line hits="1" number="405"/>
+ <line hits="1" number="408"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="410"/>
+ <line hits="1" number="411"/>
+ <line hits="1" number="413"/>
+ <line hits="1" number="415"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="424"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="433"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="435"/>
+ <line hits="1" number="436"/>
+ <line hits="1" number="438"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="440"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="442"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="446"/>
+ <line hits="1" number="447"/>
+ <line hits="1" number="448"/>
+ <line hits="1" number="449"/>
+ <line hits="1" number="450"/>
+ <line hits="1" number="451"/>
+ <line hits="1" number="453"/>
+ <line hits="1" number="455"/>
+ <line hits="1" number="456"/>
+ <line hits="1" number="458"/>
+ <line hits="1" number="459"/>
+ <line hits="1" number="460"/>
+ <line hits="1" number="461"/>
+ <line hits="1" number="462"/>
+ <line hits="0" number="463"/>
+ <line hits="0" number="464"/>
+ <line hits="1" number="466"/>
+ <line hits="1" number="467"/>
+ <line hits="1" number="468"/>
+ <line hits="0" number="469"/>
+ <line hits="0" number="471"/>
+ <line hits="0" number="472"/>
+ <line hits="1" number="474"/>
+ <line hits="0" number="475"/>
+ <line hits="0" number="476"/>
+ <line hits="1" number="478"/>
+ <line hits="1" number="479"/>
+ <line hits="1" number="481"/>
+ <line hits="0" number="482"/>
+ <line hits="1" number="484"/>
+ <line hits="1" number="485"/>
+ <line hits="1" number="486"/>
+ <line hits="0" number="488"/>
+ <line hits="0" number="489"/>
+ <line hits="1" number="490"/>
+ <line hits="1" number="492"/>
+ <line hits="0" number="493"/>
+ <line hits="1" number="494"/>
+ <line hits="1" number="496"/>
+ <line hits="1" number="498"/>
+ <line hits="1" number="500"/>
+ <line hits="1" number="502"/>
+ <line hits="0" number="503"/>
+ <line hits="1" number="504"/>
+ <line hits="0" number="505"/>
+ <line hits="1" number="506"/>
+ <line hits="0" number="507"/>
+ <line hits="1" number="508"/>
+ <line hits="0" number="509"/>
+ <line hits="1" number="510"/>
+ <line hits="0" number="511"/>
+ <line hits="1" number="513"/>
+ <line hits="1" number="515"/>
+ <line hits="1" number="516"/>
+ <line hits="1" number="517"/>
+ <line hits="1" number="519"/>
+ <line hits="1" number="520"/>
+ <line hits="1" number="522"/>
+ <line hits="1" number="524"/>
+ <line hits="1" number="526"/>
+ <line hits="1" number="528"/>
+ <line hits="1" number="529"/>
+ <line hits="1" number="531"/>
+ <line hits="1" number="532"/>
+ <line hits="1" number="533"/>
+ <line hits="1" number="534"/>
+ <line hits="1" number="536"/>
+ <line hits="1" number="537"/>
+ <line hits="0" number="539"/>
+ <line hits="1" number="541"/>
+ <line hits="1" number="544"/>
+ <line hits="1" number="550"/>
+ <line hits="1" number="552"/>
+ <line hits="1" number="556"/>
+ <line hits="1" number="557"/>
+ <line hits="1" number="559"/>
+ <line hits="1" number="562"/>
+ <line hits="1" number="566"/>
+ <line hits="1" number="569"/>
+ <line hits="1" number="570"/>
+ <line hits="1" number="572"/>
+ <line hits="1" number="573"/>
+ <line hits="1" number="574"/>
+ <line hits="1" number="576"/>
+ <line hits="0" number="577"/>
+ <line hits="1" number="579"/>
+ <line hits="1" number="580"/>
+ <line hits="1" number="581"/>
+ <line hits="1" number="584"/>
+ <line hits="1" number="588"/>
+ <line hits="1" number="590"/>
+ <line hits="1" number="591"/>
+ <line hits="1" number="592"/>
+ <line hits="1" number="593"/>
+ <line hits="0" number="594"/>
+ <line hits="0" number="595"/>
+ <line hits="1" number="596"/>
+ <line hits="1" number="601"/>
+ <line hits="1" number="602"/>
+ <line hits="1" number="603"/>
+ <line hits="1" number="604"/>
+ <line hits="1" number="606"/>
+ <line hits="1" number="607"/>
+ <line hits="1" number="610"/>
+ <line hits="0" number="611"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/export_orig.py" line-rate="0.8406" name="export_orig.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="45"/>
+ <line hits="0" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="0" number="63"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="0" number="89"/>
+ <line hits="0" number="90"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="103"/>
+ <line hits="0" number="104"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="0" number="127"/>
+ <line hits="1" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="143"/>
+ <line hits="0" number="144"/>
+ <line hits="0" number="146"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="158"/>
+ <line hits="0" number="161"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="0" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="194"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="230"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="0" number="233"/>
+ <line hits="0" number="234"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="241"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="245"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="260"/>
+ <line hits="1" number="261"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="266"/>
+ <line hits="0" number="267"/>
+ <line hits="0" number="268"/>
+ <line hits="0" number="269"/>
+ <line hits="1" number="271"/>
+ <line hits="1" number="274"/>
+ <line hits="1" number="277"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="285"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="289"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="293"/>
+ <line hits="1" number="295"/>
+ <line hits="1" number="297"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="306"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="308"/>
+ <line hits="0" number="309"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="322"/>
+ <line hits="0" number="323"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="326"/>
+ <line hits="0" number="327"/>
+ <line hits="0" number="328"/>
+ <line hits="0" number="329"/>
+ <line hits="1" number="331"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="334"/>
+ <line hits="0" number="335"/>
+ <line hits="0" number="336"/>
+ <line hits="1" number="338"/>
+ <line hits="1" number="340"/>
+ <line hits="0" number="341"/>
+ <line hits="0" number="342"/>
+ <line hits="1" number="344"/>
+ <line hits="1" number="346"/>
+ <line hits="0" number="347"/>
+ <line hits="0" number="348"/>
+ <line hits="1" number="349"/>
+ <line hits="0" number="350"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="352"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="354"/>
+ <line hits="0" number="355"/>
+ <line hits="0" number="356"/>
+ <line hits="0" number="357"/>
+ <line hits="0" number="358"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="363"/>
+ <line hits="0" number="364"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/import_dsc.py" line-rate="0.8036" name="import_dsc.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="47"/>
+ <line hits="0" number="48"/>
+ <line hits="0" number="49"/>
+ <line hits="0" number="50"/>
+ <line hits="0" number="52"/>
+ <line hits="0" number="53"/>
+ <line hits="0" number="54"/>
+ <line hits="0" number="56"/>
+ <line hits="0" number="57"/>
+ <line hits="0" number="59"/>
+ <line hits="0" number="60"/>
+ <line hits="0" number="62"/>
+ <line hits="0" number="63"/>
+ <line hits="0" number="64"/>
+ <line hits="0" number="65"/>
+ <line hits="0" number="66"/>
+ <line hits="0" number="67"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="0" number="83"/>
+ <line hits="0" number="84"/>
+ <line hits="0" number="85"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="91"/>
+ <line hits="0" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="0" number="117"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="0" number="128"/>
+ <line hits="0" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="0" number="145"/>
+ <line hits="0" number="146"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="0" number="167"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="188"/>
+ <line hits="0" number="193"/>
+ <line hits="0" number="194"/>
+ <line hits="0" number="195"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="0" number="206"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="223"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="237"/>
+ <line hits="0" number="238"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="277"/>
+ <line hits="0" number="278"/>
+ <line hits="0" number="279"/>
+ <line hits="0" number="280"/>
+ <line hits="1" number="282"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="285"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="287"/>
+ <line hits="0" number="288"/>
+ <line hits="1" number="289"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="0" number="292"/>
+ <line hits="1" number="295"/>
+ <line hits="0" number="297"/>
+ <line hits="0" number="298"/>
+ <line hits="0" number="299"/>
+ <line hits="0" number="300"/>
+ <line hits="1" number="303"/>
+ <line hits="0" number="305"/>
+ <line hits="0" number="306"/>
+ <line hits="0" number="307"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="311"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="314"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="327"/>
+ <line hits="1" number="329"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="336"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="345"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="355"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="367"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="374"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="376"/>
+ <line hits="1" number="377"/>
+ <line hits="1" number="379"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="382"/>
+ <line hits="0" number="383"/>
+ <line hits="1" number="385"/>
+ <line hits="1" number="388"/>
+ <line hits="1" number="403"/>
+ <line hits="1" number="404"/>
+ <line hits="1" number="405"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="407"/>
+ <line hits="1" number="408"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="412"/>
+ <line hits="1" number="413"/>
+ <line hits="1" number="414"/>
+ <line hits="1" number="415"/>
+ <line hits="1" number="417"/>
+ <line hits="1" number="418"/>
+ <line hits="1" number="419"/>
+ <line hits="1" number="420"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="422"/>
+ <line hits="0" number="424"/>
+ <line hits="0" number="425"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="429"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="433"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="435"/>
+ <line hits="1" number="436"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="440"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="443"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="447"/>
+ <line hits="1" number="449"/>
+ <line hits="1" number="450"/>
+ <line hits="0" number="451"/>
+ <line hits="1" number="452"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="456"/>
+ <line hits="0" number="457"/>
+ <line hits="1" number="461"/>
+ <line hits="1" number="463"/>
+ <line hits="1" number="464"/>
+ <line hits="0" number="465"/>
+ <line hits="1" number="466"/>
+ <line hits="1" number="467"/>
+ <line hits="1" number="469"/>
+ <line hits="1" number="470"/>
+ <line hits="1" number="471"/>
+ <line hits="1" number="472"/>
+ <line hits="1" number="475"/>
+ <line hits="1" number="476"/>
+ <line hits="1" number="477"/>
+ <line hits="1" number="478"/>
+ <line hits="1" number="479"/>
+ <line hits="1" number="481"/>
+ <line hits="0" number="482"/>
+ <line hits="1" number="485"/>
+ <line hits="1" number="486"/>
+ <line hits="1" number="487"/>
+ <line hits="1" number="488"/>
+ <line hits="1" number="489"/>
+ <line hits="1" number="490"/>
+ <line hits="1" number="492"/>
+ <line hits="0" number="493"/>
+ <line hits="0" number="494"/>
+ <line hits="0" number="495"/>
+ <line hits="0" number="496"/>
+ <line hits="0" number="498"/>
+ <line hits="1" number="501"/>
+ <line hits="1" number="502"/>
+ <line hits="1" number="504"/>
+ <line hits="1" number="505"/>
+ <line hits="1" number="506"/>
+ <line hits="1" number="507"/>
+ <line hits="1" number="508"/>
+ <line hits="1" number="510"/>
+ <line hits="1" number="511"/>
+ <line hits="1" number="512"/>
+ <line hits="0" number="514"/>
+ <line hits="1" number="517"/>
+ <line hits="1" number="518"/>
+ <line hits="0" number="520"/>
+ <line hits="1" number="522"/>
+ <line hits="1" number="523"/>
+ <line hits="1" number="527"/>
+ <line hits="1" number="529"/>
+ <line hits="1" number="530"/>
+ <line hits="1" number="531"/>
+ <line hits="0" number="532"/>
+ <line hits="1" number="533"/>
+ <line hits="0" number="534"/>
+ <line hits="1" number="535"/>
+ <line hits="0" number="536"/>
+ <line hits="1" number="537"/>
+ <line hits="1" number="538"/>
+ <line hits="1" number="539"/>
+ <line hits="0" number="540"/>
+ <line hits="0" number="541"/>
+ <line hits="0" number="542"/>
+ <line hits="1" number="544"/>
+ <line hits="1" number="545"/>
+ <line hits="1" number="546"/>
+ <line hits="1" number="547"/>
+ <line hits="1" number="549"/>
+ <line hits="1" number="550"/>
+ <line hits="1" number="551"/>
+ <line hits="1" number="554"/>
+ <line hits="0" number="555"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/import_dscs.py" line-rate="0.6484" name="import_dscs.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="0" number="51"/>
+ <line hits="1" number="53"/>
+ <line hits="0" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="0" number="57"/>
+ <line hits="1" number="59"/>
+ <line hits="0" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="0" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="75"/>
+ <line hits="0" number="77"/>
+ <line hits="0" number="79"/>
+ <line hits="0" number="81"/>
+ <line hits="0" number="83"/>
+ <line hits="0" number="84"/>
+ <line hits="0" number="85"/>
+ <line hits="0" number="86"/>
+ <line hits="0" number="87"/>
+ <line hits="0" number="88"/>
+ <line hits="0" number="89"/>
+ <line hits="0" number="91"/>
+ <line hits="0" number="93"/>
+ <line hits="0" number="94"/>
+ <line hits="0" number="95"/>
+ <line hits="0" number="97"/>
+ <line hits="1" number="100"/>
+ <line hits="0" number="105"/>
+ <line hits="0" number="106"/>
+ <line hits="0" number="107"/>
+ <line hits="0" number="108"/>
+ <line hits="1" number="111"/>
+ <line hits="0" number="112"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="0" number="134"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="137"/>
+ <line hits="0" number="138"/>
+ <line hits="0" number="139"/>
+ <line hits="1" number="142"/>
+ <line hits="0" number="143"/>
+ <line hits="0" number="144"/>
+ <line hits="0" number="145"/>
+ <line hits="0" number="146"/>
+ <line hits="0" number="147"/>
+ <line hits="0" number="148"/>
+ <line hits="0" number="149"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="156"/>
+ <line hits="0" number="157"/>
+ <line hits="0" number="158"/>
+ <line hits="1" number="160"/>
+ <line hits="0" number="161"/>
+ <line hits="0" number="162"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="0" number="171"/>
+ <line hits="0" number="173"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="187"/>
+ <line hits="0" number="188"/>
+ <line hits="0" number="189"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="195"/>
+ <line hits="0" number="196"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="204"/>
+ <line hits="0" number="205"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/import_orig.py" line-rate="0.8713" name="import_orig.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="0" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="0" number="81"/>
+ <line hits="0" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="106"/>
+ <line hits="0" number="107"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="129"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="0" number="161"/>
+ <line hits="0" number="162"/>
+ <line hits="0" number="163"/>
+ <line hits="0" number="164"/>
+ <line hits="0" number="165"/>
+ <line hits="0" number="166"/>
+ <line hits="0" number="167"/>
+ <line hits="0" number="168"/>
+ <line hits="0" number="170"/>
+ <line hits="0" number="171"/>
+ <line hits="0" number="172"/>
+ <line hits="0" number="174"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="0" number="186"/>
+ <line hits="0" number="187"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="194"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="199"/>
+ <line hits="0" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="223"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="228"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="244"/>
+ <line hits="0" number="245"/>
+ <line hits="0" number="246"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="251"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="260"/>
+ <line hits="1" number="261"/>
+ <line hits="1" number="262"/>
+ <line hits="0" number="263"/>
+ <line hits="0" number="264"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="271"/>
+ <line hits="1" number="272"/>
+ <line hits="1" number="274"/>
+ <line hits="1" number="275"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="287"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="295"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="297"/>
+ <line hits="1" number="298"/>
+ <line hits="1" number="300"/>
+ <line hits="0" number="301"/>
+ <line hits="0" number="302"/>
+ <line hits="0" number="303"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="309"/>
+ <line hits="1" number="311"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="319"/>
+ <line hits="1" number="321"/>
+ <line hits="1" number="323"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="326"/>
+ <line hits="1" number="328"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="335"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="345"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="349"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="355"/>
+ <line hits="1" number="357"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="364"/>
+ <line hits="1" number="366"/>
+ <line hits="1" number="370"/>
+ <line hits="1" number="372"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="381"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="384"/>
+ <line hits="1" number="385"/>
+ <line hits="1" number="387"/>
+ <line hits="0" number="388"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="391"/>
+ <line hits="1" number="394"/>
+ <line hits="1" number="395"/>
+ <line hits="1" number="396"/>
+ <line hits="1" number="397"/>
+ <line hits="1" number="398"/>
+ <line hits="1" number="399"/>
+ <line hits="1" number="401"/>
+ <line hits="1" number="402"/>
+ <line hits="1" number="403"/>
+ <line hits="1" number="405"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="407"/>
+ <line hits="1" number="408"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="411"/>
+ <line hits="1" number="413"/>
+ <line hits="0" number="414"/>
+ <line hits="1" number="416"/>
+ <line hits="1" number="417"/>
+ <line hits="0" number="418"/>
+ <line hits="0" number="419"/>
+ <line hits="1" number="422"/>
+ <line hits="0" number="423"/>
+ <line hits="1" number="425"/>
+ <line hits="1" number="426"/>
+ <line hits="0" number="427"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="432"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="438"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="442"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="446"/>
+ <line hits="1" number="451"/>
+ <line hits="1" number="452"/>
+ <line hits="1" number="453"/>
+ <line hits="1" number="454"/>
+ <line hits="0" number="455"/>
+ <line hits="1" number="457"/>
+ <line hits="1" number="458"/>
+ <line hits="1" number="459"/>
+ <line hits="1" number="461"/>
+ <line hits="1" number="464"/>
+ <line hits="1" number="465"/>
+ <line hits="1" number="467"/>
+ <line hits="1" number="469"/>
+ <line hits="1" number="476"/>
+ <line hits="1" number="477"/>
+ <line hits="1" number="478"/>
+ <line hits="1" number="479"/>
+ <line hits="0" number="483"/>
+ <line hits="1" number="485"/>
+ <line hits="1" number="491"/>
+ <line hits="1" number="492"/>
+ <line hits="1" number="493"/>
+ <line hits="1" number="496"/>
+ <line hits="1" number="497"/>
+ <line hits="1" number="498"/>
+ <line hits="1" number="499"/>
+ <line hits="1" number="500"/>
+ <line hits="1" number="501"/>
+ <line hits="1" number="502"/>
+ <line hits="1" number="503"/>
+ <line hits="1" number="507"/>
+ <line hits="1" number="508"/>
+ <line hits="1" number="510"/>
+ <line hits="1" number="512"/>
+ <line hits="1" number="513"/>
+ <line hits="1" number="514"/>
+ <line hits="1" number="515"/>
+ <line hits="0" number="516"/>
+ <line hits="0" number="517"/>
+ <line hits="1" number="518"/>
+ <line hits="1" number="519"/>
+ <line hits="1" number="520"/>
+ <line hits="1" number="521"/>
+ <line hits="1" number="522"/>
+ <line hits="1" number="524"/>
+ <line hits="0" number="525"/>
+ <line hits="1" number="527"/>
+ <line hits="1" number="528"/>
+ <line hits="1" number="530"/>
+ <line hits="1" number="531"/>
+ <line hits="1" number="532"/>
+ <line hits="1" number="535"/>
+ <line hits="0" number="536"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/import_ref.py" line-rate="0.7182" name="import_ref.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="45"/>
+ <line hits="0" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="0" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="0" number="63"/>
+ <line hits="0" number="64"/>
+ <line hits="0" number="65"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="0" number="118"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="0" number="132"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="140"/>
+ <line hits="0" number="141"/>
+ <line hits="0" number="142"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="0" number="150"/>
+ <line hits="0" number="151"/>
+ <line hits="1" number="153"/>
+ <line hits="0" number="154"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="166"/>
+ <line hits="0" number="167"/>
+ <line hits="0" number="168"/>
+ <line hits="0" number="171"/>
+ <line hits="0" number="172"/>
+ <line hits="0" number="173"/>
+ <line hits="0" number="174"/>
+ <line hits="0" number="175"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="183"/>
+ <line hits="0" number="185"/>
+ <line hits="1" number="187"/>
+ <line hits="0" number="188"/>
+ <line hits="0" number="189"/>
+ <line hits="0" number="190"/>
+ <line hits="0" number="191"/>
+ <line hits="0" number="192"/>
+ <line hits="0" number="193"/>
+ <line hits="0" number="194"/>
+ <line hits="0" number="195"/>
+ <line hits="0" number="196"/>
+ <line hits="0" number="197"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="204"/>
+ <line hits="0" number="205"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/import_srpm.py" line-rate="0.9394" name="import_srpm.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="0" number="65"/>
+ <line hits="0" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="0" number="75"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="0" number="80"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="108"/>
+ <line hits="0" number="109"/>
+ <line hits="0" number="110"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="194"/>
+ <line hits="1" number="197"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="213"/>
+ <line hits="0" number="214"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="228"/>
+ <line hits="1" number="229"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="241"/>
+ <line hits="0" number="242"/>
+ <line hits="0" number="243"/>
+ <line hits="0" number="244"/>
+ <line hits="1" number="245"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="251"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="254"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="260"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="272"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="277"/>
+ <line hits="1" number="278"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="282"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="287"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="289"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="292"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="295"/>
+ <line hits="1" number="298"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="300"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="304"/>
+ <line hits="1" number="305"/>
+ <line hits="0" number="306"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="319"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="323"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="327"/>
+ <line hits="1" number="328"/>
+ <line hits="1" number="331"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="336"/>
+ <line hits="1" number="338"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="344"/>
+ <line hits="1" number="345"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="348"/>
+ <line hits="1" number="349"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="352"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="364"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="367"/>
+ <line hits="1" number="368"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="370"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="372"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="376"/>
+ <line hits="1" number="377"/>
+ <line hits="1" number="378"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="383"/>
+ <line hits="1" number="384"/>
+ <line hits="1" number="387"/>
+ <line hits="1" number="389"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="391"/>
+ <line hits="1" number="392"/>
+ <line hits="1" number="393"/>
+ <line hits="1" number="394"/>
+ <line hits="1" number="395"/>
+ <line hits="1" number="397"/>
+ <line hits="1" number="398"/>
+ <line hits="1" number="405"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="412"/>
+ <line hits="1" number="413"/>
+ <line hits="1" number="414"/>
+ <line hits="1" number="415"/>
+ <line hits="1" number="416"/>
+ <line hits="1" number="420"/>
+ <line hits="1" number="423"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="428"/>
+ <line hits="1" number="429"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="431"/>
+ <line hits="1" number="432"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="442"/>
+ <line hits="1" number="450"/>
+ <line hits="1" number="451"/>
+ <line hits="1" number="453"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="455"/>
+ <line hits="0" number="456"/>
+ <line hits="1" number="457"/>
+ <line hits="1" number="458"/>
+ <line hits="1" number="460"/>
+ <line hits="1" number="469"/>
+ <line hits="1" number="472"/>
+ <line hits="1" number="473"/>
+ <line hits="1" number="474"/>
+ <line hits="1" number="480"/>
+ <line hits="1" number="482"/>
+ <line hits="0" number="483"/>
+ <line hits="0" number="484"/>
+ <line hits="1" number="485"/>
+ <line hits="0" number="486"/>
+ <line hits="1" number="487"/>
+ <line hits="0" number="488"/>
+ <line hits="0" number="489"/>
+ <line hits="1" number="490"/>
+ <line hits="1" number="491"/>
+ <line hits="1" number="492"/>
+ <line hits="1" number="493"/>
+ <line hits="1" number="494"/>
+ <line hits="1" number="495"/>
+ <line hits="1" number="496"/>
+ <line hits="1" number="497"/>
+ <line hits="1" number="498"/>
+ <line hits="1" number="500"/>
+ <line hits="1" number="501"/>
+ <line hits="1" number="503"/>
+ <line hits="1" number="504"/>
+ <line hits="1" number="505"/>
+ <line hits="1" number="508"/>
+ <line hits="0" number="509"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/pq.py" line-rate="0.7888" name="pq.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="0" number="58"/>
+ <line hits="0" number="59"/>
+ <line hits="0" number="60"/>
+ <line hits="0" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="0" number="76"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="0" number="108"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="0" number="140"/>
+ <line hits="0" number="141"/>
+ <line hits="0" number="142"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="0" number="187"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="0" number="213"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="229"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="246"/>
+ <line hits="0" number="256"/>
+ <line hits="0" number="257"/>
+ <line hits="0" number="259"/>
+ <line hits="0" number="260"/>
+ <line hits="0" number="261"/>
+ <line hits="0" number="263"/>
+ <line hits="0" number="264"/>
+ <line hits="0" number="266"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="286"/>
+ <line hits="1" number="287"/>
+ <line hits="1" number="289"/>
+ <line hits="0" number="290"/>
+ <line hits="0" number="291"/>
+ <line hits="0" number="292"/>
+ <line hits="0" number="293"/>
+ <line hits="0" number="295"/>
+ <line hits="1" number="297"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="300"/>
+ <line hits="1" number="301"/>
+ <line hits="0" number="303"/>
+ <line hits="1" number="306"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="314"/>
+ <line hits="1" number="315"/>
+ <line hits="0" number="316"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="321"/>
+ <line hits="1" number="322"/>
+ <line hits="0" number="323"/>
+ <line hits="1" number="324"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="326"/>
+ <line hits="0" number="327"/>
+ <line hits="0" number="328"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="331"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="335"/>
+ <line hits="0" number="336"/>
+ <line hits="0" number="337"/>
+ <line hits="0" number="338"/>
+ <line hits="0" number="339"/>
+ <line hits="0" number="340"/>
+ <line hits="0" number="341"/>
+ <line hits="1" number="344"/>
+ <line hits="0" number="345"/>
+ <line hits="0" number="347"/>
+ <line hits="1" number="349"/>
+ <line hits="0" number="350"/>
+ <line hits="0" number="351"/>
+ <line hits="1" number="353"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="357"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="361"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="368"/>
+ <line hits="1" number="370"/>
+ <line hits="1" number="371"/>
+ <line hits="1" number="372"/>
+ <line hits="1" number="375"/>
+ <line hits="1" number="379"/>
+ <line hits="1" number="381"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="383"/>
+ <line hits="1" number="384"/>
+ <line hits="1" number="385"/>
+ <line hits="1" number="388"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="391"/>
+ <line hits="1" number="392"/>
+ <line hits="1" number="393"/>
+ <line hits="1" number="395"/>
+ <line hits="1" number="396"/>
+ <line hits="1" number="399"/>
+ <line hits="1" number="400"/>
+ <line hits="1" number="413"/>
+ <line hits="1" number="414"/>
+ <line hits="1" number="415"/>
+ <line hits="1" number="417"/>
+ <line hits="1" number="418"/>
+ <line hits="1" number="419"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="422"/>
+ <line hits="1" number="423"/>
+ <line hits="1" number="424"/>
+ <line hits="1" number="426"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="428"/>
+ <line hits="1" number="429"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="431"/>
+ <line hits="1" number="433"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="436"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="438"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="440"/>
+ <line hits="1" number="443"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="446"/>
+ <line hits="1" number="447"/>
+ <line hits="1" number="450"/>
+ <line hits="1" number="451"/>
+ <line hits="1" number="453"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="455"/>
+ <line hits="1" number="457"/>
+ <line hits="1" number="459"/>
+ <line hits="0" number="460"/>
+ <line hits="0" number="461"/>
+ <line hits="1" number="463"/>
+ <line hits="1" number="465"/>
+ <line hits="1" number="466"/>
+ <line hits="0" number="467"/>
+ <line hits="0" number="468"/>
+ <line hits="0" number="469"/>
+ <line hits="0" number="470"/>
+ <line hits="0" number="472"/>
+ <line hits="0" number="474"/>
+ <line hits="0" number="475"/>
+ <line hits="1" number="477"/>
+ <line hits="1" number="478"/>
+ <line hits="0" number="479"/>
+ <line hits="0" number="480"/>
+ <line hits="0" number="481"/>
+ <line hits="1" number="483"/>
+ <line hits="1" number="484"/>
+ <line hits="1" number="485"/>
+ <line hits="1" number="486"/>
+ <line hits="1" number="487"/>
+ <line hits="1" number="488"/>
+ <line hits="1" number="489"/>
+ <line hits="1" number="490"/>
+ <line hits="1" number="491"/>
+ <line hits="1" number="492"/>
+ <line hits="1" number="493"/>
+ <line hits="0" number="494"/>
+ <line hits="0" number="495"/>
+ <line hits="0" number="496"/>
+ <line hits="1" number="497"/>
+ <line hits="1" number="498"/>
+ <line hits="0" number="499"/>
+ <line hits="0" number="500"/>
+ <line hits="0" number="501"/>
+ <line hits="0" number="502"/>
+ <line hits="0" number="503"/>
+ <line hits="0" number="504"/>
+ <line hits="0" number="505"/>
+ <line hits="0" number="506"/>
+ <line hits="0" number="507"/>
+ <line hits="1" number="509"/>
+ <line hits="1" number="512"/>
+ <line hits="0" number="513"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/pq_rpm.py" line-rate="0.9273" name="pq_rpm.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="0" number="55"/>
+ <line hits="0" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="0" number="69"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="0" number="74"/>
+ <line hits="0" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="82"/>
+ <line hits="0" number="83"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="116"/>
+ <line hits="0" number="117"/>
+ <line hits="0" number="118"/>
+ <line hits="0" number="121"/>
+ <line hits="0" number="122"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="0" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="154"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="180"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="184"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="191"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="213"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="230"/>
+ <line hits="1" number="231"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="238"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="241"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="0" number="244"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="251"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="261"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="271"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="282"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="285"/>
+ <line hits="1" number="287"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="292"/>
+ <line hits="1" number="293"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="295"/>
+ <line hits="1" number="298"/>
+ <line hits="1" number="299"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="303"/>
+ <line hits="1" number="305"/>
+ <line hits="0" number="306"/>
+ <line hits="0" number="307"/>
+ <line hits="1" number="311"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="314"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="321"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="324"/>
+ <line hits="0" number="325"/>
+ <line hits="1" number="326"/>
+ <line hits="1" number="328"/>
+ <line hits="1" number="329"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="331"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="336"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="342"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="344"/>
+ <line hits="1" number="345"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="348"/>
+ <line hits="1" number="349"/>
+ <line hits="1" number="351"/>
+ <line hits="1" number="352"/>
+ <line hits="1" number="355"/>
+ <line hits="1" number="357"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="359"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="362"/>
+ <line hits="1" number="365"/>
+ <line hits="1" number="366"/>
+ <line hits="1" number="380"/>
+ <line hits="1" number="382"/>
+ <line hits="1" number="383"/>
+ <line hits="1" number="386"/>
+ <line hits="1" number="387"/>
+ <line hits="1" number="388"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="392"/>
+ <line hits="1" number="394"/>
+ <line hits="1" number="397"/>
+ <line hits="1" number="398"/>
+ <line hits="1" number="400"/>
+ <line hits="1" number="402"/>
+ <line hits="1" number="403"/>
+ <line hits="1" number="404"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="407"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="412"/>
+ <line hits="1" number="414"/>
+ <line hits="1" number="415"/>
+ <line hits="1" number="416"/>
+ <line hits="1" number="417"/>
+ <line hits="1" number="420"/>
+ <line hits="1" number="422"/>
+ <line hits="1" number="424"/>
+ <line hits="1" number="425"/>
+ <line hits="1" number="426"/>
+ <line hits="1" number="428"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="431"/>
+ <line hits="1" number="432"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="436"/>
+ <line hits="1" number="437"/>
+ <line hits="1" number="438"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="440"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="443"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="446"/>
+ <line hits="1" number="448"/>
+ <line hits="1" number="449"/>
+ <line hits="1" number="450"/>
+ <line hits="1" number="451"/>
+ <line hits="1" number="452"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="456"/>
+ <line hits="1" number="457"/>
+ <line hits="1" number="458"/>
+ <line hits="1" number="459"/>
+ <line hits="1" number="460"/>
+ <line hits="1" number="461"/>
+ <line hits="1" number="462"/>
+ <line hits="1" number="463"/>
+ <line hits="1" number="464"/>
+ <line hits="1" number="465"/>
+ <line hits="1" number="466"/>
+ <line hits="1" number="467"/>
+ <line hits="1" number="468"/>
+ <line hits="1" number="469"/>
+ <line hits="1" number="470"/>
+ <line hits="1" number="471"/>
+ <line hits="0" number="472"/>
+ <line hits="0" number="473"/>
+ <line hits="1" number="474"/>
+ <line hits="0" number="475"/>
+ <line hits="1" number="476"/>
+ <line hits="0" number="477"/>
+ <line hits="0" number="478"/>
+ <line hits="1" number="479"/>
+ <line hits="1" number="480"/>
+ <line hits="1" number="481"/>
+ <line hits="1" number="482"/>
+ <line hits="1" number="484"/>
+ <line hits="1" number="486"/>
+ <line hits="1" number="489"/>
+ <line hits="0" number="490"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/pristine_tar.py" line-rate="0.7581" name="pristine_tar.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="0" number="43"/>
+ <line hits="0" number="44"/>
+ <line hits="0" number="45"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="0" number="66"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="0" number="79"/>
+ <line hits="1" number="81"/>
+ <line hits="0" number="82"/>
+ <line hits="0" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="89"/>
+ <line hits="0" number="90"/>
+ <line hits="0" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="103"/>
+ <line hits="0" number="104"/>
+ <line hits="0" number="105"/>
+ <line hits="0" number="106"/>
+ <line hits="0" number="107"/>
+ <line hits="0" number="108"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="119"/>
+ <line hits="0" number="120"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/pull.py" line-rate="0.7727" name="pull.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="20"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="0" number="49"/>
+ <line hits="0" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="0" number="61"/>
+ <line hits="0" number="62"/>
+ <line hits="0" number="63"/>
+ <line hits="0" number="65"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="107"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="0" number="119"/>
+ <line hits="0" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="0" number="129"/>
+ <line hits="1" number="130"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="0" number="163"/>
+ <line hits="0" number="164"/>
+ <line hits="0" number="165"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="0" number="171"/>
+ <line hits="0" number="173"/>
+ <line hits="0" number="174"/>
+ <line hits="0" number="175"/>
+ <line hits="0" number="177"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="180"/>
+ <line hits="0" number="181"/>
+ <line hits="0" number="182"/>
+ <line hits="0" number="183"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="190"/>
+ <line hits="0" number="191"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="193"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="207"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="210"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="212"/>
+ <line hits="1" number="214"/>
+ <line hits="1" number="215"/>
+ <line hits="0" number="216"/>
+ <line hits="1" number="218"/>
+ <line hits="0" number="219"/>
+ <line hits="0" number="220"/>
+ <line hits="0" number="221"/>
+ <line hits="1" number="223"/>
+ <line hits="0" number="224"/>
+ <line hits="0" number="225"/>
+ <line hits="0" number="226"/>
+ <line hits="0" number="227"/>
+ <line hits="0" number="228"/>
+ <line hits="0" number="229"/>
+ <line hits="0" number="230"/>
+ <line hits="0" number="231"/>
+ <line hits="0" number="232"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="237"/>
+ <line hits="0" number="238"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/push.py" line-rate="0.8729" name="push.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="34"/>
+ <line hits="0" number="36"/>
+ <line hits="0" number="37"/>
+ <line hits="0" number="38"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="42"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="0" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="78"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="0" number="83"/>
+ <line hits="0" number="84"/>
+ <line hits="0" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="0" number="119"/>
+ <line hits="1" number="121"/>
+ <line hits="0" number="122"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="124"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="0" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="135"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="151"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="1" number="170"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="173"/>
+ <line hits="1" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="1" number="179"/>
+ <line hits="0" number="180"/>
+ <line hits="0" number="181"/>
+ <line hits="1" number="183"/>
+ <line hits="1" number="186"/>
+ <line hits="0" number="187"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/rpm_ch.py" line-rate="0.9565" name="rpm_ch.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="82"/>
+ <line hits="0" number="83"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="117"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="0" number="122"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="127"/>
+ <line hits="1" number="128"/>
+ <line hits="0" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="150"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="1" number="160"/>
+ <line hits="1" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="171"/>
+ <line hits="1" number="174"/>
+ <line hits="0" number="175"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="179"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="1" number="187"/>
+ <line hits="1" number="188"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="190"/>
+ <line hits="1" number="194"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="196"/>
+ <line hits="1" number="198"/>
+ <line hits="1" number="199"/>
+ <line hits="1" number="200"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="204"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="206"/>
+ <line hits="1" number="208"/>
+ <line hits="1" number="209"/>
+ <line hits="1" number="211"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="217"/>
+ <line hits="1" number="218"/>
+ <line hits="1" number="220"/>
+ <line hits="1" number="221"/>
+ <line hits="1" number="224"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="229"/>
+ <line hits="1" number="230"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="234"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="237"/>
+ <line hits="1" number="240"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="245"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="250"/>
+ <line hits="1" number="253"/>
+ <line hits="1" number="254"/>
+ <line hits="0" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="0" number="260"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="265"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="270"/>
+ <line hits="1" number="273"/>
+ <line hits="1" number="274"/>
+ <line hits="1" number="276"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="282"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="289"/>
+ <line hits="1" number="290"/>
+ <line hits="1" number="291"/>
+ <line hits="1" number="292"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="297"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="306"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="310"/>
+ <line hits="1" number="312"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="315"/>
+ <line hits="1" number="316"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="319"/>
+ <line hits="1" number="321"/>
+ <line hits="1" number="323"/>
+ <line hits="1" number="325"/>
+ <line hits="1" number="326"/>
+ <line hits="1" number="327"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="336"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="352"/>
+ <line hits="1" number="354"/>
+ <line hits="1" number="356"/>
+ <line hits="1" number="358"/>
+ <line hits="1" number="360"/>
+ <line hits="1" number="363"/>
+ <line hits="1" number="366"/>
+ <line hits="1" number="368"/>
+ <line hits="1" number="369"/>
+ <line hits="1" number="373"/>
+ <line hits="1" number="377"/>
+ <line hits="1" number="379"/>
+ <line hits="1" number="381"/>
+ <line hits="1" number="383"/>
+ <line hits="1" number="386"/>
+ <line hits="1" number="388"/>
+ <line hits="1" number="389"/>
+ <line hits="1" number="390"/>
+ <line hits="1" number="392"/>
+ <line hits="1" number="394"/>
+ <line hits="1" number="395"/>
+ <line hits="1" number="397"/>
+ <line hits="1" number="399"/>
+ <line hits="1" number="402"/>
+ <line hits="1" number="404"/>
+ <line hits="1" number="405"/>
+ <line hits="1" number="406"/>
+ <line hits="1" number="408"/>
+ <line hits="1" number="409"/>
+ <line hits="1" number="411"/>
+ <line hits="1" number="412"/>
+ <line hits="1" number="414"/>
+ <line hits="1" number="415"/>
+ <line hits="1" number="418"/>
+ <line hits="1" number="421"/>
+ <line hits="1" number="422"/>
+ <line hits="1" number="425"/>
+ <line hits="1" number="426"/>
+ <line hits="1" number="427"/>
+ <line hits="1" number="429"/>
+ <line hits="1" number="430"/>
+ <line hits="1" number="431"/>
+ <line hits="1" number="434"/>
+ <line hits="1" number="436"/>
+ <line hits="1" number="439"/>
+ <line hits="1" number="441"/>
+ <line hits="1" number="442"/>
+ <line hits="1" number="444"/>
+ <line hits="1" number="445"/>
+ <line hits="1" number="446"/>
+ <line hits="1" number="447"/>
+ <line hits="0" number="448"/>
+ <line hits="0" number="449"/>
+ <line hits="0" number="450"/>
+ <line hits="1" number="452"/>
+ <line hits="1" number="454"/>
+ <line hits="1" number="457"/>
+ <line hits="0" number="458"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/supercommand.py" line-rate="0.8312" name="supercommand.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="54"/>
+ <line hits="0" number="55"/>
+ <line hits="0" number="56"/>
+ <line hits="0" number="57"/>
+ <line hits="0" number="58"/>
+ <line hits="0" number="59"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="69"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="79"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="88"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="109"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="121"/>
+ <line hits="0" number="124"/>
+ <line hits="0" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="0" number="127"/>
+ <line hits="0" number="128"/>
+ <line hits="1" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="141"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="143"/>
+ <line hits="0" number="145"/>
+ <line hits="1" number="148"/>
+ <line hits="0" number="149"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/tag.py" line-rate="0.7816" name="tag.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="44"/>
+ <line hits="1" number="45"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="55"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="0" number="85"/>
+ <line hits="0" number="86"/>
+ <line hits="0" number="87"/>
+ <line hits="1" number="89"/>
+ <line hits="1" number="91"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="106"/>
+ <line hits="1" number="108"/>
+ <line hits="1" number="111"/>
+ <line hits="1" number="112"/>
+ <line hits="1" number="113"/>
+ <line hits="0" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="121"/>
+ <line hits="1" number="122"/>
+ <line hits="0" number="123"/>
+ <line hits="1" number="125"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="127"/>
+ <line hits="0" number="128"/>
+ <line hits="0" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="135"/>
+ <line hits="0" number="136"/>
+ <line hits="0" number="139"/>
+ <line hits="1" number="142"/>
+ <line hits="1" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="0" number="145"/>
+ <line hits="0" number="146"/>
+ <line hits="0" number="147"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="150"/>
+ <line hits="0" number="151"/>
+ <line hits="0" number="152"/>
+ <line hits="0" number="153"/>
+ <line hits="0" number="154"/>
+ <line hits="0" number="155"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="160"/>
+ <line hits="0" number="161"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ <package branch-rate="0" complexity="0" line-rate="0.8321" name="scripts.common">
+ <classes>
+ <class branch-rate="0" complexity="0" filename="scripts/common/__init__.py" line-rate="0.9286" name="__init__.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="35"/>
+ <line hits="0" number="36"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="48"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="53"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="0" number="68"/>
+ <line hits="1" number="69"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/common/buildpackage.py" line-rate="0.8167" name="buildpackage.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="36"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ <line hits="1" number="40"/>
+ <line hits="1" number="41"/>
+ <line hits="1" number="43"/>
+ <line hits="1" number="46"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="49"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="0" number="54"/>
+ <line hits="1" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="58"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="61"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="1" number="64"/>
+ <line hits="1" number="66"/>
+ <line hits="1" number="67"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="69"/>
+ <line hits="0" number="70"/>
+ <line hits="0" number="71"/>
+ <line hits="0" number="72"/>
+ <line hits="0" number="73"/>
+ <line hits="0" number="74"/>
+ <line hits="0" number="75"/>
+ <line hits="0" number="76"/>
+ <line hits="0" number="77"/>
+ <line hits="0" number="78"/>
+ <line hits="0" number="79"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="85"/>
+ <line hits="1" number="87"/>
+ <line hits="1" number="90"/>
+ <line hits="1" number="92"/>
+ <line hits="1" number="93"/>
+ <line hits="1" number="94"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="98"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/common/hook.py" line-rate="1" name="hook.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="17"/>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="34"/>
+ <line hits="1" number="37"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="39"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/common/import_orig.py" line-rate="0.6471" name="import_orig.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="0" number="34"/>
+ <line hits="0" number="35"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="51"/>
+ <line hits="1" number="52"/>
+ <line hits="1" number="53"/>
+ <line hits="0" number="54"/>
+ <line hits="1" number="55"/>
+ <line hits="0" number="56"/>
+ <line hits="1" number="57"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="63"/>
+ <line hits="0" number="64"/>
+ <line hits="0" number="65"/>
+ <line hits="1" number="68"/>
+ <line hits="1" number="70"/>
+ <line hits="1" number="71"/>
+ <line hits="1" number="72"/>
+ <line hits="1" number="73"/>
+ <line hits="1" number="76"/>
+ <line hits="1" number="81"/>
+ <line hits="1" number="82"/>
+ <line hits="1" number="83"/>
+ <line hits="1" number="84"/>
+ <line hits="1" number="86"/>
+ <line hits="1" number="87"/>
+ <line hits="0" number="92"/>
+ <line hits="1" number="95"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="0" number="111"/>
+ <line hits="1" number="114"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="126"/>
+ <line hits="1" number="129"/>
+ <line hits="1" number="131"/>
+ <line hits="1" number="132"/>
+ <line hits="1" number="133"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="137"/>
+ <line hits="1" number="138"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="140"/>
+ <line hits="1" number="143"/>
+ <line hits="0" number="152"/>
+ <line hits="0" number="154"/>
+ <line hits="0" number="155"/>
+ <line hits="0" number="156"/>
+ <line hits="0" number="157"/>
+ <line hits="0" number="159"/>
+ <line hits="0" number="160"/>
+ <line hits="0" number="162"/>
+ <line hits="0" number="163"/>
+ <line hits="0" number="165"/>
+ <line hits="0" number="166"/>
+ <line hits="0" number="168"/>
+ <line hits="0" number="169"/>
+ <line hits="0" number="170"/>
+ <line hits="0" number="171"/>
+ <line hits="0" number="172"/>
+ <line hits="0" number="173"/>
+ <line hits="0" number="174"/>
+ <line hits="0" number="175"/>
+ <line hits="0" number="176"/>
+ <line hits="0" number="177"/>
+ <line hits="0" number="179"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/common/pq.py" line-rate="0.8827" name="pq.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="19"/>
+ <line hits="1" number="21"/>
+ <line hits="1" number="22"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="27"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="30"/>
+ <line hits="1" number="31"/>
+ <line hits="1" number="32"/>
+ <line hits="1" number="33"/>
+ <line hits="1" number="35"/>
+ <line hits="1" number="38"/>
+ <line hits="1" number="47"/>
+ <line hits="1" number="50"/>
+ <line hits="1" number="59"/>
+ <line hits="1" number="60"/>
+ <line hits="1" number="62"/>
+ <line hits="1" number="65"/>
+ <line hits="1" number="74"/>
+ <line hits="1" number="75"/>
+ <line hits="1" number="77"/>
+ <line hits="1" number="80"/>
+ <line hits="1" number="96"/>
+ <line hits="1" number="97"/>
+ <line hits="1" number="99"/>
+ <line hits="1" number="100"/>
+ <line hits="1" number="101"/>
+ <line hits="1" number="102"/>
+ <line hits="1" number="103"/>
+ <line hits="1" number="104"/>
+ <line hits="1" number="105"/>
+ <line hits="1" number="106"/>
+ <line hits="0" number="108"/>
+ <line hits="1" number="110"/>
+ <line hits="1" number="111"/>
+ <line hits="0" number="113"/>
+ <line hits="1" number="115"/>
+ <line hits="1" number="116"/>
+ <line hits="1" number="118"/>
+ <line hits="1" number="119"/>
+ <line hits="1" number="120"/>
+ <line hits="1" number="123"/>
+ <line hits="1" number="127"/>
+ <line hits="0" number="128"/>
+ <line hits="0" number="129"/>
+ <line hits="0" number="130"/>
+ <line hits="0" number="131"/>
+ <line hits="0" number="132"/>
+ <line hits="1" number="134"/>
+ <line hits="1" number="136"/>
+ <line hits="1" number="139"/>
+ <line hits="1" number="141"/>
+ <line hits="0" number="142"/>
+ <line hits="0" number="143"/>
+ <line hits="1" number="144"/>
+ <line hits="1" number="145"/>
+ <line hits="1" number="146"/>
+ <line hits="1" number="147"/>
+ <line hits="1" number="148"/>
+ <line hits="1" number="149"/>
+ <line hits="1" number="152"/>
+ <line hits="1" number="153"/>
+ <line hits="1" number="155"/>
+ <line hits="1" number="156"/>
+ <line hits="1" number="157"/>
+ <line hits="1" number="158"/>
+ <line hits="1" number="159"/>
+ <line hits="0" number="160"/>
+ <line hits="0" number="161"/>
+ <line hits="1" number="162"/>
+ <line hits="1" number="163"/>
+ <line hits="1" number="164"/>
+ <line hits="1" number="165"/>
+ <line hits="1" number="166"/>
+ <line hits="1" number="167"/>
+ <line hits="1" number="168"/>
+ <line hits="1" number="169"/>
+ <line hits="0" number="170"/>
+ <line hits="0" number="171"/>
+ <line hits="1" number="172"/>
+ <line hits="1" number="174"/>
+ <line hits="1" number="176"/>
+ <line hits="1" number="177"/>
+ <line hits="1" number="178"/>
+ <line hits="0" number="179"/>
+ <line hits="0" number="180"/>
+ <line hits="1" number="181"/>
+ <line hits="1" number="182"/>
+ <line hits="1" number="185"/>
+ <line hits="1" number="186"/>
+ <line hits="0" number="187"/>
+ <line hits="0" number="188"/>
+ <line hits="1" number="189"/>
+ <line hits="1" number="192"/>
+ <line hits="1" number="195"/>
+ <line hits="1" number="201"/>
+ <line hits="1" number="202"/>
+ <line hits="1" number="203"/>
+ <line hits="1" number="205"/>
+ <line hits="1" number="206"/>
+ <line hits="0" number="208"/>
+ <line hits="0" number="209"/>
+ <line hits="0" number="213"/>
+ <line hits="1" number="215"/>
+ <line hits="1" number="216"/>
+ <line hits="1" number="219"/>
+ <line hits="1" number="222"/>
+ <line hits="1" number="223"/>
+ <line hits="1" number="225"/>
+ <line hits="1" number="226"/>
+ <line hits="1" number="227"/>
+ <line hits="1" number="229"/>
+ <line hits="1" number="230"/>
+ <line hits="1" number="232"/>
+ <line hits="1" number="233"/>
+ <line hits="1" number="235"/>
+ <line hits="1" number="236"/>
+ <line hits="1" number="239"/>
+ <line hits="1" number="242"/>
+ <line hits="1" number="243"/>
+ <line hits="1" number="244"/>
+ <line hits="1" number="246"/>
+ <line hits="1" number="247"/>
+ <line hits="1" number="248"/>
+ <line hits="1" number="249"/>
+ <line hits="1" number="252"/>
+ <line hits="1" number="255"/>
+ <line hits="1" number="256"/>
+ <line hits="1" number="257"/>
+ <line hits="1" number="258"/>
+ <line hits="1" number="259"/>
+ <line hits="1" number="262"/>
+ <line hits="1" number="263"/>
+ <line hits="1" number="264"/>
+ <line hits="1" number="266"/>
+ <line hits="1" number="267"/>
+ <line hits="1" number="268"/>
+ <line hits="1" number="269"/>
+ <line hits="1" number="271"/>
+ <line hits="0" number="272"/>
+ <line hits="1" number="275"/>
+ <line hits="1" number="277"/>
+ <line hits="1" number="279"/>
+ <line hits="1" number="280"/>
+ <line hits="1" number="281"/>
+ <line hits="1" number="282"/>
+ <line hits="1" number="283"/>
+ <line hits="1" number="284"/>
+ <line hits="1" number="285"/>
+ <line hits="1" number="288"/>
+ <line hits="1" number="293"/>
+ <line hits="1" number="294"/>
+ <line hits="1" number="296"/>
+ <line hits="1" number="297"/>
+ <line hits="0" number="298"/>
+ <line hits="1" number="301"/>
+ <line hits="1" number="302"/>
+ <line hits="1" number="305"/>
+ <line hits="1" number="306"/>
+ <line hits="1" number="307"/>
+ <line hits="1" number="308"/>
+ <line hits="1" number="311"/>
+ <line hits="1" number="313"/>
+ <line hits="1" number="317"/>
+ <line hits="1" number="318"/>
+ <line hits="1" number="319"/>
+ <line hits="1" number="320"/>
+ <line hits="1" number="321"/>
+ <line hits="1" number="322"/>
+ <line hits="1" number="323"/>
+ <line hits="1" number="327"/>
+ <line hits="1" number="329"/>
+ <line hits="1" number="330"/>
+ <line hits="1" number="331"/>
+ <line hits="1" number="332"/>
+ <line hits="1" number="333"/>
+ <line hits="1" number="334"/>
+ <line hits="1" number="335"/>
+ <line hits="1" number="336"/>
+ <line hits="1" number="337"/>
+ <line hits="1" number="338"/>
+ <line hits="1" number="339"/>
+ <line hits="1" number="340"/>
+ <line hits="1" number="341"/>
+ <line hits="1" number="342"/>
+ <line hits="1" number="343"/>
+ <line hits="1" number="346"/>
+ <line hits="1" number="347"/>
+ <line hits="1" number="348"/>
+ <line hits="1" number="349"/>
+ <line hits="1" number="350"/>
+ <line hits="1" number="351"/>
+ <line hits="0" number="353"/>
+ </lines>
+ </class>
+ <class branch-rate="0" complexity="0" filename="scripts/common/repo_setup.py" line-rate="1" name="repo_setup.py">
+ <methods/>
+ <lines>
+ <line hits="1" number="18"/>
+ <line hits="1" number="20"/>
+ <line hits="1" number="23"/>
+ <line hits="1" number="24"/>
+ <line hits="1" number="25"/>
+ <line hits="1" number="26"/>
+ <line hits="1" number="28"/>
+ <line hits="1" number="29"/>
+ <line hits="1" number="30"/>
+ </lines>
+ </class>
+ </classes>
+ </package>
+ </packages>
+</coverage>
diff --git a/debian/NEWS b/debian/NEWS
new file mode 100644
index 0000000..304944a
--- /dev/null
+++ b/debian/NEWS
@@ -0,0 +1,82 @@
+git-buildpackage (0.8.14) experimental; urgency=medium
+
+ gbp import-orig's --merge-mode option now defaults to 'auto' selecting
+ 'replace' for 3.0 (quilt) packages and 'merge' otherwise. If you want to
+ retain the old behaviour of 'merge' please do so in gbp.conf.
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 04 Apr 2017 07:20:04 +0200
+
+git-buildpackage (0.6.26) unstable; urgency=medium
+
+ All gbp-* and git-* commands are now gone as announced in the
+ deprecation notice from June 2013. From now on only "gbp <command>" is
+ supported.
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 27 Apr 2015 21:55:23 +0200
+
+git-buildpackage (0.6.9) unstable; urgency=medium
+
+ This version changes the default of the cleaner options from 'debuild clean'
+ to a noop ('/bin/true'). If you want to retain the old behaviour you can still
+ configure it via gbp.conf. For more details see #670624.
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 05 Jan 2014 15:52:39 +0100
+
+git-buildpackage (0.6.0) unstable; urgency=low
+
+ In order to provide a more consistent and git like interface a new gbp super
+ command was added as a front end to all git-* and gbp-* commands. So what was
+ either git-<command> or gbp-<command> is now
+
+ gbp <command>
+
+ The old commands are still provided for backward compatibility but will be
+ removed in a future release so please update any scripts and tools relying on
+ it.
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 26 Jun 2013 13:38:16 +0200
+
+git-buildpackage (0.4.61) unstable; urgency=low
+
+ In order to not override dpkg-buildpackage's defaults for 3.0 source packages
+ the default builder command is now "debuild -i -I" instead of "debuild
+ -i\.git/ -I.git". If you want to retain the old behaviour please adjust your
+ gbp.conf accordingly.
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 22 Nov 2009 17:07:52 +0100
+
+git-buildpackage (0.4.57) unstable; urgency=low
+
+ git-import-orig dosn't generate changelog entries by default anymore. This
+ means you can safely remove --no-dch from all your scripts and config files.
+
+ If you want git-import-orig to behave as before add:
+ postimport = dch -v%(version)s New Upstream Version
+ to your gbp.conf. If you want to invoke git-dch instead, you can use:
+ postimport = git-dch -N%(version)s -S -a
+ The later is recommended since it creates git-dch compatible changelog
+ entries.
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 23 Aug 2009 17:21:50 +0200
+
+git-buildpackage (0.3.3) unstable; urgency=low
+
+ As of this version git-buildpackage doesn't pass hardcoded '-i\.git -I.git'
+ to the build command specified via --git-builder (or via the builder config
+ file options). So if you're not using the default build command (which is
+ now 'debuild -i\.git -I.git' instead of just plain 'debuild') you'll have to
+ add these options to your build command in order to exclude git metadata
+ from your diffs and source tarballs.
+ This was done to make the invocation of the build command more flexible, we
+ don't rely on debuild behaviour from now on.
+ Again: if you didn't change the default build command, you don't have to
+ change anything.
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 20 Aug 2007 18:08:37 +0200
+
+git-buildpackage (0.2.25) unstable; urgency=low
+
+ Generated tags are now by default put into debian/ and upstream/ namespaces.
+ This doesn't have any effect on already existing tags.
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 2 Feb 2007 15:56:12 +0100
diff --git a/debian/bug-presubj b/debian/bug-presubj
new file mode 100644
index 0000000..b000c9d
--- /dev/null
+++ b/debian/bug-presubj
@@ -0,0 +1,3 @@
+When reporting issues regarding git-buildpackage please run the failing command
+with --git-verbose (gbp buildpackage) or --verbose (all other commands) and
+provide the full output.
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..da0ead7
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,4767 @@
+git-buildpackage (0.9.10) unstable; urgency=medium
+
+ [ Ken Dreyer ]
+ * [f0b1bbe] deb.git: fix duplicate "tarball" in docstring.
+ Fix the docstring for create_pristine_tar_commits()
+ * [29b9f2c] deb.git: fix spelling of "described" in docstring.
+ Fix the docstring for _sanitize_version() and _unsanitize_version()
+
+ [ Guido Günther ]
+ * [62e0102] push: Allow to skip upstream and debian branch and tag push.
+ Push of tag and or branch can be skipped by setting
+ --{upstream,debian}-{branch,tag}='' . (Closes: #899234)
+ * [28a950a] tests: Check help output of tag and push too
+ * [a287bf6] config: allow to override default values via
+ add_config_file_option
+ * [b8221b8] pull: Check that repo is clean before fetching anything
+ * [6dda2da] pull: allow to set up branch tracking for missing branches.
+ If the remote branch does not exist at all that's currently not fatal.
+ (Closes: #882187)
+ * [d69006d] Depend on sensible-utils. gbp-dch uses sensible-editor.
+
+ [ Chris Lamb ]
+ * [6c30ac9] import-{dsc,orig}: Make --download deprecation text more useful.
+ Point to the manpages for usage examples. (Closes: #900606)
+
+ [ Guus Sliepen ]
+ * [48ef0ec] changelog: try iso8859-1 when utf-8 fails.
+ Fall back to iso8859-1 when opening the changelog. Helps when importing
+ old versions. (Closes: #900841)
+
+ [ Carsten Schoenert ]
+ * [50b9223] create_remote_repo: import urllib.parse
+ urllib.parse did not get imported in packaegs without changelog.
+
+ [ Iain Lane ]
+ * [5fedb2b] Ignore merge commits when looking at the pristine-tar branch.
+ (Closes: #906331)
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 19 Aug 2018 08:29:47 +0200
+
+git-buildpackage (0.9.9) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [0a6e73f] Fix typos spotted by lintian
+ * [e730865] docs: options in [DEFAULT] apply to all gbp commands
+ * [3da92ba] import-orig: Better error message when vcs-tag is not found
+ (Closes: #896654)
+ * [a6ceb00] config: when printing a single value only print the value. This
+ is more in line with what `git config` does and eases usage in scripts.
+ * [a1f4af5] docs: document how to use GBP_CONF_FILES to override
+ debian/gbp.conf
+ (Closes: #898613)
+
+ [ Ken Dreyer ]
+ * [ba32efc] bin: drop umlaut again for setuptools.
+ Setuptools cannot handle non-ascii characters in files declared in
+ "scripts". See https://github.com/pypa/setuptools/issues/761
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 21 May 2018 13:27:17 +0200
+
+git-buildpackage (0.9.8) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [c5b5d4f] Bump standards version
+ * [5f2120b] gbplogtaster: capture INFO and DEBUG log levels too.
+ This allows one to test for certain features more easily and to use it not
+ only in the error case.
+ * [e9942bd] docs: add export-orig to list of available commands
+ * [23874c2] 8bit Guido
+ * [5301692] Move rollback code out of import_orig so it can be reused in
+ 'gbp import-ref'
+ * [99b3811] RollbackDebianGitRepository: quote refs for readability
+ * [29eafbc] Makefile: Run tests verbosely by default so we see which test
+ fails / is skipped right away
+
+ [ Markus Lehtonen ]
+ * [f1b878c,042f422] tests: enable unittests without devscripts.
+ Add some additional skipIf statements, checking for the existence of the
+ debchange tool. Makes it possible to run unit tests in an environment that
+ doesn't have devscripts installed.
+
+ [ Simon McVittie ]
+ * [c159d0b] pq export: Write out patches as UTF-8 if necessary.
+ Due to behaviour changes in Python, 'gbp pq export' would previously
+ write the commit message in the base64 Content-Transfer-Encoding, which
+ is not very readable. Now it uses 8bit.
+ Based on a patch from Carlos Maddela. (Closes: #883541)
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 03 Apr 2018 13:06:47 +0200
+
+git-buildpackage (0.9.7) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [d686024] deb.source: Fix class names in doc string.
+ Thanks to Nish Aravamudan
+ * [e36592d] gbp.conf: clarify environment variables.
+ Thanks to Nish Aravamudan
+ * [74ca1fa] Enable Python3.4 in travis
+ * [c782a29] Fix typo in env var names.
+ Thanks to Nish Aravamudan for pointing this out
+ * [17914a9] docs: Although imported trees are important we mean the former.
+ Fix typo. Thanks to Ferenc Wágner (Closes: #887499)
+ * [e1aacbb] push: allow to push from detached heads
+ * [6903232] buildpackage: look at builder as well when determining the
+ changes file name
+ (Closes: #887779)
+ * [da84b60] config: strip '.py' suffix from commands.
+ This makes sure we parse config sections correctly even when running from
+ the source tree like: PYTHONPATH=. python3 "gbp/scripts/dch.py"
+ * [362f793] docs: document pq export --commit
+ * [3576ee8] tests: build docs too.
+ When running in travis build the docs too to catch errors there as well.
+ * [a708096] docs: fix section names for defaults.
+ Thanks to Christoph Berg (Closes: #888415)
+
+ [ rubicks ]
+ * [f1694df] Rework docker script to allow to build packages as well.
+
+ [ Markus Lehtonen ]
+ * [4868442] gbp.git: Python 3.4 compatibility
+ * [c8dbfe1] pq-rpm: implement --drop option.
+ Counterpart for the --drop option of gbp-pq.
+ * [5e9ab95] docs: dynamically select the DocBook-to-man tool to use to ease
+ building on other distros.
+ * [3011490] dch: implement postedit hooks.
+ Add new --postedit command line option for defining a custom hook that
+ will be run after changes to the changelog file has been finalized.
+ * [5a8bbe9] debian/gbp.conf: dch hook to automatically update version in
+ spec file.
+ * [c3e1beb] rpm packaging: update build deps of docs. Needed after docs
+ were changed to xml.
+ * [fb9cf84] import-srpm: change commit message of native sources. Include
+ the full package version in the commit message for native packages.
+ * [cb5754b] tests: add more tagging tests for import-srpm
+ * [972db70] import-srpm: implement --skip-packaging-tag option.
+ Corresponding the --skip-debian-tag options of import-dsc.
+ * [8515181] import-srpm: support --upstream-vcs-tag cmdline option.
+ Similar to what the option does in gbp import-orig.
+ * [d8ed8bd] gbp-pull: implement --all cmdline option.
+ This updates all remote-tracking branches (for the remote that is fetched
+ from) whose local branch name is identical to the remote branch name.
+ * [393edba] buildpackage-rpm: support setting the 'VCS:' tag in spec.
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 29 Jan 2018 10:46:01 +0100
+
+git-buildpackage (0.9.6) unstable; urgency=medium
+
+ * [89495f7] patch_series: Properly chain up to parent class.
+ Insted of duplicating the logic use the parent classes _read_info.
+ * [28838b9] patch_series: don't read dep3 info multiple times
+ * [7b0660a] patch_series: Remove duplicate functions.
+ No need to repeat them in Dep3Patch since the parent class Patch already
+ has them.
+ * [0730a57] patch_series: Don't fail on decoding errors when looking for
+ DEP3 headers
+ (Closes: #885929)
+ * [6979250] export-orig: fix --upstream-branch argument name in manpage
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 06 Jan 2018 20:43:18 +0100
+
+git-buildpackage (0.9.5) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [6b43cfe] docs: gbp.conf: bring back section names.
+ These got lost in f6e360f6ca0b4e3b510f82546a1daada592b18b6.
+ * [d7739b8] docs: gbp: mention push and tag
+ * [3cbdc3a] docs: gbp-buildpackage: clarify --git-no-create-orig
+ * [7bb8758] docs: gbp-buildpackage: list hooks in execution order
+ * [59f82d8] docs: gbp-buildpackage: clarify hook working directories
+ * [22d40a4] hooks: print hook name prior to execution.
+ This makes it simpler to identify build steps
+ * [56d630b] pristine-tar: print upstream version number on import.
+ Printing the debian version might be confusing
+ * [06bbc70] Fix complaints of flake8 3.5
+ * [e51358f] docs: drop duplicate "you can"
+ Thanks to Ludovic Rousseau
+ * [5aaec4f] Typo fixes.
+ Thanks to Ludovic Rousseau
+ * [4312e54] docs: Add ids to all sections
+ so we get nicer link names
+ * [13cd3b4] Handle KeyboardInterrupt in pristine_tar, push, rpm_ch and tag
+ to avoid python exceptions on the console. The other commands handle it
+ already.
+
+ [ Markus Lehtonen ]
+ * [f50069a] rpm packaging: update dependency on rpm-build.
+ Most RPM-based distribution now support weak dependencies. Make rpm-build
+ a weak dependency as it's not strictly needed by buildpackage-rpm: in
+ Fedora people probably use 'mock' and in openSUSE they use 'osc' for
+ building packages, for example.
+ * [3661127] create_remote_repo: fix typo
+ * [d9a5319] rpm.SpecFile: support %autosetup.
+ Try to do "the right thing" when %autosetup macro is used in the spec
+ file. That is, do not examine/manage %patch macros at all, but, assume
+ that patches are handled by %autosetup which was introduced in RPM
+ v4.11.
+
+ [ Maximiliano Curia ]
+ * [17a471d] pq: Parse DEP3 headers.
+ (Closes: #785274)
+
+ [ Chris West (Faux) ]
+ * [3003d07] docs: pq: Document how DEP3 headers are parsed and added to the
+ commit message.
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 29 Dec 2017 20:17:01 +0100
+
+git-buildpackage (0.9.4) unstable; urgency=medium
+
+ * [868e8ae] Bump standards version
+ * [5c078f7] docs: Add --git-overlay to synopsis
+ * [015f1a7] import-orig: use separate exit code when uscan did not find
+ anything new
+ * [45a0652] dch: Create changelog if missing.
+ Thanks to Daniel Dehennin for the groundwork on this (Closes: #669171)
+ * [c90cb78] git-pbuilder: properly escape -? (Closes: #882984)
+ * gbp-try-ff improvements
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 28 Nov 2017 12:32:01 +0100
+
+git-buildpackage (0.9.3) unstable; urgency=medium
+
+ * [9a2c5e6] import_orig: drop debian/ again in --merge-mode=replace.
+ Another fallout of the Python3 conversion.
+ Thanks to Víctor Cuadrado Juan for providing a nice reproducer
+ (Closes: #881750)
+ * [876ebb3] git-pbuilder: handle --help (Closes: #734862)
+ * [ff03bec] GitRepository: require an exact ref path when looking for
+ branches. Otherwise we might match on
+ refs/heads/refs/heads/foo instead of refs/heads/foo
+ when looking for branch named 'foo' (Closes: #813298)
+ * [3b5a7dd] changelog: handle comma in maintainers name.
+ Thanks to Andreas Beckmann for the proposed fix (Closes: #737623)
+ * [e74b40a] rpm: conditionally check for librpm flags. Not all of them are
+ in newer librpm and this way we can add new ones as needed as well.
+ This unbreaks the tests with new python3-rpm in sid.
+ * [c429d97] dch: make --auto the default. The current default mode of
+ looking at the version in the changelog only leads to problems when people
+ build up the changelog incrementally. --auto handles this since many
+ years so make this the default. There's still --since for finer control
+ where to start from. (Closes: #880552, #552624)
+ * [43d5587] docs: better explain dch operation (also addresses #880552)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 15 Nov 2017 15:33:20 +0100
+
+git-buildpackage (0.9.2) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [5b05ead] push: Don't abort on first failure. Push as many refs as
+ possible instead of aborting on the first error.
+ * Unbreak running in non-UTF8 locales
+ Thanks to Nico Schlömer for the report (Closes: #880964)
+ * [5b1614d] travis: run tests under C locale too
+ * [79d4340] setup.py: add python_requires so pip doesn't use the gbp module
+ for python2
+ Thanks to Nico Schlömer for the proposed solution
+ * [d656cbd] import-dsc: Fuzzy parse changelog date.
+ Especially older changelogs aren't 100% policy conformant (e.g. using a
+ localized day of week like). This allows us to better import older
+ histories. (Closes: #880878)
+ * [577ca32] import-orig: Run postimport hook on all successful imports.
+ So far we would skip it on initial imports which is pointless.
+ * [7d6f61a] import-orig: don't fail when importing unpacked dirs.
+ * [34b2390] import-orig: avoid master branch when importing into empty repo.
+ We don't want a master branch if debian-branch != 'master'.
+ * [c135ad4] Make import-orig-rpm tests work with import-orig.
+ We use the Debian version to import RPMs since there's currently no
+ separate import-orig-rpm needed. The tests also cover cases we were
+ currently not testing in components/deb/test_import_orig.py.
+ * [fe0022b] import-dsc: cleanup temp dirs on unexpected exceptions too
+ * [486cbfd] examples: drop gbp-posttag-push. We have "gbp push" now.
+ * [3efd339] examples: Add gbp-try-ff. Helper to fetch new upstream source,
+ refresh patches and do a test build.
+ * [1b5a344] buildpackage: Add back --git-pristine-tar-commit support.
+ The call got lost when we split out export-orig. (Closes: #880624)
+ * [4378eae] command_wrapper: quote error strings so we don't accidentally
+ try to expand values in error messages. (Closes: #881254)
+ * [e5df7d0] buildpackage: unpack additional tarballs as well in
+ --git-overlay mode. (Closes: #881253)
+ * [75273ee,53c0e7c] buildpackage: Expand --git-comp-type auto again in
+ --git-overlay mode.
+ Thanks to Maximiliano Curia for the detailed analysis (Closes: #879781)
+ * [d9fb2df] import-dsc: Apply filters on debian tarballs too
+ (Closes: #881311)
+ * Several testsuite and documentation improvements
+
+ [ Felipe Sateler ]
+ * [7f85a0d] zsh: Fix delegation of dscs options to dsc
+ * [9d276b5] zsh: dch does not use git prefix for ignore-branch option
+ (Closes: #787005)
+
+ [ Ken Dreyer ]
+ * [3d084fd] DebianChangelog: Strip trailing newlines from changes.
+ Python3's message_from_string passes on a trailing newline from
+ dpkg-parsechangelog but the consumer of the output shouldn't need to
+ bother.
+
+ [ Markus Lehtonen ]
+ * [34c22d4] tests: add tests for gbp import-orig-rpm.
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 10 Nov 2017 08:08:25 +0100
+
+git-buildpackage (0.9.1) unstable; urgency=medium
+
+ * Kill use of six.*
+ * Add gbp import-dsc hook for pk4.
+ Can be enabled via
+ mkdir -p ~/.config/pk4/hooks-enabled/unpack/
+ ln -s /usr/share/pk4/hooks-available/unpack/gbp \
+ ~/.config/pk4/hooks-enabled/unpack/
+ * [f18d6b4] import-dsc: improve error message when Debian branch does not
+ exist
+ * [01da1e6] import-dsc: make sure we don't create 'master' if not needed.
+ This way we only get the debian- and upstream-branch in empty repos and
+ not a pointless 'master' if debian-branch != master.
+ It also makes sure we don't need --create-missing-branches on empty
+ repos where it is pointless. (Closes: #750962)
+ * [cec2123] docs: Add rpm tool manpages to manual too.
+ On non-Debian platform it's sometimes cumbersome to build the manpages
+ so make sure we have them available on the web at least.
+ * [8e67334] Lock flake8 version.
+ We don't want to play catchup with new flake8 checks but rather
+ update and fix things in a controlled manner.
+ * [f1bf7c3] command_wrappers: report proper exception on bad process
+ arguments. If subprocess.communicate raised a TypeError due to bad
+ arguments 'ret' was undefined and therefore the real error reason got
+ masked.
+ Closes parts of #879495
+ * [280f4c9] pq: kill useless use of sed. This also broke paths containing
+ spaces. Thanks to André Althaus for a proposed fix.
+ * [ca273d9] dscfile: parse out upstream signatures as well
+ * [5db9f15] dscfile: Make sure we match on additional tarballs containing
+ uppercase (Closes: #880228)
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 31 Oct 2017 12:48:53 +0100
+
+git-buildpackage (0.9.0) unstable; urgency=medium
+
+ * Upload to unstable
+ * [dbb9290,9c1bd13] README: use https, shorter URLs and add image
+ * [ce7ff21] d/control: mentions new commands in packaging description
+ * [44b4e2b] import-dsc: split import of tarballs out of main. The current
+ logic tried to force native and non-native packages into the same code
+ path leading to hardly readable if-else-maybe code paths.
+ * [4b9789a] import-dsc: Look for pristine-tar reference by commit. The
+ upstream branch tip is up to date at this point already but this makes
+ sure things don't get wired if we move code around.
+ * [d0ee56b] import-dsc: don't create pristine-tar commits on every import.
+ We must not create pristine-tar commits if no new upstream sources were
+ imported. Othewise we'd create them on every Debian revision. This hardly
+ wastes space since commits are identical but does waste time.
+ * [680784b] pq: import patches before rebase if the pq branch doesn't exist
+ yet. (Closes: #876800)
+ * [c1c7fa7] pq: recommend 'rebase' and 'switch' when pq branch already
+ exists
+ * [1551051] pq: import pq branch on switch (Closes: #761166)
+ * [02978fb] pq: let drop work when pq branch is currently checked out
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 20 Oct 2017 11:41:53 +0200
+
+git-buildpackage (0.9.0~exp7) experimental; urgency=medium
+
+ * [fdf2720] Don't fail on unicode errors when importing patches. Use
+ backslashreplace not lose data.
+ * [252c102] clone: clarify what we're looking for when using vcsgit:
+ * [4e6c4cf] command_wrappers: fix path lookups.
+ Python3 performs path lookups by itself when no path is given so remove
+ our path lookup code.
+ Thanks to Nish Aravamudan
+ * [2c7a5c5] import-orig: roll back on CTRL-C too
+ * [4761cf9] import-orig: Use gz compression when repackaging non tarball
+ filtered upstream source. This makes it consistent with
+ export_orig:guess_comp_type when no compressor could be detected.
+ * [80189f2] create_remote_repo: another missing python3 string conversion
+ * [0259fd5] config: Don't require the command to be started from toplevel
+ dir. At least dch, push, tag and export-orig don't require to be run from
+ the toplevel of the git repo. Make sure we pick up debian/gbp.conf
+ nevertheless.
+ * [d75fbd4] docs: Switch to docbook-xml
+ (Closes: #877322)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 06 Oct 2017 12:03:21 +0200
+
+git-buildpackage (0.9.0~exp6) experimental; urgency=medium
+
+ * [23b334c] README.source example: fix typo.
+ Thanks to Roel van Meer
+ * [db5c670] Add tag command by splitting out the code from buildpackage
+ This is shorter than running
+ gbp buildpackage --git-tag-only
+ Closes: #797086
+ * [01ee027] bash completion: add some missing branch and file opts.
+ This unbreaks completion of options on some of the newer commands such
+ as gbp export-orig --tarball-dir=<tab>
+ * [0c6cbfa] bash completion: don't accidentally match subcommands as options.
+ We match on option first so we need to make sure we don't accidentally
+ match on subcommands. This unbreaks command completion for e.g. "gbp
+ tag"
+ * [84596ab] Unbreak dch tests with newer devscripts.
+ Due to #842468 debchange now produces shorter output omitting empty
+ blocks if there weren't any changes by the maintainer invoking
+ debchange (which is nice). So check for the matching lines in both the old
+ and new location.
+ * [6abb9e8] buildpackage: don't require debian/ prefix for --git-dist=DEP14
+ to work
+ * [d5c4804] Add id-length to gbp.conf
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 15 Sep 2017 12:51:52 +0200
+
+git-buildpackage (0.9.0~exp5) experimental; urgency=medium
+
+ * [67cdeac] push: fix option names in error output. Only buildpackage uses
+ the '--git-' prefix
+ * [04ae7d5] git: Don't decode diff output.
+ The output can be in any encoding so assuming utf-8 is plain wrong
+ * [0f84a0d] patch_series: Make sure we close the temp file
+ * [4c6f8fc] command_wrappers: allow one to look up executables in $PATH
+ * [2ca3e37] pristine-tar: look for binary in $PATH
+ to help the Ubuntu git importer that run's pristine-tar inside a snap.
+ * [b2092c1] patch_series: don't let "git mailinfo" sanitize the subject.
+ This would strip away text in []. This improves the import/export round
+ trip. (Closes: #872354)
+ * import_orig: be more descriptive what we're "using"
+ * pq: Be clearer what '--commit' did instead of a confusing "git status".
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 08 Sep 2017 11:19:36 +0200
+
+git-buildpackage (0.9.0~exp4) experimental; urgency=medium
+
+ [ Ken Dreyer ]
+ * [0ca5283] doc: expand --git-pbuilder-options docs.
+ Give an example of how to use the --git-pbuilder-options option, and
+ explain how it interacts with git-pbuilder.
+
+ [ Guido Günther ]
+ * [b7c34cc] export-orig: drop sloppy mode from the docs since it's not
+ useful here
+ * [dab6761] import-dsc: cleanup error printing and exceptions.
+ Raise exceptions with the error message instead of printing these first.
+ * [a7b96a7] export-orig: print absolute path of created tarball.
+ To avoid paths like '/a/b/../c'
+ * [d5c3101] config: make dch-opt a list option.
+ This unbreaks dch-opt when used in gbp.conf (Closes: #872141)
+ * [5032688] spawn_dch: fail if dch failed. We pass quiet=True to avoid
+ duplicate error messages
+ * [40b1883] gbp.conf: drop --noconf since it needs to be dch's first option
+ * [ec643d9] Revert "buildpackage: don't prepare any tarballs in overlay mode"
+ This breaks workflows where people have a detached debian/ dir
+ but keep upstream VCS and pristine-tar in the same repo.
+ This reverts commit 3e4e7950d2b0e81a8821f60e9a6d842c88e8b7fb.
+ Thanks to Maximiliano Curia for the report
+ * [d76834c] pq: use more gittish commit message in --commit. Use present
+ instead of past tense
+ * [5d40d44] pq-rpm: Correctly pass abbrev to format-patch
+ broken by 75e18fc5bb4831df924bc708130b0a6ab311a230
+ * [b23e958] tests: remove duplication in test options
+ * [f8714dc] log: don't color the log message, only the marker
+ to improve readability
+ * [40acd04] tests: Move pristine-tar test data to tests/data as well
+ * [19310dc] Spell pristine_tar consistently
+ * [9dc2129] pq: properly retry non-ascii charset on patch body encode.
+ The current code tried to catch the wrong exception
+ * [6d3e34c] patch_series: report decoding errors when reading patch files
+
+ [ mci156 ]
+ * [d115b67] docs: Add paragraph break
+ * [e5be602] docs: Explain the v1.0 <starting point> (Closes: #872524)
+
+ [ Chris Lamb ]
+ * [f97b910] pq: make --abbrev= configurable (Closes: #872351)
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 27 Aug 2017 17:38:19 +0200
+
+git-buildpackage (0.9.0~exp3) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [ce76320] doc: properly mark filter as repeatable in the gbp-import-dsc
+ synopsis
+ * [01a4a7f] dch: support passing arbitrary dch command line options
+ (Closes: #596513, #682854)
+ * [f208a0b] import_orig: use a more gittish commit message.
+ When replacing the debian/ dir don't used past tense and
+ keep the subject more like git-merge. (Closes: #867214)
+ * [3e4e795] buildpackage: don't prepare any tarballs in overlay mode.
+ They're not in git so we fail otherwise
+ * [66138d9] buildpackage: pass the proper tarball dir for overlays.
+ We want to look for tarballs in tarball dir (if specified) not the output
+ dir
+ * [da42f70] buildpackge: add test for overlay mode
+ * [b71a89e] export: print names of generated tarballs when using
+ pristine-tar so we get as much information as with git-archive.
+ * [3ff0fc7] pristine-tar: properly separate additional tarballs by spaces
+
+ [ Maximiliano Curia ]
+ * [a20047b] export_source: replace orig_file with
+ source.upstream_tarball_name orig_file was dropped in
+ 8edd16022d75967a8fe54ef99a0733400a287bc2
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 10 Aug 2017 14:57:39 -0300
+
+git-buildpackage (0.9.0~exp2) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [cbacdfb] push: new command to push changes in one go (Closes: #733639)
+ * [3d4d011] export-orig: fix manpage example
+ * [2320e19] pq: don't create empty pq branch on rebase and switch. We
+ always want to start from an imported series.
+ Thanks to Chris Lamb for the reproducer
+ * [c4bc656] pq: don't eagerly encode email headers.
+ Python3 changed behaviour and does not try us-ascii before the given
+ encoding so open code this in pq to avoid patch churn due to changed email
+ header encodings.
+ * [48076da] docs: expand the working with patches section
+ * [fb2e083] docs: drop the section about how to unapply patches
+ dpkg since 1.16.5 does this automatically and even oldoldstable has this
+ version.
+ * [f8c429f] docs: add images to explain pq workflow
+ * [5b3fcd6] docs: spell debian-branch consistently
+ * [f145e1d] docs: drop trailing '>' from xrefs
+ * [698e7bf] docs: add some more explanations
+ after feedback from Chris Lamb.
+ * [705f571] docs: Fix allow(s) one to spelling.
+ Thanks to lintian
+ * [4bad3c5] d/manpages: drop unused file
+ * [a5f22cb] Bump standards version
+
+ [ Chris Lamb ]
+ * [7cbe658] Correct "allow to" typos.
+ * [dff2864] Use build profiles to avoid unconditional installation of test
+ dependencies.
+
+ [ Maximiliano Curia ]
+ * [b7455ad] find_version: add a missing decode _git_getoutput returns a list
+ of bytes, that we need to decode
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 06 Aug 2017 22:55:10 -0300
+
+git-buildpackage (0.9.0~exp1) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * User visible changes:
+ * [ae7ed14] export-orig: new command to export orig tarballs from git
+ (Closes: #840089)
+ * [d9b535e] Use python3-notify2.
+ Thanks to Alexandre Detiste (Closes: #870595)
+ * [cfd884f] pristine-tar: don't print checkout errors twice
+ similar to f1c3a6f4dc6ceee5be5d49b4d193da8fd36c8920
+ * [76f247e] pristine_tar_verify_origs: skip verification if pristine-tar
+ does not support it.
+ This allows us to run with pristine-tar << 1.35
+ * [961723e] docs: generate version.py if missing.
+ This makes doc builds succeed in a clean tree.
+ * [4424008] bin: drop umlaut to make setuptools happy
+ * [e7bbd65] gbp-buildpackage: Group manpage options (Closes: #824801)
+ * [ad3515a] buildpackage: drop the long deprecated --dont-purge option.
+ Use --no-purge instead
+ * Lots of internal changes to make tarball generation simpler and avoid
+ code duplication between export-orig and build-package
+
+ [ Ken Dreyer ]
+ * [e5132b1] docs: fix spelling of "sure"
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 04 Aug 2017 20:48:22 -0300
+
+git-buildpackage (0.9.0~exp0) experimental; urgency=medium
+
+ * Switch to python3 (Closes: #810854)
+ * [889cf65] Use pydoctor to generate apidocs since epydoc
+ does not work with python3 files
+ * [6d8f386] spec: switch to python3 packaging for rpm
+ and adjust the smoketest accordingly
+ * [6b326e2] pristine-tar: add feature detection
+ so we can run tests with older pristine-tar not supporting verify
+ * [f4574be] travis: run test with different python versions and run at least
+ parts of the tests outside the container
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 02 Aug 2017 15:32:46 -0300
+
+git-buildpackage (0.8.18) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [4e6dd9c] Use install_requirements for dateutil to make using gbp in
+ virtualenv simpler
+ Thanks: Ken Dreyer
+ * [0388f9e] import-orig: Always raise an error if we find <error/> or
+ <warning/> when invoking uscan.
+ Older uscan does not abort with a non-zero exit status when it e.g. fails
+ to verify gpg signatures. So abort when we find a warning or error. (See
+ #841910 for reference).
+ * [561286d] Devscripts 2.17.7 properly exits on gpg signature validation
+ errors. So revert the above and depend on a fixed version. We keep the
+ above for reference since this is the right thing to do with older
+ uscan (e.g. in backports).
+ * [9fc1774] import-dscs: don't use long gone log function (Closes: #868733)
+ * [aaee8bb] tests: add initial component test for import-dscs
+ * [62ab1eb] tests: properly restore import_dscs so later testcases don't
+ break
+
+ [ Christos Trochalakis ]
+ * [61f4625] dch: unmangle upstream_tag when guessing upstream version.
+ Running `gbp dch` with a mangled upstream tag resulted in a KeyError while
+ trying to build a tag pattern.
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 31 Jul 2017 18:06:21 -0300
+
+git-buildpackage (0.8.17) unstable; urgency=medium
+
+ * [0b4a348] imort-dsc: Allow for shorter apt: pseudo URLs instead of the
+ apt:/// scheme.
+ * [021fe9d] git-pbuilder: drop environment variables that passed in our
+ config. We don't want to pass them on to pdebuild since this can trip up
+ the invoked build system.
+ * [b70d859] Fix typos in comments
+ * [d022623] pq_rpm: clarify doc string
+ mention RPM and start with a capital letter to make
+ gbp --list-cmds
+ output consistent.
+ * [956982c] New command 'pristine-tar'
+ This allows one to run the pristine-tar import at a later point. This
+ is useful when using component tarballs and we need to reference the
+ right trees for the commits.
+ * [f1c3a6f] DebianGitRepository: better pristine-tar errors.
+ We should not raise CommandErrors from methods on a GitRepository so
+ convert the error and suppress the output. This will make us use the
+ handler for GitRepositoryError in import_orig and pristine_tar which
+ default to printing the error message. (Closes: #829252)
+ * [f1e9d85] Don't print debsnap errors twice (Closes: #851128)
+ * [7fd6ba1] docs: merge histories when importing.
+ This is only necessary for non 3.0 (quilt) packages when not using
+ merge-mode=auto with "gbp import-orig".
+ Thanks to Sean Whitton (Closes: #864881)
+ * [f905594] docs: drop commands for ancient git versions. Even oldoldstable
+ has a more recent one.
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 28 Jun 2017 19:26:50 +0200
+
+git-buildpackage (0.8.16.1) unstable; urgency=medium
+
+ * Upload to unstable
+ * [57f2c4a] docs: simplify commands
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 22 Jun 2017 15:57:41 +0200
+
+git-buildpackage (0.8.16) experimental; urgency=medium
+
+ * [2f806bc] Update to git-pbuilder 1.48
+ incorporating the changes we made since 0.8.3
+ Thanks to Russ Allbery
+ * [8ea5d6f] Don't fail tests on older git versions.
+ There are some nuances in reflog handling.
+ Thanks to jean-christophe manciot for reporting this
+ * [be6af1e] Test that we don't fail tagging when on pq branch
+ (Closes: #863167)
+ * [20ece98] pq: Disable rename tracking to always produce 'diff' compatible
+ patches even when rename tracking is enabled in the config.
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 26 May 2017 16:28:41 +0200
+
+git-buildpackage (0.8.15) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [9fa917a] pull: give more detail about what we updated
+ * [1bf6ef4] docs: use [DEFAULT] section in import example so it also fits
+ the "gbp dch" usecase
+ * [ead7157] buildpackage: make --git-export=WC simpler to use by implying
+ --git-ignore-new --git-ignore-branch. There's no point in these safety
+ checks when we export the working copy as is. (Closes: #822823)
+ * [1832d14] Drop long deprecated --no-dch
+ * [000f924] buildpackage: add sloppy mode to build upstream tarballs. When
+ starting with packaging people often stumble over a mismatch between what
+ dpkg-source expects to be in the tarball and the generated tarball for
+ various reasons. Give them a way to say: "Use what I have on my current
+ debian branch" as upstream source. Also helpful for snapshot builds with
+ upstream modifications. (Closes: #861004)
+ * [1113c95] clone: Add support for pseudo protocols like vcsgit: and github:
+ to make cloning simpler (Closes: #861206)
+ * [a4667c7] dockerfile: add src uri for the gbp clone network test
+
+ [ Kevin Locke ]
+ * [a03af0d] git-pbuilder: Check $OPTIONS for --basepath.
+ Previously only the arguments were checked for --basepath. This
+ resulted in --basepath being ignored and overridden by the default when
+ passed in --git-pbuilder-options
+ (Closes: #856263)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 28 Apr 2017 08:36:16 +0200
+
+git-buildpackage (0.8.14) experimental; urgency=medium
+
+ * [fe821e2] GitRepository: use stdout as last resort if stderr is empty.
+ This e.g. gives:
+ GitRepositoryError: Error running git commit: On branch master
+ Untracked files:
+ debian/
+ nothing added to commit but untracked files present
+ Instead of
+ GitRepositoryError: Error running git commit:
+ * [1dd290a] pq: unbreak --commit when patch series becomes empty. We
+ skipped the commit when the patch series became completely empty but we
+ want to commit that change too.
+ * [75bf793] posttag-push: Use named parameter for dry_run
+ * [844e4f8] import-orig: only abort merge if it got started.
+ This isn't be the case when there are e.g. files that would be overwritten
+ by the merge in the workig copy (Closes: #859439)
+ * [3b70ad2] GitRepository: add is_in_merge()
+ * [5d414c1] buildpackage: cleaner does not default to debuild since ages
+ (Closes: #859594)
+ * [e890ce3] ipmort_orig: pick best way to import upstream tarball
+ automatically. Add new value --merge-mode=auto that selects either
+ 'replace' or 'merge' depending on the source format version.
+ (Closes: #700411)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 12 Apr 2017 13:53:52 +0200
+
+git-buildpackage (0.8.13) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [22013ad] pq: Filter out comments from series diff.
+ When creating the delta of added and dropped pachtches ignore comment
+ lines in the series file.
+ (Closes: #852817)
+ * [8103903] import_dsc: use three slashes by default for apt URLs.
+ We don't have a host part so leave that empty. Since there very likely
+ won't be any ever accept only two slashes as well.
+ * [d904b61] Use a symlink to supercomand instead of
+ pkgresources.load_entry_point. This sp:eds up startup by a factor of 10
+ and thus makes bash completion much more responsive:
+ $ gbp buildpackge --help # entrypoints
+ real 0m0,573s
+ user 0m0,532s
+ sys 0m0,036s
+ $ gbp buildpackage --help # symlink
+ real 0m0,051s
+ user 0m0,040s
+ sys 0m0,012s
+ (Closes: #853283)
+ * [d31fb0b] On a patch-queue branch tag the corresponding debian branch
+ instead.
+ (Closes: #583938)
+ * [ac34351] import_dsc: delay pristine-tar import to the very end.
+ This makes sure we have a sane debian and upstream branch already so we
+ don't leave the repo in an inconsistent state in case of failure.
+ (Closes: #851287)
+ * [0e6e23e] Improve error message for non-native packages with broken
+ version numbers
+ (Closes: #824634)
+ * [8f93dc0] git-pbuilder: don't do set -x by default to avoid spewig on
+ stderr. Use GIT_PBUILDER_DEBUG to enable it.
+ * Test suite improvements
+ * Doc updates including some CSS improvements
+
+ [ Ben Finney ]
+ * [0fc89bc] Extract a function to clean the working tree.
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 02 Mar 2017 09:58:42 +0100
+
+git-buildpackage (0.8.12.2) unstable; urgency=medium
+
+ * [80044e1] GitRepository: shorten reflog message.
+ Thanks to Chris Lamb for the report (Closes: #854333)
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 07 Feb 2017 07:34:57 +0100
+
+git-buildpackage (0.8.12.1) unstable; urgency=medium
+
+ * Upload to unstable
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 27 Jan 2017 14:50:30 +0100
+
+git-buildpackage (0.8.12) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [5c23d41] GitRepository: properly determine git-dir
+ instead of assuming '.git'
+ Heavily based on a patch by Markus Lehtonnen (Closes: #674015)
+ * [53b5af5] gbp dch: Allow to run from subdirectory
+ * [390d34a] git-pbuilder: print pdebuild command as executed
+ to ease debugging things like #852264
+ * Test suite improvements
+
+ [ Markus Lehtonen ]
+ * [f30bb98] buildpackage: fix exporting of working copy when .git is not a directory
+ as is the case with git submodules lately, for example
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 27 Jan 2017 14:06:33 +0100
+
+git-buildpackage (0.8.11) experimental; urgency=medium
+
+ * [8bd74df] command_wrappers: Simplify RunAtCommand
+ * [1140886] buildpackage: Don't set a compression level if unset and make
+ this the default. This allows compressors to use the their default
+ compression level. Only applies when not using pristine-tar.
+ Thanks to Antoine Beaupré for investigating (Closes: #820846)
+ * [0fdff8e] buildpackage: Use commit instead of tree to create archive.
+ This makes sure we use the timestamps of the commit when creating the
+ tarball. Only applies when not using pristine-tar
+ Thanks to Ximin Luo for investigating (Closes: #851645)
+ * [3f4b9f8] make: Check if we can use parallel flake
+ * [534c055] git-pbuilder: Don't remove changes file (Closes: #850478)
+ * [b863399] pull: Allow to specify remote on the command line
+ (Closes: #851844)
+ * [d3cb4db] gbp-posttag-push: add missing dry-run for debian branch push
+ * [f9a7640] buildpackage: verify generated tarballs when using pristine-tar
+ * [b8ea662] pq: Hardcode commit abbrev to 7 when exporting patches.
+ This avoids path churn with git >= 2.11.0 (Closes: #848354)
+ * Test suite enhancements
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 20 Jan 2017 12:54:40 +0100
+
+git-buildpackage (0.8.10) unstable; urgency=medium
+
+ * [ede603e] GitRepository: properly use pipe on empty strings.
+ (Closes: #850319)
+ * [42ee784] buildpackage: print proper dist when using sid.
+ * [e9ffa80] buildpackage: create all missing dirs with --export
+ Thanks to Andrea Zagli for the suggestion
+ * [42878ff] dch: honor --no-git-author
+ Thanks to David Kalnischkies for investigating (Closes: #796913)
+ * [4086fc9] import_dsc: Store debian/changelog in commit message
+ when importing packages. (Closes: #577810)
+ * [5fb0749] import_orig: Delay unpack of component tarballs.
+ (Closes: #840602)
+ * [67d8b9f] Move default gbp.conf back to the right location.
+ Thanks to Luca Boccassi (Closes: #850937)
+ * [80a1c39] Quote arguments passed to builder.
+ Thanks to Simon McVittie for the detailed report (Closes: #850869)
+ * [4a41d49] import-orig: Move orig.tar.gz with filter-pristine-tar.
+ (Closes: #558777)
+ * [661444b] Update docs regarding --download and drop it from completions
+ * flake8 test suite and example cleanups
+ * Update test data submodule URLs to https
+ * Make gbp-posttag-push easier to use
+ * [bba1ec7] docs: changelog entry is not created after gbp import-orig.
+ Thanks to Guillaume Millet
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 12 Jan 2017 10:08:28 +0100
+
+git-buildpackage (0.8.9) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [b6a94b6] git-pbuilder: Run actions under "set -x"
+ cowbuilder is currently very terse about certain errors so run the
+ action under "set -x" to print the exact command invocation.
+ This will also help to diagnose sudo issues.
+ * [ef1d8e7] create-remote-repo: allow to list config sections via
+ gbp create-remote-repo list
+ * [be44031] gbp create-remote-repo: autocomplete --remote-config= too
+ * [1291b17] Update to git-builder 1.43. Thanks Russ Allberry!
+ * [0e3deeb] setup.py: use a valid classifier
+ from https://pypi.python.org/pypi?%3Aaction=list_classifiers
+ * [9cbb9df] Use relative patch to install gbp.conf to make
+ pip install --user work
+ * [6f5070d] bash completion: don't complete options values with options. So
+ far hitting <tab> on --bar= would have all other options as suggestions
+ which is confusing.
+ * [a21e394] bash completion: complete tags on --upstream-vcs-tag= too
+ * [fe39b8e] bash completion: complete filenames on hooks, cleaners and builders
+ * [f7a61fa] Only set username and email when creating repos. Don't modify
+ existing ones.
+ * [cb8169d] gbp {clone, import_dsc, import_srpm}: Document new
+ --repo-{user,email} options
+ * [7b297b7] GitRepository: allow to check cleanliness of arbitrary paths
+ * [e31f15b] gbp pq: Don't fail --commit on empty commits. So far we would fail
+ empty commits with a confusing error
+ * [bad1f23] import-orig: Handle download errors properly.
+ * [d90e6be] import-orig: determine download automatically making --download
+ superfluous and deprecated.
+ * [58271d6] import-dsc: determine download automatically making --download
+ superfluous and deprecated.
+ * [8038a6f] import-dsc: Improve error message if there is no dsc file
+ instead of spewing the exception on the console.
+ * [9ecb5a4] Add helper to run tests in docker container
+ and use this on travisci. Some ideas taken form travis.debian.net.
+ * [3b1b9bc] GitRepository: use GitArgs for update_ref
+ * [1ac2724] GitRepository.commit_dir: add a proper reflog entry
+ otherwise 'gbp import-dsc' adds reflog entries without a description.
+ * [7ae2778] GitRepository: allow for dry-run push
+ * Various enhancements to the test-suite including tests that reach out to
+ the network to test these import options
+ * Various enhancements to the gbp-posttag-push example hook like trying the
+ git push with --dry-run before the upload.
+
+ [ Michael Stapelberg ]
+ * [67cf3ed] gbp clone: configure user.email, user.name from DEBEMAIL/DEBFULLNAME.
+ Close: #845536
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 31 Dec 2016 17:13:03 +0100
+
+git-buildpackage (0.8.8) unstable; urgency=medium
+
+ [ Chris Lamb ]
+ * [03276df] Add ability to specify the clone target to gbp-import-{dsc,srpm}
+ (Closes: #846567)
+
+ [ Guido Günther ]
+ * [0a9cf44] docs: setting target dir is supported for --download too
+ * [f631e64] import_dsc: fail properly again without a package to import
+ * [a10c77b] import_srpm: allow for more than one argument otherwise giving a
+ target dir always raises an error
+ * [e1aec57,627b858] import_rpms: use upstream version for upstream tag
+ (Closes: #846936)
+ * [ce42aadc] pq_rpm: use relative path names for patches Based on a patch
+ from Tzafrir Cohen
+ (Closes: #834582)
+ * [fa091b6] rpm: improve error message on specfile patch parser errors.
+ * [bf4afbe] gbp-posttag-push: catch exceptions instead of printing the full
+ trace
+ * [1d44f48] buildpackage_rpm: drop unused vcs_info()
+ * flake8 cleanups all over the rpm code
+
+ [ Tzafrir Cohen ]
+ * [73d30ef] specfile: handle %patch -F.
+ (Closes: #846479)
+ * [d9b28f9] gbp-mock: handle single letter options by properly handling '-?'
+ to request help output.
+ (Closes: #847464)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 08 Dec 2016 14:52:18 +0100
+
+git-buildpackage (0.8.7) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [924f809] Build-depend on libdistro-info-perl.
+ This silences the testsuite and unbreaks the build on Ubuntu
+ (Closes: #842960)
+ * [36b8a58] rpm: don't warn about deprecated $repo/.gbp.conf.
+ People use it a lot with rpm based tools so don't warn there until we
+ clearly suggested a better location.
+ * [3c498e1] docs: Don't suggest deprecated section names in examples
+ * [42657fc] docs: Rework "upstream uses git" section
+ * [ced46db] tests: Move doctests to subdir
+ * [ea5775a] commands: allow to fall back to error reason if stderr is empty.
+ Use this in PristineTar and SrcRpmFile to give better error messages if
+ the command doesn't even get to print to stderr (i.e. missing on disk).
+ (Closes: #842592)
+ * [4cacaeb] Avoid printing deprecation twice. Some tools reparse the
+ config. We don't want another deprecation in this case.
+ * [039a286] Avoid deprecation outside of Debian package repositories. E.g.
+ ~ might have a .gbp.conf for the user and might be git managed.
+ * [acf7a73] Avoid deprecation on bash completion
+ * [6f75d7c] Fix new flake8 errors. (Closes: #844932)
+ * [420e299] DebianGitRepository: Handle dot escaping as specified in DEP-14
+ (Closes: #843840)
+ * [d360a6d] DebianGitRepository: simplify version mangling '%' is not valid
+ in a Debian version number and we only want single character replacements
+ for now.
+ * [7ed5e2b] DebianGitRepository: Unmangle version. When mapping from a git
+ tag to a Debian version unamngle it.
+ * [0b317ee] docs: Use version mangling with care
+ * [c96a5b4] docs: Recommend DEP-14
+ * [9cb3966] examples/gbp-posttag-push: allow to upload packages too after
+ pushing all the git data to the remote end. Making one more custom
+ packaging script obsolete.
+
+ [ Jonas Meurer ]
+ * [dd5c3bc] Add version mangling.
+ This allows to replace characters in upstream version numbers. I.e.
+ replace '-' by '.'. (Closes: #842638)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 28 Nov 2016 08:39:35 +0100
+
+git-buildpackage (0.8.6) unstable; urgency=medium
+
+ * [a35d7d6] buildpackage: handle <vendor>/master
+ * [12fda23] create-remote-repo: print proper error message on missing
+ remote-configs instead of printing the exception to the console.
+ * [e917d3a] config: Turn dict of config files into a list so we get a fixed
+ order with all Python versions
+ * [c092d73] config: warn on deprecated $REPO/.gbp.conf location. The file
+ is deprecated at least since 2012 but we never warned about it. Warning
+ can be disabled via GBP_DISABLE_GBP_CONF_DEPRECTATION.
+ * [507ae50] import-orig: Document --rollback option
+ * [eb3be92] import-orig: Give some context on --merge-mode=replace option
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 23 Oct 2016 17:16:23 +0200
+
+git-buildpackage (0.8.5) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * Fixes:
+ * [7c964ea] buildpackage: add back export-dir options
+ (Closes: #839581)
+
+ * Improvements:
+ * [9817e89] posttag-push: Print branches we push to
+
+ * Cleanups:
+ * [a5a8a1c] pq: remove redundancies in --pq-from code paths
+ * [59dce12] pq: introduce pq_on_upstream_tag
+ * [f31bcd2] pq: make TestFromTAG tests silent
+ * [c1ab85a] test_pq_rpm: flake8 clean
+ * [86068d4] test_pq_rpm.py: don't spew on stderr
+ * [46787e3] test_buildpackage_rpm.py: flake8 clean
+ * [f96c8e1] tests: capture stderr in gbp rpm-ch component tests - reduces
+ spurious output
+ * [e4c893d] tests: capture stderr in gbp import_srpm component tests -
+ reduces spurious output
+ * [43beb38] GitRepository.archive: use _git_inout instead of the
+ deprecated _git_getoutput that spews to stderr This silences a spurious
+ output to stderr in test_buildpackage_rpm
+ * [55e8c8f] GitRepository.list_submodules: use _git_inout and check exit
+ status.
+ * [53f9f40] test_buildpackage_rpm: check that we fail subtarball
+ generation correctly
+ * [f87c21c] tests/buildpackage: test --git-export-dir
+
+ [ Linn Crosetto ]
+ * [c1b32fa] dch: avoid adding section in snapshot mode if distribution is
+ UNRELEASED. (Closes: #838714)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 10 Oct 2016 18:06:32 +0200
+
+git-buildpackage (0.8.4) unstable; urgency=medium
+
+ [ Maximiliano Curia ]
+ * [e615c65] pq: Handle unmerged debian branches. This allows one to import
+ the patch-queue branch onto the upstream tag.
+ (Closes: #834726)
+ * [f6433f9] pq: Ensure we are working in the right git directory.
+
+ [ Guido Günther ]
+ * [6909bb1] Unbreak gbp-pq manpage generation
+ * flake8 cleaniness
+ * [7a7068d] create_remote_repo: allow one to create non bare repositories
+ (Closes: #837158)
+ * [98cea5f] import_orig: Use gbp.scripts.common.hook
+ * [5ff1be8] test_rpm_ch: use GIT_AUTHOR_* env vars.
+ * [98ea945] flake8: Ignore rpm related files for now. This makes it easier
+ to integrate changes from git-buildpackage-rpm.
+ * [3ded91b] Move doc generation and test invocation to separate Makefile
+ * [2845359] Switch from pychecker to flake8. The later is maintained and
+ has more checks Use '-j1' for now since otherwise we need /dev/shm access
+ which fails in a pbuilder chroot with EPERM.
+ * [264090e] Rename README to README.md to get Markdown formatting
+ * [2809284] dch: document which options can't be set via gbp.conf
+ * [2122d8b] dch: document --security
+ * [ef7ca4a] config: allow one to specify short options
+ * [3feba49] dch: make urgency a config file option so it can be set via
+ gbp.conf (Closes: #837680)
+ * [fdcee06] dch: add missing short opts to manpage
+ * [dbb9623] Unbreak patch delta output broken by "pq: Ensure we are working
+ in the right git directory"
+ * [3e26d02] Vcs-Git: use https transport
+ * [74c7bc0] autopkgtest: Skip build if setuptools is not available
+ * [6ec588a] pq: restrict choices of pq-from to 'DEBIAN' and 'TAG'
+
+ [ Markus Lehtonen ]
+ * [c6b32c8] Command: redirect stdout/stderr to sys.stdout/stderr.
+ * [245b443] tests: capture stderr in buildpackage-rpm unit tests.
+ Reduces spurious output from rpmbuild. (Closes: #829690)
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 27 Sep 2016 18:56:12 +0200
+
+git-buildpackage (0.8.3) unstable; urgency=medium
+
+ * Upload to unstable
+ * [51620e9] import-orig: export version information to postimport hook
+ (Closes: #833429)
+ * [086ff78] Drop dependencies on sp and jade.
+ Thanks to Neil Roeth
+ * [bf46e26] Update to git-pbuilder 1.42
+ * [99e7703] import_orig: Properly abort merge on rollbacks.
+ If merging fails we need to call "git merge --abort"
+ * [a868977] tests: match env vars values in hook checks too
+ * [fae6456] tests: only check gbp related env vars in hooks.
+ This avoids test failures when env vars contain newlines
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 26 Aug 2016 14:18:17 +0200
+
+git-buildpackage (0.8.2) experimental; urgency=medium
+
+ * [1f0013c] import-orig: Switch to Debian branch before merging in changes
+ otherwise we'd always merge into the current working copy
+ (Closes: #832016)
+ * [a52aff5] download_orig: unlink file before raising the exception
+ otherwise we would not clean up
+ * [3d261da] import_orig: Only try rollbacks if necessary.
+ * [61e659d] pq: strip comments with multiple leading whitespace. Really
+ Closes: #825536
+ * [68940fb] pq: Retry patch with whitespace fixup on failure. This helps
+ patches with CRLF line endings and we don't lose anything since we'd
+ failed otherwise anyway. (Closes: #833066)
+ * [e2671b9] docs: fix --postclone and --hooks options of git-clone.
+ Thanks to IOhannes m zmoelnig for pointing this out
+ * [e68a0a1] docs: Document --postclone hook behaviour with debian/gbp.conf
+ Thanks to IOhannes m zmoelnig for pointing this out
+ (Closes: #833143)
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 02 Aug 2016 19:26:33 +0200
+
+git-buildpackage (0.8.1) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [3a5a497] import_dsc: Use the same patch options as dpkg-source for 1.0
+ packages (except for creating backup files) (Closes: #670099)
+ * [8d84fd3] dch: Match 'thanks' case insensitive (Closes: #746753)
+ * [29f16ca] Unbreak autopkgtest by setting git user and email
+ * [2b33349] import_orig: Drop duplicate log message when rolling back a branch
+ * [7ba43f6] Improve check for empty_repository.
+ Based on a patch by Carlos Maddela (Closes: #791472)
+ * [55fd73a] Remove outdated ref on git-import-dsc
+ * [1fae819] Use proper test fixtures.
+ (Closes: #723888)
+ * [95c8c53] clone: add postclone hook (Closes: #812816, #812815)
+ * [491adcf] clone: document missing directory option
+
+ [ Michael Elovskikh ]
+ * [a6311e6] Added option `never` to dch `--spawn-editor` choices
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 08 Jul 2016 15:44:28 +0200
+
+git-buildpackage (0.8.0) experimental; urgency=medium
+
+ [ Markus Lehtonen ]
+ * [c5cfb5e] Introduce gbp-rpm-ch.
+ Initial version of gbp rpm-ch command, a tool for maintaining
+ RPM changelogs. The new command supports %changelog section inside spec
+ files as well as separate changelog files ("OBS style"). (Closes: #808027)
+
+ [ Guido Günther ]
+ * [b4b7f9f] clone: Be a bit more verbose and let the user know that we
+ started cloning
+ * [1f58d21] PatchSeries: Strip comment from patch names (Closes: #825536)
+ * [1989c32] pq: Report number of imported patches
+ * [dcb145f] Use existing option as example in gbp-buildpackage manpage.
+ Thanks to Nicolas Braud-Santoni (Closes: #828703)
+ * [f1c64e2] manpages: add examples for gbp {clone,import-dsc}
+ * [83dfa67] GitRepository: Deleting a non-existing branch should not throw
+ an error
+ * [ba7b087] docs: Clarify some gbp pq options
+ * [ebc6b91] import_orig: Recover from import errors by winding back branches
+ and tags to their pre-error states (Closes: #828838)
+ * [4964234] dch: Log version number when preparing a snapshot
+ (Closes: #829025)
+ * [7a6641a] import-dsc: Don't fail on 1.0 non-native packages without a
+ Debian version
+ (Closes: #829070)
+ * [8c4460c] Actually install gbp-rpm-ch and unbreak manpage generation
+ * [d92b656] tests: Omit build dependend variables from epydocs
+ based on a patch from Sascha Steinbiss (Closes: #827546)
+ * [d977377] Set date in manpages based on last changelog entry
+ for reproducibility. This can be reverted once ocbook2man is fixed
+ (#800797).
+ Thanks to Sascha Steinbiss for the patch
+ * [53f37eb] import-orig: Make default import message more gbp-dch friendly.
+ * [312c9b9] Use imperative for git messages as suggested in gh:#26.
+ * [2bc3801] import_dsc: don't assume component tarballs have the correct name.
+ (Closes: #829458)
+ * [bc3805c] import_orig: add support for importing additional tarballs.
+ We expect the additional tarballs to be located next to the orig tarball
+ and to be already named properly. (Closes: #561071)
+ * [870c901] Docs: Clarify on possible key names and priorities in gbp.conf
+ * [7a521d4] Handle Ctrl-C more gracefully
+ * [b540c98] additional tarballs: allow one to configure components via gbp.conf
+ * [1d4d4a2] config: Allow one to give list values in plural form
+ * [700e164] config: Properly abort on config file parsing errors
+ instead of printing an exception on the console
+ * [3b4912d] config: always use 'gbp <cmd>' in help output.
+ The {git,gbp}- versions went away ages ago
+ * [f545010] Give more instructions when config is unparseable
+ and make return codes and messages consistent.
+ (Closes: #733640)
+ * [a0ed81b] Require python 2.7. It's the default up to Wheezy
+ (Closes: #685031)
+
+ [ Otto Kekäläinen ]
+ * [af16f59] Fix simple spelling errors in comments and strings
+ * [f28a26b] Fix spelling of existant->existent in function names, strings
+ and comments
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 04 Jul 2016 21:47:42 +0200
+
+git-buildpackage (0.7.5) unstable; urgency=medium
+
+ [ Dmitry Teselkin ]
+ * [3a93ada] Use tox for testing
+
+ [ Guido Günther ]
+ * [b56f7b0] buildpackage: don't claim the tarball does not exist. It can
+ also be triggered by --force-create
+ * [4ff5c3a] buildpackage: Simplify tarball generation logic.
+ * [cfcac02] buildpackage: Allow to generate subtarballs.
+ * [508656d] buildpackage: Support pristine-tar with multiple tarballs
+ but disable pristine-tar-commit in this case for now
+ * [22435b2] buildpackage: Check for existence of subtarballs as well
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 27 May 2016 10:27:07 +0200
+
+git-buildpackage (0.7.4) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [bb373cf] buildpackage: Improve error message when using DEP14
+ * [074e8e6] import_dsc: Allow to use pristine-tar with multiple tarballs
+ * [1aa9fc3,31754ac,6681989,1d8fb9d]: more multiple tarball tooling to
+ support it in other commands besides import-dsc with upcoming releases
+ * [060408f] create_remote_repo: Don't fail if current repo does not have any branches
+ (Closes: #822089)
+ * [d232194] gbp.conf: Add customization example
+ * [816829a] import_dscs: Mention debsnap command in options so it shows up
+ in the bash completion
+
+ [ Daniel Kahn Gillmor ]
+ * [a507ce2] docs: correct gbp.conf example of multiple filter options.
+ Commit 7e8f3bb1223269cf1b646 fixed the section that explains how to use
+ multiple filter options but forgot to clean up the import-orig example
+ at the end of the manpage.
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 05 May 2016 13:50:41 +0200
+
+git-buildpackage (0.7.3) unstable; urgency=medium
+
+ * [324b833] Fixup trailer in README.source example
+ * [3a84c11] Add testcase for non-native build-package
+ * [61ced0a] Mention debian branch (Closes: #817980)
+ * [9ef2aff] Clarify {upstream,debian}-{branch,tag} options
+ (Closes: #817980, #817981)
+ * [511b67f] gbplogtester: Check if we have enough log lines
+ * [9990b88] import_dsc: Make 'gbp import-dsc' aware of component tarballs
+ instead of plain failing. Just import the additional tarball but disable
+ pristine-tar until we sorted out how to export things again.
+ (Closes: #561072)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 01 Apr 2016 10:50:05 +0200
+
+git-buildpackage (0.7.2) unstable; urgency=medium
+
+ * [81ca19d] import_orig: Check git repo before downloading the source. This
+ makes sure we fail fast and not after the 30MB download
+ * [9dbe0cf] import_orig: Check upfront if the upstream tag already exists.
+ This makes sure we fail fast instead of importing first and then failing
+ to tag.
+ * [0b5a0a2] import_dsc: Check git repo before downloading the source. This
+ makes sure we fail fast and not after the 30MB download
+ * [9cc0a3f] buildpackage: Move git-pbuilder environemt variables into GBP_
+ namespace.
+ Keep the old ones for backward compatibility but this way they're all
+ available to all hooks and not only to prebuild. In order to not put more
+ stuff into the process enviroment dont use os.environ but only pass these
+ in via extra_env.
+ While at that test that hooks actually get their environment variables set
+ up correctly.
+ * [36893cb] buildpackage: make sure hook_vars is always available.
+ --tag-only uses different code paths so far only covered by the external
+ test suite. Add a component test to fix that.
+ * [92b7f1b] upstream_source: Don't silently ignore filters when importing
+ zip archives
+ * [35bd093] Don't throw exception when printing help an getting EPIPE.
+ Python's optparse does not ignore EPIPE so just add our own print_help().
+ * [2f0d139] docs: Clarify relation between git- prefixed options and
+ gbp.conf
+ * [fe37670] docs: add options passed to build command to synopsis
+ * [abe4e40] Update to git-pbuilder 1.40.
+ Thanks to Russ Allberry
+ * [2cc710d] docs: Fix sgml parsing errors spotted by docbook2html
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 29 Jan 2016 09:13:18 +0100
+
+git-buildpackage (0.7.1) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [0bb2064] Python3 cleanups
+ * [003c7cf,8583e03] Update to git-pbuilder 1.37 and 1.38
+ incorporating our changes. Many thanks to Russ Allberry!
+ * [7cc509d] manpages: mention git-pbuilder upfront in the workflow and add
+ missing pre-build hook
+ * [ebe31e6] manpages: consistency for gbp buildpackage.
+ * [7835216] Use faster CGit URL for Vcs-Browser
+ * [bf4a67d] manpages: document GBP_DISABLE_SECTION_DEPRECTATION
+ * [bfe32f1] buildpackage-rpm: add support for mock chroot builder.
+ Try: gbp buildpackage-rpm --git-mock --git-dist=epel-6
+ This is very heavily based on a patch from Tzafrir Cohen.
+ * [84a2025] Support "gbp help <command>" (Closes: #791759)
+ * [cbd40e5] import_dsc: Fail if a package has additional unhandled tarballs.
+ * [8583e03] import-dsc: Bail out if target dir exists.
+ If import-dsc is not run from within a Git repository it tries to create
+ one named like the source package. If that directory already exists we
+ get confusing message like:
+ gbp:info: No git repository found, creating one.
+ gbp:warning: Version 0.7.0 already imported.
+ gbp:info: Everything imported under /foo/git-buildpackage
+ Avoid this by checking if the directory exists and aborting in this
+ case. (Closes: #766350)
+ * [6d80239] config: Use the same logic for printing a single and all values.
+ Printing single values didn't populate the parser with defaults so we
+ ended up with empty values for options not set in a config file.
+ * [0d6e99d] Add README.source example. This can be used for gbp managed
+ projects. Different tools are mentioned in different paragraphs so they
+ can be extended or deleted if unused.
+ * [d02d812] docs: Update Patches chapter. Mostly mention --commit and remove
+ referenes to outdated tools.
+ * [597e040] docs: Use <programlisting> consistently. Use it for command
+ blocks instead of a mixture of programlisting and screen.
+ * [8e1020d] docs: Move all debian packaging branches into the debian/
+ namespace to be consistent with DEP-14
+
+ [ Markus Lehtonen ]
+ * [ac020c3,ec331ef,c195442] rpm packaging cleanups
+ * [82999a8] docs: enable building html docs with docbook2html if sgxml2x is
+ unavailable.
+ * [af17809] tests: enable buildpackage-rpm component tests.
+ * [89648de] buildpackage_rpm: implement --native option.
+ * [fe5ffe0] tests: enable unit tests for buildpackage-rpm
+
+ [ Ken Dreyer ]
+ * [aba67ba] git-pbuilder: spelling fix
+
+ [ Lucas Nussbaum ]
+ * Import content of https://honk.../debian_packages_in_git/ into the
+ official manual.
+ (Closes: #809023)
+
+ [ Anthony Fok ]
+ * [96df9e3] docs: Restore coloured background for <programlisting> in docs
+ by using lower case class names.
+ Closes: #gh:14
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 28 Dec 2015 15:26:56 +0100
+
+git-buildpackage (0.7.0) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [17e8a51] docs: Explain gbp dch --upstream-tag. (Closes: #795774)
+ * [4da6402] docs: Split manpages between debian and rpm related tools.
+ * [16dc010] docs: Add manpages for gbp import-srpm and gbp pq-rpm based on
+ Markus Lehtonens work.
+ * [24ee8fc] docs: Add missing EOF.
+ * [01acdd7] docs: git-buildpackage is not a command anymore
+ * [db5d50f] docs: Update pbuilder invocation section. The current docs were
+ badly outdated not even mentioning --git-pbuilder. Also document that we
+ use cowbuilder with git-pbuilder. (Closes: #703969)
+ * [c39e779] docs: Update gbp.conf manpage. Better explain parse order and
+ option format.
+ * [26c726e] docs: Add meta-closes-bugnum to gbp-pq manpage since it's used
+ with "export --commit".
+ * [45a5d08] docs: Use "hook" consistently for commands that aren't strictly
+ necessary to run a build.
+
+ * [6da8e46] buildpackage: Pass pbuilder dist to prebuild hook. This can e.g.
+ be used to setup a tmpfs for git-pbuilder.
+
+ * [422ae85] buildpackage-rpm: Add a minimal gbp buildpackage-rpm based
+ on Markus Lehtonens work.
+
+ * [c57c9fc] config: Don't print None for unset values. Just leave them
+ empty.
+ * [98df835] config: Don't skip empty values when printing all options, just
+ print an empty value. This allows us to get a complete option list.
+
+ * [e4704a6] git-pbuilder: Update to 1.35. (Closes: #765724)
+ * [4be0df7] git-pbuilder: Check for sudo. (Closes: #796046)
+
+ * [ffdfcd0] packaging: Update description for rpm packages. Make it clear
+ that we're still experimenting with layouts and names.
+ * [5704478,da78c09,50767fd,fc60141] rpm: spec file improvements
+
+ * [488ba32] examples: gbp-posttag-push: Be more verbose when pushing.
+ * [cf69c46] examples: Remove gbp-cowbuilder-sid, git-pbuilder replaced it
+ ages ago.
+ * [7630523] examples: Update jenkins-scratchbuilder to not use "git-" but
+ "gbp ".
+
+ * [c227c70] tests: Skip buildpackage-rpm component tests until we setup a
+ proper test data repo.
+ * [48d9d58] tests: Allow to easily check tags in a repo too.
+ * [20d3c1d] tests: Add simple component test for buildpackage.
+ * [3528b0f] tests: Add simple component test for gbp import-orig.
+ * [34b4f65] tests: Add autopkgtest that builds a RPM of ourself.
+
+ * [386d3a7] bash completion: Move from /etc to /usr/share.
+ * [57f810c] bash completion: Use _have() instead of have() the later not
+ being deprecated.
+ * [25ca642] bash completion: Ensure autoload. The completion needs to be
+ named like the command to be autoloaded.
+
+ [ Lucas Nussbaum ]
+ * [a954d1f] docs: Document gbp import-orig --debsnap.
+ * [88833aa] docs: Document gbp import-orig --uscan.
+ * [bf60f8a] docs: Fix typos in documentation and man pages.
+
+ [ Markus Lehtonen ]
+ * [5cab1bc] tests: tests.testutils: Add ls_dir(), ls_tar() and ls_zip()
+ * [7ce69de] tests: Rewrite gbp.tmpfile.
+ * [f45585f,6d56f24,a645073,3474d74] rpm packaging improvements
+ * [3474d74] docs: Document gbp meta-tag commands of pq-rpm.
+ * [a9ed4b2] docs: Document some options of buildpackage-rpm.
+ Add manpage documentation for some previously undocumented options.
+ Also, drop non-existing --git-patch-export and --git-export-only options
+ from the manpage.
+ * [f157bfb] buildpackage_rpm: Fix crash when package has no source archive.
+
+ [ Jonathan Toppins ]
+ * [c89c29d] dch: Allow bug number format to be overridden to help
+ derivatives like EX-12345. This also helps in pulling CVE numbers simply
+ by letting the user modify the regex to something like 'cve-\d+-\d+'.
+
+ [ Florian Haftmann ]
+ * [2dc9177] buildpackage: Early check before attempting to brand using
+ pre-existing tag
+
+ [ Rafael Laboissiere ]
+ * [cd6d7da] docs: Clarify Gbp-Dch vs Git-Dch meta tag wording.
+ (Closes: #797343)
+
+ [ Daniel Gollub ]
+ * [5210026] dch: Restore correct version-guessing for -0releases.
+
+ [ Anthony Fok ]
+ * [3e1240a] packaging: Add dh-python to Build-Depends
+ * [b49bb0d] docs: Change "it's" to "its" in comments where appropriate
+ * [881087a] docs: Copy-edit git-buildpackage documentations
+ * [d8a30df] docs: Fix SGML validation errors in documentation
+
+ [ James Clarke ]
+ * [ae6826b] git-pbuilder: Check correct config file when using qemubuilder.
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 21 Oct 2015 08:30:40 +0200
+
+git-buildpackage (0.6.33) experimental; urgency=medium
+
+ [ Carlos Maddela ]
+ * [da4d469] Add support for referencing existing local repositories when
+ cloning repositories. (Closes: #790889)
+ * [97c7a32] Allow exported patches to be renumbered and the patch number
+ prefix format to be specified as an option. (Closes: #790890)
+ * [279e946] import-orig: Honour --debian-branch option when importing into
+ an empty repo. (Closes: #791473)
+
+ [ Guido Günther ]
+ * [035b179] import-orig: Add new --merge-mode=replace.
+ This allows one to not merge upstream versions into the debian branch
+ but rather replace the content of the debian branch and only preserve
+ the debian/ dirs content. (Closes: #778594)
+ * [3d86432] buildpackage: in overlay mode remove debian/ from unpacked
+ tarball. We don't have a way to influence the upstream tarball in overlay
+ mode so remove debian/ after unpack if the source format expects this.
+ (Closes: #792692)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 14 Aug 2015 18:29:53 +0200
+
+git-buildpackage (0.6.32) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [82a3971] import-dsc: mention that we create new repos if none found
+ (Closes: #748340)
+ * [dff628b] Don't insist on cowbuilder that much.
+ Allow for pbuilder or sbuild either. Since the integration with
+ cowbuilder is currenty best let's see how this turns out. It would be
+ nice if somebody would provide some code to detect which builder is
+ there and automatically adjust configuration for that one.
+ Closes: #695280
+ * [eff79b1] Add symlink for git-buildpackage.1.
+ Make sure "man git-buildpackage" at least gives enough information where
+ to look further.
+ * [6a5675c,901581b,64e6fff,5f9e72a] Non C Locale related fixes
+ * [a331b7f] Add spec file taken from git-buildpackage-rpm.
+ * [46eacf2] Use --set-upstream-to instead of deprecated --set-upstream
+ (Closes: #791798)
+ * [cf67fc5] Fix urllib.urlopen import six.moves.urllib.urlopen does not
+ exist
+
+ [ Felipe Sateler ]
+ * [3b99a6d] import-orig: Document pattern replacing in upstream-vcs-tag
+ (Closes: #787832)
+
+ [ Evgeni Golov ]
+ * [b3341e5] Fix FSF address - thanks rpmlint
+ use gnu.org/licences instead of a postal address, as suggested by Guido
+ * [df215f3] call debchange instead of dch.
+ Call the tool by its actual name instead of the abbriviation.
+ dch is a symlink to debchange, which is not present on Fedora
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 08 Jul 2015 20:07:15 +0200
+
+git-buildpackage (0.6.31) unstable; urgency=medium
+
+ * [8241459] pq: Allow to preserve the patch name on import/export
+ (Closes: #761161)
+ * [32c52b7,58ea067] pylint and python3 cleanups
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 02 Jun 2015 20:00:47 +0200
+
+git-buildpackage (0.6.30) unstable; urgency=medium
+
+ * [474acd9] Allow to automatically determine the dist to build for.
+ Setting --git-dist=DEP14 will follow the DEP14 proposal to determine the
+ suite to build for via vendor/suite. The exception is sid where DIST is
+ just set to be empty as well as native packages.
+ * [18d83d5,3894520,c4f82d3] Python3 compatibility
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 30 May 2015 19:41:25 +0200
+
+git-buildpackage (0.6.29) unstable; urgency=medium
+
+ * [14c4f41] Drop my_collections from spec files. It trips up recent librpm
+ and we don't need the data for the tests anyway.
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 26 May 2015 22:33:06 +0200
+
+git-buildpackage (0.6.28) unstable; urgency=medium
+
+ * [7a0b9df] git-pbuilder: Support creation of chroots for LTS
+ * [c11a4d3] Add back completion for pq subcommands. This got lost in
+ 270f41.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 23 May 2015 12:43:44 +0200
+
+git-buildpackage (0.6.27) unstable; urgency=medium
+
+ * [15187ba,270f41b] bash-completion: Simplify the code. This additionally
+ avoids warnings on unknown commands and gives us basic completion for
+ the RPM commands for free.
+ * [f22785b] Allow to disable config section deprecations. Sections named
+ [gbp-<command>] or [git-<command>] in gbp.conf cause a warning. In order
+ to make it simpler to be compatible with ancient gbp versions these
+ can be disabled by setting
+ GBP_DISABLE_SECTION_DEPRECTATION=true
+ in the environment.
+ * [3585fdb] bash-completion: Disable config section deprecation warnings.
+ It's of no use if we get these warnings on bash completion, it only
+ confuses users. They're still shown when running the actual command.
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 05 May 2015 08:55:11 +0200
+
+git-buildpackage (0.6.26) unstable; urgency=medium
+
+ * [72e129b] Drop git-* commands from manpages too.
+ Thanks to Michael Biebl (Closes: #783537)
+ * [ee2d421] Add a note to NEWS.Debian about the removal of the git-* symlinks
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 28 Apr 2015 00:19:03 +0200
+
+git-buildpackage (0.6.25) unstable; urgency=medium
+
+ [ Markus Lehtonen ]
+ * [f10d67f] UpstreamSource.guess_version: recognise debian native tarballs
+ * [3ef3304] tests: add tests for UpstreamSource.guess_version()
+ * [60ad28f] rpm tests: use eq_ and ok_ from nose tools.
+ Provides better error messages as is more consistent as eq_ was already
+ used in some test cases.
+ * [f089021] tristate: implement __nonzero__() method.
+ Returns False if tristate is 'off', otherwise True ('on' or 'auto').
+ * [e07aaba] config: support for older six
+ * [e4f767f] GitRepository/has_submodules: add treeish argument. For
+ defining a Git treeish which to look into, instead of the current working
+ copy.
+ * [5a74d4e] tests: add unit tests for gbp.rpm.SrcRpmFile
+ * [1e347cc] rpm tests: disable one anomalous pylint error
+ * [bd72c30] rpm tests: unbreak spec parsing test for openSUSE 13.2.
+ Some SUSE-specific RPM tags are not supported anymore.
+
+ [ Guido Günther ]
+ * Upload to unstable
+ * Some Python3 compat including using six.
+ * [74008c7] Turn off coverage output by default. It's much simpler to spot
+ the cause of test failures this way.
+ * [19d9280] Update docs on component tests and mailing list
+ * [0b98c48,49f0e44,7ebf843] gbplogtester: robustness so we can finally
+ silence the test suite.
+ * [b1b7761] config test: fix missing prefix. We want to test no prefix,
+ 'gbp-' and 'git-'.
+ * [335dfdb] command_wrapper: Make error reporting more flexible.
+ We allow to substitute stderr, stdout and error_reason in run_error now.
+ These changes the API for derived classses slightly so fix them up as
+ well.
+ * [89574a0] Don't let pristine-tar spew on stderr.
+ Capture its output and only dump it on failure. This makes "gbp
+ import-orig" and test runs less verbose.
+ * [430be38] testutils: Fix __all__. The values weren't correctly seperated
+ * [36b90e9] SrcRpmFile: Add stderr to error messages
+ so we can better debug failures like
+ http://honk.sigxcpu.org:8001/job/git-buildpackage/230/changes
+ * [53fbee6] Depend on cpio needed by gbp import-srpm
+
+ [ Daniel Kahn Gillmor ]
+ * [83c5cc5] Allow to set upstream-vcs-tag via gbp.conf.
+ Also pass it through version_to_tag so
+ upstream-vcs-tag = libgpg-error-%(version)s
+ properly expands the version replacement. (Closes: #780602)
+ * [cb03293] Add %(hversion)s to version_to_tag to support some upstreams
+ enigmail upstream uses tags named enigmail-1-8 for 1.8. Other
+ upstreams have used similar conventions, likely as holdovers from CVS
+ (e.g. gnupg 1.4.2 was tagged with V1-4-2). This patch helps packagers
+ work with these upstreams. (Closes: #780679)
+
+ [ Martin Erik Werner ]
+ * [4a53c0f] docs: Fix diirectory->directory typo in gbp.conf manpage
+ (Closes: #783013)
+
+ [ Daniel Gollub ]
+ * [aa8548d,e0f8b5b] gbp-dch: Guess upstream-tag based on merge-base to not
+ accidentally pick up tags from the Debian branch.
+ (Closes: #gh:7)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 27 Apr 2015 09:05:16 +0200
+
+git-buildpackage (0.6.24) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [1d6c4c7] Deprecate legacy config sections.
+ We deprecate sections starting with git- and gbp- to reduce the confusion
+ about what gets parsed first. Output a warning if the old format is in use.
+ Jessies gbp already supports both formats as does the one in
+ wheezy-backports.
+ * [e3a8666] Drop all gbp-* and git-* commands as announced in the
+ deprecation notice from June 2013. From now on only "gbp <command>" is
+ supported.
+ * [229f259] buildpackage: Tag currently checked out head.
+ Tag the head checked out at command invocation. The build can
+ take some time and the repo might have changed underneath us.
+ (Closes: #776506)
+ * [428e895] Make Debian and Git spelling consistent
+ * [644c97f] dch: Support Gbp-Dch: besides Git-Dch: as meta tag
+ * [64be54d] pq: Generate new style Gbp-Pq: Topic <topic> entries.
+ * [ff40c5d] Move requests import to downloads.
+ The requests import takes ages and therefore considerably slows
+ down program start up.
+ This is very much noticeably during bash completion. Before:
+ $ time gbp --list-cmds >/dev/null
+ real 0m0.559s
+ user 0m0.528s
+ sys 0m0.028s
+ After:
+ $ time gbp --list-cmds >/dev/null
+ real 0m0.092s
+ user 0m0.088s
+ sys 0m0.000s
+ * [d86c624] bash-completion: Avoid a pointless fork
+ * [0881bfc] Bump standards version
+ * Many fixes towards Python3 compatibility
+
+ [ Markus Lehtonen ]
+ * [7ce15d2] pq: deprecate the usage of 'gbp-pq-topic:'
+ Replaced by the "Gbp[-Pq]: Topic <topic>" command.
+ * [aa22e22] import-srpm: support gbp-config command.
+ * [1c6df3e] ComponentTestBase: ignore system and user config.
+ User might have a system and/or user specific gbp config files. Disable
+ these config files so that they don't affect the component tests.
+ * [55d5422] patchseries: strip numbering when guessing subject from filename
+ * [600d5d7] rpm: suppress stderr when unpacking src.rpm
+
+ [ Dmitrij Tejblum ]
+ * [296796f] git-dch --git-author: separate author and email.
+ Allow --git-author to work if either author or email is not specified
+ in the git config, taking the other config option into account.
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 20 Feb 2015 19:19:30 +0100
+
+git-buildpackage (0.6.23) experimental; urgency=medium
+
+ [ Markus Lehtonen ]
+ * [7a503e9] pq: move switch_pq() to common.
+ * [cb9271f] GitRepository: add diff_status method.
+ This is a method of getting the filename and status information of a
+ diff. That is, a list of files that changed and their status, "added",
+ "modified" etc.
+ * [410e613] GitRepository.create_branch: add 'force' option
+ * [a84f6c1] ComponentTestBase: add a per-class toplevel temp dir
+ * [0e7cb0d] ComponentTestBase: add check_files() method
+ * [194b6b8] ComponentTestBase: add dirs argument to _check_repo_state()
+ Make difference between regular files and directories, eliminating the
+ requirement of listing directories in the file list.
+ * [01470e1] gbp-dch: fix handling of the '--meta' option.
+ Make it effective again - previously it was totally ignored. Also,
+ change it's default value to True to match the current behavior.
+ * [f48f0ff] notify: catch RuntimeError when importing pynotify.
+ Work around a problem in some distros (e.g. Fedora) where "import
+ pynotify" crashes in RuntimeError in some cases, e.g. when DISPLAY env
+ variable is not set.
+ * [7df4d8c] log: fix auto colorizing for custom streams.
+ Check for existence of isatty() method in the stream object. Some custom
+ streams (e.g. in nose) do not necessarily have this.
+ * [4cd6627] buildpackage/dump_tree: add 'recursive' option.
+ For selecting whether to dump all the files recursively or just the top
+ level directory of the tree.
+ * [ff4cc8b] config: read the right config if run from subdir.
+ A step towards being able to run GBP tools from subdirectories.
+ Now expands '%(top_dir)s' and '%(git_dir)s' in config file path to root
+ of the working directory and git metadata directory, respectively.
+ Also, adds a new method _read_config_file() in preparation for
+ supporting per-tree config files.
+ Fixes tests.test_Config: currently the only correct way to define the
+ config file(s) to be parsed is by using the GBP_CONF_FILES environment
+ variable.
+ * [60479af] Introduce gbp-pq-rpm.
+ Initial version of gbp-pq-rpm - a tool for managing patch queues for rpm
+ packages. The functionality more or less corresponds to that of the
+ (Debian) gbp-pq. The only major difference probably being (in addition
+ to the obvious of working with .spec files instead of debian/) is that
+ patches are always imported on top of the upstream version, not on top
+ of the packaging branch (which might not even contain any source code).
+ (Closes: #771215)
+
+ [ Ed Bartosh ]
+ * [76739f8] GitRepository: Implement status method. Simple wrapper to the
+ git-status command.
+
+ [ Guido Günther ]
+ * [578e394] pq: print which patch got just applied
+ * [e098857] test data: Add .git for bare repository
+ * [813d01d] Fix argument type
+ * [15663e7] import-orig: Add --download option to download tarballs via HTTP
+ (Closes: #747101)
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 18 Jan 2015 15:29:57 +0100
+
+git-buildpackage (0.6.22) unstable; urgency=medium
+
+ * [3d8939d] git.vfs: fix close method. Preventing a infinite recursion
+ which can be triggered by gbp pq export --commit.
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 29 Oct 2014 07:47:21 +0100
+
+git-buildpackage (0.6.21) unstable; urgency=medium
+
+ * [81dab4b] pq: Don't fail commit if the series file is empty on the source
+ branch
+ * [740e431] man: fix option argument for --git-pbuilder-options
+ * [22a6987] Improve change reporting a bit
+ * [e08d64d] Complete setup.py for pypi
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 17 Oct 2014 18:41:14 +0200
+
+git-buildpackage (0.6.20) unstable; urgency=medium
+
+ * [ee44479] Allow to always drop pq branch after export (Closes: #761160)
+ * [51ac0a5] pq: document --drop
+ * [90b283f] meta-closes: Move help text to GbpOptionParser.help
+ * [0afcd3d] pq: Add "pq export --commit" option. This commits the changes
+ in the pq right away. This options is currently experimental and subject
+ to change.
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 12 Oct 2014 11:42:27 +0200
+
+git-buildpackage (0.6.19) unstable; urgency=medium
+
+ * [5d4cb92] Update to git-pbuilder 1.33
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 07 Sep 2014 09:25:48 +0200
+
+git-buildpackage (0.6.18) unstable; urgency=medium
+
+ * Upload to unstable
+ * [6edd836] Don't delete *_source.changes on source only builds
+ (Closes: #758726)
+ * [a37832e] Mention --no-merge (Closes: #760091)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 06 Sep 2014 13:41:05 +0200
+
+git-buildpackage (0.6.17) experimental; urgency=medium
+
+ [ Guido Günther ]
+ * [692e5da] Make sure we fixup the changelog trailer with newer devscripts.
+ We don't change any mainttrailer options already given.
+ Thanks to James McCoy for the detailed explanation (Closes: #740566)
+ * [ae5805e] Improve error messages on formatting errors to
+ make it easier for the user to detect misformated replacement strings in
+ config files and command line options.
+ * [5f82f44] gbp: add --version option (Closes: #758909)
+ * [04aa92f] Allow to list all available gbp subcommands
+ * [68c053f] Unify doc strings a bit since they now show up with --list-cmds
+ * [6d510ce] bash completion: make command list dynamic.
+ Use "gbp --list-cmds" so we don't have to hardcode the available
+ commands and get support for the RPM ones as they show up.
+
+ [ Kamal Mostafa ]
+ * [6823e51] buildpackage: Make debian-tag message configurable via
+ --git-debian-tag-msg.
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 24 Aug 2014 11:31:28 +0200
+
+git-buildpackage (0.6.16) experimental; urgency=medium
+
+ [ Markus Lehtonen ]
+ * Doc cleanups and reformatting
+ * [736b9d8] Introduce git-import-srpm tool.
+ * [ed228a2] import-srpm: add 'vendor' config option.
+ Intended to represent the distribution vendor (e.g. 'Debian').
+ Currently, this can be used in tag format strings.
+
+ [ Guido Günther ]
+ * [0b1fc0d] buildpackage: Also print tag name when tagging the Debian
+ release. Based on a patch by Kamal Mostafa
+ * [2bf944f] Pass --no-pristine-tar to SRPM compnent tests
+ to avoid pristine-tar showing up in the matched branches
+ * [23090c9] Introduce git-buildpackage-rpm. Currently only including "gbp
+ import-srpm".
+ * Remove newly introducted spurious log outputs from tests
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 28 Jul 2014 15:57:49 +0200
+
+git-buildpackage (0.6.15) unstable; urgency=medium
+
+ * [5cde49a] Revert "Determine build_dir upfront"
+ This reverts commit b2549fac19f2d666552291a4fcf2020ca0570834.
+ Closes: #749104
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 24 May 2014 18:40:44 +0200
+
+git-buildpackage (0.6.14) unstable; urgency=medium
+
+ * [949ce84] Slightly improve usage output
+ * [045e607] gbp pq: document --force
+ * [817976e] Handle version format errors more gracefully.
+ * [b0390d1] Fix committer vs committer typos.
+ Thanks to Sandro Tosi (Closes: #748339)
+ * [dc231f2] Document that the patches must apply without fuzz
+ * [de77df7] pq: Print proper error message if we fail to apply the tree
+ * [54e4542] pq: Try harder to cleanup after a failed patch.
+ Thanks to Marco d'Itri for the repo to debug this
+ * [b2549fa] Determine build_dir upfront
+ * [a050942] Improve error reporting on failed commands.
+ (Closes: #748248)
+ * [030ff96] Improve error reporting on hooks.
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 22 May 2014 22:47:37 +0200
+
+git-buildpackage (0.6.13) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [14f6ded] Test option parser fallbacks more thoroughly
+ revealing another bug where we overwrote parsed values with defaults
+ (Closes: #733759)
+ * [03ada72] .gitignore: ignore editor backup files
+ * [4c6b067] Add minimal 'config' command. This only allows to print single
+ config values so far. (Closes: #733470)
+ * [b393080] Import command not module. This matches the function name.
+ * [459d9bf] config: add decorator to add_option_* functions.
+ This allows us to build an internal list of valid options and print
+ these.
+
+ [ Markus Lehtonen ]
+ * [a8cfd88] Revert "GitRepository/add_remote_repo: fix the 'tags' argument"
+ Which caused an unwanted behavior of not fetching refs/heads at all --
+ only tags were fetched.
+ This reverts commit 5dd598fb8f2a9109189fad6cf631811f12ce2130
+ * [4131b90] docs: re-enable missing manpage
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 03 Apr 2014 21:31:38 +0200
+
+git-buildpackage (0.6.12) unstable; urgency=medium
+
+ * [89f3005] Use a much simpler version to fix the command name in --help
+ mostly reverting e1780f0. (Closes: #742907)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 29 Mar 2014 00:17:59 +0100
+
+git-buildpackage (0.6.11) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [38cfa6c] Request verbose output on bug reports
+ * [d000b0c] Remove incorrect docs about multiple filter options
+ (Closes: #718536)
+ * [fd440e2] Use a temporary directory. This avoids file name collisions and
+ weired files in the working copy.
+ * [e1780f0] Fix command output.
+ The first line lacked the subcommand like:
+ $ gbp pull --help
+ Usage: gbp [options] - safely update a repository from remote
+ instead of
+ $ gbp pull --help
+ Usage: gbp pull [options] - safely update a repository from remote
+ ^^^^
+
+ [ Mario Lang ]
+ * [077dc35] Fix config file name
+
+ [ Markus Lehtonen ]
+ * [3808523] docs: add some missing sgml closing tags
+ * [d8f55da] GitRepository/get_commit_info: correctly handle file
+ renames/copies. Use the '--no-renames' git command line option in order
+ to prevent two filenames per entry.
+ * [5dd598f] GitRepository/add_remote_repo: fix the 'tags' argument.
+ Explicitly use --tags in git arguments - otherwise tags might not be
+ fetched.
+ * [15d87fb] tests: more robust check for the default urgency level of dch.
+ The dch tool might not report it's version if it is incorrectly compiled.
+ Create a dummy changelog and parse it "manually" to determine the default
+ urgency level.
+ * [7774375] pq: minor log message typo fix
+ * [bbf21bf] ComponentTestBase: use regex matching in log checking
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 27 Mar 2014 22:27:38 +0100
+
+git-buildpackage (0.6.10) unstable; urgency=medium
+
+ * [f1bc542,1b0b17c] config: Don't pull in config defaults twice.
+ This would otherwise overwrite values set in the legacy config sections.
+ (Closes: #733759)
+ * [7e26f91] Remove cover-min-percentage. It makes running single tests
+ harder than necessary since it requires the percentage also when using
+ "nose <testname>".
+ * [d533e0f] import_dsc: Create missing debian branch with
+ --create-missing-branches (Closes: #739888)
+ * [df32264] git-import-dsc: Document --create-missing-branches
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 23 Feb 2014 17:56:47 +0100
+
+git-buildpackage (0.6.9) unstable; urgency=medium
+
+ [ Guido Günther ]
+ * [78f3673] Depend on newer devscripts.
+ These contain a uscan that handles repackaging at the right level and
+ won't let us pick up the wrong tarball name.
+ Thanks to gregor herrmann and all others involved (Closes: #635920)
+ * [7c64575] Avoid backtrace on config file parse errors
+ "gbp buildpackage" did this already but others didn't (Closes: #733759)
+ * [7b1eadd] Make parsing config file sections symmetric.
+ Always read the legacy command's config file section prior to the
+ subcommand's config file section.
+ Until now 'gbp <subcommand>' would read '[subcommand]' as well as
+ '[gbp-<subcommand>]' sections while 'gbp-<subcommand>' would only read
+ '[gbp-<subcommand>]' sections. (Closes: #733759)
+ * [49f0e44] Change the default of cleaner to /bin/true.
+ Running a clean command within version control by default nowadays
+ causes more trouble than it helps. It's unnecessary with
+ pbuilder/cowbuilder/sbuild and with export-dir. So change the default
+ from 'debuild clean' to a noop ('/bin/true'). (Closes: #670624)
+ * [d93c89f] import_orig: test error paths of find_source
+ * [daf249f] docs: improve docs on using upstream's git repo.
+ The documentation on using upstream's git directly instead of tarballs
+ was very terse. Add a step by step guide on how to get started and
+ how to update to new upstream versions.
+
+ [ Markus Lehtonen ]
+ * [e876beb] tests: adapt dch tests for older devscripts.
+ Determine the default urgency level by determining the version number of
+ the dch tool from command line.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 15 Feb 2014 11:45:00 +0100
+
+git-buildpackage (0.6.8) unstable; urgency=medium
+
+ * [f5718b8] No need to document --verbose. Properly document --help
+ instead.
+ Thanks to Olivier Berger for pointing this out (Closes: #731149)
+ * [e746d5f] Adjust test cases to newer devscripts (Closes: #732384)
+ * [ca93ccb] Run nosetest on executables too.
+ They're all well behaved and this allows us to use doctests in them
+ * [eec8ce3] Determine changes file name based on dpkg-buildpackage options
+ (Closes: #732678)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 20 Dec 2013 17:21:20 +0100
+
+git-buildpackage (0.6.7) unstable; urgency=low
+
+ * [ab5a708] import-dsc; Merge upstream version by tag
+ instead of simply using the upstream branch name. THis makes sure
+ we also merge upstream versions that were imported previously with
+ e.g. "gbp import-orig". (Closes: #698222)
+ * [2042144] log: add error and warning aliases since I tend to use them
+ instead of err and warn. (Closes: #728896)
+ * [bc5ce39] Fix description of backports branches
+ * [00d830c] Use --distribution instead of --dist (Closes: #730788)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 29 Nov 2013 20:52:03 +0100
+
+git-buildpackage (0.6.6) unstable; urgency=low
+
+ [ Guan Junchun ]
+ * [284eea5] gbp-clone: support repo URLs like "host:repo.git" Previously,
+ gbp didn't correctly parse this kind of URL and clone failed.
+ * [d52abf3] gbp-clone: support cloning to a specific directory
+ (Closes: #725666)
+
+ [ Guido Günther ]
+ * [3092623] Add missing bash completion for gbp import-dscs
+ (Closes: #727749)
+ * [fc5b485] import-dsc: allow to skip Debian tag creation
+ (Closes: #636368)
+ * [6f0c8f9] Update to git-pbuilder 1.30 that resyncs with what we shipped in
+ gbp.
+ * [3e09958] Disable html coverage generation until python-nose is fixed.
+
+ [ Markus Lehtonen ]
+ * [c661c71] pq: rewrite patch export functionality.
+ Use our own function for constructing the patch files instead of using
+ the format-patch command of git. This way, we get the desired output
+ format directly, without the need for the error-prone "format-patch,
+ parse patch files, mangle and re-write patch files" cycle.
+ Also, fix patch naming in patch generation when '--no-patch-numbers' is
+ used. Previously, multiple commits with the same subject resulted in
+ multiple patches having the same filename. This lead into broken series
+ with missing patches as patch files were overwritten by the topmost
+ commit.
+ * [017fac3] pq.format_patch: support file path filtering.
+ Implements a filter option that allows filtering out changes to certain
+ files/paths in the patch-generation. A commit is totally ignored if all
+ files would be filtered out. The path filter is given as a Python
+ regexp.
+ * [a0d6eb7] pq: properly generate non-ascii patch files.
+ Encode non-ascii email headers properly. Also, set MIME headers
+ correctly for the message body if the commit message body contains
+ non-ascii characters.
+ The reason for constructing the message in a little bit "clumsy" way is
+ the intention is to match the output of git-format-patch as closely as
+ possible.
+ * [91fbdc1] pq: support patch-export commands.
+ Support giving commands to pq as a meta tag in commit message. The
+ format is "Gbp: <command> [args]".
+ Currently, only one command is supported. namely 'ignore'. That is, one
+ can use 'Gbp: Ignore' in the commit message for ignoring the commit in
+ patch-generation.
+ * [3dccca6] pq: add format_diff() function.
+ For generating a patch file from a diff between two arbitrary commits.
+ * [34e85f7] pq: listen to 'Gbp-Pq:' commands, too
+ * [99c6995] pq: support 'Topic' patch-export command.
+ Topic can be defined with either 'Gbp: Topic <topic>' or 'Gbp-Pq: Topic
+ <topic>' in the commit message.
+ This is to replace the "gbp-pq-topic: <topic>" command.
+ * [0c0086b] docs: update pq manpages regarding pq command meta tags
+
+ [ Felipe Sateler ]
+ * [babbc5e] Add zsh completion.
+ (Closes: #717002)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 06 Nov 2013 08:21:55 +0100
+
+git-buildpackage (0.6.5) unstable; urgency=low
+
+ [ Etienne Millon ]
+ * [ba4f778] doc: Fix git-pbuilder example (Closes: #725875)
+
+ [ Guido Günther ]
+ * [3be2947] Be more robust about git status output changes by using
+ --porcelain. Heavily based on a patch by rian m. carlson
+ (Closes: #726260)
+ * [ae460e1] Bump standards version
+
+ [ Andrew Starr-Bochicchio ]
+ * [4368694] Don't export DEB_VENDOR="Debian"
+ Tests now take Ubuntu versions into consideration. (Closes: #723756)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 16 Oct 2013 10:29:58 +0200
+
+git-buildpackage (0.6.4) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [0385dc5] docs: having an upstream branch is a recommendation rather than
+ a hard requirement, we can use any treeish to build the upstream tarball
+ from.
+ * [e398469] Remove unused import
+ * [f738be3] Add link to online docs
+ * [ec7bd94] pq: print which patch failed to apply
+ * [820100d] pq: Only print number or tries if we try more than once
+ * [a86ccb7] Update to git-pbuilder 1.29
+ * [b494801] Backports got integrated into the main archive. From wheezy on
+ we can use the main archive
+
+ [ Markus Lehtonen ]
+ * [4030c19] docs: minor sgml syntax fix
+ * [e72a1e2] config: restore mangled env in doctests.
+ * [0eeafba] GitRepository/diff: add 'stat' and 'summary' options.
+ * [8cdc06f] ComponentTestBase: more verbose output in case of branch mismatch
+ * [af2034c] ComponentTestBase: fix env restore in teardown
+ * [54099f5] import-orig: import readline in the common module.
+ * [6c478a6] ComponentTestBase: keep tmpdir if GBP_TESTS_NOCLEAN is in env
+ * [bc40956] GitRepository: Add clean() method
+ * [e48f0a0] GitRepository.diff: add 'text' option. for generating textual
+ diffs.
+ * [3c0a022] GitRepository.diff: prevent usage of external diff. External
+ diff might break patch generation, for example.
+ * [6f3d63a] ComponentTestBase: use eq_() ok_() from nose.tools for better
+ assert messages.
+ * [e54289e] common/buildpackage: fix handling of empty tarball prefix as
+ needed for rpm builds
+ * [39f581e] GitRepository.diff: add 'ignore_submodules' option
+ * [b9b9eea] GitRepository.list_tree: add 'paths' option.
+ * [3eb401d] git: new class and method for remote repositories.
+ * [28fdf4c] GitRepository.has_remote_repo: use get_remotes method.
+ * [b4e5b73] GitRepository: deprecate the get_remote_repos method.
+ * [c222197] tests: add test for archiving without git submodules
+ * [92f29be] tests: use eq_ and ok_ from nose tools in submodule tests.
+ For consistency and better assert error messages.
+ * [f858f87] tests: add some docstrings to git submodule tests.
+ * [1d92bfe] tests: upper case global variables in submodule tests.
+ * [6b8037e] tests: remove unused import in submodule tests.
+
+ [ Lingchaox Xin ]
+ * [07b3054] GitRepository.has_treeish: minor pylint fix
+ * [eca5a33] GitRepository.describe: add 'tags' and 'extra-match' options
+ * [1320de2] GitRepository.fetch: Add 'refspec' option
+ * [171579f] GitRepository.fetch: Add 'all_remotes' option
+ * [68baa9a] GitRepository.push: Add 'force' option
+ * [d28c7cc] GitRepository.push: Add 'tags' option
+ * [db79c5d] GitRepository.pull: Add 'all_remotes' option.
+ Also changes the method to utilize the GitArgs class.
+
+ [ Osamu Aoki ]
+ * [8441bba] typo s/bei/be/ (Closes: #722359)
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 06 Oct 2013 17:35:14 +0200
+
+git-buildpackage (0.6.3) unstable; urgency=low
+
+ * [60ffe95] --git-hooks doesn't take an argument
+ * [7a29e42] Move MockedChangelog to testutils and allow to specify the
+ content so we can reuse it for other dch tests.
+ * [938e4cd] dch: make automatic adding of new sections more robust.
+ The code that determined if we found a snapshot header was obfuscated by
+ the code that determines the commits to add. Split those and better
+ document their purpose. Also always return the commit to start from so we
+ don't need to repeat the logic in the upper levels.
+ * [d0fb6d8] Minor docstring updates
+ * [388cfb8] docs: Add recommended branch layout.
+ It'd be nice to have similar branch layouts among packages so recommend
+ one that worked well so far.
+ * [34b2079] GitRepository: allow to use '..' instead of '...'
+ The symmetric difference isn't always useful since it includes changes
+ from both branches. See #680705.
+ * [f793ed0] pq: exclude patches from Debian packaging branch.
+ When regenerating the patch queue including patches from the Debian
+ branch is not useful. Addresses parts of #680705.
+ Thanks to Benjamin Cama for the detailed analysis.
+ * [283c847] docs: Refert to gbp in the introduction since this wraps all
+ commands now.
+ Thanks to Ross Boylan for pointing out that the current wording is
+ confusing.
+ * [8f073eb] docs: remove superfluous at
+ * [c9d3d93] Use open() instead of file() since the later doesn't exist in
+ python3
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 28 Aug 2013 19:35:34 +0200
+
+git-buildpackage (0.6.2) unstable; urgency=low
+
+ * [194d396] Document gbp's help option
+ * [56068e5] Git.Repository.__git_inout: properly set stdin.
+ We should set stdin to subprocess.PIPE when piping input according
+ to the subprocess docs.
+ * [585439e] Git.Repository.__git_inout: Close all other file descriptors.
+ No need to leak fds to the child.
+ * [9af1868] gbp.git.Repository.get_branch(): use _git_command
+ instead of the deprecated _git_inout and clarify the return codes and
+ exceptions raised.
+ * [a2ce387] gbp.git.GitRepository.has_branch(): use _git_command instead of
+ the deprecated _git_getoutput
+ * [2145c58] Test succesful import too and make sure we don't spew to stderr
+ during this test
+ * [8d2ceba] Make sure we keep our test coverage
+ * [00a4162] Switch to debhelper level 9 which is available in Wheezy
+ * [55ba9b8] Update Vcs-Browser URL
+ * [e829dc5] Add symlinks to the manpages for the deprecated commands and
+ make lintian happy (Closes: #714489)
+ * [fc74aa2] Fix broken xrefs
+ * [2a233a7] Add examples for importing upstream sources
+ * [cef5bae] Add gbp buildpackage example showing git-pbuilder invocation
+ * [1b38d90] Remove duplicate config files section in the "gbp buildpackage"
+ man page
+ * [a22eee2] GitRepository.set_upstream_branch: Newer git wants a valid
+ remote repo entry so set one in the tests before trying to set the
+ upstream branch. Found with git 1.8.3.2
+ * [7762b17] GitRepository.set_upstream_branch: don't try to set upstream
+ twice. Move setting of the upstream branch out of the loop. This worked
+ by accident so far.
+ * [b615db5] GitRepository.set_upstream_branch: Catch errors to set upstream
+ branch
+ * [b8e921e] GitRepository.set_upstream_branch: use --set-upstream-to if
+ available since --set-upstream is deprecated.
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 09 Jul 2013 21:46:00 +0200
+
+git-buildpackage (0.6.1) unstable; urgency=low
+
+ * [42769e3] Split {Build-,}Deps per line
+ and sort alphabetically
+ * [747a273] Depend on python-pkg-resources (Closes: #714238)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 27 Jun 2013 11:23:03 +0200
+
+git-buildpackage (0.6.0) unstable; urgency=low
+
+ [ Guido Günther ]
+ * Introduce gbp supercommand to get rid of the git-<command> vs.
+ gbp-<command> inconsistencies:
+ * [12dce5f] Add wrapper for all gbp commands. So like git you can now
+ use gbp <command> instead of git-<command> or gbp-<command>. The
+ manpages and docs aren't adjusted yet.
+ * [416f690] Bash complete on "gbp <command>" too
+ * [5684b18] Add gbp manpage
+ * [6b1b41f] docs: Use gbp <command> instead of {git,gbp}-<command>
+ * [daccfa0] manpages: Changes references from git- to gbp-
+ * [1d90e73] docs: Rename the entities from &git-<command>; to
+ &gbp-<command>;
+ * [1eea5d5] gbp.conf: Use command names instead of {git,gbp}-
+ * [c365f7c] Rename the sgml files from git- to gbp-
+ * [55fded7] NEWS: add a note about the new gbp super command
+ * [b47b7e8] Update package description
+ * [fe9f925] GbpOptionParser: Make sure we parse the old config sections.
+ For backward compatibility between {gbp,git}-<command> and "gbp
+ <command>" make sure we parse the former sections if using the later.
+ * [375014d] gbp: support --help
+ * [a5aff11] Consistently call gbp the supercommand and robustify against
+ invalid modules names.
+ * [e09ef94] gbp-import-dscs: invoke gbp import-dsc instead of
+ git-import-dsc
+ * [38d43b4] config: Don't fill in the parser with all defaults.
+ * [4d6eecb] GitRepository.get_subject: use get_commit_info.
+ This kills another _git_getoutput. Also deprecate the method since we
+ don't need to special case the subject.
+ * [caff99c] GbpOptionParser: Make sure we access the GbpOptionParser*'s
+ default dict and not the one from the OptionParser. The instance's default
+ dict this gets reset to empty when invoking OptionParser.__init__.
+ * [1158503] GbpOptionParser: add test for 'filter' option handling
+ * [ed63276] Git-Ignore generated gbp.conf.5 too
+ * [3966076] Remove unused imports
+ * [b59818d] import-dsc: print filename instead of object name
+
+ [ Markus Lehtonen ]
+ * [fa414e5] gitmodifier: make the datetime object always have timezone. To
+ be consistent with the date property and for easier compatibility with git
+ dates.
+ * [f717975] GitRepository/get_commit_info: add patchname to info.
+ Add a new 'patchname' field to the information returned by
+ get_commit_info. That is, the subject in a sanitized format, similar to
+ what git-format-patch uses.
+ * [74d956f] import-orig: keep working copy in sync with branch HEAD.
+ Update working copy and index (to branch HEAD) if we modify the
+ currently checked-out branch. Otherwise the repository is left in
+ unclean state when the current branch is upstream or pristine-tar and
+ the '--no-merge' option is used.
+ * [a30f9e7] buildpackage: use get_commit_info() instead of get_subject()
+ Utilize the get_commit_info() method of GitRepository instead of the
+ deprecated get_subject().
+ * [75cbd9a] GitRepository/diff: allow single object.
+ Allow diffing a single object, e.g. getting diff from single commit.
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 26 Jun 2013 16:36:41 +0200
+
+git-buildpackage (0.6.0~git20130530) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [f4acd7a] GitRepository: Use LC_ALL=C when checking features.
+ This fixes the doctest and also makes sure we use an up to date manpage.
+ Thanks to Daniel Dehennin for the report.
+ * [9c5bd03] Add Jenkins Scratchbuilder example.
+ It's a simple script that can be used to build Debian packages via gbp
+ and Jenkins.
+
+ [ Gaudenz Steinlin ]
+ * [47f2dea] git-buildpackage: Unbreak overlay mode (Closes: #708636)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 30 May 2013 15:24:05 +0200
+
+git-buildpackage (0.6.0~git20130506) unstable; urgency=low
+
+ [ Guido Günther ]
+ * Get rid of lots of spurious output to stderr, only report it in error
+ cases. This makes the tools and the testsuite less chatty.
+ * [4abfa6a] gbp-pull: Fix typo in help output
+ * [90ffae8] manpages: Document gbp-pull's --ignore-branch option
+
+ [ Markus Lehtonen ]
+ * [9e3267a] DebianSource: raise DebianSourceError if reading changelog fails
+ * [a36dc64] buildpackage: catch and handle DebianSourceError gracefully
+ * tests: fixes for older dch
+ * [36341dd] GitRepository: add describe() method.
+ * [ae63dba] DebianSource: fix is_native()
+ Determine from changelog if debian/source/format does not provide any type
+ (1.0 format).
+ * [f880910] tests: Fix tests for Ubuntu.
+ * [f9722f6] buildpackage: implement --[no-]hooks option.
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 06 May 2013 19:18:49 +0200
+
+git-buildpackage (0.6.0~git20130414) unstable; urgency=low
+
+ * [e948bd3] Don't try access source before it's filled
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 13 Apr 2013 19:36:57 +0200
+
+git-buildpackage (0.6.0~git20130413) unstable; urgency=low
+
+ [ Daniel Dehennin ]
+ * [a9bf9cf] Move debian/changelog manipulation to gbp.deb.changelog.ChangeLog.
+ (Closes: #672954)
+ * [ae4a368] Add option to manage distribution fields for non snapshot mode.
+ The snapshot mode fails to merge two "debian/changelog" entries if the
+ distribution is not "UNRELEASED".
+ (Closes: #646684)
+ * [920053e] Add urgency management.
+
+ [ Guido Günther ]
+ * [62d3b99] Move DebianPkgPolicy to separate module
+ * [7335735] Move DscFile to separate module
+ * [e5d9816] Generate coverage xml for cobertura style coverage information
+ that can be picked up by e.g. Jenkins
+ * [6eb2ddc] Use _git_inout for git.show
+ so we don't spew the error message on stdout for nonexistent objects
+ * [d510f2a] Honor debian/source/format when checking if a package is a
+ native package (Closes: #669267)
+
+ [ Markus Lehtonen ]
+ * [54679eb] Add .coveragerc.
+ To get correct report if coverage is invoked directly (not through
+ nose/disttools) in order to get an xml report, for example.
+
+ [ Thomas Koch ]
+ * [3d6b68a] tests: Use tempfile.mkdtemp to create temp dirs for tests.
+ This puts test dirs below /tmp which often is a tmpfs.
+ All tests include the context module which consolidates tmpdir creation
+ and cleanup, undoes a chdir in teardown and silences log messages.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 13 Apr 2013 14:27:06 +0200
+
+git-buildpackage (0.6.0~git20130329) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [703da99] Remove unused imports
+ * [db66286] Return boolean types from is_ methods instead of a match object
+ or None
+ * [db3505f] Better document how one derives from PkgTypes
+ * [eb999f7] Allow for upper case characters in the upstream version
+ (Closes: #703694)
+ * [fc9d019] Purging of the build dir should be configurable via a config file
+ so introduce --git[-no]-purge which is consistent with the other
+ boolean options and deprecate --git-dont-purge. (Closes: #702200)
+ * [5925499] Split out building a debian version from an upstream commit
+ based on a patch by Daniel Dehennin
+ Needed for #672954, #646684, #669171
+ * [dafb5a3] Fix docstring
+
+ [ Markus Lehtonen ]
+ * [8b80e38] ComponentTestBase: close streamhandler when stopping log capture
+ * [5ef9e77] gbp-pull: update tags, too.
+ To make sure that the tags are in sync with the remote.
+ * [6fc1c08] tests/component: pylint fixes.
+ Add module docstring and wrap one overlong line.
+ * [7709f21] tests: skip test_Changelog if 'dch' tool is not available
+ * [dcf7467] setup: possibility to skip nosetest requirements.
+ Makes it possible to not require test requirements by defining
+ WITHOUT_NOSETESTS environment variable when running setup.
+ * [3d80b2f] GitRepository/_cmd_has_feature: more intelligent parsing.
+ More intelligent parsing of the git output (man page). Try to
+ parse optional options like '--[no-]standard-notes' of git-show
+ correctly. In this example both 'no-standard-notes' and 'standard-notes'
+ would be available.
+ * [57bbd0a] GitArgs: utilize the add() method in other add_X methods.
+ Only use the add() method for updating the argument list. This makes the
+ code more robust and makes all add method variant types support the same
+ argument types.
+ * [3b873f7] ComponentTestBase: capability to check files of repo.
+ Makes it possible to check that the correct files are present in
+ the working copy of the repo.
+ * [64ed77c] tests.test_GitRepository: import and setup gbp.log.
+ In order to initialize gbp logging properly. Fixes false positives when
+ the GitRepository tests are run alone.
+
+ [ Zhang Qiang ]
+ * [6703edc] packaging: Add man as dependency.
+ Required by GitRepository._cmd_has_feature() method.
+
+ [ Daniel Kahn Gillmor ]
+ * [4323cc8] Include the name of the package being built in the debian tag
+ message. (Closes: #704018)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 29 Mar 2013 12:34:07 +0100
+
+git-buildpackage (0.6.0~git20130314) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [4a5fa0d] Ignore rope project files as generated by ropemacs
+ * [c6b7bc2] gbp-pq: Improve patch header write out. Avoid trailing new
+ lines in the patch header when reexporing patches. to avoid diffs when
+ nothing changed.
+ * [2fa7cac] gbp.log: lowercase the levelname too match the output before the
+ switch to Python's logging module
+ * [00ed38f] gbp.log: Improve error message on broken color lists
+ * [744f85b] gbp-create-remote-repo: Set HEAD in remote repo
+ to debian branch (Closes: #692006)
+ * [3591792] git-dch: Allow to create changelogs when not on a branch.
+ This makes snapshot changelog generation e.g. within jenkins builds
+ simpler since jenkins by defaults checkouts out the commit without
+ creating a branch.
+ * [5176d2a] Check the sha1 doesn't exceed 40 bytes
+ * [c097397] Document --git-pristine-tar-commit
+ * [9e30bf2] Add component test initialization very heavily based on code by
+ Markus Lehtonen
+ * [3820adc] Add test data submodule
+ * [9ec98c5] Move over import dsc test from external test suite. Move over
+ the first test from our external (shell based) test suite to the component
+ tests.
+ * [80b5c64] Add missing --git- (LP: #1112831)
+ * [b678c6a] Fix typo.
+ Thanks to Andreas Beckmann (Closes: #700443)
+ * [4ef1081] examples: fix logging.
+ Thanks to Carsten Schoenert
+
+ [ Markus Lehtonen ]
+ * [83c2c3f] log: rewrite the module to use Python logging module. Replaces
+ the Logger with a new one, derived from the Logger class of the Python
+ standard library. Colorized output is handled by a separate handler class.
+ * [48c48c9] log: add support for setting the color scheme. Implements
+ changeable color schemes in the gbp.log module. Color scheme is given as a
+ colon-separated list, with one color for each log level:
+ '<debug_color>:<info_color>:<warning_colro>:<error_color>'. Colors can be
+ given as an integer number (ANSI terminal color code) or color name (.e.g
+ 'red'). Missing or empty fields are interpreted as using the default color
+ for that log level.
+ * [ebbe635] New configuration option for setting the output color scheme.
+ Adds a new command line and config file option 'color-scheme' for
+ selecting the colors used in log output.
+ * [ec2b74c] GitRepository/strip_sha1: fix length checking.
+ Accept longer sha1 than what was asked for. The length option given to
+ git is merely a "wish to get a sha1 of this length". Git may also return
+ longer sha1 if truncating to given length would give
+ ambiguous/non-unique sha1.
+ * [c9011f7] Tristate: allow to init from another Tristate object
+ * [4b719cd] Tristate: drop broken and unused is_valid_state() method
+ * [4bdfe2a] log: make color setting a tristate on/off/auto.
+ Makes it possible to force color=on e.g. when piping output. Also, moves
+ all 'auto' logic to one single place, i.e. the streamhandler.
+ * [ab7a732] pq: do author guessing outside the apply_patch functions.
+ Call the author parsing/guessing function outside the apply patch
+ functions. This way, the caller can decide when to do the guessing, and
+ with which parameters. Now the apply_patch functions do what their name
+ suggests.
+ * [9c80f14] buildpackage: add 'force' option to write_wc()
+ To select whether to include file that would otherwise be ignored by
+ gitignore (.gitignore or .git/info/exclude).
+ * [5e6f163] tests.testutils: baseclass for testing commandline tools.
+ Introduce a new baseclass to be utilized in testing the git-buildpackage
+ command line tools.
+ * [af0ee43] pq/apply_and_commit_patch: fix date parsing.
+ GitModifier doesn't support dates in the format used in git format-patch
+ emails.
+ * [97cd5bd] gbp-clone: fix return value in case of GitRepositoryError
+ * [377de2d] gbp-pull: fix --depth cmdline option
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 14 Mar 2013 07:16:49 +0100
+
+git-buildpackage (0.6.0~git20121124) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [c3c2778] Add minimal debian/source/format parser
+ * [3878867] PEP-8 and pyflakes cleanups
+ * [30c1821] Add support dch's --security option
+ * [4970b42] gbp-pq: Print number of import tries left
+ * [cf48b03] Allow to remove the orig tarball symlink that's used make
+ pristine-tar see the correct orig tarball name.
+ (Closes: #692401)
+ * [09ba538] GbpError accepts an error message so no need to print it
+ separately.
+ * [a488695] Minor git-import-orig manpage improvements
+ * [4c9bb9f] Add missing git- prefix to --color and --notify options.
+ Thanks to Filippo Rusconi (Closes: #693978)
+ * [9478bad] testutils: create missing directories when adding a file
+ * [691856d] Use an absolute path when parsing the control file so we can't
+ accidentally parse a file from another dir.
+ * [d248720] Test apply_and_commit_patch
+ * [3952091] gbp-pq: Allow to pass in custom function to fetch authorship
+ information so the RPM based tools don't need to rely on a control file
+ but can e.g. look at the spec file.
+ * [7f2c9c1] gbp-pq: Test if setting a topic works
+ * [f4d493d] Add sha1 stripping in a single place so we can easily sanity
+ check the result.
+ * [95ec21a] Output test results in xunit format too to make it more useful
+ in Jenkins CI builds
+ * [24fdd97] Test gbp.pq.common.write_patch
+ * [824e83e] gbp-pq: don't use plural form when there's only one try left
+ * [8b01d37] git-import-dscs: Properly catch import errors
+ (Closes: #694113)
+
+ [ Dmitrijs Ledkovs ]
+ * [323bdcf] Fix testsuite failure if DEB_VENDOR != Debian.
+ (Closes: #692289)
+
+ [ Markus Lehtonen ]
+ * [f4d2e21] GitRepository: implement _cmd_has_feature() method to allow
+ compatibility with older git versions.
+ * [dfbedfe] GitRepository: fix merge() for older git versions using
+ _cmd_has_feature() in GitRepository.merge().
+ * [ba854d8] GitRepository/diff: add 'paths' argument to makes the diff
+ function more versatile
+ * [b7cdef5] GitRepository/diff: catch git error.
+ * [fe1e120] command_wrappers: suppress some pylint warnings
+ * [4ed1b43] notifications.py: remove unused import
+ * [0a9bf99] tests: make tests a Python module to make it possible to reuse
+ code between various tests.
+ * [932b1c0] pq: fix "no authorship" warning messages.
+ * [1da7e0d] import-orig: remove dead if statement.
+ * [3678906] import-orig: remove dead GbpNothingImported exception
+ * [8bc7921] GitRepository/get_commits: more flexible revision ranges.
+ * [36d13cb] GitRepository/get_submodules: use correct path.
+ * [3362147] GitRepository/get_commit_info: support tags.
+ * [03cdea0] GitRepository/has_submodules: use correct .gitmodules file.
+
+ [ Ed Bartosh ]
+ * [8bb62f5] GitRepository.find_tag: change deprecated _git_getoutput to
+ _git_inout
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 24 Nov 2012 14:55:24 +0100
+
+git-buildpackage (0.6.0~git20120822) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [e05bfb9] dch: Fix error reporting on parsing errors
+ * [f4162be] manpage: Fix typo in git-dch's multimain-merge option
+ (Closes: #684322)
+ * [e8d175a] manpages: correct manual section and remove duplicate entry
+
+ [ Zhang Qiang ]
+ * [0af1e72] GitRepository/branch_contains: remove prefix '*' in branch name.
+
+ [ Markus Lehtonen ]
+ * [678f85f] docs: fix cross-referencing in manpages.
+ * [f0d4b0d] docs: make refentry ids of manpages more consistent
+ * [dc3b3b6] GitArgs/add: support iterable and non-string args.
+
+ [ Ed Bartosh ]
+ * [7524bbb] GitRepository: Implement set_upstream_branch and
+ get_upstream_branch methods
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 22 Aug 2012 10:58:07 +0200
+
+git-buildpackage (0.6.0~git20120803) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [6287b0d] gbp.conf: Make config option printing more consistent
+ * [9430ee1] Make TestDir testcase use a newly created directory neeeded for
+ the upcoming packagename/version parsing support
+ * [e9d239b] UpstreamSource: automatically detect package name and versions
+ from directories of the form packagename-<version>
+ * [41482a3] tests: test GitRepository.get_commit_info()
+ * [dd593a0] GitRepository: return GitModifier object instead of separate
+ fields this make sure the number of return values doesn't stays sane when
+ also returning timestamps and committer information.
+ * [92a744e] GitRepository: Add test that covers remote branches in
+ has_branch()
+ * [e6e0150] Make exception syntax consistent
+ * [d3ee4b0] GitRepository: Make rev_parse's short option an int everywhere
+ * [891ea2b] git.repository.GitRepository.add_remote_repo: use GitArgs
+ * [036db95] gbp.git.GitRepository: Add remove_remote
+ * [4db02e5] Fix variable renaming
+ * [f4da964] GitModifier: More flexible date handling.
+ Allow to pass in the date as datetime object, timestamp or git raw date.
+ and allow to retrieve these values. This make constructing GitModifiers
+ from python simpler.
+ * [cd829c9] GitRepository: raise GitRepositoryError on git errors.
+ Raise GitRepositoryError in cases where CommandExecFailed (from
+ GitCommand) was previously silently passed forward.
+ Heavily based on a patch by Markus Lehtonen.
+ * [7a9f947] GitModifier: use __getitem__ to fetch date
+ instead of accessing __dict__ directly which only has _date.
+ * [c4524f8] GitModifier: add tests for dict interface
+ * [c47deff] Remove multiple spaces
+ * [56a8672] Skip tests requiring devscripts if dch is not there
+ so tests don't fail on rpm based systems.
+ * [5961a4d] Test help output by importing the modules
+ so we get correct coverage information for the scripts
+ * [68efed1] gbp.command_wrappers: Reformat to 80 chars line length
+ * [aa2ba85] Add tests for gbp.deb.{DpkgCompareVersions,DscFile}
+ * [83577af] Move uscan to separate class
+ * [776cbb7] Improve error reporting from uscan
+ by parsing out the warnings and error fields from the dehs output.
+
+ [ Markus Lehtonen ]
+ * [9b68e37] import-orig: move is_link_target() to common module.
+ This change makes is_link_target() re-usable in the upcoming RPM-tools.
+ * [36e0986] common/pq: use strip in apply_and_commit_patch()
+ Use the strip information of the patch when applying patches. Also,
+ changes GitRepository.apply_patch() to accept integer values as 'strip'
+ argument.
+ * [cc1ebfd] PristineTar: move Debian-specific stuff to DebianPristineTar.
+ Continuation to the PristineTar refactoring, makes the "common"
+ PristineTar independent of DebianPkgPolicy. This commit moves the
+ Debian-specific has_commit() and checkout() methods to DebianPristineTar
+ class and replaces them with more generic functions in the base class.
+ Also, drops the Debian-specific get_commit() method completely, as it
+ was not used outside the PristineTar class itself.
+ * [18fc698] GitRepository: fix process cwd in _git_inout()
+ * [1e85978] GitRepository: make get_commit_info() more robust.
+ Now uses git-show instead of git-log. This is needed for further
+ enhancements (namely to get name-status for merge commits). Also, use
+ null-character as the field separator which makes parsing more reliable.
+ The method now returns 'body' of the commit message as is, without
+ stripping or splitting to lines.
+ In addition, get_commit_info() now uses GitArgs and _git_inout() instead
+ of the deprecated _git_getoutput().
+ * [2c668bf] GitRepository/get_commit_info: check return value of git command
+ correctly
+ * [4d56ab6] GitRepository/rev_parse: add new argument 'short'
+ Adds a new argument to get abbreviated SHA1.
+ Also, modifies rev_parse() to use GitArgs class.
+ * [ba55f9e] GitRepository/get_author_info: return user.name as name.
+ Return users full name (user.name) instead of email (user.email) as the
+ 'name' of the author when taking the value from git config.
+ * [21ac2d8] GitRepository: option to ignore untracked in is_clean()
+ Add an option to ignore untracked files when checking if the repository
+ is clean.
+ * [7f9776f] GitRepository/get_commit_info: add author timestamp.
+ Add author timestamps to the info returned by get_commit_info() method.
+ * [32f725f] GitRepository/get_commit_info: add committer info.
+ Add committer to the info returned by get_commit_info() method. Returns
+ committer name, email and timestamp as a GitModifier object.
+ * [6e4138f] GitRepository/get_commit_info: add file status.
+ Add file status and name to the info returned by the get_commit_info()
+ method.
+
+ [ Ed Bartosh ]
+ * [9c54298] deb.git: Fixed typo in method name
+ the double underscore prefix was dropped ages ago.
+
+ [ Daniel Dehennin ]
+ * [b970ca0] Test behavior of gbp.scripts.dch.main().
+ * tests/11_test_dch_main.py: Test common cases with "--release" and
+ "--snapshot". Try 2 consecutive snapshots to check for merged entries.
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 03 Aug 2012 19:26:48 +0200
+
+git-buildpackage (0.6.0~git20120601) unstable; urgency=low
+
+ * Upload to unstable
+ * [a5f0d87] git-import-dsc: add --allow-unauthenticated
+ (Closes: #670623)
+ * [a116edd] Refactor deb helpers: move PristineTar class based on a patch
+ by Markus Lehtonen. This refactor is preparation to the upcoming rpm
+ support.
+ * [f52a417] git-buildpackage: add missing import so move_old_export works
+ again
+ * [16f0309] git-buildpackage overlay-mode: don't fail on files named like
+ the tarball. Instead of moving individual files around simply rename the
+ directories. This is faster and independent of the tarballs content.
+ (Closes: #675412)
+ * [7f580d9] git-buildpackage: allow to build packages from detached HEAD
+ state with --git-ignore-branch.
+ (Closes: #661598)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 01 Jun 2012 23:44:17 +0200
+
+git-buildpackage (0.6.0~git20120524) experimental; urgency=low
+
+ [ Markus Lehtonen ]
+ * [3308868] Refactor deb helpers: introduce PkgPolicy class. Create a new
+ 'pkg' basemodule, intended to be re-used by the upcoming rpm package
+ helpers. Move some deb functionality to a new pkg.PkgPolicy class, to be
+ used as a base for different package types. Introduces Deb-specific
+ deb.DebianPkgPolicy.
+ * [082679d] Refactor deb helpers: move build_tarball_name() from
+ UpstreamSource class to DebianPkgPolicy.
+ * [f495df9] Refactor deb helpers: move UpstreamSource class to pkg base
+ module. This refactor is preparation to the upcoming rpm support.
+
+ [ Guido Günther ]
+ * [83165aa] GitRepository.format_patches: allow to set threading format
+ and disable it by default.
+ * [5de6410] docs: complete git-dch synopsis
+ * [1eeb298] Add gbp.deb.ChangeLogSection to parse package and version out of
+ a changelog section
+ * [1ea487e] Don't explicitly refer to lenny-backports.
+ Thanks to Salvatore Bonaccorso
+ * [9d1459b] gbp.deb.changelog: Split parsing into a separate function
+ this will allow us to reparse the changelog after manipulation with dch.
+ * [2ecf9e3] gbp.deb.ChangeLog: Add filename property
+
+ [ Jérémy Lal ]
+ * [ae850da] --ignore-branch config help fix
+
+ [ Daniel Dehennin ]
+ * [c57d4af] gbp.git.repository: Add a "git merge-base" wrapper
+ (Closes: #672642)
+ * [edc6483] Provide minimalist debian/control object
+ (Closes: #673473)
+
+ [ Salvatore Bonaccorso ]
+ * [bb32e94] git-dch: Add support for --team switch for Team uploads
+ (Closes: #673368)
+ * [016bf21] Document --bpo, --nmu and --qa in git-dch's manpage
+ (Closes: #673422)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 24 May 2012 14:05:47 +0200
+
+git-buildpackage (0.6.0~git20120419) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [3b34b26] Make building with DEB_BUILD_OPTIONS=nocheck skip all tests
+ heavily based on a patch by Matthijs Kooijman (Closes: #669149)
+
+ [ Matthijs Kooijman ]
+ * [a43e9b9] Git-Dch: Full in commit messages.
+ This new tag makes git-dch use the full commit message when generating
+ the Debian changelog file, even when --full is not given. (Closes: #669159)
+ * [b72702f] Bug#669145: Add git-dch --commit and --commit-msg options
+ (Closes: #669145)
+
+ [ Markus Lehtonen ]
+ * [19b6e24] docs: add some missing tags to sgml files
+ * [9c4f34c] Refactor gbp-pq as preparation for rpm support.
+ Separate some functions of gbp-pq into a basemodule, intended to be
+ re-used by the upcoming rpm variant of the tool.
+ Also, introduces a new python subpackage gbp.scripts.common to be used
+ for the re-usable parts of the scripts in the refactoring work.
+ * [c8e4807] Refactor git-buildpackage as preparation for rpm support.
+ Separate some functions of git-buildpackage into a basemodule, intended
+ to be re-used by the upcoming rpm variant of the tool.
+ * [27c35b1] import-orig: refactor args of ask_package_* functions.
+ Makes ask_package_name() and ask_package_version() more generic and
+ re-usable in the upcoming RPM-tools.
+ * [5826e9c] Refactor git-import-orig as preparation for rpm support.
+ Separate some functions of git-import-orig into a baselib, intended to
+ be re-used by the upcoming rpm variant of the tool.
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 19 Apr 2012 00:24:03 +0200
+
+git-buildpackage (0.6.0~git20120415) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [1e68f6e] gbp.git.repository: don't append a second .git when mirroring a
+ repository
+ * [b7797f9] ALlow to set the output directory via GIT_PBUILDER_OUTPUT_DIR.
+ Helps with #657277
+ * [407dfa1] Make the upstream version check match policy
+ (Closes: #668554)
+ * [d1ed77a] Ignore locale when checking for a clean repository
+ (Closes: #668896)
+
+ [ Jö Fahlke ]
+ * [43e3a60] docs: correct --git-builder default value
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 15 Apr 2012 19:05:32 +0200
+
+git-buildpackage (0.6.0~git20120404) unstable; urgency=low
+
+ * [f4529a4] docs: add --git-upstream-tag to git-buildpackage's manpage
+ * [96332cc] Start documenting useful options when upstream uses git. This is
+ quiet terse yet so follow up bugs are welcome. (Closes: #664771)
+ * [eae4e4a] gbp-posttag-push: Tighten branch regexp so we don't push to
+ branches ending with the same name accidentally.
+ * [169a924] gbp-add-patch: Fix typo
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 04 Apr 2012 18:50:19 +0200
+
+git-buildpackage (0.6.0~git20120324) experimental; urgency=low
+
+ * [8fd6162] gbp-create-remote-repo: Use DebianGitRepository (Closes: #664283)
+ * [ddaa653] gbp-create-remote-repo: don't expect a terminal so you can do
+ 'echo y | gbp-create-remote-repo'
+ * [92cc7ba] gbp-create-remote-repo: allow to use local file transport too
+ * [6d4b4ff] gbp create-remote-repo: allow to pass template directory to git
+ init so it's possible to set up remote hooks.
+ * [b9d03af] gbp-create-remote-repo: add --remote-config. This specifies an
+ additional config file section that can be used to preconfigure different
+ remote locations.
+ * [d6e0e8d] git-import-orig: Allow to pass in an upstream vcs tag. This
+ allows to link the import-orig commit with the corresponding upstream vcs
+ tag. See #664771 for a discussion.
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 23 Mar 2012 23:58:42 +0100
+
+git-buildpackage (0.6.0~git20120315) unstable; urgency=low
+
+ * [3cac77f] docs: Fix path to gbp-configure-unpatched-source. Thanks to
+ Tobias Frost (Closes: #664063)
+ * [2f08cbd] Parse author and committer from changelog for native package too
+ (Closes: #662993)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 15 Mar 2012 22:39:00 +0100
+
+git-buildpackage (0.6.0~git20120311) unstable; urgency=low
+
+ * [83dcb28] Move gbp.conf manpage to section 5 and add references from the
+ other manpages
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 11 Mar 2012 19:23:35 +0100
+
+git-buildpackage (0.6.0~git20120227) unstable; urgency=low
+
+ * Upload to unstable
+ * [45d7dc8] import_dsc: Create debian branch for native packages too
+ (Closes: #663001)
+ * [80cf6d4] Add manual page for gbp.conf.
+ Thanks to Jari Alto for the POD version (Closes: #661748)
+ * [a8b8536] Build-require a git version that supports --no-edit
+ (Closes: #663103)
+ * [b4cfcab] Remove build/ directory during clean so we don't step on any
+ epydoc leftovers
+ * [b6a4387] Bump standards version to 3.9.3
+ * [6f2f554] GbpOptionParser: allow to modify list of parsed conf files
+ by setting the GBP_CONF_FILES environment variable.
+ * [38870d5] git-import-dscs: allow to ignore gbp.conf files stored in the
+ repository (Closes: #660898)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 08 Mar 2012 18:56:42 +0100
+
+git-buildpackage (0.6.0~git20120226) experimental; urgency=low
+
+ [ Markus Lehtonen ]
+ * [bca9f76] Refactor config.py as preparation for rpm support.
+ * [38afb27] docs: add missing tags to sgml files
+ * [2081900] gbp-pq: refactor args of write_patch()
+ Allows defining the formerly hardcoded patch_dir. This makes write_patch()
+ re-usable in the upcoming RPM-tools.
+
+ [ Guido Günther ]
+ * Upload to experimental so the current version in sid can move to testing
+ first.
+ * [7726bca] Make sure we test the built modules not the system ones
+ * [1018809] copy_from: remove, not used anymore
+ * [09e4080] docs: make version pattern match the code (Closes: #650465)
+ Thanks: Leo 'costela' Antunes
+ * [970fbaa] config.py: Add default value print out to all options
+ * [f3aa87f] GitRepository.merge: add edit option defaulting to False
+ (Closes: #659239)
+ * [67c8513] Depend on git >= 1:1.7.9.1-1~ for a working "git merge
+ --no-edit". Thanks to Jonathan Nieder
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 26 Feb 2012 12:09:23 +0100
+
+git-buildpackage (0.6.0~git20120218) unstable; urgency=low
+
+ [ Markus Lehtonen ]
+ * [24e7725] Make pristine_tar testcases pass with older git versions that
+ don't support user-defined output formats (tar filters) in git-archive.
+
+ [ Guido Günther ]
+ * [14d36c0] Depend on git instead of git-core (Closes: #659661)
+ * [f25fa9b] Lower dependencies on python-nose and python-coverage the
+ versions in Squeeze are recent enough.
+ * [7e41626] Require git 1.7 which is already in Squeeze and Lenny Backports
+ * [63411b7] gbp-pq: Properly print patch name when guessing authorship
+ information
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 18 Feb 2012 16:16:48 +0100
+
+git-buildpackage (0.6.0~git20120210) unstable; urgency=low
+
+ * [d279757] PristineTar: fix links in testcases
+ * [44814b7] Include missing doctests in generated API docs
+ * [3875d5d] gbp-pull: Use a DebianGitRepository (Closes: #659290)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 10 Feb 2012 09:30:47 +0100
+
+git-buildpackage (0.6.0~git20120209) unstable; urgency=low
+
+ * Upload to unstable
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 09 Feb 2012 14:08:43 +0100
+
+git-buildpackage (0.6.0~git20120207) experimental; urgency=low
+
+ * Upload to unstable
+ * [1e51936] git-import-orig: Properly detect .tgz and other abbreviated file
+ extensions (Closes: #658777)
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 07 Feb 2012 23:24:48 +0100
+
+git-buildpackage (0.6.0~git20120124) experimental; urgency=low
+
+ [ Markus Lehtonen ]
+ * [b7d7016] gbp-pq: don't crash in get_maintainer_from_control() even if
+ debian/control is missing.
+
+ [ Guido Günther ]
+ * [c5eee2b] PristineTar: add missing import for debug output
+ * [3ca0a98] PristineTar: fix match when not passing in a compression type
+ * [45ab61b] Depend on pristine-tar for the tests
+ * [041dbf2] GitRepository: allow to capture stderr in __git_inout
+ stderr was always None.
+ * [e6fd2ce] GitRepository: Use _git_inout() in __init__()
+ to avoid misleading error message
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 31 Jan 2012 10:22:13 +0100
+
+git-buildpackage (0.6.0~git20120123) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [90fb9b2] Add "gbp-pq switch" to bash completion
+ * [8328c32] Allow to specify the upstream tree via --upstream-tree without
+ the indirection to --upstream-branch.
+ * [3d4adca] gbp-posttag-push: Allow to push the upstream tag too via the -u
+ option.
+ * [2d84986] gbp-post-tag-push: Add --verbose option to ease debugging
+ * [cd41023] git-buildpackage: Allow to pass pbuilder options via commandline
+ or gbp.conf
+ * [a31c95b] Update to git-pbuilder 1.27. This adds support for creating
+ backport build environments. Thanks to Russ Allbery
+ * [338ee68] git-buildpackage: Add --pristine-tar-commit option. This option
+ allows to auto commit a generated tarball to the pristine-tar branch to
+ simplify building non-dfsg clean packages and to make it easier to track
+ uptream git without having to commit the generated tarball manually.
+
+ [ Markus Lehtonen ]
+ * [0c47432] Add git-depth option to gbp-clone and gbp-pull. Allows creating
+ and deepening shallow clones. This is sometimes useful for e.g. saving
+ bandwidth when cloning.
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 23 Jan 2012 22:13:38 +0100
+
+git-buildpackage (0.6.0~git20111229) experimental; urgency=low
+
+ * [686f29c] scripts/pq: handle patches without filename extension. Don't
+ fail if patches don't have a proper patch header and filename extensions
+ (like in the heimdal package)
+ * [248b550] Update git-pbuilder to 1.25.
+ Thanks to Russ Allbery (Closes: #635061, #642355)
+ * [58978c5] Add --[no-]-pbuilder-autoconf to pass GIT_PBUILDER_AUTOCONF=no
+ to git-pbuilder.
+ * [01a2689] gbp-pq: add "switch" action to quickly switch between
+ patch-queue and base branch.
+ * [8f41ffb] docs: add missing dependency to manpage generation
+ * [6bfc8e5] Fix typo in clean target override
+ * [747c05d] dch: Honor epoch when guessing new upstream version.
+ Thanks to a lot to Daniel Dehennin for the testcase (Closes: #652366)
+ * [ed4e523] import_dsc: remove debian/ dir from upstream source after
+ importing it. This gives us the orig tarball on upstream but the debian
+ branch looks the same as with dpkg-source -x. (Closes: #653472)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 29 Dec 2011 14:52:29 +0100
+
+git-buildpackage (0.6.0~git20111217) experimental; urgency=low
+
+ * [baeee74] docs: fix version string
+ * [2ff6119] pq: add --force option to fore recreation of pq branch so one
+ doesn't have to drop the old one first.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 17 Dec 2011 13:28:12 +0100
+
+git-buildpackage (0.6.0~git20111202) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [3a3539a] Document that gbp-create-repo is noninteractive
+ (Closes: #643752)
+ * [bf397fc] gbp-pq: Properly check for start of signature (Closes: #646400)
+ * [718f0c3] gbp-pq: Strip .patch and .diff when importing patches without a
+ subject
+ * [3fcf009] command_wrappers.Command: Don't print several error lines
+ * [37e8310] Allow to import upstream tarballs into bare repositories
+ (Closes: #582775)
+ * [abf90ab] Allow to import Debian packages into bare repositories
+ * [8c22803] gbp-pull: massive speedup by just updating the ref to the
+ remote's SHA1 instead of checking out the branch and doing a fast-forward
+ merge.
+ * [af07c40] gbp-pq: Instead of looking for the signature don't let git
+ generate it. Thanks to Robert Luberda
+ * [69d348c] Support postexport hooks. Heavily based on a patch by Jan ÄŒapek
+ (Closes: #640982)
+ * [030744d] Parse the changelog of the exported tree instead of the working
+ copy when using --export. This reduces the number of options needed when
+ using an export dir since the correct tarball is now being created without
+ additional options. (Closes: #564791)
+ * GitRepository: Lots of api, documentation and test updates replacing Git*
+ classes by GitRepository methods. Check the apidocs for details.
+ * [7168735] Switch to dh
+
+ [ Jan ÄŒapek ]
+ * [d2c1033] Documentation update for --git-postexport, --git-postbuild typo
+ fix
+ * [f7a6b07] Additional documentation for postexport hook in the manual - the
+ documentation now provides sample postexport script and gbp.conf
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 02 Dec 2011 19:19:51 +0100
+
+git-buildpackage (0.5.32) unstable; urgency=low
+
+ * [efe9220] Use known_compressions in guess_upstream_version too
+ (Closes: #645477)
+ * [e984baf] git-import-orig: fix --filter
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 17 Oct 2011 10:15:22 +0200
+
+git-buildpackage (0.5.31) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [3588d88] Fix pristine-tar error message
+ * [8da98da] gbp-pq: don't fail on missing series file but create an empty
+ branch instead
+
+ [ Salvatore Bonaccorso ]
+ * [b33cf74] Fix URL to cl2vcs service.
+ Refer to https://honk.sigxcpu.org/cl2vcs instead of
+ https://honk.sigxcpu.org/cl2vcs for the cl2vcs service. (Closes: #640141)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 28 Sep 2011 20:21:34 +0200
+
+git-buildpackage (0.5.30) unstable; urgency=low
+
+ * [37f16cc] Document meta tags. Thanks to Raphaël Hertzog for the
+ suggestion (Closes: #636088)
+ * [f2efdaf] Add known_compressions() to UpstreamSource so we can use it for
+ the DscFile parsing. This fixes the import of xz compressed upstream
+ tarballs.
+ * [2da9e9e] Leave compression type detection to tar by default
+ * [a878947] Ignore comments and empty lines in series files.
+ (Closes: #637224)
+ * [b3931e0] Fix missing branch name in error message
+ * [4e2f7de] gbp-pq: Use latest patches with --time-machine. When going back
+ in history to find the point where the patches in debian/patches still
+ apply make sure we use the latest ones not the one currently in the tree.
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 10 Aug 2011 22:25:11 +0200
+
+git-buildpackage (0.5.29) unstable; urgency=low
+
+ * [00c2b8e] Move GbpPatchQueue and GbpPatch into gbp.pq
+ * [2fbac77] Build epydoc API documents by default
+ * [42a13a1] gbp-pq: add --time-mache=N option to find the last commit the
+ patch-queue applies to.
+ * [d052ba2] Document --time-machine option
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 30 Jul 2011 15:07:41 +0200
+
+git-buildpackage (0.5.28) unstable; urgency=low
+
+ * [3c72dfa] git-import-orig: Clarify missing argument error message
+ * [d34e82e] Be less picky about empty filters. (Closes: #635641)
+ * [a692745] Report errors reading the patch file instead of throwing an
+ exception. (Closes: #635872)
+ * [9bbbc77] Better document --git-upstream-tree. (Closes: #635883)
+ * [3fb7fd6] Add strip option to GitRepository.apply
+ * [dbe68fb] Split out GbpPatchQueue and GbpPatch to handle "-p<num>" without
+ munging the code further (Closes: #635873)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 29 Jul 2011 19:00:25 +0200
+
+git-buildpackage (0.5.27) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [8ce3500] Add autocompletion for --git-dist= based on the available
+ cowbuilder images
+ * [7fc837a] git-import-orig: Better support uscan for non tar.gz tarballs
+ (Closes: #629538)
+ * [2d4034d] Drop suggests of git-load-dirs. It's not in the archive anymore
+ * [41a5d0d] Switch to dh_python2
+ * [305e871] Re-enable pychecker checks
+ * [7360496] Support importing zip archives. This can be extended to support
+ other formats.
+ * [00e1d97] Remove fastimport code since it didn't support filters, etc.
+ * [8d0143a] git-import-orig: support filters for all input formats
+ (Closes: #628645)
+ * [e05e985] Add tests for UpstreamSource
+ * [f0ba62c] Drop unpack_orig and tar_toplevel
+ * [5bd3ff2] git-dch: add formatter that wraps the changelog entry
+ (Closes: #626439)
+ * [0ecd9f7] git-buildpackage: Add upstream-tree option to specify where the
+ upstream tarball should be created from. The default is to create the it
+ from the exact tag and fail otherwise.
+ Based on a patch by Ricardo Salveti de Araujo
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 26 Jul 2011 22:18:29 +0200
+
+git-buildpackage (0.5.26) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [d841899] git-pbuilder: Add git-pbuilder 1.22.
+ Thanks to a lot Russ Allbery (Closes: #623117)
+ * [43e11c7] Bump standards version. No changes necessary.
+ * [f5344c6] gbp-create-remote-repo: switch to git.debian.org.
+ Thanks to Ritesh Raj Sarraf (Closes: #632897)
+
+ [ Courtney Bane ]
+ * [3a68566] gbp-create-remote-repo: Improve url handling.
+ Support user name expansion and different ssh ports.
+ Closes: #630832
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 06 Jul 2011 23:09:21 +0200
+
+git-buildpackage (0.5.25) unstable; urgency=low
+
+ * [f07c1fc] Make qemubuilder really a boolean option.
+ Thanks to Gregor Herrmann <gregoa@debian.org> for tracking this down
+ (Closes: #627541)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 21 May 2011 22:14:30 +0200
+
+git-buildpackage (0.5.24) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [033f359] git-pbuilder: Distinguish 1.0 and 3.0 source format and set
+ exclude patterns for the 1.0 format. Otherwise the build fails since
+ dpkg-source tries to include .git.
+ * [09bf9f1] Add git-pbuilder 0.21. Thanks to Russ Albery.
+ (Closes: #601045, #601298)
+ * [7e022fb] Add support for qemubuilder (Closes: #601298)
+ * [e66c9e8] git-pbuilder: remove single quotes from '$BASE' to fix "cannot
+ canonicalize filename '/var/cache/pbuilder/base-sid.cow', does not exist"
+ failure
+
+ [ Jon Dowland ]
+ * [a645073] Spelling corrections (Closes: #627378)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 21 May 2011 13:51:29 +0200
+
+git-buildpackage (0.5.23) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [6710462] git-pbuilder: Drop diffignore and tarignore patterns
+ (Closes: #606412, #610662)
+ * [5b63e3f] tests: add doctests for gbp.deb.orig_file()
+ * [9f3030b] git-dch: improve formatting of commit subject by taking into
+ account idlength and the prefix added by git-dch itself.
+
+ [ Peter Eisentraut ]
+ * [0958897] Correct option name in git-buildpackage man page
+ * [3e1fd70] Fix various typos in man pages
+ * [e5dd444] gbp-clone: check out the debian branch after the clone is
+ complete.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 07 May 2011 14:38:58 +0200
+
+git-buildpackage (0.5.22) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [61513e6] git.rev_parse: unconditionally use --quiet to avoid the
+ confusing "fatal: Needed a single revision" error message
+ * [c7bb3f1] Add aliases for gz and bz2 compression types
+ * [dc39596] gbp: Handle whitespace in submodule names (Closes: #622103)
+ * [f5db59d] Disable submodule processing by default.
+
+ [ Loïc Minier ]
+ * [2d620e4] Ignore .noseids from nosetests
+ * [8189cc9] Add tests for orig autodetection
+ * [85c5d8e] Detect compression from orig tarball (Closes: #621701)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 11 Apr 2011 09:42:50 +0200
+
+git-buildpackage (0.5.21) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [d8142c7] git-import-dsc: don't silently drop the epoch in tags
+ (Closes: #620950)
+ * [ebc19c7] docs: Document Gbp-Pq-Topic: tag
+ * [82e5f4d] git-buildpackage: Call gbp.update_submodules before exporting a
+ tarball or creating a separate build tree. We invoke git-submodule with
+ --no-fetch to not break offline operation.
+ * [dff62f2] Make submodule support conditional
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 06 Apr 2011 20:35:37 +0200
+
+git-buildpackage (0.5.20) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [a618bdc] git-buildpackage: Make the desktop notification transient so
+ they timeout and don't clutter the notification area.
+ * [3b0f296] Add gbp-configure-unpatched-source example to ignore .pc and
+ tell dpkg-source to unpatch the source. See #591858.
+ * [258743a] gbp-pull: make sure git interprets arguments as revisions in
+ is_fast_forward so it prints a clearer error message on configuration
+ errors in .git/config.
+ * [40f4709] gbp-pq: Allow to specify subdirs for patches via the
+ "Gbp-Pq-Tag: <subdir>" directive in the patch header. This also gets rid
+ sed callouts. Add "apply" action to apply single patches.
+ * [fadcfcb] gbp: Add git.archive() and git.{has,get,update,add}_submodules()
+ and testcases.
+ Heavily based on work by Sean Finney and Chow Loong Jin
+ * [2353ab7] git-buildpackage: submodule support for dump_tree.
+ Heavily based on work by Sean Finney and Chow Loong Jin
+ * [97c32c7] git-buildpackage: submodule support for git_archive.
+ Heavily based on work by Sean Finney and Chow Loong Jin (Closes: #588752)
+ * [3b0ebe9] gbp: Don't fail on paths without extensions in get_compression()
+ and add doctests for that. (Closes: #618893)
+ * [1021f84] git-buildpackage: special case non-submodule tarfile generation.
+ Tarfile generation with submodules is slower since we need to
+ concatenate several tarfiles and compress afterwards. So special case
+ the common non submodule case and add a testcase to check the tarfiles
+ content.
+ * [80048e9,dcc2562,0827a33,93b4ab7,adc50a3,431075b] improve our testsuite
+
+ [ Sean Finney ]
+ * [ddf5ea3] gbp: Allow to pass cwd to git.__get_output()
+
+ [ Charles Plessy ]
+ * [c9f1e1e] docs: Added markup to the example for creating upstream branch.
+ * [0095808] docs: Easier to remember commands to create orphan upstream branch.
+ Closes: #619084
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 21 Mar 2011 21:09:32 +0100
+
+git-buildpackage (0.5.19) unstable; urgency=low
+
+ [ Jonathan Nieder ]
+ * [7f00d39] git-import-dsc: improve error message when upstream branch is
+ missing
+
+ [ Guido Günther ]
+ * [b9acbf9] logging: don't use color inside Emacs's comint shell
+ (Closes: #612384)
+ * [14fdbce] gbp-clone: reparse the configuration after cloning the
+ repository (Closes: #607936)
+ * [d21506e] git-import-dscs: really use git-import-dsc from the same
+ location as git-import-dscs
+ * [b92b88f] git-import-dsc: auto create upstream branch if it's missing.
+ This allows to mass import old history of packages that were native and
+ switched to non-native later.
+ * [0363f47] git-import-dsc: better explain missing upstream branch.
+ Thanks to Jonathan Nieder for the suggestion. (Closes: #610379)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 11 Feb 2011 18:20:36 +0100
+
+git-buildpackage (0.5.18) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [0b2b869] git-buildpackage: fix fallback to auto detection for unknown
+ compression types
+ * [b7136f3] gbp-pull: Adjust incorrect logging level
+ * [d8d2347] git-import-dscs: Ignore debsnap download errors to cope with
+ binNMUs. Based on a patch from Jonathan Nieder. (Closes: #610376)
+ * [e1661bd] git-buildpackage: print the "Looking for tarball..." message at
+ debug level. There's no need to print two messages in the default logging
+ level.
+ * [9054ae1] git.force_head: quiet git reset so we don't see pointless 'HEAD
+ is now at ...' messages during imports.
+ * [d36077c] Add --author-is-committer and --author-date-is-committer-date
+ commandline options (Closes: #610381)
+
+ [ Jonathan Nieder ]
+ * [f2f03b7] git-import-dscs: Fix --debsnap doc and option error handling
+ (Closes: #610368)
+ * [2984d27] git-import-dsc doc updates for new options --author-is-committer
+ and --author-date-is-committer-date
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 23 Jan 2011 14:52:25 +0100
+
+git-buildpackage (0.5.17) experimental; urgency=low
+
+ * [244c1d2] gbp-create-remote-repo: allow for repository names without
+ substitutions
+ * [ebc67f3] gbp-create-remote-repo: get repository name from
+ debian/changelog if possible
+ * [eb9c0e6] git-create-remote-repo: don't hardcode 'origin' as remote. This
+ makes it possible to create different remote repos from within the same
+ repository.
+ * [b383c96] Document --remote-name
+ * [8d5f78a] Use the latest commit instead of the earliest one
+ when guessing the compression type for pristine-tar.
+ Thanks to Andreas Rottmann for the detailed report (Closes: #609980)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 15 Jan 2011 13:46:36 +0100
+
+git-buildpackage (0.5.16) experimental; urgency=low
+
+ * [b9aae05] git-import-orig: Make --[no-]merge a proper option so it can be
+ configured via gbp.conf.
+ * [05d9ecb] gbp-create-remote-repo: allow to set up remote branch tracking
+ * [03f3d75] Make gbp-create-remote-repo a first class citizen. Add manpage,
+ move into path, add bash completion
+ * [5ca57cd] git-import-dsc: also set the commit date to the changelog date
+ when importing old history. This makes sure we get proper sorting with
+ e.g. gitk.
+ Thanks to Rob Browning for the hint and explanation
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 11 Jan 2011 07:48:02 +0100
+
+git-buildpackage (0.5.15) experimental; urgency=low
+
+ * [b770493] git-buildpackage: Add support for sending notifications via
+ libnotify after the build finished.
+ * [bfb233a] git-dch: don't add empty changelog entries with "Git-Dch: Ignore"
+ * [ab06072] Require python 2.6 since we use a context manager
+ * [79ed2e0] Use tristate option for --color=value this allows true and false
+ as alias for on and off.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 08 Jan 2011 19:09:41 +0100
+
+git-buildpackage (0.5.14) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [2a5df0a] docs/gbp-clone: better document --all
+ * [c3c76ac] docs/gbp-pull: Better document --force and --redo-pq
+ * [b23f05b] docs/git-import-dscs: document --debsnap to fetch from
+ snapshots.debian.org
+ * [8040433] docs/gbp-pq: fix typo in manpage.
+ Thanks to Emilio Pozuelo (Closes: #609166)
+ * [8cabb0e] Add gbp-{pull,clone,pq} examples to gbp.conf
+ * [eac621a] Add generated files to .gitignore
+ * [91b0aa8] pychecker warning cleanups (mostly unused variables and imports)
+ * [63d9624] Change dirs declaration to avoid false positives from pychecker.
+ See #608153
+ * [bd2ac3a] Enable pychecker warnings
+ * [67bdb1f] git-import-dscs: Remove duplicate log message
+ * [d523065] command_wrappers: Use logging functions
+ * [9411f76] bash completion: parse short options too
+ * [7374d00] bash completion: also complete on tags
+ * [25677d0] bash completion: add completion for gbp-{pq,pull,clone}
+ * [3f05eba] bash completion: add support for tristate options like --color
+ * [d9ed286] bash completion: avoid space after options taking an argument.
+ This makes branch and tag completion more useful.
+
+ [ Yaroslav Halchenko ]
+ * [2558ea7] git-import-dscs: Use git-import-dsc from the same location as
+ git-import-dscs. With hardcoded path it is impossible to use
+ git-import-dscs directly from the repository while developing both tools
+ * [31b4854] Spit out debug message in __git_inout
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 07 Jan 2011 09:58:39 +0100
+
+git-buildpackage (0.5.13) experimental; urgency=low
+
+ * [7e697a9] Update the repo after the first import so master matches the
+ debian branch. Thanks to Rob Browning for the testcase
+ * [83b9235] Initialize Logger.get_color{,off} so we can print errors before
+ the option parsing finished. (Closes: #608004)
+ * [b1f081a] Rewrite gbp-pq in python so we get consistent logging and
+ debugging options.
+ * [5a312db] Allow to drop numbers from patch names via --no-patch-numbers
+ so patch names remain constant when interim patches are dropped.
+ (Closes: #592129)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 27 Dec 2010 00:05:30 +0100
+
+git-buildpackage (0.5.12) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [3e0d663] Also allow for 'issue' as bugnumber prefix as used in Grml.
+ * [4323487] Fix off by one error by not counting. Thanks to Olivier Aubert
+ for pointing this out
+ * [5ace5eb] Better wrap thanks and closes (Closes: #529332)
+ * [cf98258] Move Debian tag with --ignore-same-version
+ so the import doesn't fail. (Closes: #606204)
+ * [29feba8] Change --ignore-same-version to --allow-same-version
+ and properly document it. This matches the defaults we had since ages.
+ * [3b858a9] Fix debug string formatting (Closes: #606771)
+ * [8a46f74] Favor ARCH environment variable over dpkg's architecture
+ based on a patch by Jacob Helwig. (Closes: #607318)
+ * [2c94f9d] Add option to open editor (Closes: #565553)
+
+ [ Rob Browning ]
+ * [98b034c] Remove unused commit_msg variable from git-dch.
+ * [5b7b0f1] Use "log -n1" instead of "show" to retrieve commit log
+ information.
+ * [e875704] Add "*.pyc" to .gitignore.
+ * [3640569] Add git-dch --customizations FILE to allow changelog entry
+ customization:
+ Add support for git-dch --customizations FILE. FILE must be Python code,
+ and for now, the only useful thing it can do is define a
+ format_changelog_entry() function which will override
+ kgbp.dch.format_changelog_entry().
+ Add a new customization option group for --customizations. Create a
+ gbp.dch module and move the changelog entry formatting functions there.
+ Create separate procedures to handle extracting metadata from the git log,
+ and use them in the default format_changelog_entry(). These functions are
+ also available for use by custom formatters: extract_git_dch_cmds(),
+ filter_ignore_rx_matches(), extract_bts_cmds(), extract_thanks_info(), etc.
+ Add a GitRepository.get_commit_info() method, and use it in git-dch
+ parse_commit(). (Closes: #536719)
+ * [d12a32f] Add --multimaint support to git-dch
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 20 Dec 2010 16:31:12 +0100
+
+git-buildpackage (0.5.11) unstable; urgency=low
+
+ [ Matthijs Kooijman ]
+ * [083834b] git-import-orig: Ask the user for the package name and version
+ if needed. If there is no debian/changelog to find the source package
+ name, and/or the upstream version was not given on the commandline, ask
+ the user for them. The values guessed from the original tarball name are
+ offered as defaults to the user. Previously, this guessed version was used
+ without confirmation. (Closes: #479172)
+ * [ee0e9ff] Let git-import-orig find the changelog when not on the
+ debian-branch. When no changelog file is available in the checkout, look
+ for the changelog in the repository.
+
+ [ Guido Günther ]
+ * [beaee6e] git-import-orig: Add --[no]-interactive to avoid prompts on
+ import.
+ * [223b97a] Fall back to source only changes file if the architecture one
+ doesn't exist. (Closes: #593598)
+ * [a6f2974] Makes uscan options explicit - thanks to Chris Butler.
+ (Closes: #596003)
+ * [ea9a656] Don't put generated gbp/gbp_version.py into the source package.
+ * [c385e76] Fix download location - thanks to Rob Browning.
+ * [7e79bcd] Add logging functions. This allows us to color and prefix the
+ output. Coloring can be disanbled via the --color option.
+ (Closes: #544332)
+
+ [ Yaroslav Halchenko ]
+ * [d62fadd] Document --git-overlay. (Closes: #598530)
+
+ [ Benoît Knecht ]
+ * [dfe9104] Expand environment variables and '~' in gbp.conf paths.
+ Options that expect a path in gbp.conf can now be given as
+ '~/path/to/dir' or '$HOME/path/to/dir' (or any other environment
+ variable for that matter). (Closes: #545692)
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 16 Nov 2010 13:27:41 +0100
+
+git-buildpackage (0.5.10) unstable; urgency=low
+
+ * [4b23f2d] Warn if not invoked by git-buildpackage git-pbuilder is
+ supposed to be invoked by git-buildpackage. Otherwise things like
+ export-dir aren't respected. (Closes: #596625)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 27 Sep 2010 09:46:52 +0200
+
+git-buildpackage (0.5.9) experimental; urgency=low
+
+ * [6a200ad] Resync with git-pbuilder 0.16 (Closes: #595055)
+ * [ed97b84] Add --git-arch option
+ * [257a3e1] Switch to debian branch before forcing the HEAD update
+ otherwise we might forward the current branch to another branch's
+ ref - thanks to Rolf Leggewie for the testcase
+ * [af97781] Avoid unnecessary branch switch when fixing the previous
+ issues. (LP: #637286)
+ * [70c5b22] Improve compression format detection by checking if a
+ version is already on the pristine-tar branch and use it's
+ compression type if found. (LP: #615212)
+ * [02b1a03] Don't fail on missing debian/rules. (LP: #638371)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 17 Sep 2010 10:42:32 +0200
+
+git-buildpackage (0.5.8) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [a09cce7] gbp-svn-tag: Simple helper to tag a version if using git-svn
+ * [3262621] gbp-pull: Properly handle local only changes as no update needed
+ * [b1bd417] git-import-dsc: Remove file and commit ambiguity
+ * [65bf135] git-dch: Fix exception name so we don't fail on repos without
+ tags.
+
+ [ Svend Sorensen ]
+ * [39b1a07] Fix --git-cleaner option name in git-buildpackage man page
+ (Closes: #594612)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 01 Sep 2010 10:56:34 +0200
+
+git-buildpackage (0.5.7) experimental; urgency=low
+
+ * [07c11d5] git-pbuilder: Only output dist if set.
+ * [3d97dfe] git-pbuilder: Check for cowbuilder. The cowbuilder package
+ depends on pbuilder so this is enough.
+ * [7f07ed6] Recommend cowbuilder
+ * [0a081d4] Update copyright file
+ * [af3dc21] Don't fail on gbp_version import errors. This makes it easier to
+ test from the source tree without starting a build first.
+ * [c0dad1a] git-import-dsc: Catch KeyboardInterrupt
+ * [cc557e7] git-dch: Check for greater not unequal when looking for a new
+ upstream version
+ * [74aa351] git-buildpackage: Use --pretty=format: instead of --format= to
+ support the git version in Lenny.
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 18 Aug 2010 18:18:35 +0200
+
+git-buildpackage (0.5.6) experimental; urgency=low
+
+ * [1d06128] git-pbuilder: When creating a new base image don't fail if it's
+ not there already.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 14 Aug 2010 01:26:42 +0200
+
+git-buildpackage (0.5.5) experimental; urgency=low
+
+ * [36edd3c] git-dch: Support --nmu, --bpo and --qa. (Closes: #561535)
+ * [7200035] git-dch: Pass dch_options to all calls of spawn_dch. This fixes
+ the problem of an empty [maintainer] at the end of the changelog section.
+ - thanks to Michael Prokop
+ * [8d33c92] git-dch: Guess changelog version number from upstream version
+ * [595d6d8] git-buildpackage: Add --git-pbuilder and --git-dist=<dist>
+ options
+ * [b193936] docs: Use git-pbuilder in the pbuilder chapter
+ * [61e0c3c] docs: Simplify example using --download
+ * [76b16df] docs: Build and install git-pbuilder manpage
+ * [67287d0] Add git-pbuilder (Closes: #583078) - thanks to Russ Allbery
+ * [bc52725] Deprecate our own pbuilder helpers
+ * [3c6bbd0] Better version number replacement (Closes: #534494)
+ * [55fdbc6] Check for legacy tags where necessary.
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 12 Aug 2010 23:35:30 +0200
+
+git-buildpackage (0.5.4) unstable; urgency=low
+
+ * [88afa61] git-dch: Pass --multimaint-merge on to dch (Closes: #586165)
+ * [e8b6b49] gbp-pq: Use the maintainer of the Debian package as fallback
+ patch author
+ * [f63c4ed] git-import-dsc: Don't add superfluous parents to imports on the
+ Debian branch. Only set a parent on the first import per upstream version.
+ * [af2a435] gbp-pull: Don't update already up to date branches
+ * [407b614] docs: Drop git_load_dirs reference we're not using it anymore.
+ * [dbc7fe3] docs: We don't only support .gz tarballs
+ * [34d6d84] Bump standards version
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 06 Aug 2010 17:07:47 -0400
+
+git-buildpackage (0.5.3) unstable; urgency=low
+
+ * [2808207] git-dch: Terminate the patch description with a dot in case of
+ multiline commits where the second line starts with a uppercase letter.
+ Heavily based on a patch by Jonathan Nieder.
+ * [47463da] Better document the default config file.
+ * [57b2abf] git-dch: Set author information from git on --release when using
+ --git-author - thanks to Boleslaw Tokarski for his feedback.
+ * [ad2416e] examples/zeitgeist-git: Adjust for zeitgeist 0.4.0.
+ * [8676193] examples/gbp-create-remote-repos: Simple helper to create remote
+ repos based on dom-new-git-repo from the OCaml maintainers as well as
+ aa-create-repo. (Closes: #540185)
+ * [3ed1a5c] git-import-dscs: Fetch all snapshots from snapshots.debian.org
+ using debsnap and import them (Closes: #591218)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 02 Aug 2010 22:10:18 +0200
+
+git-buildpackage (0.5.2) unstable; urgency=low
+
+ * Upload to unstable
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 20 Jul 2010 22:13:57 +0200
+
+git-buildpackage (0.5.1) experimental; urgency=low
+
+ [ Jonathan Nieder ]
+ * [a650ce3] Add prebuild hook Can be used to add an upstream changelog
+ generated from the git repository to the build dir. (Closes: #587652)
+
+ [ Guido Günther ]
+ * [9b8bc60] Bump standards version
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 20 Jul 2010 21:31:30 +0200
+
+git-buildpackage (0.5.0) experimental; urgency=low
+
+ [ Guido Günther ]
+ * [bd1ad03] Drop dependency on python-dev since it's not needed for arch all
+ packages. Depend on python instead.
+ * [077bdb0] git-buildpackage: Add compression=auto to guess compression type
+ of upstream tarball from pristine-tar branch. This is now the default.
+ (Closes: #566993)
+ * [3bcb1ef] Abort if upstream tarball contains git metadata
+ (Closes: #571717)
+ * [24410e9] docs: Mention patch-queue branches
+ * [877166c] docs: Don't document Git-Dch: twice. Thanks to Matthijs Kooijman
+ (Closes: #587456)
+ * [719f1ae] git-import-dsc: Use commit_dir to write directly commit the
+ content of the unpacked orig tarball and the patched Debian tree instead
+ of using replace_tree. Thanks to Roger Leigh for his nice explantion on
+ howto do this.
+ (Closes: #506211, #588061)
+ * [fc270b5] git-import-dsc: Drop --no-merge We don't invoke 'git merge'
+ anymore.
+ * [d35ee45] git-import-orig: Use commit_dir instead of replace_tree
+ (Closes: #526022, #569031)
+
+ [ Torsten Werner ]
+ * [d3c0901] fix typo in gbp-pq
+ (Closes: #587673)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 05 Jul 2010 21:58:14 +0200
+
+git-buildpackage (0.4.68) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [88c2d6d] gbp-pq export: Instead of failing switch branch if on a
+ patch-queue branch.
+ * [65ea70f] gbp-pq: Use run_git to catch git errors
+ * [488f16c] No need to assign repo
+ * [7f0b81f] Strip traling slashes from repo path (Closes: #584775)
+ * [a831329] Add 'ignore-branch' option This disables the 'current branch' ==
+ 'debian-branch' check.
+ * [5988e20] Add 'ignore-branch' option This disables the 'current branch' ==
+ 'debian-branch' check.
+
+ [ Christian Kastner ]
+ * [2173157] Add option --git-force-create to force tarball creation
+ (Closes: #519297)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 19 Jun 2010 16:07:05 +0200
+
+git-buildpackage (0.4.67) unstable; urgency=low
+
+ * [6427e2a] git-import-dsc: Add --download option. This allows to directly
+ import source packages either via git-import-dsc --download <pkg> or
+ git-import-dsc --download <url-to-dsc> The former uses "apt-get soure" the
+ later "dget". (Closes: #510036)
+ * [823f49f] gbp-pull: Document exit codes
+ * [9fb419f] git-import-orig: Document --uscan
+ * [3473689] git-buildpackage: Document --git-compression and
+ --git-compression-level
+ * [f31c82d] git-dch: Document 'Git-Dch: Ignore' and 'Git-Dch: Short'
+ * [65b9e0d] gbp-pq: Improve manpage and help output
+ * [33e56ca] git-import-dsc: Document --download
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 15 May 2010 14:01:03 +0200
+
+git-buildpackage (0.4.66) unstable; urgency=low
+
+ [ Paul Menzel ]
+ * [1072473] docs/manpages/git-dch.sgml: Consistency fixes and typo.
+ • Start sentences with capital letter and end them with a full stop.
+ • s/enty/entry/
+
+ [ David Paleino ]
+ * [49e31b1] Add --uscan command-line option Launch uscan and use the
+ tarball, if there's a new upstream version. (Closes: #577394)
+
+ [ Guido Günther ]
+ * [0826409] Bump standards version
+ * [6646d63] Depend on git | git-core (Closes: #577731)
+ * [f7a085a] Make --uscan more robust by not throwing exceptions onto the
+ console and catching download errors.
+ * [9521e6c] Fix typo - thanks to Paul Menzel
+ * [a2dd58d] gbp-pull: Requests for non fast-forward updates should exit with
+ a non-zero result (Closes: #579997)
+ * [b0100b6] Add simple Zeitgist data provider
+ * [5aefe29] Add 'Git-Dch: Short' tag to changelog parser This omits
+ the long description of the commit from the changelog.
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 04 May 2010 09:08:40 +0200
+
+git-buildpackage (0.4.65) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [07d54ef] gbp-add-patch: Strip path from patchname and handle missing
+ files gracefully.
+ * [a7fe7c4] Add basic bash completion for git-buildpackage (Closes: #567313)
+ - thanks to Siegfried-Angel Gevatter
+ * [aec185f] Add gbp-{clone,pull,pq} to description
+ * [24ac91c] bash-completion: Add git-dch, git-import-{orig,dsc} and tab
+ completion for branch names.
+ * [cbc0577] Add GitRepository.get_remotes() to easily query remote branches
+ * [4654425] gbp-clone: Add --all to track all remote branches
+
+ [ Matthijs Kooijman ]
+ * [2874c23] Make the commit message for upstream imports configurable.
+ This adds the import-msg commandline and configuration file option
+ to change the commit message. (Closes: #474457)
+
+ [ Jon Bernard ]
+ * [ad9b7f5] Prevent git-add-patch from being gzip'd
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 13 Feb 2010 12:37:41 +0100
+
+git-buildpackage (0.4.64) unstable; urgency=low
+
+ * [ab40623] Add gbp-add-patch to easily commit patches from debian/patches
+ * [34c1c43] Allow to skip imports of same version Based on a patch by
+ Christoph Göhre.
+ * [ea6311e] DscFile: Parse name of debian.tar.gz
+ * [237a547] Import version 3 source format (Closes: #552771)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 25 Jan 2010 22:08:26 +0100
+
+git-buildpackage (0.4.63) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [a0c7a91] docs: fix git-init call - thanks to Pietro Battiston
+ * [02ab603] docs: Drop superfluous upstream branch creation
+ * [e339c70] gbp: Make sure we drop the generated custom index file
+ (Closes: #561454)
+ * [d436612] git-dch: Add body regex filter (Closes: #544238)
+ * [cf45595] gbp-{pull,clone}: Don't hardcode pristine-tar branch
+ * [0eb4580] gbp: Allow to set compression type (Closes: #554520)
+ * [7ad35fb] tests: Add other gbp-* commands
+ * [f1f3d8e] tests: Add unpack test
+
+ [ Matthijs Kooijman ]
+ * [725b9d3] git-dch: Add support for a Git-Dch: Ignore metaheader.
+ (Closes: #561346)
+ * [e54b7bf] git-dch: There was a second use of parse_commit. Both uses of
+ parse_commit now support the None return value. The shortlog_to_dch
+ function is now superfluous and was removed.
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 26 Dec 2009 18:17:39 +0100
+
+git-buildpackage (0.4.62) unstable; urgency=low
+
+ * [dce995d] Improve error handling on broken dsc files. (Closes: #560689)
+ * [bcdd6b5] Improve error message when trying to import 3.0 source format
+ packages until we properly support them.
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 14 Dec 2009 09:15:49 +0100
+
+git-buildpackage (0.4.61) unstable; urgency=low
+
+ * [fc96f75] switch to 3.0 (native) source format
+ * [bf258de] use git;// instead of http://
+ * [043c75a] Make gbp-clone, gbp-pull and gbp-pq first class citizens
+ by moving them from examples/ to /usr/bin/
+ * [c5e4148] Add documentation for gbp-{pg,pull.clone}
+ * [06059df] Make the default build command 3.x source format safe Instead of
+ passing "-i\.git/ -I.git" to debuild use "-i -I". This makes sure we use
+ the default behaviour of dpkg-source. (Closes: #553079)
+ * [ecec4d5] gbp-pull: Don't fail if no tracking branch exists.
+ * [5539417] cleanup symlinks
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 26 Nov 2009 19:05:53 +0100
+
+git-buildpackage (0.4.60) unstable; urgency=low
+
+ [ Guido Günther ]
+ * gbp-pull:
+ * [9960f24] check for clean repo
+ * [9d190a5] add --redo-pq so a "gbp-pull --redo-pq" also refreshes the
+ patch-queue branch
+ * gbp-pq:
+ * [526fc0c] add option to drop patch-queue branch
+ * [3bf8288] Don't fail when series file is missing. Create an empty
+ patch-queue branch instead.
+ * git-import-orig:
+ * [eef5eca] Use "imported" instead of "merged". This way we don't claim
+ we merged something but we possibly didn't. (Closes: #545908)
+ * debian/rules:
+ * [5372050] Don't compress the example scripts
+ * docs:
+ * [4da84c0] document --git-retag
+
+ [ Charles Plessy ]
+ * [509c1c3] Stefano Zacchiroli’s workaround for creating empty
+ upstream branches. (See ‘http://bugs.debian.org/cgi-
+ bin/bugreport.cgi?bug=471560’)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 07 Nov 2009 15:30:49 +0100
+
+git-buildpackage (0.4.59) unstable; urgency=low
+
+ * new tools:
+ * [9ccbcd2] add examples/gbp-pull. Addresses another part of #540185.
+ * git-buildpackage:
+ * [38fcab2] add --git-retag (Closes: #521329)
+ * git-import-orig:
+ * [ee3209c] Drop superfluous argument. Fixes TypeError on merge failures.
+ (Closes: #549885)
+ * examples/gbp-clone:
+ * [1d8fb9d] gbp-clone: import Command too. Fixes "--verbose".
+ * gbp module:
+ * [db7cbab] add GitFetch
+ * [6f03267] add GitRepository.get_merge_branch()
+ * [b543f67] add GitRepository.is_fast_forward()
+ * [5bf8201] move FastImport into gbp/git.py
+ * docs:
+ * [189ff52] fix link to "homepage"
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 24 Oct 2009 21:31:08 +0200
+
+git-buildpackage (0.4.58) unstable; urgency=low
+
+ * [78bcf5e] catch config file parse errors (Closes: #545690)
+ * [8a9b813] docs: mention pristine-tar branch and pristine-tar
+ * [260afa1] docs: add missing --git- prefix
+ * [af4265e] git-dch: export GBP_BRANCH to postimport hook so we can pass the
+ current branch to git-dch
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 05 Oct 2009 18:16:10 +0200
+
+git-buildpackage (0.4.57) unstable; urgency=low
+
+ * new tools:
+ * [13316be] Add gbp-clone to examples. Using this to clone
+ repositories will automatically track the remote branches used by
+ gbp. Addresses parts of #540185.
+ * git-dch:
+ * [d42ed40] Trim commit display.
+ * [a335e04] Catch gbp.deb.NoChangelogError.
+ * [98a9b88] Add changelog section if current topmost version is already
+ tagged. This makes sure we add a new changelog section after a
+ release. This was broken due to 016318.
+ * git-import-orig:
+ * [dcbe091] Use option groups for nicer --help output
+ * [ee6c238] Add postimport hook for git-import-orig Allows to run git-
+ dch after import. Drop the dch invocation and warn when --no-dch gets
+ passed on the commandline. (Closes: #520355)
+ * [aecb9b4] NEWS: --no-dch is no more (Closes: #540750)
+ * git-import-dsc:
+ * [dcbe091] Use option groups for nicer --help output
+ * packaging:
+ * [4f82dfb] Honor DEB_BUILD_OPTIONS=nocheck
+ * [29cacc0] Run doctests with nose
+ * [1f8abec] Bump standards version
+ * [c84fd78] TODO list maintained in the wiki
+ * gbp module:
+ * [715d42a] Drop superfluous _utils from module names
+ * [46d6c1b] Make sanitize_version a private function
+ * [75eedb8] Add doctest for __sanitize_version() and build_tag()
+ * [4cd72bc] Add doctest for Command.__call__()
+ * [a4fd8eb] Add doctest for Command.call() and fix error in exception
+ handling revealed by the test.
+ * [edfe670] Add GitClone
+ * [2663f23] Make GitBranch remote branch aware
+ * [838b3f4] Make has_branch aware of remote branches
+ * tests:
+ * [6cbbddf] Add unit test for --help
+ * [52d9b5a] Fix testcase name
+ * [e4abaad] document changes so far
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 05 Sep 2009 16:06:11 +0200
+
+git-buildpackage (0.4.56) unstable; urgency=low
+
+ * [d6dae62] depend on devscripts >= 2.10.49 for --no-force-save-on-release
+ in dch. (Closes: #541420)
+ * [080b1eb] make parameters for GitRepository.commits() optional
+ * [016318e] git-dch: use the last commit in which debian/changelog was
+ touched as starting point if no snapshot header was found. This can still
+ be overriden by using --since. Based on a patch by Felipe Sateler.
+ (Closes: #511269)
+
+ -- Guido Günther <agx@sigxcpu.org> Sat, 15 Aug 2009 19:24:23 +0200
+
+git-buildpackage (0.4.55) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [651f723] fix release
+ * [e1bdfdb] update Homepage:
+ * [8ea4747] add gbp-pq to examples (Closes: #537212)
+ * [bedacee] check for correct overlay usage
+ * [849c801] bump standards version
+ * [f636022] add --no-force-save-on-release so dch saves the changlog
+ even when there are no changes so we don't end up with UNRELEASED
+ when passing -R.
+
+ [ أحمد المحمودي ]
+ * [14915d9] make tar_toplevel safer
+ * [b86ddcc] add --git-overlay option (Closes: #411206)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 30 Jul 2009 11:27:17 +0200
+
+git-buildpackage (0.4.54) unstable; urgency=low
+
+ [ Damyan Ivanov ]
+ * [b577f01] Unconfuse git-dch when commit message starts with "--"
+ (Closes: #531985)
+
+ [ Guido Günther ]
+ * [55a89e1] allow for uppercase characters in the version pattern and
+ in the package name if it's not a debian source package's name. Also
+ allow for ':' and '~' which are allowed accoring to Debian Policy.
+ Based on a patch by Felipe Sateler. (Closes: #531819)
+ * [882f971] check for snapshot mode when checking if we need to add a
+ new section. (Closes: #532583) - thanks to Ove Kaaven for sorting
+ this out
+ * [f80ee2e] fix one digit version numbers
+ * [5edecd9] + is a valid character in version numbers
+
+ [ Mehdi Dogguy ]
+ * [7de9f12] add filter-pristine-tar to filter upstream tarball before
+ passing it to pristine-tar (Closes: #520722)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 03 Jul 2009 16:16:41 +0200
+
+git-buildpackage (0.4.53) unstable; urgency=low
+
+ * [b772300] pass --pretty=medium to git show (Closes: #525969)
+ * [5ed3078] add commit argument to GitTag
+ * [129b3c4] add get_author_email that parses git config and
+ environment
+ * [9ed19e7] add --fast-import. This uses git-fast-import to import the
+ upstream tarball, speeds up imports of upstream tarballs by a
+ factor of two. This options is experimental and will become the
+ default once it got more testing. (Closes: #449075)
+
+ -- Guido Günther <agx@sigxcpu.org> Sun, 03 May 2009 18:35:52 +0200
+
+git-buildpackage (0.4.52) unstable; urgency=low
+
+ * [a2e42cd] move to section vcs
+ * [2892e7f] document postbuild hook
+ * [66f1027] promote pristine-tar to Recommends:
+ * [54b9da0] fix missing argument in error message
+ * [3e4b08e] restore default signal handlers before subprocess.call, python
+ changes them to SIG_IGN. (Closes: #525411)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 24 Apr 2009 17:35:44 +0200
+
+git-buildpackage (0.4.51) unstable; urgency=low
+
+ * [74a0954] git-import-dsc: don't ignore --debian-branch on import into non
+ empty archives
+ * [abe7de4] store version without epoch and simplify version parsing
+ * [8d2c6bd] add get_arch returns dpkg's notion of the architecture
+ * [e4db34b] add postbuild hook can be used to e.g. run lintian.
+ (Closes: #521358)
+ * [829db7c] pass GBP_BUILD_DIR to the build command this allows hooks
+ to figure out where to copy back the build result. Adjust the
+ cowbuilder example accordingly.
+ * [eaacadf] bump standards version
+ * [d113a3b] let the version detection patter match debian policy
+ (Closes: #522888)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 16 Apr 2009 11:47:27 +0200
+
+git-buildpackage (0.4.50) unstable; urgency=low
+
+ * [eceac16] be less strict on the spelling of boolean config file
+ options any capitalization of 'true' or 'false' as well as '0' and
+ '1' are allowed. (Closes: #517376)
+ * [94084b0] Skip all comments before looking for clean_msg (Closes: #518008)
+ * [e56c9f5] add git-builder cowbuilder example
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 05 Mar 2009 21:08:24 +0100
+
+git-buildpackage (0.4.49) unstable; urgency=low
+
+ * [4ddcd5c] gbp-posttag-push: fix for remote repos containing '.'
+ * [1e62d1a] Pass "-d" to debuild when run as cleaner. This way build-
+ deps don't have to be fulfilled in the source tree but only in the
+ build tree. (Closes: #516876)
+ * [660acbe] better document 'Closes:' tag (Closes: #516877)
+ * [e3b7610] simplify boolean option handling
+ * [1e1a1ca] add --no-pristine-tar (Closes: #517024)
+ * [600a16d] add --git-no-ignore-new
+ * [2fa0cc8] make --[no-]full a config file option
+ * [6ccd482] make --export a config file option
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 26 Feb 2009 13:48:50 +0100
+
+git-buildpackage (0.4.48) unstable; urgency=low
+
+ * [ae575e3] Add --export=WC to export the working copy into export-
+ dir. (Closes: #509138)
+ * [9f42e53] run git-cleaner with --git-ignore-new Makes running with
+ and without --git-ignore-new more consistent.
+ * [b8a35b4] docs: add --git-export=INDEX to the manual
+ * [47c8a38] docs: add --git-dont-purge to the manual
+ * [616bff3] docs: fix typo
+ * [15042e3] rename git-pbuilder to gbp-pbuilder
+ * [ffbb38a] add gbp-posttag-push example to examples/
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 23 Feb 2009 18:24:12 +0100
+
+git-buildpackage (0.4.47) unstable; urgency=low
+
+ * [b5a3215] export sha1, branch and tagname into the commit hooks
+ environment this makes it possible to push out that specific tag only
+ * [2be813e] document exported env vars and add example posttag hook
+ * [a498bdf] add GitRepository.rev_parse
+ * [28973fb] fix VCS-Browser URL
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 19 Feb 2009 20:37:49 +0100
+
+git-buildpackage (0.4.46) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [746d63b] fix typos (Closes: #510479) - thanks to Jonathan Wiltshire
+ * [07ceb2c] add Vcs-Browser
+
+ [ Dietmar Winkler ]
+ * [a92c398] documentation typo
+
+ [ Jonathan Wiltshire ]
+ * [a748f52] typo in docs/manpages/git-dch.sgml (Closes: 511096)
+
+ [ Guido Günther ]
+ * [5c8de27] handle dpkg-parsechangelog errors (Closes: #512765)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 23 Jan 2009 18:32:27 +0100
+
+git-buildpackage (0.4.45) unstable; urgency=low
+
+ [ Robie Basak ]
+ * [6f4af4a] Use name and email from git (Closes: #509867)
+
+ [ Guido Günther ]
+ * [b097286] document --no-sign-tags
+ * [6fe5985] add --no-git-author
+ * [7c2034c] add --no-sign-tags to git-import-{dsc,orig} too
+ (Closes: #508889)
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 01 Jan 2009 20:11:16 +0100
+
+git-buildpackage (0.4.44) unstable; urgency=low
+
+ * [c54e6dc] help option cleanup (Closes: #505787)
+ * [45541fe] don't require bugnumbers to start with '#' - use the regex
+ from Debian policy instead
+ * [77a875c] move common help messages into config.py
+ * [9223997] allow to import into empty repositories this makes git-
+ import-dsc's behaviour finally consistent with git-import-orig
+ (Closes: #500458, #504075)
+ * [aacd04a] require python 2.5
+ * [54b9a3e] better descriptions
+ * [e806326] add --no-sign-tags (Closes: #508889)
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 29 Dec 2008 00:29:43 +0100
+
+git-buildpackage (0.4.43) unstable; urgency=low
+
+ * [8b8c137] gather all invocations of dch in one function - fixes dch
+ failures due to missing quotes introduced by [7f24b98]
+ * [a328fa2] fix off by one introduced by not counting first_commit
+ when creating a new header in [7f24b98]
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 14 Nov 2008 14:12:04 +0100
+
+git-buildpackage (0.4.42) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [b5e8691] debian/control: fix maintainer
+ * [ee9e51d] doc: mention cl2vcs
+ * [a0c03e1] doc: fix typo
+ * [0314acc] git-buildpacakge: use option groups
+ * [2d44dad] git-dch: merge sha and snapshot parameter
+
+ [ Felipe Sateler ]
+ * [7f24b98] git-dch: Don't include first UNRELEASED line - There's not much
+ point in keeping this line when either doing a release or when having a
+ snapshot header (nor does it help much if one invokes git-dch without
+ options). (Closes: #505400)
+
+ [ Guido Günther ]
+ * [a6f5472] git-import-orig: Don't fail on symlink creation (Closes: #502565)
+ * [b7f8efd] git-dch: add missing call to escape_commit
+
+ -- Guido Günther <agx@sigxcpu.org> Thu, 13 Nov 2008 17:08:52 +0100
+
+git-buildpackage (0.4.41) unstable; urgency=low
+
+ * [8b15994] allow for another config file $REPO/debian/gbp.conf
+ (Closes: #502253)
+ * [1ef4e04] add repo.set_branch() to switch branches
+ * [d4d4580] use repo.set_branch()
+ * [245d5f1] add GbpNothingImported exception
+ * [c410c0b] switch back to original branch on empty imports
+ (Closes: #504029, #504072)
+
+ -- Guido Günther <agx@sigxcpu.org> Fri, 31 Oct 2008 10:28:47 +0100
+
+git-buildpackage (0.4.40) unstable; urgency=low
+
+ [ Guido Guenther ]
+ * [d87e2ab] fix typo (Closes: #500167) - thanks to Aleksej R. Serdyukov
+ * [632c9b4] gbp.git_utils.GitRepository: make indentation more consistent
+ * [b1a2847] adjust is_clean for git 1.6 (Closes: #500238)
+ * [3332982] gbp.deb_utils.symlink_orig: remove superflous printout
+ * [fb6187f] silence parse_dsc and move sanity checks into DscFile
+
+ [ Kurt B. Kaiser ]
+ * [bbd9946] Force tarball symlink on second export to build-area
+ (Closes: #500498)
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 02 Oct 2008 12:26:56 +0200
+
+git-buildpackage (0.4.39) unstable; urgency=low
+
+ [ Kurt B. Kaiser ]
+ * [ac30b9b] Don't buffer stdout; let git-bp messages print in-line
+ (Closes: #479846)
+
+ [ Guido Guenther ]
+ * [4da9954] git-import-dscs: likewise
+ * [2162ead] git-dch: demangle adding commits and sections
+ * [af70d4d] git-dch: improve wording
+ * [bbf5020] git-dch: more option grouping
+ * [3931f70] git-import-orig: print version number on failed imports
+ * [745fbf6] git-import-dsc: merge by default when importing into an existing archive
+ (Closes: #475571)
+ * [631c347] git-import-dsc.sgml: document --no-merge
+ * [2f46ba4] gbp: Don't honor .gitignore during replace_source_tree
+ (Closes: #467504)
+ * [624bdc9] gbp: move replace_source_tree into GitRepository
+ * [519901e] gbp: add find_tag(branch)
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 22 Sep 2008 20:55:07 +0200
+
+git-buildpackage (0.4.38) unstable; urgency=low
+
+ * [1623560] "git-commond" vs. "git command" cleanup (Closes: #497335)
+ * [0a34c3b] add --new-version
+ * [2a17687] document --new-version
+ * [fc67ac2] document short options and snapshot mode
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 08 Sep 2008 18:48:58 +0200
+
+git-buildpackage (0.4.37) unstable; urgency=low
+
+ * [d1ea39d] allow setting the bug-closing meta tag to look for this
+ way we can generate bug-closing entries for different BTSs such as
+ Debian or Launchpad.
+ * [0376265] document meta-closes
+ * [4da3586] fix commit order of GitRepository.commits() - we request
+ since..until but got until..since. This makes the order of changelog
+ entries generated by git-dch the same order as if git-dch would be
+ called individually for each commit.
+
+ -- Guido Guenther <agx@sigxcpu.org> Sat, 30 Aug 2008 14:20:11 +0200
+
+git-buildpackage (0.4.36) unstable; urgency=low
+
+ * [64808df] Warn if we can't parse the changelog - without a changelog we
+ can't parse the package name. Without that we might end up with wrong
+ names on the pristine tar branch and with missing symlinks in tarball-dir.
+ Since this might be intentional we issue a warning only.
+ * [32b2e89] detect flat tar archives in git-import-dsc too
+ * [277581c] don't fail import on non rfc822 adressess in the dsc file
+ (Closes: #494753)
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 12 Aug 2008 19:31:26 +0200
+
+git-buildpackage (0.4.35) unstable; urgency=low
+
+ * [e012d8b] add epoch parsing to DscFile (Closes: #493214)
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 01 Aug 2008 19:32:01 +0200
+
+git-buildpackage (0.4.34) unstable; urgency=low
+
+ * [4ac0aa8] git-buildpackage: always symlink orig.tar.gz from tarball dir
+ (Closes: 490706)
+ * [fb94fea] git-buildpackage: print default export-dir on --help
+ * [ffeb40e] git-dch: escape backticks (`) (Closes: 491104)
+ * [4e398cc] git-dch: --auto and --since are incompatible
+ * [3537f24] git-dch: use option groups
+ * [18d8405] git-dch: split git-log options into a list (Closes: #479267)
+ * [044083f] docs: readd list import line (Closes: #488120)
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 22 Jul 2008 00:29:49 -0230
+
+git-buildpackage (0.4.33) unstable; urgency=low
+
+ [ Adeodato Simó ]
+ * [35c92a4] git-import-orig: merge upstream by tag name instead of by
+ branch name.
+ * [f1ca044] command_wrappers.py: add a GitMerge wrapper class.
+
+ [ Guido Guenther ]
+ * [9bbd5d2] make symlink_orig's error message more helpful
+ * [26c0310] make no-dch configurable via gbp.conf
+ * [e6ce2df] bump standards version
+ * [45b060e] mention git-import-dscs
+ * [721068a] add "meta" to sample config file
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 26 Jun 2008 16:47:55 +0200
+
+git-buildpackage (0.4.32) unstable; urgency=low
+
+ * [217263e] Don't set the version number twice - makes sure git-import-dsc
+ doesn't use the Version: line from the message instead of the signature on
+ old PGP signatures (Closes: #486397)
+ * [063fab5] escape $s in commit messages so variable names like
+ $remote_fs don't get dropped silently. (Closes: #486447)
+ * [6008c37] don't try to cleanup in case of tag_only (Closes: #486398)
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 16 Jun 2008 17:54:09 +0200
+
+git-buildpackage (0.4.31) unstable; urgency=low
+
+ * [bf8738f] add --git-tag-only (Closes: #485114)
+ * [b06bcf0] document git-import-dscs
+ * [c1deeb2] add epoch to dch call (Closes: #483718)
+ * [6640ac7] Fix typo (Closes: #484243)
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 12 Jun 2008 16:53:45 +0200
+
+git-buildpackage (0.4.30) unstable; urgency=low
+
+ * [cc9c42a] Try harder to find/build the upstream tarball (Closes:
+ #482786)
+ * [e6dd31e] Help pristine-tar to find the branch name (Closes:
+ #481806)
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 29 May 2008 13:24:51 +0200
+
+git-buildpackage (0.4.29) unstable; urgency=low
+
+ * [ce152d4] git-dch: include the commit id in the changelog entry
+ * [ad6ff99] git-dch: print default value of --meta
+ * [2c1c265] git-dch: avoid extra space before "(Closes: )"
+ * [6ddf168] add git-import-dscs (Closes: #471580)
+ * [2cfbf05] move dsc handling into deb_utils
+
+ -- Guido Guenther <agx@sigxcpu.org> Sat, 17 May 2008 22:01:49 +0200
+
+git-buildpackage (0.4.28) unstable; urgency=low
+
+ * git-buildpackage: add --git-dont-purge to leave the exported build dir
+ intact (Closes: #479848) - thanks to Kurt B. Kaiser for the patch
+ * git-dch: make --meta a config file option
+ * git-import-orig: fix symlink creation
+ * docs: missing EOF (Closes: #479726) - thanks to Sedat Dilek
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 12 May 2008 18:03:47 +0200
+
+git-buildpackage (0.4.27) unstable; urgency=low
+
+ * make author parsing more robust (Closes: #479263)
+ * fix --git-export (Closes: #479147)
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 04 May 2008 16:43:43 +0200
+
+git-buildpackage (0.4.26) unstable; urgency=low
+
+ * git-export=INDEX exports the index into git-export-dir
+ (Closes: #471568)
+ * make ignore-new a config file option (Closes: #466900)
+ * build-depend on python-dateutil (Closes: #477959)
+ * git-dch:
+ * use --no-auto-nmu
+ * error handling fixes
+ * depend on devscripts that has dch --no-auto-nmu
+ * gbp/config.py: make boolean options parsing more robust
+ * add a link to the online version of the manual
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 28 Apr 2008 16:36:44 +0200
+
+git-buildpackage (0.4.25) unstable; urgency=low
+
+ * create a symlink archive -> <package>_<version>.orig.tar.gz this makes
+ sure pristine_tar uses the correct filename for the orig.tar.gz. (Closes:
+ #475316)
+ * fix unpack_orig error reporting by adding the missing imports
+ * fix "pristine-tar missing" error message
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 18 Apr 2008 11:33:46 +0200
+
+git-buildpackage (0.4.24) unstable; urgency=low
+
+ [ Adeodato Simó ]
+ * Make commits from git-import-dsc get author and date from
+ debian/changelog.
+
+ [ Guido Guenther ]
+ * add rfc822_date_to_git() this function converts a date in RFC822
+ format to a string 'seconds_since_epoch tz' that can be used for eg.
+ GIT_AUTHOR_DATE.
+ * better handle broken dsc files
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 14 Apr 2008 14:39:37 +0200
+
+git-buildpackage (0.4.23) unstable; urgency=low
+
+ [ Frank S. Thomas ]
+ * docs/manpages/*.sgml: Use the citerefentry for references to other
+ manpages.
+ * Do not list the --upstream-version option twice in the synopsis
+ section of git-import-orig's manpage. (Closes: #472496)
+
+ [ Guido Guenther ]
+ * don't pass nonexisting branches to pristine-tar (Closes: #475554)
+ * doc cleanups (Closes: 473610):
+ * mention the html manual in the manpages
+ * reference pristine-tar
+ * filter uses glob
+ * move conffiles section into separate sgml file
+ * fix doc-base section
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 11 Apr 2008 19:18:08 +0200
+
+git-buildpackage (0.4.22) unstable; urgency=low
+
+ * git-dch: don't skip meta tags without --force
+ * gbp: git-core 1.5.3.4 doesn't support -q so use --quiet instead
+ * fix typos in documentation (Closes: #471582) - thanks to Michael
+ Biebl
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 19 Mar 2008 18:03:20 +0100
+
+git-buildpackage (0.4.21) unstable; urgency=low
+
+ * allow meta tags in the commit logs
+ * add --full and --meta options --full (Closes: #468118)
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 14 Mar 2008 20:42:53 +0100
+
+git-buildpackage (0.4.20) unstable; urgency=low
+
+ * add missing pngs (Closes: #469403)
+ * be more verbose on pbuilder (Closes: #469138)
+ * explain howto push tags and mention linda and lintian (Closes: #469436)
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 06 Mar 2008 08:48:02 +0100
+
+git-buildpackage (0.4.19) unstable; urgency=low
+
+ * don't fail of the pristine-tar branch doesn't exist
+ (Closes: #468675)
+
+ -- Guido Guenther <agx@sigxcpu.org> Sat, 01 Mar 2008 14:22:20 +0100
+
+git-buildpackage (0.4.18) unstable; urgency=low
+
+ * update documentation about git-import-dsc and --filter
+ * gbp/command_wrappers.py: make error messages more consistent
+ * git-import-dsc: don't throw a python exception on tag failures or non
+ existant branches
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 29 Feb 2008 18:05:40 +0100
+
+git-buildpackage (0.4.17) unstable; urgency=low
+
+ * make dsc import repeatable (Closes: #468120, #432082)
+ * drop now (due to git-apply) unneeded code
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 29 Feb 2008 16:09:47 +0100
+
+git-buildpackage (0.4.16) unstable; urgency=low
+
+ [ Guido Guenther ]
+ * speed up git-import-dsc by using git-apply (addresses parts of
+ #449075)
+ * update docs and gbp.conf with --filter and filter = [ ... ]
+
+ [ Harald Braumann ]
+ * allow multiple file filters for git-import-{orig,dsc} (Closes: #464653)
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 24 Feb 2008 14:56:47 +0100
+
+git-buildpackage (0.4.15) unstable; urgency=low
+
+ * add (for now experimental) pristine-tar support based on a patch from
+ Julian Andres Klode (Closes: #463580)
+ * document pristine-tar options
+ * suggest pristine-tar
+ * don't use the deprecated git-* command versions
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 21 Feb 2008 16:25:33 +0100
+
+git-buildpackage (0.4.14) unstable; urgency=low
+
+ * correct several typos in the manual (Closes: #464582, #464583,
+ #464617)
+ * better dpkg-parsechangelog error reporting (Closes: #460195)
+ * document default tag formats (Closes: #464100)
+ * git-import-orig: detect flat tar archives (Closes: #463822)
+ * git-import-orig: add --no-dch options
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 08 Feb 2008 17:38:29 +0100
+
+git-buildpackage (0.4.13) unstable; urgency=low
+
+ * git-import-orig: don't fail when importing into empty git archives
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 28 Dec 2007 23:01:29 +0100
+
+git-buildpackage (0.4.12) unstable; urgency=low
+
+ * remove unused debian/dirs
+ * bump standards version
+ * add a homepage field
+ * add doc-base file (Closes: #457495)
+ * git-import-orig: fix doc url (Closes: #456535)
+ * use export-dir instead of build-area in gbp.conf because git-
+ buildpackage has no --git-build-area option - thanks to Frank S. Thomas
+ for the patch.
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 23 Dec 2007 20:35:45 +0100
+
+git-buildpackage (0.4.11) unstable; urgency=low
+
+ * --export-dir and --export are actually --git-export-dir and --git-
+ export (Closes: #456384) - thanks to Frank S. Thomas for the patch.
+ * don't start a new changelog section if we found a snapshot header
+ even when distribution != UNRELEASED
+
+ -- Guido Guenther <agx@sigxcpu.org> Sat, 15 Dec 2007 17:16:34 +0100
+
+git-buildpackage (0.4.10) unstable; urgency=low
+
+ * git-ls-files: separate filenames by '\0', based on a patch from Uwe
+ Kleine-König <Uwe.Kleine-Koenig@digi.com> (Closes: #454470)
+ * git-import-dsc: return non null on failure
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 07 Dec 2007 23:19:39 +0100
+
+git-buildpackage (0.4.9) unstable; urgency=low
+
+ * fix "gpb gets confused by color enabled on branches..." - thanks to
+ Niv Sardi for the patch (Closes: #452921)
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 26 Nov 2007 17:22:48 +0100
+
+git-buildpackage (0.4.8) unstable; urgency=low
+
+ * git-buildpacakge: make sure we don't switch directories during build
+ (Closes: #451550)
+ * debian/control: use the now official Vcs-Git
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 19 Nov 2007 18:50:08 +0100
+
+git-buildpackage (0.4.7) unstable; urgency=low
+
+ * add tarball-dir option losely based on patch from Sjoerd Simons
+ (Closes: #448357)
+ * don't print a commit summary when importing into empty repos
+ (addresses parts of #449075)
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 05 Nov 2007 20:09:15 +0100
+
+git-buildpackage (0.4.6) unstable; urgency=low
+
+ * fix url (Closes: #448350)
+ * call gzip with "-n" (Closes: #449094) - thanks to Romain Francoise
+ * add .gitignore
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 04 Nov 2007 16:20:36 +0100
+
+git-buildpackage (0.4.5) unstable; urgency=low
+
+ * git-import-orig: fix missing s/upstream/upstream_branch/ rename
+ (Closes: #447920) - Thanks to Arnaud Cornet
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 24 Oct 2007 22:56:24 +0200
+
+git-buildpackage (0.4.4) unstable; urgency=low
+
+ * git-buildpackage: use upstream-branch in case the tag doesn't exist
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 22 Oct 2007 17:00:12 +0200
+
+git-buildpackage (0.4.3) experimental; urgency=low
+
+ * git-dch: properly quote "" (Closes: #447211)
+ * git-dch: use Command() instead of implementing it again
+ * documentation updates
+ * don't split up the manual that much (local.dsl taken from
+ darcs-buildpackage)
+ * add version number to docs and a tools (--version)
+ * pylint and other consistency updates
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 19 Oct 2007 10:12:40 +0200
+
+git-buildpackage (0.4.2) experimental; urgency=low
+
+ * git-dch:
+ * make --git-log a config file option
+ * git-dch: fix help message for --debian-branch
+ * doc: snapshot-number can be used in gbp.conf too
+ * git-buildpackage:
+ * add --git-export-dir=dir/, --git-export=treeish (Closes: #446042)
+ * allow to use any treeish object for --git-upstream-branch
+ * add missing examples to gbp.conf
+ * parse .gbp.conf in the repository directory (Closes: #426009)
+ * minor doc updates and clarifications
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 17 Oct 2007 00:00:13 +0200
+
+git-buildpackage (0.4.1) unstable; urgency=low
+
+ * make sure the changelog section's trailer points to the person
+ invoking git-dch
+ * depend on devscripts with a working 'dch ""'
+ * git-dch: eval() the snapshot number calculation
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 10 Oct 2007 18:15:54 +0200
+
+git-buildpackage (0.4.0) experimental; urgency=low
+
+ * add support for automatic snapshot releases as suggested by Ottavio
+ Salvador
+ * make git-dch actually useful:
+ * --auto: guess last changelogged commit from the snapshot header
+ * add short options for --snapshot and --release
+ * don't fail if the commit msg starts with '--'
+ * add a check for the debian branch instead of always using it as the
+ tip
+ * handle versions containing epochs
+ * add --git-log to pass options along to git-log
+ * allow to specify paths to look at: git-dch path1 path2 - useful if
+ upstream uses git
+ * add some basic documentation for git-dch
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 07 Oct 2007 15:32:40 +0200
+
+git-buildpackage (0.3.6) unstable; urgency=low
+
+ * create upstream branch when importing into an empty archive
+ (Closes: #443305)
+ * detect upstream version from common tarball formats
+ (Closes: #443306)
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 03 Oct 2007 18:01:15 +0200
+
+git-buildpackage (0.3.5) unstable; urgency=low
+
+ * add a minimalistic git-dch that creates changelog entries from git commit
+ messages
+ * s/reopsitory/repository/ - thanks to Loïc Minier (Closes: #444702)
+ * update TODO
+ * short paragraph on hacking on arbitrary debian packages
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 03 Oct 2007 14:10:05 +0200
+
+git-buildpackage (0.3.4) unstable; urgency=low
+
+ * introduce --git-no-create-orig to skip building of any orig.tar.gz - this
+ is especially usefull if you're working on an NMU that has a X-0.Y version
+ number although it's a Debian native package
+ * fix the error path in case the tgz can't be unpacked
+ * git-pbuilder: add filter for git meta data and allow to pass options to
+ pbuilder vi $PBUILDER_OPTS (Closes: #439535)
+
+ -- Guido Guenther <agx@sigxcpu.org> Sat, 08 Sep 2007 20:40:36 +0200
+
+git-buildpackage (0.3.3) unstable; urgency=low
+
+ * git-buildpackage: don't hardcode -i\.git -I.git as build arguments
+ (Closes: #438669)
+ * git-import-dsc: support --debian-branch (Closes: #432084)
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 20 Aug 2007 19:22:24 +0200
+
+git-buildpackage (0.3.2) unstable; urgency=low
+
+ * git-import-orig: allow to import into an empy git repository
+ * docs: we don't use git_load_dirs internally anymore
+ * docs: howto start a package from scratch
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 27 Jun 2007 04:06:33 +0300
+
+git-buildpackage (0.3.1) unstable; urgency=low
+
+ * don't fail imports on large archives
+ * print a sensible error message, when a git repository isn't a debian
+ source package
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 11 Jun 2007 18:06:15 +0200
+
+git-buildpackage (0.3.0) unstable; urgency=low
+
+ * don't use git_load_dirs for imports, this addresses:
+ * upstream directory removal (#423363)
+ * empty dirs and files not under version control (#409606)
+ * filtering out of files (Closes: #425986)
+ * importing of an empty .diff.gz (like libx86 0.99-1.2)
+ * doc: remove stray ';' from special.sgml. Thanks to Loïc Minier
+ * doc: gbp.conf is per working copy, not per repository
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 28 May 2007 03:19:32 +0200
+
+git-buildpackage (0.2.31) unstable; urgency=low
+
+ * git-import-orig: rename --upstreamversion to --upstream-version, to match
+ the documentation and the other --upstream-* options
+ * add several missing options to manpages and documentation
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 24 May 2007 09:38:35 +0200
+
+git-buildpackage (0.2.30) unstable; urgency=low
+
+ * fix changelog upstream version parsing (Closes: #425615)
+ * git-import-orig: allow to import from an unpacked source tree
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 24 May 2007 06:19:06 +0200
+
+git-buildpackage (0.2.29) unstable; urgency=low
+
+ * git-import-orig: more specific error message, when the merge merge fails
+ (Closes: #424036)
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 15 May 2007 23:55:07 +0200
+
+git-buildpackage (0.2.28) unstable; urgency=low
+
+ * git-import-orig: support import of tar.bz2 (Closes: #423254)
+ * type fixes by Aurélien GÉRÔME. Thanks! (Closes: #422171)
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 13 May 2007 14:13:00 +0200
+
+git-buildpackage (0.2.27) unstable; urgency=low
+
+ * git_load_dirs now supports --summary, use this for nicer import messages
+ * bump versioned dependency on git-load-dirs therefore
+ * improve tagging messages
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 12 Apr 2007 18:25:40 +0200
+
+git-buildpackage (0.2.26) unstable; urgency=low
+
+ * use GbpError everywhere
+ * move commands into a submodule
+ * git-import-orig: pass --verbose on to git_load_dirs
+ * use default python version in setup.py
+ * depend on a git that has the "removed files left in working copy after
+ merge" bug (#410325)
+ * adjust to new output of git-status in git 1.5
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 27 Feb 2007 19:11:06 +0100
+
+git-buildpackage (0.2.25) unstable; urgency=low
+
+ * posttag support for git-buildpackage: use --git-posttag to run a command
+ after a successfull build and tag (e.g. --git-posttag='git-push --tags
+ git.alioth.org')
+ * repositories that weren't created by git-import-dsc and never had the
+ upstream sources as a single commit (let alone an upstream branch) can be
+ made git-import-orig compatible by using git's grafts - mention this in
+ the manual. This (Closes: #403988) since with this every git repo can use
+ git-import-orig (with some preparation).
+ * Allow to specify the tag format. The format of generated tags can now be
+ specified via the debian-tag and upsteam-tag options. The default now puts
+ debian tags and upstream tags into different namespaces (Closes: #408990).
+ * add example for pbuilder
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 2 Feb 2007 16:26:52 +0100
+
+git-buildpackage (0.2.24) unstable; urgency=low
+
+ * add XS-Vcs-Git
+ * fix error message in case the upstream branch doesn't exist
+
+ -- Guido Guenther <agx@sigxcpu.org> Sat, 13 Jan 2007 22:05:23 +0100
+
+git-buildpackage (0.2.23) unstable; urgency=low
+
+ * depend on gtk-doc-tools, sgml2x and jade (Closes: #404673)
+ * use docbook-utils insted of docbook-to-man
+ * docbook updates
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 29 Dec 2006 18:18:50 +0100
+
+git-buildpackage (0.2.22) unstable; urgency=low
+
+ * git-buildpackage: check if upstream branch exists
+ * git-import-dsc: improve error handling
+ * add GitRepository class
+ * make pylint a bit happier
+ * update manpages
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 26 Dec 2006 00:27:44 +0100
+
+git-buildpackage (0.2.21) unstable; urgency=low
+
+ * While dpkg-buildpackage's -i argument takes a regexp -I doesn't - fix
+ inclusion of git metadata in debian native packages
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 22 Dec 2006 17:59:49 +0100
+
+git-buildpackage (0.2.20) unstable; urgency=low
+
+ * git-import-orig: improve error message when the upstream branch cannot be
+ found. We cannot create the branch automatically since on repositories
+ not created by git-import-dsc it's not clear where to branch from.
+ (Closes: #403990)
+ * remove pointless patch arguments from most of the git helper functions
+ * add some more documentation
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 22 Dec 2006 17:30:11 +0100
+
+git-buildpackage (0.2.19) unstable; urgency=low
+
+ * git-buildpackage: add an option (--git-cleaner) that allows to specify
+ a different clean command (Closes: #403987)
+ * depend on a git-core that has git-archive
+ * s/keyid/GPG keyid/
+ * minor fixes all over the place
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 21 Dec 2006 15:18:40 +0100
+
+git-buildpackage (0.2.18) experimental; urgency=low
+
+ * git-buildpackage: detect the branch we're on and bail out if it's not the
+ debian branch
+ * doc updates
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 10 Dec 2006 14:57:35 +0100
+
+git-buildpackage (0.2.17) experimental; urgency=low
+
+ * fix wrong default debian branch
+ * git-buildpackage: fix typo in git-ignore-new
+
+ -- Guido Guenther <agx@sigxcpu.org> Sat, 9 Dec 2006 13:22:33 +0100
+
+git-buildpackage (0.2.16) experimental; urgency=low
+
+ * git-buildpackage: properly pass builder args
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 8 Dec 2006 17:13:32 +0100
+
+git-buildpackage (0.2.15) experimental; urgency=low
+
+ * git-buildpackage now supports signed tags via the "sign-tag" and "keyid"
+ options. This allows us to have a nice "trustable" history. See:
+ http://www.kernel.org/pub/software/scm/git/docs/
+ on how this ensured.
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 6 Dec 2006 22:04:41 +0100
+
+git-buildpackage (0.2.14) experimental; urgency=low
+
+ * config file parsing to set default branches and build commands
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 5 Dec 2006 19:36:41 +0100
+
+git-buildpackage (0.2.13) experimental; urgency=low
+
+ * fix typos in 'git-buildpackage -v'
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 3 Dec 2006 22:27:11 +0100
+
+git-buildpackage (0.2.12) experimental; urgency=low
+
+ * upload to experimental (Closes: #389710)
+ * bump dependency on git-load-dirs again
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 30 Nov 2006 09:47:33 +0100
+
+git-buildpackage (0.2.11) git-buildpackage; urgency=low
+
+ * git-buildpackage: use pipes module instead of os.system
+ * git-import-orig: fix printout of branch name
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 28 Nov 2006 16:33:54 +0100
+
+git-buildpackage (0.2.10) git-buildpackage; urgency=low
+
+ * build an orig.tar.gz if nones there
+
+ -- Guido Guenther <agx@sigxcpu.org> Sun, 19 Nov 2006 14:17:40 +0100
+
+git-buildpackage (0.2.9) git-buildpackage; urgency=low
+
+ * depend on a fixed git-load-dirs that contains the git_load_dirs executable
+ * git-import-dsc: fix importing debian native packages
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 14 Nov 2006 12:39:26 +0100
+
+git-buildpackage (0.2.8) git-buildpackage; urgency=low
+
+ * sanitze_version: strip of epochs
+ * don't use a regexp for version parsing on import
+ * minor cosmetic cleanups all over the place
+ * README: no need to mention git-load-dirs anymore, it's in unstable now.
+ Thanks John.
+ * git-import-orig: implement "--no-merge"
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 8 Nov 2006 10:36:55 +0100
+
+git-buildpackage (0.2.7) git-buildpackage; urgency=low
+
+ * more robust regex matching
+ * sanitize all version numbers before running git-tag
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 6 Oct 2006 18:40:12 +0200
+
+git-buildpackage (0.2.6) git-buildpackage; urgency=low
+
+ * run pychecker during build
+ * also filter out git metadata when building tarballs, not only when
+ building diffs (affects Debian native packages)
+ * git-import-dsc: allow for '+' in Debian as well as upstream version
+ numbers
+ * check if we're at the top level of a git repository before starting the
+ build
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 5 Oct 2006 19:46:23 +0200
+
+git-buildpackage (0.2.5) git-buildpackage; urgency=low
+
+ * fix syntax error in tag replacement
+ * minor manpage reformating
+ * cleanup generated manpage.* files
+
+ -- Guido Guenther <agx@sigxcpu.org> Thu, 5 Oct 2006 11:21:01 +0200
+
+git-buildpackage (0.2.4) git-buildpackage; urgency=low
+
+ * add manpages
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 4 Oct 2006 19:39:13 +0200
+
+git-buildpackage (0.2.3) git-buildpackage; urgency=low
+
+ * git doesn't like '~' in tag names so replace this with a dot when tagging
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 2 Oct 2006 18:30:20 +0200
+
+git-buildpackage (0.2.2) git-buildpackage; urgency=low
+
+ * git-import-dsc: don't fail if the dsc is a plain filename without a
+ directory component
+ * git-buildpackage: add --git-verbose option
+ * git-import-{orig,dsc}: allow to set the name of the upstream branch
+
+ -- Guido Guenther <agx@sigxcpu.org> Fri, 29 Sep 2006 19:08:08 +0200
+
+git-buildpackage (0.2.1) git-buildpackage; urgency=low
+
+ * git-import-orig: don't try import new upstream versions when there
+ are uncommitted changes
+ * git-buildpackage: use helpers from git_buildpackage
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 27 Sep 2006 12:15:47 +0200
+
+git-buildpackage (0.2) git-buildpackage; urgency=low
+
+ * git-import-dsc: import of debian native packages
+
+ -- Guido Guenther <agx@sigxcpu.org> Wed, 27 Sep 2006 00:40:46 +0200
+
+git-buildpackage (0.01) unstable; urgency=low
+
+ * Initial release
+
+ -- Guido Guenther <agx@sigxcpu.org> Tue, 12 Sep 2006 14:55:57 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..37d5732
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,93 @@
+Source: git-buildpackage
+Section: vcs
+Priority: optional
+Maintainer: Guido Günther <agx@sigxcpu.org>
+Build-Depends:
+ bash-completion (>= 1:2.1-4.2~),
+ debhelper (>= 9~),
+ dh-python,
+ docbook2x,
+ flake8,
+ gtk-doc-tools,
+ libdistro-info-perl,
+ perl,
+ python-pydoctor,
+ python3 (>= 3.5),
+ python3-coverage,
+ python3-dateutil,
+ python3-mock,
+ python3-nose,
+ python3-nosexcover,
+ python3-pkg-resources,
+ python3-rpm,
+ python3-setuptools,
+ xsltproc,
+# For the testsuite
+ bzip2 <!nocheck>,
+ cpio <!nocheck>,
+ devscripts (>= 2.17.7~) <!nocheck>,
+ git (>= 1:1.7.9.1-1~) <!nocheck>,
+ pristine-tar <!nocheck>,
+ rpm <!nocheck>,
+ unzip <!nocheck>,
+ zipmerge <!nocheck>,
+# For the network tests
+ curl <!nocheck>,
+ python3-requests <!nocheck>,
+Standards-Version: 4.1.3
+Vcs-Git: https://git.sigxcpu.org/cgit/git-buildpackage/
+Vcs-Browser: https://git.sigxcpu.org/cgit/git-buildpackage/
+Homepage: https://honk.sigxcpu.org/piki/projects/git-buildpackage/
+X-Python3-Version: >= 3.5
+
+Package: git-buildpackage
+Architecture: all
+Depends: ${python3:Depends},
+ ${shlibs:Depends},
+ ${misc:Depends},
+ devscripts (>= 2.13.5~),
+ git (>= 1:1.7.9.1-1~),
+ man-db,
+ python3-dateutil,
+ python3-pkg-resources,
+ sensible-utils,
+Recommends: pristine-tar (>= 0.5),
+ cowbuilder | pbuilder | sbuild,
+ python3-requests
+Suggests: python3-notify2, unzip, sudo
+Description: Suite to help with Debian packages in Git repositories
+ This package contains the following tools:
+ * gbp buildpackage: build a package out of a git repository, check for local
+ modifications and tag appropriately
+ * gbp import-orig: import a new upstream version into the git repository
+ * gbp export-orig: export an upstream tarball from the git repository
+ * gbp import-{dsc,dscs}: import existing Debian source packages into a git
+ repository
+ * gbp dch: generate Debian changelog entries from Git commit messages
+ * gbp {pull,clone}: clone and pull from remote repos
+ * gbp pq: manage debian/patches easily
+ * gbp create-remote-repo: create remote repositories
+ * gbp push: push content to remote repositories
+ * gbp tag: tag a Debian package in git
+ * gbp pristine-tar: create pristine-tar commits
+
+Package: git-buildpackage-rpm
+Architecture: all
+Depends: ${python3:Depends},
+ ${misc:Depends},
+ cpio,
+ git-buildpackage (= ${binary:Version}),
+ python3-rpm,
+ rpm,
+Recommends: pristine-tar (>= 0.5)
+Suggests: python3-notify, unzip, zipmerge, mock
+Description: Suite to help with RPM packages in Git repositories
+ This package contains the following tools:
+ * gbp buildpackage-rpm: build a package out of a git repository, check for
+ local modifications and tag appropriately
+ * gbp import-srpm: import existing RPM source packages into a git
+ repository
+ * gbp pq-rpm: manage patches easily
+ .
+ These tools are currently in an experimental state. Branch names and
+ repository layouts might change without lots of prior warning.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..32a2019
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,30 @@
+This package was debianized by Guido Günther <agx@sigxcpu.org> on
+Tue, 12 Sep 2006 14:55:57 +0200.
+
+It was downloaded from https://honk.sigxcpu.org/piki/projects/git-buildpackage/
+
+Upstream Author: Guido Günther <agx@sigxcpu.org>
+
+Copyright: 2006,2007,2008,2009,2010, Guido Günther
+
+License:
+
+You are free to distribute this software under the terms of the GNU General
+Public License Version 2. The full text of this license can be found in the
+file /usr/share/common-licenses/GPL-2
+
+Git-buildpackage includes git-pbuilder written by Russ Allbery
+<rra@stanford.edu>
+
+Copyright: 2008, 2009, 2010 Board of Trustees, Leland Stanford Jr. University
+License:
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted, provided
+that the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation, and that the name of Stanford University not be used in
+advertising or publicity pertaining to distribution of the software without
+specific, written prior permission. Stanford University makes no
+representations about the suitability of this software for any purpose. It
+is provided "as is" without express or implied warranty.
diff --git a/debian/doc-base b/debian/doc-base
new file mode 100644
index 0000000..e24e821
--- /dev/null
+++ b/debian/doc-base
@@ -0,0 +1,11 @@
+Document: git-buildpackage
+Title: Git-Buildpackage Manual
+Author: Guido Günther
+Abstract: git-buildpackage is a suite to help with Debian packages in Git
+ repositories. This manual describes the utilities in this package, their
+ configuration and possible workflows.
+Section: Programming
+
+Format: HTML
+Index: /usr/share/doc/git-buildpackage/manual-html/index.html
+Files: /usr/share/doc/git-buildpackage/manual-html/*.html
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..00356a6
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,3 @@
+README.md
+docs/manual-html/
+build/apidocs/
diff --git a/debian/examples b/debian/examples
new file mode 100644
index 0000000..e39721e
--- /dev/null
+++ b/debian/examples
@@ -0,0 +1 @@
+examples/*
diff --git a/debian/gbp.completion b/debian/gbp.completion
new file mode 100644
index 0000000..706cfdf
--- /dev/null
+++ b/debian/gbp.completion
@@ -0,0 +1,189 @@
+# -*- shell-script -*-
+#
+# Bash tab auto-completion rules for git-buildpackage.
+#
+# Copyright (C) 2010 Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
+# Copyright (C) 2010,2013,2015 Guido Günther <agx@sigxcpu.org>
+#
+# Distributed under the GNU General Public License, version 2.0.
+
+_gbp_branches ()
+{
+ [ -d .git ] || return 0
+ git for-each-ref --format="%(refname:short)" refs/heads
+}
+
+
+_gbp_tags ()
+{
+ [ -d .git ] || return 0
+ git for-each-ref --format="%(refname:short)" refs/tags
+}
+
+
+_gbp_remote_configs ()
+{
+ GBP_DISABLE_GBP_CONF_DEPRECATION=true \
+ GBP_DISABLE_SECTION_DEPRECATION=true \
+ gbp create-remote-repo list | sed -ne 's/^ \+\([a-z]\+\)/\1/p'
+}
+
+
+_gbp_options ()
+{
+ GBP_DISABLE_GBP_CONF_DEPRECATION=true \
+ GBP_DISABLE_SECTION_DEPRECATION=true \
+ gbp "${1}" --help | sed -ne 's/^ \+\(\(\-[a-z]\), \)\?\(\-\-[a-z\-]\+\=\?\).*/\2 \3/p'
+}
+
+
+_gbp_commands ()
+{
+ gbp --list-cmds | sed -ne 's/^ \+\([a-z\-]\+\) \-.*/\1/p'
+}
+
+
+_gbp_comp ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ local prev="${COMP_WORDS[COMP_CWORD - 1]}"
+ local options=$1
+ local branch_opts=${2:-"--debian-branch\= --upstream-branch\= --upstream-tree\="}
+ local tag_opts=${3:-"--debian-tag\= --upstream-tag\= --upstream-vcs-tag\="}
+ local tristate_opts=${4:-"--color\="}
+ local cbdist_opts=${5:-"--git-dist\="}
+ local remote_config_opts="--remote-config\="
+ local file_opts="--postimport\= --git-builder\= --git-cleaner\= \
+ --git-export-dir\= --git-postbuild\= --git-postexport\= \
+ --git-posttag\= --git-prebuild\= --git-tarball-dir\= \
+ --posttag\= --tarball-dir\="
+ local start_opt=""
+
+# COMPREPLY considers '=' as a word. For $prev we prefer the word before the actual "="
+ if [[ "$prev" == "=" ]]; then
+ prev="${COMP_WORDS[COMP_CWORD - 2]}"
+ elif [[ "$cur" == "=" ]]; then
+ start_opt=1
+ cur=""
+ fi
+
+ if [[ ${prev} == --* ]]; then
+ if [[ "${branch_opts}" == *$prev* && "${prev}" == --* ]]; then
+ local refs=$(_gbp_branches)
+ COMPREPLY=( $(compgen -W "$refs" -- $cur ) )
+ return 0
+ fi
+
+ if [[ "${tag_opts}" == *$prev* && "${prev}" == --* ]]; then
+ local refs=$(_gbp_tags)
+ COMPREPLY=( $(compgen -W "$refs" -- $cur ) )
+ return 0
+ fi
+
+ if [[ "${tristate_opts}" == *$prev* ]]; then
+ COMPREPLY=( $(compgen -W 'on off auto' -- $cur ) )
+ return 0
+ fi
+
+ if [[ "${cbdist_opts}" == *$prev* ]]; then
+ local BASE="/var/cache/pbuilder/base-"
+ COMPREPLY=( $( compgen -o dirnames -G "${BASE}${cur}*.cow" \
+ | sed -e "s,${BASE}\(.*\)\.cow,\1,g" ) )
+ return 0
+ fi
+
+ if [[ "${remote_config_opts}" == *$prev* ]]; then
+ local remote_configs=$(_gbp_remote_configs)
+ COMPREPLY=( $(compgen -W "$remote_configs" -- $cur ) )
+ return 0
+ fi
+
+ if [[ "${file_opts}" == *$prev* ]]; then
+ COMPREPLY=( $(compgen -f -- $cur ) )
+ return 0
+ fi
+ fi
+
+ # separate opts by tab so we can append a space to all options not ending
+ # with an equal sign
+ tab_opts=$(echo "$options" | sed -e 's/ \+/\t/g' -e 's/[^=]$/& /g')
+ type compopt &>/dev/null && compopt -o nospace
+ local IFS=$'\t\n'
+ if [ -n "$start_opt" ]; then
+ COMPREPLY="" # we don't have any good suggestions
+ else
+ COMPREPLY=($(compgen -W "$tab_opts" -- $cur))
+ fi
+}
+
+# check if we can find a gbp command on the commandline
+_gbp_find_cmd_on_cmdline ()
+{
+ local cmds="$1" # list of commands to check for
+ local word cmd c=1
+
+ while [ $c -lt $((COMP_CWORD)) ]; do
+ word="${COMP_WORDS[c]}"
+ for cmd in $cmds; do
+ if [ "$cmd" = "$word" ]; then
+ echo "$cmd"
+ return
+ fi
+ done
+ ((c++))
+ done
+}
+
+_gbp-buildpackage()
+{
+ local options=$(_gbp_options buildpackage)
+ local branch_opts="--git-debian-branch\= --git-upstream-branch\= --git-upstream-tree\="
+ local tag_opts="--git-debian-tag\= --git-upstream-tag\="
+ local tristate_opts="--git-color\= --git-notify\="
+
+ _gbp_comp "$options" "$branch_opts" "$tag_opts" "$tristate_opts" \
+ "$cbdist_opts"
+}
+
+
+_gbp-pq ()
+{
+ local options=$(_gbp_options pq)
+ options="$options export import rebase drop apply switch"
+ _gbp_comp "$options"
+}
+
+
+_gbp-create-remote-repo ()
+{
+ local options=$(_gbp_options create-remote-repo)
+ options="$options list"
+ _gbp_comp "$options"
+}
+
+
+_gbp-generic-cmd()
+{
+ local options=$(_gbp_options "${1}")
+ _gbp_comp "$options"
+}
+
+
+_have gbp &&
+_gbp ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ local commands=$(_gbp_commands)
+ local func
+
+ command=$(_gbp_find_cmd_on_cmdline "$commands")
+ if [ -z "${command}" ]; then
+ COMPREPLY=( $(compgen -W "$commands" -- "${cur}" ) )
+ else
+ if type _gbp-"${command}" >& /dev/null; then
+ _gbp-"${command}"
+ else
+ _gbp-generic-cmd "${command}"
+ fi
+ fi
+} && complete -F _gbp -o default gbp
diff --git a/debian/gbp.conf b/debian/gbp.conf
new file mode 100644
index 0000000..ef14e41
--- /dev/null
+++ b/debian/gbp.conf
@@ -0,0 +1,6 @@
+[DEFAULT]
+debian-branch = master
+
+[dch]
+id-length = 7
+postedit = sed -i s"!^Version:.*!Version: $GBP_DEBIAN_VERSION!" packaging/git-buildpackage.spec
diff --git a/debian/git-buildpackage-rpm.install b/debian/git-buildpackage-rpm.install
new file mode 100644
index 0000000..503e495
--- /dev/null
+++ b/debian/git-buildpackage-rpm.install
@@ -0,0 +1,6 @@
+usr/bin/gbp-builder-mock /usr/share/git-buildpackage/
+usr/lib/python3.?/dist-packages/gbp/rpm usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?//dist-packages/gbp/scripts/import_srpm.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/pq_rpm.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/buildpackage_rpm.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/rpm_ch.py usr/lib/python3/dist-packages/gbp/scripts/
diff --git a/debian/git-buildpackage-rpm.manpages b/debian/git-buildpackage-rpm.manpages
new file mode 100644
index 0000000..183ab2f
--- /dev/null
+++ b/debian/git-buildpackage-rpm.manpages
@@ -0,0 +1,4 @@
+docs/gbp-import-srpm.1
+docs/gbp-pq-rpm.1
+docs/gbp-buildpackage-rpm.1
+docs/gbp-rpm-ch.1
diff --git a/debian/git-buildpackage.bash-completion b/debian/git-buildpackage.bash-completion
new file mode 100644
index 0000000..f87e558
--- /dev/null
+++ b/debian/git-buildpackage.bash-completion
@@ -0,0 +1 @@
+debian/gbp.completion gbp
diff --git a/debian/git-buildpackage.install b/debian/git-buildpackage.install
new file mode 100644
index 0000000..4ed4cc7
--- /dev/null
+++ b/debian/git-buildpackage.install
@@ -0,0 +1,37 @@
+usr/bin/gbp
+usr/bin/git-pbuilder
+usr/lib/python3.?/dist-packages/gbp-* usr/lib/python3/dist-packages/
+usr/lib/python3.?/dist-packages/gbp/command_wrappers.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/config.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/dch.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/deb/ usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/errors.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/format.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/git/ usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/__init__.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/log.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/notifications.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/paths.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/patch_series.py usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/pkg/ usr/lib/python3/dist-packages/gbp/
+usr/lib/python3.?/dist-packages/gbp/scripts/buildpackage.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/clone.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/common/ usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/config.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/create_remote_repo.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/dch.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/import_dsc.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/import_dscs.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/import_orig.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/export_orig.py usr/lib/python3/dist-packages/gbp/scripts/
+usr/lib/python3.?/dist-packages/gbp/scripts/__init__.py usr/lib/python3/dist-packages/gbp/scripts/
+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/scripts/tag.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/
+usr/lib/python3.?/dist-packages/gbp/version.py usr/lib/python3/dist-packages/gbp/
+usr/share/git-buildpackage/gbp.conf etc/git-buildpackage/
diff --git a/debian/git-buildpackage.manpages b/debian/git-buildpackage.manpages
new file mode 100644
index 0000000..60f3762
--- /dev/null
+++ b/debian/git-buildpackage.manpages
@@ -0,0 +1,17 @@
+docs/gbp.1
+docs/gbp-buildpackage.1
+docs/gbp-clone.1
+docs/gbp.conf.5
+docs/gbp-config.1
+docs/gbp-create-remote-repo.1
+docs/gbp-dch.1
+docs/gbp-export-orig.1
+docs/gbp-import-dsc.1
+docs/gbp-import-dscs.1
+docs/gbp-import-orig.1
+docs/gbp-pq.1
+docs/gbp-pristine-tar.1
+docs/gbp-pull.1
+docs/gbp-push.1
+docs/gbp-tag.1
+docs/git-pbuilder.1
diff --git a/debian/git-buildpackage.postinst b/debian/git-buildpackage.postinst
new file mode 100644
index 0000000..390bceb
--- /dev/null
+++ b/debian/git-buildpackage.postinst
@@ -0,0 +1,42 @@
+#!/bin/sh
+# postinst script for git-buildpackage
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ configure)
+ dpkg-maintscript-helper rm_conffile \
+ /etc/bash_completion.d/git-buildpackage 0.6.34~ git-buildpackage -- "$@"
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/git-buildpackage.postrm b/debian/git-buildpackage.postrm
new file mode 100644
index 0000000..67b835a
--- /dev/null
+++ b/debian/git-buildpackage.postrm
@@ -0,0 +1,43 @@
+#!/bin/sh
+# postrm script for git-buildpackage
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postrm> `remove'
+# * <postrm> `purge'
+# * <old-postrm> `upgrade' <new-version>
+# * <new-postrm> `failed-upgrade' <old-version>
+# * <new-postrm> `abort-install'
+# * <new-postrm> `abort-install' <old-version>
+# * <new-postrm> `abort-upgrade' <old-version>
+# * <disappearer's-postrm> `disappear' <overwriter>
+# <overwriter-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ purge)
+ dpkg-maintscript-helper rm_conffile \
+ /etc/bash_completion.d/git-buildpackage 0.6.34~ git-buildpackage -- "$@"
+ ;;
+ remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/git-buildpackage.preinst b/debian/git-buildpackage.preinst
new file mode 100644
index 0000000..e7988c2
--- /dev/null
+++ b/debian/git-buildpackage.preinst
@@ -0,0 +1,52 @@
+#!/bin/sh
+# preinst script for git-buildpackage
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <new-preinst> `install'
+# * <new-preinst> `install' <old-version>
+# * <new-preinst> `upgrade' <old-version>
+# * <old-preinst> `abort-upgrade' <new-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+cleanup_misplaced()
+{
+ ver=$1
+ misplaced=/etc/git-buildpackage/gbp.conf/gbp.conf
+ gbp_conf=/etc/git-buildpackage/gbp.conf
+ if [ -f "${misplaced}" ] && [ "$ver" = "0.8.9" ]; then
+ mv -f "${misplaced}" "${gbp_conf}.dpkg-bak"
+ if ! rmdir "${gbp_conf}"; then
+ echo "Cannot remove bad dir ${gbp_conf}, trying rename" 1>&2
+ mv -f "${gbp_conf}" "${gbp_conf}".dpkg-dir-bak
+ fi
+ mv -f ${gbp_conf}.dpkg-bak ${gbp_conf}
+ fi
+}
+
+case "$1" in
+ install|upgrade)
+ dpkg-maintscript-helper rm_conffile \
+ /etc/bash_completion.d/git-buildpackage 0.6.34~ git-buildpackage -- "$@"
+ cleanup_misplaced $2
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/git-buildpackage.zsh-completion b/debian/git-buildpackage.zsh-completion
new file mode 100644
index 0000000..03c2d9e
--- /dev/null
+++ b/debian/git-buildpackage.zsh-completion
@@ -0,0 +1,488 @@
+#compdef gbp
+#description build Debian packages from a Git repository
+
+__gbp_common_options() {
+ local prefix="$1"
+ # these can't be prefixed
+ _arguments '--help[Show help]' \
+ '--version[Show version information]'
+
+ _arguments "--${prefix}verbose[Verbose execution]" \
+ "--${prefix}color=-[Use colored output]:color:(on auto off)"
+
+}
+
+__gbp_branch_options() {
+ local prefix="$1"
+ _arguments \
+ "--${prefix}debian-branch=-[The branch the Debian package is being developed on]" \
+ "--${prefix}upstream-branch=-[The branch the upstream sources are put onto]" \
+ "--${prefix}pristine-tar[Track pristine tar branch]"
+}
+
+__gbp_tag_format_options() {
+ local prefix="$1"
+ _arguments \
+ "--${prefix}debian-tag=-[format string for debian tags]" \
+ "--${prefix}upstream-tag=-[format string for upstream tags]"
+}
+
+__gbp_tag_sign_options() {
+ local prefix="$1"
+ _arguments \
+ "(--${prefix}sign-tags --${prefix}no-sign-tags)--${prefix}sign-tags[GPG sign all generated tags]" \
+ "(--${prefix}sign-tags --${prefix}no-sign-tags)--${prefix}no-sign-tags[Do not GPG sign generated tags]" \
+ "--${prefix}keyid=-[GPG keyid to sign tags with]:GPG key:"
+}
+
+_gbp() {
+ local curcontext="$curcontext" state line
+ typeset -A opt_args
+ _arguments -C \
+ ':command:->command' \
+ '*::options:->options' \
+
+ case $state in
+ (command)
+ #breaks if defined outside the func
+ local -a subcommands
+ subcommands=(
+ 'buildpackage:Build a Debian package'
+ 'clone:Clone a Git repository from a remote and set up the necessary branch tracking.'
+ 'create-remote-repo:Create a remote Git repository'
+ 'dch:Generate the debian/changelog from Git commit history'
+ 'import-dsc:Import a single Debian source package'
+ 'import-dscs:Import multiple Debian source packages'
+ 'import-orig:Import a new upstream tarball'
+ 'pq:Manage debian/patches using Git rebase'
+ 'pull:Update a Git repository from a remote'
+ )
+
+ _describe -t commands gbp subcommands
+ ;;
+ (options)
+ local funcname
+ funcname=_gbp-$line[1]
+ if type $funcname | grep -q "shell function" ; then
+ $funcname
+ fi
+ ;;
+ esac
+}
+
+_gbp-buildpackage() {
+ __gbp_common_options git-
+ __gbp_branch_options git-
+ __gbp_tag_format_options git-
+ __gbp_tag_sign_options git-
+ _arguments \
+ '--git-ignore-new[build with uncommitted changes in the source tree]' \
+ '--git-no-ignore-new[negates --git-ignore-new]' \
+ '--git-tag[create a tag after a successful build]' \
+ '--git-tag-only[do not build, only tag and run the posttag hook]' \
+ '--git-retag[do not fail if the tag already exists]' \
+ '--git-force-create[force creation of orig.tar.gz]' \
+ '--git-no-create-orig[do not create orig.tar.gz]' \
+ '--git-tarball-dir=-[location to look for external tarballs]:tarball directory:_files -/' \
+ '--git-compression=-[compression type]:compression:(auto gzip bzip2 lzma xz)' \
+ '--git-compression-level=-[set compression level]:level:(1 2 3 4 5 6 7 8 9)' \
+ '--git-ignore-branch[build although debian-branch != current branch]' \
+ '--git-no-ignore-branch[negates --git-ignore-branch]' \
+ '--git-builder=-[command to build the Debian package]:command:' \
+ '--git-cleaner=-[command to clean the working copy]:command:' \
+ '--git-prebuild=-[command to run before a build]:command:' \
+ '--git-postbuild=-[hook run after a successful build]:command:' \
+ '--git-posttag=-[hook run after a successful tag operation]:command:' \
+ '--git-pbuilder[invoke git-pbuilder for building]' \
+ '--git-no-pbuilder[negates --git-pbuilder]' \
+ '--git-dist=-[build for this distribution when using git-pbuilder]:distribution:' \
+ '--git-arch=-[build for this architecture when using git-pbuilder]:architecture:' \
+ '--git-export-dir=-[before building the package export the source into this directory]:directory:_files -/' \
+ '--git-export=-[export treeish object instead of HEAD]:treeish:' \
+ '--git-dont-purge[retain exported package build directory]' \
+ '--git-overlay[extract orig tarball when using export-dir option]' \
+ '--git-no-overlay[negates --git-overlay]' \
+ '--git-notify=-[Send a desktop notification after build]:notify:(on auto off)' \
+ '*:Other options:_dpkg-buildpackage'
+}
+
+_gbp-clone() {
+ __gbp_common_options
+ __gbp_branch_options
+ _arguments \
+ '--all[Track all branches, not only debian and upstream]'
+}
+
+_gbp-create-remote-repo() {
+ __gbp_common_options
+ _arguments \
+ '--remote-url-pattern=-[Where to create remote repository]' \
+ '--remote-name=-[What name git will use when refering to that repository]' \
+ '--template-dir=-[Template dir to pass to git init]' \
+ '--remote-config=-[Name of config file section to specify params]' \
+ '(--track --no-track)--track[Set up branch tracking]' \
+ '(--track --no-track)--no-track[Do not set up branch tracking]'
+
+}
+
+_gbp-dch () {
+ __gbp_common_options
+ __gbp_branch_options
+ __gbp_tag_format_options
+
+ _arguments \
+ '--ignore-branch[build although debian-branch != current branch]' \
+ '--since=-[Start point for reading commits]:commitish:' \
+ '--auto[Guess the last commit documented in the changelog]' \
+ '(--meta --no-meta)--meta[Parse meta tags]' \
+ '(--meta --no-meta)--no-meta[Do not parse meta tags]' \
+ '--meta-closes=-[What meta tags to look for to generate bug-closing changelog entries]' \
+ '--meta-closes-bugnum=-[What bug number format to look for to generate bug-closing changelog entries]' \
+ '(--full --no-full)--full[Include the full commit message]' \
+ '(--full --no-full)--no-full[Do not include the full commit message]' \
+ '(--snapshot -S)'{-S,--snapshot}'[Create a snapshot release entry]' \
+ '--snapshot-number=-[Python expression that gets eval()ed to the new snapshot number]' \
+ '(--release -R)'{-R,--release}'[Remove any snapshot release banners]' \
+ '(--new-version -N)'{-R,--release}'=[Specify changelog version]' \
+ '--team[Create a team upload entry]' \
+ '--bpo[Increment the release number for a backports upload]' \
+ '--nmu[Increment the release number for a NMU upload]' \
+ '--qa[Increment the release number for a QA upload]' \
+ '--distribution=-[Set the distribution field]' \
+ '--force-distribution[Force distribution]' \
+ '--urgency=-[Set the upload urgency]' \
+ '--git-log=-[Options passed to git log]' \
+ '--id-length=-[Number of commit id digits to include]' \
+ '--ignore-regex=-[Ignore matching commit lines]' \
+ '--git-author[Use git name configuration for changelog signature]' \
+ '(--multimaint-merge --no-multimaint-merge)--multimaint-merge[Merge commits by maintainer]' \
+ '(--multimaint-merge --no-multimaint-merge)--multimaint-merge[Do not merge commits by maintainer]' \
+ '--spawn-editor=[Spawn an editor]:when:(always never snapshot release)' \
+ '--commit-msg=[Commit message format string]' \
+ '--commit[Commit the generated changelog]' \
+ '*:Paths:_files -/'
+}
+
+_gbp-import-dsc() {
+ __gbp_common_options
+ __gbp_branch_options
+ __gbp_tag_format_options
+ __gbp_tag_sign_options
+ _arguments \
+ '--filter=-[Filter out files]' \
+ '--allow-unauthenticated[Skip signature verification on downloads]' \
+ '--allow-same-version[Import a package with the same debian version]' \
+ '--author-is-committer[Use the author identity as committer identity]' \
+ '--author-date-is-committer-date[Use author date as commit date]' \
+ '*:package:_files -g "*.dsc"'
+ # TODO: complete source package names
+ # TODO: pass only one tarball/source package name
+}
+
+_gbp-import-dscs() {
+ # same options
+ _gbp-import-dsc
+ _arguments \
+ '--debsnap[Fetch snapshots from snapshots.debian.org]' \
+ '--ignore-repo-config[Ignore options in gbp.conf]'
+ # TODO: multiple dscs or one source package name + debsnap
+}
+
+_gbp-import-orig() {
+ __gbp_common_options
+ __gbp_branch_options
+ __gbp_tag_format_options
+ __gbp_tag_sign_options
+ _arguments \
+ '(--upstream-version -u)'{--upstream-version,-u}'=[The upstream version number]' \
+ '--merge[Merge the upstream branch into the debian branch]' \
+ '--upstream-vcs-tag=-[Add a tag as an additional parent to the upstream tarball commit]' \
+ '--import-msg=-[Commit message format string]' \
+ '--filter=-[Filter out files]' \
+ '--filter-pristine-tar[When filtering also filter out of pristine-tar tarballs]' \
+ '(--symlink-orig --no-symlink-orig)--symlink-orig=[Create a symlink with a debian-compliant name]' \
+ '(--symlink-orig --no-symlink-orig)--no-symlink-orig=[Do not create a symlink with a debian-compliant name]' \
+ '--postimport=-[Run a command after import]' \
+ '--uscan[Use uscan to fetch the new upstream version]' \
+ '*:file:_files'
+
+ # TODO: pass only one tarball
+ # TODO: Do not complete files when uscan option is enabled
+}
+
+_gbp-pq() {
+ __gbp_common_options
+ _arguments \
+ '(--patch-numbers --no-patch-numbers)--patch-numbers[Add numbers to patch files]' \
+ '(--patch-numbers --no-patch-numbers)--no-patch-numbers[Do not add numbers to patch files]' \
+ '--topic=-[Topic to use when importing a single patch]' \
+ '--time-machine=-[Go back N commits trying to apply patch queue]'
+
+ local -a pqcommands
+ pqcommands=(
+ 'import:Create a patch queue branch'
+ 'export:Export the patches on the patch-queue branch'
+ 'rebase:Rebase the patch-queue branch against the current branch'
+ 'drop:Drop the patch queue'
+ 'apply:Add a single patch to the patch-queue'
+ 'switch:Switch to the patch-queue branch if on the base branch and viceversa'
+ )
+ # TODO: only display these commands once
+ _describe -t pqcommands gbp-pq pqcommands
+}
+
+_gbp-pull() {
+ __gbp_common_options
+ __gbp_branch_options
+ _arguments \
+ '--force[Update even non fast-forward]' \
+ '--redo-pq[Rebuild the patch queue]' \
+ '--ignore-branch[Dont care if on a detached state]' \
+ '--depth=-[Depth for deepening shallow clones]'
+}
+
+_gbp "$@"
+
+
+
+__gbp_common_options() {
+ local prefix="$1"
+ # these can't be prefixed
+ _arguments '--help[Show help]' \
+ '--version[Show version information]'
+
+ _arguments "--${prefix}verbose[Verbose execution]" \
+ "--${prefix}color=-[Use colored output]:color:(on auto off)"
+
+}
+
+__gbp_branch_options() {
+ local prefix="$1"
+ _arguments \
+ "--${prefix}debian-branch=-[The branch the Debian package is being developed on]" \
+ "--${prefix}upstream-branch=-[The branch the upstream sources are put onto]" \
+ "--${prefix}pristine-tar[Track pristine tar branch]"
+}
+
+__gbp_tag_format_options() {
+ local prefix="$1"
+ _arguments \
+ "--${prefix}debian-tag=-[format string for debian tags]" \
+ "--${prefix}upstream-tag=-[format string for upstream tags]"
+}
+
+__gbp_tag_sign_options() {
+ local prefix="$1"
+ _arguments \
+ "(--${prefix}sign-tags --${prefix}no-sign-tags)--${prefix}sign-tags[GPG sign all generated tags]" \
+ "(--${prefix}sign-tags --${prefix}no-sign-tags)--${prefix}no-sign-tags[Do not GPG sign generated tags]" \
+ "--${prefix}keyid=-[GPG keyid to sign tags with]:GPG key:"
+}
+
+_gbp() {
+ local curcontext="$curcontext" state line
+ typeset -A opt_args
+ _arguments -C \
+ ':command:->command' \
+ '*::options:->options' \
+
+ case $state in
+ (command)
+ #breaks if defined outside the func
+ local -a subcommands
+ subcommands=(
+ 'buildpackage:Build a Debian package'
+ 'clone:Clone a Git repository from a remote and set up the necessary branch tracking.'
+ 'create-remote-repo:Create a remote Git repository'
+ 'dch:Generate the debian/changelog from Git commit history'
+ 'import-dsc:Import a single Debian source package'
+ 'import-dscs:Import multiple Debian source packages'
+ 'import-orig:Import a new upstream tarball'
+ 'pq:Manage debian/patches using Git rebase'
+ 'pull:Update a Git repository from a remote'
+ )
+
+ _describe -t commands gbp subcommands
+ ;;
+ (options)
+ local funcname
+ funcname=_gbp-$line[1]
+ if type $funcname | grep -q "shell function" ; then
+ $funcname
+ fi
+ ;;
+ esac
+}
+
+_gbp-buildpackage() {
+ __gbp_common_options git-
+ __gbp_branch_options git-
+ __gbp_tag_format_options git-
+ __gbp_tag_sign_options git-
+ _arguments \
+ '--git-ignore-new[build with uncommitted changes in the source tree]' \
+ '--git-no-ignore-new[negates --git-ignore-new]' \
+ '--git-tag[create a tag after a successful build]' \
+ '--git-tag-only[do not build, only tag and run the posttag hook]' \
+ '--git-retag[do not fail if the tag already exists]' \
+ '--git-force-create[force creation of orig.tar.gz]' \
+ '--git-no-create-orig[do not create orig.tar.gz]' \
+ '--git-tarball-dir=-[location to look for external tarballs]:tarball directory:_files -/' \
+ '--git-compression=-[compression type]:compression:(auto gzip bzip2 lzma xz)' \
+ '--git-compression-level=-[set compression level]:level:(1 2 3 4 5 6 7 8 9)' \
+ '--git-ignore-branch[build although debian-branch != current branch]' \
+ '--git-no-ignore-branch[negates --git-ignore-branch]' \
+ '--git-builder=-[command to build the Debian package]:command:' \
+ '--git-cleaner=-[command to clean the working copy]:command:' \
+ '--git-prebuild=-[command to run before a build]:command:' \
+ '--git-postbuild=-[hook run after a successful build]:command:' \
+ '--git-posttag=-[hook run after a successful tag operation]:command:' \
+ '--git-pbuilder[invoke git-pbuilder for building]' \
+ '--git-no-pbuilder[negates --git-pbuilder]' \
+ '--git-dist=-[build for this distribution when using git-pbuilder]:distribution:' \
+ '--git-arch=-[build for this architecture when using git-pbuilder]:architecture:' \
+ '--git-export-dir=-[before building the package export the source into this directory]:directory:_files -/' \
+ '--git-export=-[export treeish object instead of HEAD]:treeish:' \
+ '--git-dont-purge[retain exported package build directory]' \
+ '--git-overlay[extract orig tarball when using export-dir option]' \
+ '--git-no-overlay[negates --git-overlay]' \
+ '--git-notify=-[Send a desktop notification after build]:notify:(on auto off)' \
+ '*:Other options:_dpkg-buildpackage'
+}
+
+_gbp-clone() {
+ __gbp_common_options
+ __gbp_branch_options
+ _arguments \
+ '--all[Track all branches, not only debian and upstream]'
+}
+
+_gbp-create-remote-repo() {
+ __gbp_common_options
+ _arguments \
+ '--remote-url-pattern=-[Where to create remote repository]' \
+ '--remote-name=-[What name git will use when refering to that repository]' \
+ '--template-dir=-[Template dir to pass to git init]' \
+ '--remote-config=-[Name of config file section to specify params]' \
+ '(--track --no-track)--track[Set up branch tracking]' \
+ '(--track --no-track)--no-track[Do not set up branch tracking]'
+
+}
+
+_gbp-dch () {
+ __gbp_common_options
+ __gbp_branch_options
+ __gbp_tag_format_options
+
+ _arguments \
+ '--ignore-branch[build although debian-branch != current branch]' \
+ '--since=-[Start point for reading commits]:commitish:' \
+ '--auto[Guess the last commit documented in the changelog]' \
+ '(--meta --no-meta)--meta[Parse meta tags]' \
+ '(--meta --no-meta)--no-meta[Do not parse meta tags]' \
+ '--meta-closes=-[What meta tags to look for to generate bug-closing changelog entries]' \
+ '--meta-closes-bugnum=-[What bug number format to look for to generate bug-closing changelog entries]' \
+ '(--full --no-full)--full[Include the full commit message]' \
+ '(--full --no-full)--no-full[Do not include the full commit message]' \
+ '(--snapshot -S)'{-S,--snapshot}'[Create a snapshot release entry]' \
+ '--snapshot-number=-[Python expression that gets eval()ed to the new snapshot number]' \
+ '(--release -R)'{-R,--release}'[Remove any snapshot release banners]' \
+ '(--new-version -N)'{-R,--release}'=[Specify changelog version]' \
+ '--team[Create a team upload entry]' \
+ '--bpo[Increment the release number for a backports upload]' \
+ '--nmu[Increment the release number for a NMU upload]' \
+ '--qa[Increment the release number for a QA upload]' \
+ '--distribution=-[Set the distribution field]' \
+ '--force-distribution[Force distribution]' \
+ '--urgency=-[Set the upload urgency]' \
+ '--git-log=-[Options passed to git log]' \
+ '--id-length=-[Number of commit id digits to include]' \
+ '--ignore-regex=-[Ignore matching commit lines]' \
+ '--git-author[Use git name configuration for changelog signature]' \
+ '(--multimaint-merge --no-multimaint-merge)--multimaint-merge[Merge commits by maintainer]' \
+ '(--multimaint-merge --no-multimaint-merge)--multimaint-merge[Do not merge commits by maintainer]' \
+ '--spawn-editor=[Spawn an editor]:when:(always never snapshot release)' \
+ '--commit-msg=[Commit message format string]' \
+ '--commit[Commit the generated changelog]' \
+ '*:Paths:_files -/'
+}
+
+_gbp-import-dsc() {
+ __gbp_common_options
+ __gbp_branch_options
+ __gbp_tag_format_options
+ __gbp_tag_sign_options
+ _arguments \
+ '--filter=-[Filter out files]' \
+ '--allow-unauthenticated[Skip signature verification on downloads]' \
+ '--allow-same-version[Import a package with the same debian version]' \
+ '--author-is-committer[Use the author identity as committer identity]' \
+ '--author-date-is-committer-date[Use author date as commit date]' \
+ '*:package:_files -g "*.dsc"'
+ # TODO: complete source package names
+ # TODO: pass only one tarball/source package name
+}
+
+_gbp-import-dscs() {
+ # same options
+ _gbp-import_dsc
+ _arguments \
+ '--debsnap[Fetch snapshots from snapshots.debian.org]' \
+ '--ignore-repo-config[Ignore options in gbp.conf]'
+ # TODO: multiple dscs or one source package name + debsnap
+}
+
+_gbp-import-orig() {
+ __gbp_common_options
+ __gbp_branch_options
+ __gbp_tag_format_options
+ __gbp_tag_sign_options
+ _arguments \
+ '(--upstream-version -u)'{--upstream-version,-u}'=[The upstream version number]' \
+ '--merge[Merge the upstream branch into the debian branch]' \
+ '--upstream-vcs-tag=-[Add a tag as an additional parent to the upstream tarball commit]' \
+ '--import-msg=-[Commit message format string]' \
+ '--filter=-[Filter out files]' \
+ '--filter-pristine-tar[When filtering also filter out of pristine-tar tarballs]' \
+ '(--symlink-orig --no-symlink-orig)--symlink-orig=[Create a symlink with a debian-compliant name]' \
+ '(--symlink-orig --no-symlink-orig)--no-symlink-orig=[Do not create a symlink with a debian-compliant name]' \
+ '--postimport=-[Run a command after import]' \
+ '--uscan[Use uscan to fetch the new upstream version]' \
+ '*:file:_files'
+
+ # TODO: pass only one tarball
+ # TODO: Do not complete files when uscan option is enabled
+}
+
+_gbp-pq() {
+ __gbp_common_options
+ _arguments \
+ '(--patch-numbers --no-patch-numbers)--patch-numbers[Add numbers to patch files]' \
+ '(--patch-numbers --no-patch-numbers)--no-patch-numbers[Do not add numbers to patch files]' \
+ '--topic=-[Topic to use when importing a single patch]' \
+ '--time-machine=-[Go back N commits trying to apply patch queue]'
+
+ local -a pqcommands
+ pqcommands=(
+ 'import:Create a patch queue branch'
+ 'export:Export the patches on the patch-queue branch'
+ 'rebase:Rebase the patch-queue branch against the current branch'
+ 'drop:Drop the patch queue'
+ 'apply:Add a single patch to the patch-queue'
+ 'switch:Switch to the patch-queue branch if on the base branch and viceversa'
+ )
+ # TODO: only display these commands once
+ _describe -t pqcommands gbp-pq pqcommands
+}
+
+_gbp-pull() {
+ __gbp_common_options
+ __gbp_branch_options
+ _arguments \
+ '--force[Update even non fast-forward]' \
+ '--redo-pq[Rebuild the patch queue]' \
+ '--ignore-branch[Dont care if on a detached state]' \
+ '--depth=-[Depth for deepening shallow clones]'
+}
+
+_gbp "$@"
diff --git a/debian/links b/debian/links
new file mode 100644
index 0000000..1e696e3
--- /dev/null
+++ b/debian/links
@@ -0,0 +1,2 @@
+/usr/share/man/man1/gbp.1.gz /usr/share/man/man1/git-buildpackage.1.gz
+/usr/lib/python3/dist-packages/gbp/scripts/supercommand.py /usr/bin/gbp
diff --git a/debian/pk4 b/debian/pk4
new file mode 100644
index 0000000..7f795be
--- /dev/null
+++ b/debian/pk4
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -e
+
+gbp import-dsc --debian-branch=pk4 --upstream-branch=upstream "$1" "$2"
+# Add local gbp.conf to override any branch settings in 'debian/gbp.conf'
+# (since this checkout wasn't created by "gbp clone"). We want to be sure the
+# branches exist so we can build the package right away.
+cat <<EOF > "$2"/.git/gbp.conf
+[DEFAULT]
+upstream-branch = upstream
+debian-branch = pk4
+EOF
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..94e3782
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,50 @@
+#!/usr/bin/make -f
+
+EXAMPLE_SCRIPTS=\
+ gbp-add-patch \
+ gbp-cowbuilder-sid \
+ gbp-posttag-push \
+ gbp-configure-unpatched-source \
+ wrap_cl.py
+
+DEB_COMPRESS_EXCLUDE=$(EXAMPLE_SCRIPTS)
+
+ZSH_COMPDIR = /usr/share/zsh/vendor-completions/
+PK4_DIR = /usr/share/pk4/hooks-available/unpack/
+
+PYCHECKER_ARGS=-boptparse --no-override --no-shadowbuiltin
+
+%:
+ dh $@ --with python3 --buildsystem=pybuild
+
+override_dh_auto_test:
+ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+ make
+else
+ @echo "Checks disabled via DEB_BUILD_OPTIONS"
+endif
+
+override_dh_auto_build:
+ dh_auto_build
+ make docs
+
+override_dh_auto_install:
+ dh_auto_install
+ dh_bash-completion
+ mkdir -p debian/git-buildpackage/$(ZSH_COMPDIR)
+ install -m644 debian/git-buildpackage.zsh-completion \
+ debian/git-buildpackage/$(ZSH_COMPDIR)/_gbp
+ mkdir -p debian/git-buildpackage/$(PK4_DIR)
+ install -m755 debian/pk4 \
+ debian/git-buildpackage/$(PK4_DIR)/gbp
+ chmod a+x debian/tmp/usr/lib/python3.?/dist-packages/gbp/scripts/supercommand.py
+
+override_dh_auto_clean:
+ dh_auto_clean
+ rm -rf build/
+ make -C docs/ clean
+ -rm gbp/version.py
+
+override_dh_compress:
+ dh_compress --exclude=usr/share/doc/git-buildpackage/examples/
+
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/debian/tests/control b/debian/tests/control
new file mode 100644
index 0000000..c862151
--- /dev/null
+++ b/debian/tests/control
@@ -0,0 +1,4 @@
+Tests: smoke-rpm
+Depends: @,
+ python-rpm
+Restrictions: allow-stderr
diff --git a/debian/tests/smoke-rpm b/debian/tests/smoke-rpm
new file mode 100755
index 0000000..de0a62f
--- /dev/null
+++ b/debian/tests/smoke-rpm
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+set -e
+set -x
+
+git init .
+git config user.email "you@example.com"
+git config user.name "Doesnot Matter"
+git add .
+git commit -m"Smoketest" -a
+cat <<EOF >> ~/.rpmmacros
+%__python3 /usr/bin/python3
+%python_sitelib %(%{__python3} -c "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib().replace('dist-', 'site-'))")
+%python_sitearch %(%{__python3} -c "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib(1).replace('dist-', 'site-'))")
+EOF
+
+gbp buildpackage-rpm --help
+
+# Build an RPM of ourselfes if build-deps are available
+if python3 -c "import setuptools"; then
+ gbp buildpackage-rpm --git-packaging-dir=packaging/ -bb --nodeps
+ rm -r .git ~/.rpmmacros
+fi
diff --git a/dev_requirements.txt b/dev_requirements.txt
new file mode 100644
index 0000000..096760b
--- /dev/null
+++ b/dev_requirements.txt
@@ -0,0 +1,6 @@
+-r requirements.txt
+
+coverage>=2.85
+flake8==3.5.0
+nose==1.3.7
+nosexcover>=1.0.7
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..b7258a4
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,87 @@
+#!/usr/bin/make
+
+MAN1S = \
+ gbp \
+ gbp-buildpackage \
+ gbp-clone \
+ gbp-config \
+ gbp-create-remote-repo \
+ gbp-dch \
+ gbp-export-orig \
+ gbp-import-dsc \
+ gbp-import-dscs \
+ gbp-import-orig \
+ gbp-pq \
+ gbp-pristine-tar \
+ gbp-pull \
+ gbp-push \
+ gbp-tag \
+ gbp-buildpackage-rpm \
+ gbp-import-srpm \
+ gbp-pq-rpm \
+ gbp-rpm-ch \
+ $(NULL)
+
+MAN5S = \
+ gbp.conf \
+ $(NULL)
+
+MANUAL=manual-html
+XML_MANPAGES=$(patsubst %,%.1,$(MAN1S)) $(patsubst %,%.5,$(MAN5S))
+POD_MANPAGES=git-pbuilder.1
+MANPAGES=$(XML_MANPAGES) $(POD_MANPAGES)
+VERSION_ENT=version.ent
+GBP_VERSION=../gbp/version.py
+DEB_VERSION=$(shell sed -ne 's/^gbp_version\s\+=\s\+"\([.a-z0-9~-]\+\)"/\1/p' $(GBP_VERSION))
+CHANGELOG=../debian/changelog
+MAN_DATE=$(shell dpkg-parsechangelog -l ../debian/changelog -SDate | TZ=UTC LC_ALL=C date -f- +'%d %B %Y')
+IMAGES=$(wildcard images/*png)
+DEST_IMAGES=$(subst images/,$(MANUAL)/images/,$(IMAGES))
+
+# Select docbook-to-man tool
+ifeq ($(shell which docbook2x-man), )
+ DOCBOOK_TO_MAN=docbook-to-man
+else
+ DOCBOOK_TO_MAN=docbook2x-man
+endif
+
+all: manual $(MANPAGES)
+
+manual: $(MANUAL)/index.html $(DEST_IMAGES) css
+
+$(MANUAL)/index.html: manual.xml chapters/*.xml manpages/*.xml common.ent $(VERSION_ENT)
+ mkdir -p $(MANUAL)
+ xsltproc -o $(MANUAL)/ /usr/share/gtk-doc/data/gtk-doc.xsl $<
+ cp /usr/share/gtk-doc/data/*.png \
+ /usr/share/gtk-doc/data/*.css \
+ $(MANUAL)
+
+$(MANUAL)/gbp.pdf: manual.xml chapters/*.xml manpages/*.xml common.ent $(VERSION_ENT)
+ xsltproc -o $(MANUAL)/ /usr/share/gtk-doc/data/gtk-doc.xsl $<
+
+css: $(MANUAL)/gbp.css $(MANUAL)/gbp.svg
+$(MANUAL)/gbp.css $(MANUAL)/gbp.svg: gbp.css gbp.svg
+ cp gbp.css gbp.svg $(MANUAL)/
+
+html_images: $(DEST_IMAGES)
+$(MANUAL)/images/%.png: images/%.png
+ mkdir -p $(basename $@)
+ install $< $@
+
+%.1 %.5: man.gbp.xml manpages/%.xml
+ $(DOCBOOK_TO_MAN) -o . $<
+
+git-pbuilder.1: ../bin/git-pbuilder
+ pod2man $< $@
+
+manual.xml: $(VERSION_ENT)
+
+$(GBP_VERSION): ../debian/changelog
+ cd .. && python setup.py build --help >/dev/null
+
+$(VERSION_ENT): $(GBP_VERSION)
+ echo '<!ENTITY gbp-version "$(DEB_VERSION)">' > $(VERSION_ENT)
+
+clean:
+ -rm -r $(MANUAL)
+ -rm *.1 *.5 $(VERSION_ENT)
diff --git a/docs/chapters/building.xml b/docs/chapters/building.xml
new file mode 100644
index 0000000..1fbd5ae
--- /dev/null
+++ b/docs/chapters/building.xml
@@ -0,0 +1,271 @@
+<chapter id="gbp.building">
+ <title>Building Packages from the &git; Repository</title>
+ <para>
+ In order to build a &debian; package from the &git; repository, you use:
+ &gbp-buildpackage;. This builds the upstream tarball (as will be described below) and
+ invokes &debuild; to build the package. To use another build command, you
+ can use the <option>--git-builder</option> option as described later in the manual,
+ but &debuild; is nice since it can invoke <productname>lintian</productname>.
+ During the development phase (when you're either not on the
+ <emphasis>debian-branch</emphasis> or when you have uncommitted changes in
+ your repository), you'll usually use:
+ </para>
+<programlisting>
+&gbp-buildpackage; <option>--git-ignore-new</option>
+</programlisting>
+ <para>If &gbp-buildpackage; doesn't find a valid upstream tarball, it will
+ create one by looking at the tag matching the upstream version. To change
+ this behaviour, see the <option>--git-upstream-tree</option> option.
+ </para><para>
+ If you want to recreate the original tarball using the additional
+ information from the <option>pristine-tar branch</option>, you have to
+ specify the <option>--git-pristine-tar</option> option. This will make sure
+ the upstream tarball matches exactly the one imported. Using this option is
+ the recommended way of recreating the upstream tarball.
+ </para>
+ <para>Once you're satisfied with the build and want to do a release, you commit all
+ your changes and issue:</para>
+<programlisting>
+&gbp-buildpackage; <option>--git-tag</option>
+</programlisting>
+ <para>This will again build the &debian; package and tag the final result after
+ extracting the current version from the changelog. If you want &gpg;-signed
+ tags, you can use the <option>--git-sign</option> and
+ <option>--git-keyid</option> options. To save typing, these option can be
+ specified via the configuration files. You can furthermore change the tag
+ format used when creating tags with the <option>debian-tag</option>
+ option; the default is <replaceable>debian/&lt;version&gt;</replaceable>.</para>
+ <sect1 id="gbp.building.export">
+ <title>Using a separate build dir</title>
+ <para>Tools like &svn-buildpackage; use a separate build-area. To achieve a similar behaviour
+ with &gbp-buildpackage;, use the <option>--git-export-dir</option> option:</para>
+<programlisting>
+&gbp-buildpackage; <option>--git-export-dir</option>=<replaceable>../build-area/</replaceable>
+</programlisting>
+ <para>This will export the head of the current branch to
+ <replaceable>../build-area/package-version</replaceable> and build the
+ package. If you don't want to export the current branch head, you can use
+ <option>--git-export</option> to export any treeish object. Here are some
+ examples:</para>
+<programlisting>
+&gbp-buildpackage; <option>--git-export-dir</option>=<replaceable>../build-area</replaceable> <option>--git-export</option>=<replaceable>debian/0.4.3</replaceable>
+&gbp-buildpackage; <option>--git-export-dir</option>=<replaceable>../build-area</replaceable> <option>--git-export</option>=<replaceable>etch</replaceable>
+&gbp-buildpackage; <option>--git-export-dir</option>=<replaceable>../build-area</replaceable> <option>--git-export</option>=<replaceable>8caed309653d69b7ab440e3d35abc090eb4c6697</replaceable>
+&gbp-buildpackage; <option>--git-export-dir</option>=<replaceable>../build-area</replaceable> <option>--git-export</option>=<replaceable>INDEX</replaceable>
+&gbp-buildpackage; <option>--git-export-dir</option>=<replaceable>../build-area</replaceable> <option>--git-export</option>=<replaceable>WC</replaceable>
+</programlisting>
+ <para>The special argument <replaceable>INDEX</replaceable> exports the
+ state of the current index, which can be used to include staged but uncommitted
+ changes in the build. Whereas the special argument
+ <replaceable>WC</replaceable> exports the current working copy as is.</para>
+ <para>If you want to default to build in a separate build area, you can
+ specify the directory to use in the <filename>gbp.conf</filename> file.
+<programlisting>
+[buildpackage]
+# use a build area relative to the git repository
+export-dir=../build-area
+# to use the same build area for all packages use an absolute path:
+#export-dir=/home/debian-packages/build-area
+</programlisting>
+ &gbp-buildpackage; will cleanup the build-area after a successful build. If
+ you want to keep the build tree, use <replaceable>--git-no-purge</replaceable>.
+ </para>
+ </sect1>
+ <sect1 id="gbp.building.hooks">
+ <title>Invoking external programs</title>
+ <para>
+ Besides the commands for cleaning the package build dir
+ (<option>cleaner</option>) and building the package
+ (<option>builder</option>), you can also invoke hooks during the package
+ build: immediately before a build (<option>prebuild</option>),
+ after a successful build (<option>postbuild</option>), and after
+ creating a tag (<option>posttag</option>). Typical applications are running
+ <productname>lintian</productname> or pushing changes into a remote
+ repository.
+ </para>
+ <sect2 id="gbp.building.lintian">
+ <title>Running lintian</title>
+ <para>&gbp-buildpackage; exports several variables into the
+ <option>posttag</option>'s environment (for details see the <xref
+ linkend="man.gbp.buildpackage"/> manual page).
+ To invoke &lintian;, we need to tell it where to find the changes file:
+<programlisting>
+&gbp-buildpackage; <option>--git-postbuild</option>=<replaceable>'lintian $GBP_CHANGES_FILE'</replaceable>
+</programlisting>
+ To call &lintian; automatically after each successful build, add:
+<programlisting>
+<option>postbuild</option>=<replaceable>lintian $GBP_CHANGES_FILE</replaceable>
+</programlisting>
+ to your <filename>.gbp.conf</filename>.
+ </para>
+ </sect2>
+ <sect2 id="gbp.building.push">
+ <title>Pushing into a remote repository</title>
+ <para>If you want to push your changes automatically after a successful build and tag,
+ you can use &gbp-buildpackage;'s posttag hook. A very simple invocation would look like this:
+<programlisting>
+&gbp-buildpackage; <option>--git-tag</option> <option>--git-posttag</option>=<replaceable>"git push &amp;&amp; git push --tags"</replaceable>
+</programlisting>
+ This assumes you have set up a remote repository to push to in
+ <filename>.git/config</filename>.</para>
+
+ <para>Usually, you want to make sure you don't push out any
+ unrelated changes into the remote repository. This is handled by the
+ following hook which only pushes out the created tag to where you pulled
+ from and also forwards the corresponding remote branch to that position:
+<programlisting>
+#!/bin/sh -e
+#
+# gbp-posttag-push: post tag hook to push out the newly created tag and to
+# forward the remote branch to that position
+
+if ! REMOTE=$(git config --get branch."${GBP_BRANCH}".remote); then
+ REMOTE=origin
+fi
+
+if [ "$GBP_TAG" ]; then
+ echo "Pushing $GBP_TAG to $REMOTE"
+ git push "$REMOTE" "$GBP_TAG"
+else
+ echo "GBP_TAG not set."
+ exit 1
+fi
+
+if [ "$GBP_SHA1" ] &amp;&amp; [ "$GBP_BRANCH" ]; then
+ git push "$REMOTE" "$GBP_SHA1":"$GBP_BRANCH"
+else
+ echo "GBP_SHA1 or GBP_BRANCH not set."
+ exit 1
+fi
+echo "done."
+</programlisting>
+ <envar>GBP_TAG</envar>, <envar>GBP_SHA1</envar>
+ and <envar>GBP_BRANCH</envar> are passed to the hook via the
+ environment. To call this hook automatically upon tag creation, add:
+<programlisting>
+<option>posttag</option>=<replaceable>"gbp-posttag-push"</replaceable>
+</programlisting>
+ to your <filename>.gbp.conf</filename> and make sure <filename>gbp-push</filename>
+ is somewhere in your <envar>$PATH</envar>. On &debian;
+ systems, a more complete example can be found in
+ <filename>/usr/share/doc/examples/git-buildpackage/examples/gbp-posttag-push</filename>.
+ </para>
+ </sect2>
+ <sect2 id="gbp.building.postexport">
+ <title>Running postexport hook</title>
+ <para>&gbp-buildpackage; exports several variables into the
+ <option>postexport</option>'s environment (for details see
+ the <xref linkend="man.gbp.buildpackage"/> manual page). The motivation
+ for the postexport action is to allow further adjustment of
+ the sources prior to building the package. A typical use case
+ scenario is to allow creating multiple source and binary
+ packages from one &debian; branch, e.g. the bootstrap gcc and
+ in the next stage the full gcc.
+ </para>
+ <para>The postexport action postpones the creation of the
+ upstream tarball, so that the metadata for creating it is
+ already present in the exported source tree. The example
+ postexport script below (<filename>crosstoolchain-expand.sh</filename>)
+ expands changelog, lintian override files, rules and control files
+ according to an environment variable <envar>PKG_FLAVOR</envar>.
+ </para>
+
+ <para>Sample <filename>gbp.conf</filename> - enables source tree export
+ by specifying the export directory:
+ </para>
+<programlisting>
+[buildpackage]
+# use a build area relative to the git repository
+export-dir = ../build-area
+# disable the since the sources are being exported first
+cleaner =
+# post export script that handles expansion of &debian; specific files
+postexport = crosstoolchain-expand.sh
+</programlisting>
+
+
+<para>Sample postexport script: <filename>crosstoolchain-expand.sh</filename></para>
+<programlisting>
+#!/bin/sh
+#
+# Purpose: this script is intended for creating multiple source and
+# binary Debian packages from one source tree. It can be used in
+# conjunction with git-buildpackage that support a postexport hook
+#
+# A typical use is preparing a bootstrap gcc package that is needed
+# for building newlib and then preparing a full gcc package from the
+# same source tree. The user may specify the package flavor via
+# PKG_FLAVOR environmental variable.
+#
+#
+# The script expands/processes the following files:
+#
+# - changelog.tmpl is converted to standard Debian changelog
+#
+#
+# - all binary package lintian override template files are expanded
+# and renamed to the requested package flavor
+#
+# - source package lintian override template file is expanded and
+# renamed
+#
+# - rules.$PKG_FLAVOR and control.$PKG_FLAVOR are renamed to rules and
+# control resp.
+
+
+# the template string has been carefully chosen, so that
+# e.g. changelogs that refer to the source package can still be
+# processed by dch/git-dch resp.
+TMPL_STR=-XXXXXX
+
+# by default replace string for the template is empty
+REPLACE_STR=
+
+if [ -n "$PKG_FLAVOR" ]; then
+ REPLACE_STR=-$PKG_FLAVOR
+fi
+
+REPLACE_EXPR="s/$TMPL_STR/$REPLACE_STR/g"
+
+
+# actual processing of relevant files
+cd debian
+
+# expand the template changelog
+# remove the symlinked version
+rm changelog
+chglog_tmpl=changelog.tmpl
+[ -f "$chglog_tmpl" ] || {
+ echo "Missing changelog template (debian/$chglog_tmpl)"
+ exit 1
+}
+cat changelog.tmpl | sed -e "$REPLACE_EXPR" > changelog
+rm changelog.tmpl
+
+# process binary package lintian overrides - each override must match
+# its package name
+for f in *.lintian-overrides.tmpl; do
+ outfile=${f%.tmpl}
+ [ -f "$f" ] || {
+ echo "Missing lintian override files for binary packages"
+ exit 1
+ }
+ cat $f | sed -e "$REPLACE_EXPR" > ${outfile/$TMPL_STR/$REPLACE_STR}
+ rm $f
+done
+
+# process the only source package lintian override
+source_lintian=source/lintian-overrides.tmpl
+cat $source_lintian | sed -e "$REPLACE_EXPR" > ${source_lintian%.tmpl}
+rm $source_lintian
+
+# rules and control file are package flavor specific
+[ -f rules.$PKG_FLAVOR ] &amp;&amp; mv rules.$PKG_FLAVOR rules
+[ -f control.$PKG_FLAVOR ] &amp;&amp; mv control.$PKG_FLAVOR control
+rm -f rules.* control.*
+
+exit 0
+</programlisting>
+ </sect2>
+ </sect1>
+</chapter>
diff --git a/docs/chapters/cfgfile.xml b/docs/chapters/cfgfile.xml
new file mode 100644
index 0000000..16272a7
--- /dev/null
+++ b/docs/chapters/cfgfile.xml
@@ -0,0 +1,137 @@
+<chapter id="gbp.cfgfile">
+ <title>Configuration Files</title>
+ <para>The configuration files are parsed in this order:
+ <variablelist>
+ <varlistentry>
+ <term>/etc/git-buildpackage/gbp.conf</term>
+ <listitem><para>system wide configuration</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>~/.gbp.conf</term>
+ <listitem><para>per user configuration</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>.gbp.conf</term>
+ <listitem><para>per repository/branch configuration (deprecated)</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>debian/gbp.conf</term>
+ <listitem><para>per repository/branch configuration</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>.git/gbp.conf</term>
+ <listitem><para>per (local) repository configuration</para></listitem>
+ </varlistentry>
+ </variablelist>
+ All have the same format. They consist of several sections, all of them are optional:
+ <variablelist>
+ <varlistentry>
+ <term>[DEFAULT]</term>
+ <listitem>
+ <para>Options in this section apply to all &gbp; commands.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>[buildpackage]</term>
+ <listitem><para>Options in this section apply to &gbp-buildpackage; only and override options from the
+ [DEFAULT] section.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>[import-orig]</term>
+ <listitem><para>Options in this section apply to &gbp-import-orig; only and override options from the
+ [DEFAULT] section.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>[import-dsc]</term>
+ <listitem><para>Options in this section apply to &gbp-import-dsc; only and override options from the
+ [DEFAULT] section.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>[dch]</term>
+ <listitem><para>Options in this section apply to &gbp-dch; only and override options from the
+ [DEFAULT] section.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>...</term>
+ <listitem><para>Same for the other &gbp; commands</para></listitem>
+ </varlistentry>
+ </variablelist>
+ The actual options in these sections are the command line options without
+ the '--' prefix. So <option>--upstream-branch=</option><replaceable>dfsgfree</replaceable> would read:
+ </para>
+ <screen>
+ <option>upstream-branch</option>=<replaceable>dfsgfree</replaceable>
+ </screen>
+ <para>
+ in the config file. In the special case of &gbp-buildpackage;, the stripped
+ prefix is not '--' but '--git-'. Here's a more complete example:
+ </para>
+ <programlisting>
+ [DEFAULT]
+ # the default build command
+ builder=debuild -i\.git -I.git
+ # the default branch for upstream sources
+ upstream-branch=upstream
+ # the default branch for the debian patch
+ debian-branch=master
+
+ [buildpackage]
+ upstream-branch=dfsgclean
+ # Sign tags with GPG:
+ sign-tags = True
+ # Keyid to sign tags with
+ #keyid = 0xdeadbeef
+
+ [import-orig]
+ upstream-branch=notdfsgclean
+
+ [import-dsc]
+ upstream-branch=notdfsgclean
+
+ [dch]
+ git-log=--no-merges
+ </programlisting>
+ <para>
+ For more details, see the <xref linkend="man.gbp.conf"/> manual page.
+ </para>
+
+ <sect1 id="gbp.cfgfile.order">
+ <title>Overriding Parsing Order</title>
+ <para>
+ The environment variable <envar>GBP_CONF_FILES</envar> can be
+ used to override the order in which configuration files are
+ parsed. The following example parses two configuration files in the
+ users home directory (<filename>~/.gbp.conf</filename> and
+ <filename>~/.gbp.late.conf</filename>) and in the debian
+ directory (<filename>debian/gbp.conf</filename>).
+ The file are parsed from left to right. Since <filename>
+ ~/.gbp.late.conf</filename> is at the very end of the list it
+ can be used to override configuration entries shipped by the
+ package. This can be useful if packages set
+ e.g. <option>export-dir</option> or
+ <option>tarball-dir</option> and you perfer different locations:
+ <programlisting>
+ $ cat &lt;&lt;EOF &gt;~/.gbp.late.conf
+ [DEFAULT]
+ export-dir = ../build-area
+ tarball-dir = ../tarballs
+ EOF
+
+ $ export GBP_CONF_FILES=~/.gbp.conf:debian/gbp.conf:~/.gbp.late.conf
+ $ cd &lt;mypkg&gt;
+ $ gbp config buildpackage.export-dir
+ ../build-area
+ </programlisting>
+ </para>
+ <para>
+ If you want all configuration files to be skipped, set
+ <envar>GBP_CONF_FILES</envar> to a non existing location. This way
+ only default values and options from the command line will be used:
+ <programlisting>
+ $ export GBP_CONFI_FILES=/does/not/exist
+ $ gbp config buildpackage.debian-branch
+ master
+ </programlisting>
+ </para>
+ </sect1>
+</chapter>
diff --git a/docs/chapters/chapters.ent b/docs/chapters/chapters.ent
new file mode 100644
index 0000000..56c5980
--- /dev/null
+++ b/docs/chapters/chapters.ent
@@ -0,0 +1,8 @@
+<!ENTITY ch.intro SYSTEM "intro.xml">
+<!ENTITY ch.workflow SYSTEM "workflow.xml">
+<!ENTITY ch.import SYSTEM "import.xml">
+<!ENTITY ch.building SYSTEM "building.xml">
+<!ENTITY ch.patches SYSTEM "patches.xml">
+<!ENTITY ch.releases SYSTEM "releases.xml">
+<!ENTITY ch.cfgfile SYSTEM "cfgfile.xml">
+<!ENTITY ch.special SYSTEM "special.xml">
diff --git a/docs/chapters/import.xml b/docs/chapters/import.xml
new file mode 100644
index 0000000..443255d
--- /dev/null
+++ b/docs/chapters/import.xml
@@ -0,0 +1,479 @@
+<chapter id="gbp.import">
+ <title>Importing Sources</title>
+
+ <sect1 id="gbp.import.existing">
+ <title>Importing already existing &debian; packages</title>
+ <para>Importing an already existing &debian; package into a &git; repository is as easy as:
+<programlisting>
+ &gbp-import-dsc; package_0.1-1.dsc
+</programlisting>
+ This will create a new &git; repository named after the imported package, put
+ the upstream sources onto the <option>upstream-branch</option> and the
+ &debian; patch on the <option>debian-branch</option>. In case of a &debian;
+ native package, only the <option>debian-branch</option> is being used.
+ You can specify alternative branch names via the
+ <option>--upstream-branch</option> and <option>--debian-branch</option>
+ options, or via the <option>upstream-branch</option> and
+ <option>debian-branch</option> options in the configuration file.
+ </para>
+ <para>
+ If you want to be able to exactly recreate the original tarball
+ (orig.tar.gz) from &git;, you should also specify the
+ <option>--pristine-tar</option> option. This is recommended.
+ </para>
+ <para>
+ If you want to import further versions, you can change into your shiny new
+ &git; repository and just continue with the same command:
+<programlisting>
+ cd package/
+ &gbp-import-dsc; package_0.1-2.dsc
+ &gbp-import-dsc; package_0.1-3.dsc
+ &gbp-import-dsc; package_0.2-1.dsc
+</programlisting>
+ </para>
+ <para>
+Or you can import all versions at once using &gbp-import-dscs;:
+<programlisting>
+ &gbp-import-dscs; /path/to/history/package_*.dsc
+</programlisting>
+This will create a &git; repository if necessary and import all versions sorted
+by version number.
+ </para>
+ <para>You can also import all versions of a package known from the
+ <ulink url="http://snapshot.debian.org/">snapshot.debian.org</ulink> service
+ using the <option>--debsnap</option> option of &gbp-import-dscs;:
+<programlisting>
+ &gbp-import-dscs; --debsnap package
+</programlisting>
+ </para>
+ </sect1>
+
+ <sect1 id="gbp.import.new.upstream">
+ <title>Importing a new upstream version</title>
+ <para>Change into your &git; repository (which can be empty), make sure it
+ has all local modifications committed, and run either of:
+<programlisting>
+ &gbp-import-orig; <filename>/path/to/package_0.2.orig.tar.gz</filename>
+ &gbp-import-orig; <filename>/path/to/package_0.2.tar.bz2</filename>
+ &gbp-import-orig; <filename>/path/to/package-0.2/</filename>
+</programlisting>
+ This puts the upstream sources onto the <option>upstream-branch</option> and
+ tags them accordingly (the default tag format is
+ <replaceable>upstream/%(version)s</replaceable>).
+ </para>
+ <para>
+ The result is then placed onto
+ the <option>debian-branch</option>. The way this happens is
+ determined by the <option>--merge-mode</option> option. The
+ default mode <option>auto</option> replaces the upstream sources
+ while preserving the <filename>debian/</filename> directory for
+ 3.0 (quilt) packages. A &git; merge is used for all other source
+ format versions.
+ </para>
+ <para>
+ You can again specify different branch names via
+ the <option>--upstream-branch</option> and
+ <option>--debian-branch</option> options.
+ </para>
+ <para>If you are using <filename>debian/watch</filename> to keep
+ track of how to retrieve upstream sources, you can simply use
+ the <option>--uscan</option> option to download and import the
+ latest upstream version:
+<programlisting>
+ &gbp-import-orig; --uscan
+</programlisting>
+ </para>
+ <para>You can also filter out content
+ you don't want imported:
+<programlisting>
+ &gbp-import-orig; <option>--filter</option>=<replaceable>'CVS/*'</replaceable> <filename>/path/to/package_0.2.orig.tar.gz</filename>
+</programlisting>
+ The <option>--filter</option> option can be used multiple times for more
+ complex filtering.
+ </para>
+ <para>
+ If you expect a merge conflict, you can delay the merge to the
+ <option>debian-branch</option> via the <option>--no-merge</option> option and pull in
+ the changes from the <option>upstream-branch</option> later.
+ </para>
+ <para>
+ If you want to be able to exactly recreate the original tarball
+ (orig.tar.gz) from &git;, you should also specify the
+ <option>--pristine-tar</option> option. This is recommended.
+ </para>
+ <para>To customize the commit message used by &gbp-import-orig;, use
+ the <option>--import-msg</option> option. This string is a
+ &pyformat;, into which the
+ <replaceable>version</replaceable> variable is
+ interpolated. (i.e., use <replaceable>%(version)s</replaceable> in
+ your message to get the imported upstream version).
+ </para>
+
+ <sect2 id="gbp.import.error.recovery">
+ <title>Recovering from errors</title>
+ <para>In case of an error &gbp-import-orig; will by default
+ rollback (undo) all changes it has done to the repository so far:
+ </para>
+ <programlisting>
+ $ &gbp; import-orig --verbose --filter='CVS/*' --filter='.bzr/*' --filter='.hg/*' --filter='.svn/*' --upstream-version=1.9 ../gif2apng-1.9.tar.gz
+ gbp:info: Importing '../gif2apng-1.9.tar.gz' to branch 'upstream' (filtering out ["'CVS/*'", "'.bzr/*'", "'.hg/*'", "'.svn/*'"])...
+ gbp:info: Source package is gif2apng
+ gbp:info: Upstream version is 1.9
+ gbp:info: Merging to 'master'
+ gbp:error: Automatic merge failed.
+ gbp:error: Error detected, Will roll back changes.
+ gbp:info: Rolling back branch upstream by resetting it to a6733c1f1e190ac0ed4774abc3466e9986a6df5e
+ gbp:info: Rolling back branch pristine-tar by resetting it to 0ee24ac614c920e30af82d602882c2ee841c88e5
+ gbp:info: Rolling back tag upstream/1.9 by deleting it
+ gbp:info: Rolling back branch master by resetting it to ce99782336e83a56e8e579b3cdadf93b0c19e1a8
+ gbp:info: Rolling back failed merge of upstream/1.9
+ gbp:error: Rolled back changes after import error.
+ </programlisting>
+ <para>
+ In this case the import failed due to a merge conflict. Other
+ reasons are running out of disk space, problems when generating
+ the pristine-tar delta. If you don't want &gbp-import-orig; to
+ undo changes made to the repository use
+ the <option>--no-rollback</option>.
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="gbp.import.convert">
+ <title>Converting an existing &git; repository</title>
+ <para>
+ If the &git; repository wasn't created with &gbp-import-dsc;, you have to
+ tell &gbp-buildpackage; and friends where to find the upstream sources.
+ </para>
+ <sect2 id="gbp.import.upstream.on.branch">
+ <title>Upstream sources on a branch</title>
+ <para>
+ If the upstream sources are already on a separate branch, things are pretty
+ simple. You can either rename that branch to the default
+ <option>upstream-branch</option> name <emphasis>upstream</emphasis> with:
+<programlisting>
+ &gitcmd; branch -m upstream theupstream-branch
+</programlisting>
+ or you can tell &gbp-buildpackage; the name of the branch to use as
+ <option>upstream-branch</option>:
+<programlisting>
+<command>cat</command> &lt;&lt;EOF &gt; <filename>.git/gbp.conf</filename>
+[DEFAULT]
+# this is the upstream-branch:
+upstream-branch=theupstream-branch
+EOF
+</programlisting>
+ If you then use &gbp-import-orig; to import new upstream sources, they will
+ from now on end up on <emphasis>theupstream-branch</emphasis> and
+ merged to the <option>debian-branch</option>.
+ </para>
+ </sect2>
+ <sect2 id="gbp.import.upstream.not.on.branch">
+ <title>Upstream sources not on a branch</title>
+ <para>
+ If you don't have an upstream branch but started your repository with only
+ the upstream sources (not the &debian; patch), you can simply branch from that
+ point. So use &gitkcmd; or &gitcmd;-log to locate the commit-id of that commit
+ and create the upstream branch from there, e.g.:
+<programlisting>
+ &gitcmd; branch upstream $(&gitcmd; log --format='%H' | tail -1)
+</programlisting>
+ The important thing here is that the <envar>COMMIT_ID</envar> specifies a
+ point on the master branch that carried <emphasis>only</emphasis> the
+ upstream sources and not the &debian; modifications. The above example
+ assumes that this was the first commit to that repository.
+ </para>
+ <warning><para>There's currently no <emphasis>easy</emphasis> way to create the
+ <option>upstream-branch</option> if you never had the upstream sources
+ as a single commit. Using &gbp-import-orig; on such repositories might lead
+ to unexpected merge results.</para></warning>
+ <para>In order to fix this you can prepend the upstream sources as a
+ single commit to your tree using &git;'s <ulink
+ url="http://git.or.cz/gitwiki/GraftPoint">grafts</ulink>. Afterwards you
+ can simply create a branch as explained above and &gbp-import-orig; will
+ work as expected.</para>
+ <para>Alternatively, if you are only importing source from original tarballs
+ (for instance when converting from a Subversion repository where the
+ mergeWithUpstream was set for svn-buildpackage), you can create an empty
+ upstream branch with the following commands:
+<programlisting>
+ <command>git checkout</command> <option>--orphan</option> <replaceable>upstream</replaceable>
+ <command>git rm</command> <option>-rf</option> <replaceable>.</replaceable>
+ <command>git commit</command> <option>--allow-empty</option> <option>-m</option> <replaceable>'Initial upstream branch.'</replaceable>
+ <command>git checkout</command> <option>-f</option> <replaceable>master</replaceable>
+ # When not using 3.0 (quilt) with the default --merge-mode=auto
+ <command>git merge</command> <option>--allow-unrelated-histories</option> <replaceable>upstream</replaceable>
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="gbp.import.fromscratch">
+ <title>Starting a &debian; package from scratch</title>
+ <para>
+ So far, we assumed you already have a &debian; package to start with, but
+ what if you want to start a new package? First, create an empty repository:
+ </para>
+<programlisting>
+ <command>mkdir</command> package-0.1
+ <command>cd</command> package-0.1
+ <command>git init</command>
+</programlisting>
+ <para>Then, you import the upstream sources, branch off the
+ <option>upstream-branch</option> branch and add the &debian; files (e.g. via dh_make):
+<programlisting>
+ &gbp-import-orig; <option>-u</option> <replaceable>0.1</replaceable> <filename>../package-0.1.tar.gz</filename>
+ <command>dh_make</command>
+</programlisting>
+ That's it, you're done. If you want to publish your new repository, you can use &gbp-create-remote-repo;.
+ </para>
+ </sect1>
+
+ <sect1 id="gbp.import.upstream-git">
+ <title>When upstream uses Git</title>
+ <para>
+ If upstream uses &git; for development (and you don't want to
+ ignore that fact entirely), there are at least three ways to
+ handle packaging. The first one uses &git; exclusively and
+ creates the upstream tarballs from the upstream tag while the
+ second one still uses upstream tarballs but links your packaging
+ &git; history with upstreams &git; history. The third one also
+ uses a tarballs but does not link to the upstream history.
+ </para>
+
+ <sect2 id="gbp.import.upstream.git.notarball">
+ <title>No upstream tarballs</title>
+ <para>If upstream doesn't build upstream tarballs, or you
+ don't care about them, the simplest way is to clone
+ upstream's repository and create a separate packaging branch
+ in there. You will not need &gbp-import-orig; at all with
+ this workflow. &gbp-buildpackage; will handle creating the
+ upstream tarballs needed for the Debian source package.
+ </para>
+ <para>
+ For that to work you need to tell &gbp; what the
+ upstream tag format looks like. Therefore you either
+ use the <option>--git-upstream-tag</option> command line option
+ or the <option>upstream-tag</option> configuration file
+ variable to specify upstream's tag format.
+ </para>
+
+ <para>
+ For example a common upstream format is to put
+ a <replaceable>v</replaceable> in front of the version
+ number. In this case, the configuration option would look
+ like:
+ </para>
+<programlisting>
+[DEFAULT]
+upstream-tag = v%(version)s
+</programlisting>
+ <para>
+ <replaceable>version</replaceable> will be replaced by &gbp;
+ with the upstream version number as determined from
+ <filename>debian/changelog</filename>. The <replaceable>%()s</replaceable>
+ might be familiar from &pyformat;s. The option was placed in
+ the <emphasis>[DEFAULT]</emphasis> section instead of
+ the <emphasis>[buildpackage]</emphasis> section of the
+ configuration so other tools like &gbp-dch; make use of it
+ too.
+ </para>
+
+ <para>
+ Some upstreams use other formats though and don't separate
+ numbers by dots but rather by
+ underscore(<replaceable>_</replaceable>),
+ hyphen(<replaceable>-</replaceable>) or anything else. In
+ order to cope with that you can use version mangling of these
+ characters via substitution. The substitution works as
+ follows:
+ </para>
+<programlisting>
+[DEFAULT]
+upstream-tag = v%(version%.%_)s
+</programlisting>
+ <para>
+ This means that each occurrence
+ of <replaceable>.</replaceable> will be replaced
+ by <replaceable>_</replaceable> in the upstream version
+ number. For example the upstream
+ version <replaceable>1.2.3</replaceable> as determined from
+ the <filename>debian/changelog</filename> will be looked up
+ as &git; tag <replaceable>v1_2_3</replaceable> by
+ &gbp-buildpackage;.
+ </para>
+ <para>
+ If you want the substitution to be the <replaceable>%</replaceable> character
+ you have to escape it. E.g. <replaceable>%(version%-%\%)s</replaceable> will replace <replaceable>-</replaceable> with
+ <replaceable>%</replaceable>, transforming <replaceable>1-A.B.C</replaceable> to <replaceable>1%A.B.C</replaceable>.
+ Only a single replacement is supported and it can only replace a single character.
+ <warning>
+ <para>
+ Since some of the possible mangling characters
+ like <replaceable>_</replaceable> and <replaceable>%</replaceable> are also used to denote epochs and tilde revisions
+ these versions can't be reconstructed when mapping from &git; tags back to &debian; versions and will therefore break other tools
+ like &gbp-dch;. So use version mangling with care. It's better to come up with a Debian compatible tag format upstream.
+ See &dep14; for the currently used expansion rules for Debian version numbers.
+ </para>
+ </warning>
+ </para>
+
+ <para>If you're using &pristine-tar; you can make
+ &gbp-buildpackage; commit the generated tarball back to the
+ pristine-tar branch using
+ the <option>--git-pristine-tar-commit</option> option or you
+ can use &gbp-pristine-tar; after you've created the
+ tarballs.
+
+ This will make sure others building your package can exactly
+ regenerate the tarball you created when building the
+ &debian; package.
+ </para>
+
+ <sect3>
+ <title>Step by step</title>
+ <para>To not make any assumptions about &gbp;'s configuration, the following steps have all options given
+ in its long versions on the command line. You can add these
+ to &gbp.conf; to save lots of typing.
+ </para>
+
+ <para>First, we clone the upstream repository. To avoid any ambiguities between the &debian; packaging repository
+ and the upstream repository, we name the upstream repository <replaceable>upstream</replaceable> instead of the
+ default <replaceable>origin</replaceable>.
+<programlisting>
+ <command>git clone</command> --no-checkout -o upstream git://git.example.com/libgbp.git
+ <command>cd</command> libgbp
+ <command>git checkout</command> -b debian/sid v1.0
+</programlisting>
+ The above makes sure we have <replaceable>debian/sid</replaceable> for the &debian; packaging. We didn't create
+ any <replaceable>upstream/*</replaceable> branches; they're not needed for the packaging and only need to be
+ kept up to date. We started the branch at the commit corresponding to the tag <replaceable>v1.0</replaceable>.
+ </para>
+
+ <para>After adding the &debian; packaging, we build the package. This assumes you're using &pristine-tar;
+ and upstream uses a version number format as described above:
+<programlisting>
+ <command>gbp buildpackage</command> --git-pristine-tar --git-pristine-tar-commit --git-upstream-tag='v%(version)s' --git-debian-branch=debian/sid
+</programlisting>
+ When updating to a new upstream version, we simply fetch from upstream and merge in the new tag. Afterwards, we
+ update the changelog and build the package:
+<programlisting>
+ <command>git fetch</command> upstream
+ <command>git merge</command> v1.1
+ <command>gbp dch</command> --debian-branch=debian/sid --snapshot --auto debian/
+ &gbp-buildpackage; --git-ignore-new --git-pristine-tar --git-pristine-tar-commit --git-upstream-tag='v%(version)s'
+</programlisting>
+ Note that the above &gbp-dch; call makes sure we only pickup changes in the <filename>debian/</filename>
+ directory. Since we told it to build a snapshot changelog entry and we hadn't commit the changelog yet,
+ we need to tell &gbp-buildpackage; that the working directory is unclean via the <option>--git-ignore-new</option> option.
+ Once everything looks good, commit the changelog and build a release version:
+<programlisting>
+ <command>gbp dch</command> --release --auto --git-debian-branch=debian/sid
+ <command>git commit</command> -m"Release 1.1-1" debian/changelog
+ &gbp-buildpackage; --git-upstream-tag='v%(version)s' --git-debian-branch=debian/sid
+</programlisting>
+ If you want to share your repository with others, you can use &gbp-create-remote-repo; and &gbp-pull; as usual.
+ </para>
+ </sect3>
+ </sect2>
+
+ <sect2 id="gbp.import.upstream.git.tarball">
+ <title>Upstream tarballs and linked upstream history</title>
+ <para>If you want to track upstream's &git; but continue to import the upstream tarballs,
+ e.g. to make sure the tarball uploaded
+ to &debian; has the same checksum as upstream's, you can use the <option>--upstream-vcs-tag</option> option
+ when importing new tarballs with &gbp-import-orig;. Assuming you have the upstream source in your
+ repository with a tag <replaceable>v0.0.1</replaceable>, you can use:
+<programlisting>
+ &gbp-import-orig; --upstream-vcs-tag=v0.0.1 foo_0.0.1.orig.tar.gz
+</programlisting>
+ to add upstream's tag as additional parent to the merge commit.
+ See <ulink url="http://bugs.debian.org/664771">#664771</ulink> for more details.
+ </para>
+ </sect2>
+
+ <sect2 id="gbp.import.upstream.git.separate">
+ <title>Upstream tarballs and separate upstream history</title>
+ <para>
+ If you want to have upstream's &git; history available but
+ don't want to link it to your packaging history you can
+ simply keep it as a separate history. E.g. if you already have
+ a &git; repository with your packaging, change into that
+ repository and do:
+<programlisting>
+ &gitcmd; remote add upstream https://upstream.example.com/upstream.git
+ &gitcmd; fetch upstream
+</programlisting>
+ This will pull in upstream's &git; history into your repo but since
+ your packaging commits and upstreams commits have no common
+ parents the two histories will stay nicely separated.
+ Of course you can browse it and cherry-pick from it but
+ any remote repos you push to will not get upstream's history
+ by default unless you push any of upstream's refs.
+ <warning>
+ <para>
+ Since &git; has a single tag namespace pushing
+ changes with <command>git push --tags</command> will
+ push upstream's tags (and therefore it's history) too so
+ be sure to only push dedicated tag names.
+ </para>
+ </warning>
+ </para>
+ </sect2>
+
+ </sect1>
+ <sect1 id="gbp.branch.naming">
+ <title>Branch layout</title>
+ <para>
+ By default, &gbp; uses one branch to keep the &debian; packaging called <emphasis>master</emphasis>
+ and a branch to keep the upstream packaging called <emphasis>upstream</emphasis>.
+ </para>
+ <para>
+ This layout is simple to get started but falls short if one needs to maintain several versions of
+ the package at the same time. Therefore the following the &dep14; layout is recommended:
+ </para>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ debian/&lt;release&gt;
+ </term>
+ <listitem>
+ <para>
+ the &debian; packaging for a release <emphasis>jessie</emphasis>,
+ <emphasis>stretch</emphasis>, <emphasis>sid</emphasis>, <emphasis>jessie</emphasis>, <emphasis>jessie-backports</emphasis>, <emphasis>jessie-security</emphasis>
+ or <emphasis>experimental</emphasis>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ upstream/latest
+ </term>
+ <listitem>
+ <para>
+ the latest upstream sources.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ dfsg/latest
+ </term>
+ <listitem>
+ <para>
+ the DFSG-clean upstream sources in case the cleanup is done via a &git;
+ merge from upstream to this branch.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ In case &pristine-tar; is being used, there will be a single <emphasis>pristine-tar</emphasis>
+ branch that keeps all binary deltas.
+ </para>
+ </sect1>
+</chapter>
+
+<!-- LocalWords: tarballs
+ -->
diff --git a/docs/chapters/intro.xml b/docs/chapters/intro.xml
new file mode 100644
index 0000000..88c45a6
--- /dev/null
+++ b/docs/chapters/intro.xml
@@ -0,0 +1,134 @@
+<chapter id="gbp.intro">
+ <title>Introduction</title>
+ <para>
+ Welcome to git-buildpackage (short &gbp;), a system that integrates the
+ <ulink url="http://www.debian.org/">Debian</ulink> package build
+ system with <ulink url="http://git.or.cz/">Git</ulink>. The most
+ recent version of this manual can be found &manual;.
+ </para>
+ <para>
+ This is what &gbp; can do for you:
+ <itemizedlist>
+ <listitem><para>Initially import an existing &debian; package into &git;</para></listitem>
+ <listitem><para>Incrementally import new versions of a Debian
+ package into &git; e.g. for importing NMUs or to maintain
+ downstream modifications</para></listitem>
+ <listitem><para>Import new upstream versions from tarballs with optionally filters applied</para></listitem>
+ <listitem><para>Recreate the upstream tarball from information stored in &git;</para></listitem>
+ <listitem><para>Maintain a consistent branch and tag naming within a &git; repository, across
+ repositories or across a team of developers</para></listitem>
+ <listitem><para>Automatically sign generated tags</para></listitem>
+ <listitem><para>Make sure you have committed all changes to the right
+ branch before building and releasing</para>
+ </listitem>
+ <listitem><para>Execute hooks at various points of the package
+ build process e.g. to automatically push changes to remote
+ repositories</para>
+ </listitem>
+ <listitem><para>Integrate the build process with cowbuilder or other builders</para></listitem>
+ <listitem><para>Export to a clean build area before building the package</para></listitem>
+ <listitem><para>Generate debian/changelog automatically</para></listitem>
+ <listitem><para>Manage your quilt patches when using 3.0 (quilt)
+ source format</para></listitem>
+ </itemizedlist>
+ All of this is (hopefully) being done without restricting the user to certain usage patterns.
+ </para>
+
+<sect1 id="gbp.repository">
+ <title>Repository Layout and Terminology</title>
+ <para>It is recommended to have the &debian; packaging on a separate
+ branch than the upstream source <footnote><para>this, of course, has
+ no meaning for &debian; native packages</para></footnote>.
+ This is necessary to be able to import
+ and merge in new upstream versions via &gbp-import-orig;.
+ To distinguish these two branches, the following terminology
+ <footnote><para>corresponding to the command
+ line and config file options</para></footnote> is used:
+ </para>
+ <itemizedlist>
+ <listitem><para>The <option>debian-branch</option> (the default branch
+ name used in the &git; repository is <emphasis>master</emphasis>) holds
+ your current development work. That's the branch you usually cut your
+ releases from and the default branch new upstream releases are merged
+ onto.</para></listitem>
+
+ <listitem><para> The <option>upstream-branch</option> (the default
+ branch name used in the &git; repository is
+ <emphasis>upstream</emphasis>) holds the upstream releases. This can
+ either be a branch you import to or a branch of an upstream &git; repository
+ you pull from.</para></listitem>
+
+ <listitem><para> The <option>pristine-tar branch</option> (the default
+ branch name used in the &git; repository is
+ <emphasis>pristine-tar</emphasis>) holds the necessary additional
+ information to recreate the original tarball from the
+ <option>upstream-branch</option>. In order to use this feature, you need
+ to install the &pristine-tar; package.</para></listitem>
+
+ <listitem><para> There can be one or more <option>patch-queue</option> branches.
+ Every patch-queue branch is related to a
+ <option>debian-branch</option>. If the <option>debian-branch</option> is called
+ <emphasis>master</emphasis>, the corresponding patch-queue branch is
+ called <emphasis>patch-queue/master</emphasis>. The patch-queue branch is
+ the &debian; branch plus the contents of
+ <emphasis>debian/patches</emphasis> applied. These branches are managed
+ with &gbp-pq;.
+ </para></listitem>
+ </itemizedlist>
+
+ <para>You're completely
+ free to pick any repository layout; the branch names above are only
+ &gbp;'s defaults. They can be changed at any point in time,
+ and you can work with an arbitrary number of branches.
+ For example, branches like <emphasis>nmu</emphasis>,
+ <emphasis>backports</emphasis> or <emphasis>stable</emphasis> might
+ (temporarily or permanently) become your <option>debian-branch</option>,
+ and branches like <emphasis>dfsg</emphasis> or
+ <emphasis>snapshots</emphasis> might become your
+ <option>upstream-branch</option>&mdash;it doesn't matter if these branches
+ are maintained with &gbp-import-orig; or not.</para>
+ <para>
+ A recommended branch layout is described in <xref linkend="gbp.branch.naming"/>.
+ </para>
+
+ <para>Since &gbp-buildpackage; only works with local &git;-repositories,
+ you have to use <command>git push</command> in order to publish your
+ changes to remote repositories like <ulink
+ url="http://git.debian.org/">git.debian.org</ulink>; this can be
+ automated with &gbp-buildpackage;'s <option>post-tag</option>
+ hook.</para>
+</sect1>
+
+<sect1 id="gbp.workflow">
+ <title>Workflow</title>
+ <para>
+ A typical, simple workflow consists of the following steps:
+ </para>
+ <orderedlist>
+ <listitem><para>Initially import a &debian; package via &gbp-import-dsc;. This
+ imports the &debian; Package on the <option>debian-branch</option>
+ and the upstream sources on the <option>upstream-branch</option>.</para></listitem>
+ <listitem><para>Develop, test, commit changes. During this time, you can
+ always build the package with &gbp-buildpackage;. In case you have
+ uncommitted changes in your source tree, you can use the
+ <option>--git-ignore-new</option> option.</para></listitem>
+ <listitem><para>Optionally you can create the &debian; changelog entries
+ using &gbp-dch; and create snapshot releases for testing using its
+ <option>--snapshot</option> option.</para></listitem>
+ <listitem><para>Once satisfied, you can build the final package with
+ &gbp-buildpackage; <option>--git-tag</option>. This additionally
+ creates a tag within &git; so you can switch back to that version later
+ at any time. The format of the tags can be specified; tags can
+ be &gpg; signed.</para></listitem>
+ <listitem><para>When a new upstream version is released and upstream
+ isn't using &git;, you can import the new version via &gbp-import-orig;
+ onto the <option>upstream-branch</option>. &gbp-import-orig; will
+ by default try to merge the new upstream version onto the
+ <option>debian-branch</option> (you can skip the merge with
+ <option>--no-merge</option>). After resolving any potential conflicts,
+ go back to the second step.</para></listitem>
+ </orderedlist>
+ <para>These steps will be explained in more details in the following sections.</para>
+</sect1>
+
+</chapter>
diff --git a/docs/chapters/patches.xml b/docs/chapters/patches.xml
new file mode 100644
index 0000000..e93be5c
--- /dev/null
+++ b/docs/chapters/patches.xml
@@ -0,0 +1,387 @@
+<chapter id="gbp.patches">
+ <title>Working with Patches</title>
+
+ <para>
+ &gbp-pq; can be used to manage patches that modify the upstream
+ source inside
+ <filename>debian/patches/</filename>. This is mostly intended for
+ 3.0 (quilt) format source packages.
+ </para>
+
+ <para>
+ The basic idea is that patches are imported from you
+ &debian-branch; to a patch-queue branch with one patch file
+ in <filename>debian/patches/</filename> becoming one commit on the
+ the patch-queue branch. The created branch will be named after
+ the branch you imported from
+ with <filename>patch-queue/</filename> prepended. So if you do
+ your &debian; packaging on <filename>debian/sid</filename> and do
+ a
+ </para>
+<programlisting>
+ &gbp-pq-import;
+</programlisting>
+ <para>
+ then the newly created branch will be
+ called <filename>patch-queue/debian/sid</filename>.
+ </para>
+
+ <para>On the patch-queue branch you can work on the commits using
+ familiar &git; commands (rebase, commit --amend, etc). When done,
+ &gbp-pq; will be used to turn the commits on the patch-queue
+ branch into patch in <filename>debian/patches/</filename> files
+ again.
+ </para>
+
+ <para>
+ This workflow facilitates e.g. the cherry-picking of patches for
+ stable releases, the forward-porting of patches to new upstream
+ versions by using git rebase on the patch-queue branch (patches
+ already applied upstream are detected automatically) as well as
+ the reordering, dropping and renaming of patches without having to
+ resort to &quilt;. The generated patches
+ in <filename>debian/patches/</filename> have all the necessary
+ information to forward them upstream since they use a format similar
+ to <command>git-format-patch</command>.
+ </para>
+
+ <para>
+ The main drawback of this workflow is the lack of history on the
+ patch-queue branch since it is frequently droppend and
+ recreated. But there is full history on
+ the your &debian-branch;, of course.
+ </para>
+
+ <para>
+ Also, beware that &gbp-pq; currently has incomplete support for
+ <ulink url="http://dep.debian.net/deps/dep3/">DEP3</ulink> headers.
+ Initially, parsing with <command>git-mailinfo(1)</command> is attempted,
+ which supports only the <computeroutput>From</computeroutput> and
+ <computeroutput>Subject</computeroutput> fields. If neither of these are
+ present, &gbp-pq; will attempt to convert the patch from DEP3 format into
+ a <command>git-mailinfo(1)</command> compatible format. This involves first
+ loading <computeroutput>From</computeroutput> using the
+ <computeroutput>Author</computeroutput> field and
+ <computeroutput>Subject</computeroutput> using the first line of the
+ <computeroutput>Description</computeroutput> field. Then, any additional
+ fields (such as <computeroutput>Origin</computeroutput> and
+ <computeroutput>Forwarded</computeroutput>), and the remainder of the
+ <computeroutput>Description</computeroutput> (if any), will be appended to
+ the body.
+ </para>
+
+ <sect1 id="gbp.patches.workflow">
+ <title>Basic Workflow</title>
+
+ <para>
+ This example assumes you're working on a source 3.0 (quilt)
+ format package with patches
+ in <filename>debian/patches</filename> parseable
+ by <command>git-quiltimport(1)</command>. The git branch
+ currently checked out is named <filename>debian/sid</filename>.
+ </para>
+
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/pq-unapplied.png" format="PNG"/>
+ </imageobject>
+ <caption>
+ <para>
+ The &debian-branch; we start from.
+ </para>
+ </caption>
+ </mediaobject>
+
+ <para>Let's first create the patch-queue branch and import the
+ contents of <filename>debian/patches</filename> onto it using
+ &gbp-pq;
+ </para>
+
+<programlisting>
+ <command>cd <replaceable>REPO</replaceable></command>
+ &gbp-pq; import
+</programlisting>
+
+<para>
+ This will generate output like:
+<screen>
+ gbp:info: Trying to apply patches at 'aaa1011bfd5aa74fea43620aae94709de05f80be'
+ gbp:info: 18 patches listed in 'debian/patches/series' imported on 'patch-queue/debian/sid'
+</screen>
+ What happened is that &gbp-pq; imported each patch file and switched
+ you to the newly created patch-queue branch
+ (<filename>patch-queue/debian/sid</filename>) automatically.
+</para>
+
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/pq-applied.png" format="PNG"/>
+ </imageobject>
+ <caption>
+ <para>
+ The patch-queue branch with patches
+ from <filename>debian/patches</filename> applied.
+ </para>
+ </caption>
+ </mediaobject>
+
+<para>
+ Now you can work on the patch-queue branch (add, remove, rebase,
+ test) to get your patches into shape:
+ <itemizedlist>
+ <listitem>
+ <para>
+ To add what will later become a patch
+ in <filename>debian/patches/</filename> simply make a
+ commit. The first line of the commit message will become the
+ patch name later. The following lines include the details of
+ what the patch does.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ To remove or edit commits use git rebase -i . The git
+ documentation explains how to work with git-rebase.
+ </para>
+ </listitem>
+ </itemizedlist>
+</para>
+
+<para>
+ Once satisfied with the commits let's regenerate the patches
+ in <filename>debian/patches/</filename> using &gbp-pq;. This will
+ switch you back to the branch <filename>debian/sid</filename> and
+ regenerate the patches using a method similar
+ to <command>git-format-patch</command>:
+</para>
+
+<programlisting>
+ &gbp-pq; export
+</programlisting>
+
+<para>You can now commit the result by using:</para>
+<programlisting>
+ &gitcmd; add debian/patches
+ &gitcmd; commit
+</programlisting>
+<para>
+ If you don't want to commit the result by hand each time you can also
+ pass <option>--commit</option> to the &gbp; <option>export</option>
+ command above.
+</para>
+
+<para>
+ Next you can update <filename>debian/changelog</filename> (e.g. by
+ running "&gbp-dch; <option>-S</option> <option>-a</option>") and
+ build the package as usual.
+</para>
+
+</sect1>
+<sect1 id="gbp.patches.newupstream">
+ <title>Importing a new upstream version</title>
+
+ <para>
+ To update your patches for a new upstream version one
+ </para>
+ <orderedlist>
+ <listitem>
+ <para>
+ Imports the current patches onto the patch-queue branch (if
+ not done already) using &gbp-pq-import;. This will allow you
+ to rebase the patches on the new upstream version later.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Imports the new upstream version with
+ &gbp-import-orig; <option>--uscan</option>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Rebases the patches onto the new upstream version using
+ &gbp-pq-rebase;. This will bring the patches up to date
+ regarding the new upstream version. Patches already applied
+ upstream can be dropped and remaining patches can be modified
+ to apply to the new version.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Exports the patches to <filename>debian/patches</filename> using
+ &gbp-pq-export;.
+ </para>
+ </listitem>
+ </orderedlist>
+
+ <para>
+ But don't worry if you forgot to do so before importing the new
+ version (or if another team member imported the version already).
+ </para>
+
+ <para>
+ In this case you can make &gbp-pq; figure out where to apply the
+ patches by using the <option>--time-machine=</option> option. The
+ following command
+ </para>
+
+<programlisting>
+ &gbp-pq-import; --force --time-machine=10
+</programlisting>
+
+ <para>
+ would drop your current patch-queue branch (if existent) and
+ create a new one by going back in your commit history as far as 10
+ commits to find a place where your patches still
+ apply <footnote><para>This is not necessarily
+ your debian-branch; HEAD since the new upstream
+ version might have changed so that the patches no longer apply
+ cleanly there.</para></footnote>. If it finds such a commit on
+ your &debian-branch; it will create the patch-queue branch from
+ there and switch you to that branch. You can now rework your
+ patches to apply to the new upstream version by using
+ &gbp-pq-rebase;:
+ </para>
+
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/pq-time-machine.png" format="PNG"/>
+ </imageobject>
+ <caption>
+ <para>
+ The patch-queue branch and &debian-branch; after importing the
+ patches.
+ </para>
+ </caption>
+ </mediaobject>
+
+<programlisting>
+ &gbp-pq-rebase;
+</programlisting>
+
+<para>
+ or you can invoke &gitcmd; <option>rebase</option> directly:
+</para>
+
+<programlisting>
+ &gitcmd; rebase -i debian/sid
+</programlisting>
+
+ <para>
+ Should the rebase fail (e.g. because the upstream source changed
+ at the same place your patches modify the code) you can resolve
+ this by using the options of &gitcmd; <option>rebase</option> (if
+ you simply want to abort use
+ &gitcmd; <option>rebase</option> <option>--abort</option>).
+ </para>
+
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/pq-rebase.png" format="PNG"/>
+ </imageobject>
+ <caption>
+ <para>
+ The patch-queue branch after rebasing the patches. Patches
+ that were merged upstream were dropped.
+ </para>
+ </caption>
+ </mediaobject>
+
+ <para>
+ Once done you can export your commits to patch files again:
+ </para>
+<programlisting>
+ &gbp-pq-export; --commit
+</programlisting>
+
+ <para>
+ The export will also switch you back to the &debian-branch;.
+ </para>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="images/pq-export.png" format="PNG"/>
+ </imageobject>
+ <caption>
+ <para>
+ The &debian-branch; after exporting the patches and committing the changes.
+ </para>
+ </caption>
+ </mediaobject>
+
+ <para>
+ See this in action in a
+ <ulink url="https://honk.sigxcpu.org/piki/projects/git-buildpackage/videos/gbp-pq-new-upstream-version.ogv">short
+ video</ulink>.
+ </para>
+
+</sect1>
+<sect1 id="gbp.patches.firstpatch">
+ <title>Adding your first patch</title>
+
+ <para>
+ If a package doesn't have any patches yet, these are the steps to add
+ your first patch:
+ </para>
+ <orderedlist>
+ <listitem>
+ <para>Launch an import. If there's nothing to import &gbp-pq; will just
+ create an empty branch and switch your working copy to it:
+<programlisting>
+ &gbp-pq-import;
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Create your first patch: edit files, test, commit your changes
+ using <command>git commit</command>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ To generate the new Quilt patch set use
+ </para>
+<programlisting>
+ &gbp-pq-export; --commit
+</programlisting>
+<para>
+ This will switch you back to your &debian-branch; branch, generate the
+ patches and commit them right away to this branch.
+</para>
+
+<para>
+ Skip the <option>--commit</option> if you don't want to commit
+ right away. If you want to pick the changelog message from the patch
+ see
+ <filename>/usr/share/doc/git-buildpackage/examples/gbp-add-patch</filename>.
+</para>
+ </listitem>
+ </orderedlist>
+</sect1>
+
+ <sect1 id="gbp.patches.team">
+ <title>Team maintenance</title>
+
+ <para>The easiest way is to not push out any patch-queue/* branches at all.
+ They can be recreated by any team member easily by using</para>
+
+<programlisting>
+ &gbp-pq-import; --force
+</programlisting>
+
+<para>
+ The patch-queue branch can also be re-created when pulling (this
+ will additionally drop your current patch-queue branch and recreate it
+ from <filename>debian/patches</filename>):</para>
+<programlisting>
+ &gbp-pull; --redo-pq
+</programlisting>
+
+<para>
+ Note that you can push out patch-queue branches. Other team
+ members must just be aware that branches in the patch-queue/
+ namespace are being rebased frequently and therefore cause
+ non fast-forward updates.
+</para>
+ </sect1>
+</chapter>
diff --git a/docs/chapters/releases.xml b/docs/chapters/releases.xml
new file mode 100644
index 0000000..50aa87c
--- /dev/null
+++ b/docs/chapters/releases.xml
@@ -0,0 +1,224 @@
+<chapter id="gbp.releases">
+ <title>Releases and Snapshots</title>
+
+ <para>
+ The &changelog; gives the &debian; package a version number
+ and keeps track of changes made to the package in a particular
+ version. While the changelog can still be maintained by hand we
+ can make use of &gbp-dch; to have our &git; commit messages end up
+ in &changelog;. This avoids the double maintenance of the &git;
+ commit history and the &debian; changelog. If you don't want a
+ one to one mapping of changelog entries to &git; commits you're
+ free to edit the changelog by hand at any point.
+ </para>
+
+ <para>
+ Not committing changelog entries with the actual modifications
+ also has the advantage that the changelog won't cause any trouble
+ when cherry-picking patches from different branches. It can be
+ created when releasing the package or after performing several
+ commits. Invocations of &dch; and &gbp-dch; can be mixed.
+ </para>
+
+ <sect1 id="gbp.maintaining.changelog">
+ <title>Maintaining <filename>debian/changelog</filename></title>
+ <sect2 id="gbp.changelog.release">
+ <title>Creating &changelog; just before the release</title>
+ <para>
+ The simplest way is doing all the changes to the
+ <option>debian-branch</option> without touching
+ <filename>debian/changelog</filename> at all. Then, when done, do:
+ </para>
+<programlisting>
+&gbp-dch; <option>--release</option>
+</programlisting>
+ <para>
+ This will look up the latest released version in the changelog,
+ increment the version in the &debian; changelog, generate changelog
+ messages from the corresponding &git; commit id up to the branch head, and
+ finally spawns an editor for final changelog editing.
+ </para>
+ </sect2>
+ <sect2 id="gbp.incremental.changelog">
+ <title>Incrementally filling &changelog;</title>
+ <para>
+ You can run &gbp-dch; without any options to make it add all
+ commit messages since the last &debian; tag to a new UNRELEASED
+ changelog section:
+<programlisting>
+&gbp-dch;
+</programlisting>
+ You can then commit the <filename>debian/changelog</filename>
+ to have your current changes recorded. Later invocations of
+ &gbp-dch; will check when
+ <filename>debian/changelog</filename> was last modified and
+ not add these commits again. Upon your last call of &gbp-dch;
+ before releasing the package add <option>--release</option>
+ again to have the <emphasis>UNRELEASED</emphasis> distribution
+ in the changelog turned into <emphasis>unstable</emphasis>.
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="gbp.snapshots">
+ <title>Creating snapshots with increasing version numbers</title>
+ <para>
+ The downside of the above methods is that you either have the
+ version number of the last release in &changelog; or that you
+ have a changelog entry with <emphasis>UNRELEASED</emphasis> that
+ has the same version number for all commits you do during the
+ development cycle of your package. Although this is common
+ practice in &debian; it means that also all your test builds
+ have the same version number which makes them hard to
+ distinguish in e.g. continuous integration pipelines.
+ </para>
+
+ <para>
+ To address these shortcomings &gbp-dch; has a
+ <option>--snapshot</option> option that can be used to create
+ (unreleased) snapshots for intermediate testing with a version
+ number that is lower than the one of the final package:
+ </para>
+<programlisting>
+&gbp-dch; <option>--snapshot</option>
+</programlisting>
+ <para>
+ will generate a snapshot release with a specially crafted version number
+ and a warning in the changelog that this is a snapshot release:
+ </para>
+<programlisting>
+git-buildpackage (0.3.7~1.gbp470ce2) UNRELEASED; urgency=low
+
+ ** SNAPSHOT build @470ce29ec7877705c844474a2fd89869aea0406d **
+
+ * add support for automatic snapshot
+</programlisting>
+ <para>
+ During subsequent calls with <option>--snapshot</option>, this
+ version number will continue to increase. Since the snapshot
+ banner contains the commit id of the current branch HEAD,
+ &gbp-dch; can figure out what to append to the changelog by itself
+ (even without committing the changelog first):
+<programlisting>
+&gbp-dch; <option>--snapshot</option>
+</programlisting>
+ will fetch the commit id from &changelog; and add changelog entries from that point to the
+ current HEAD&mdash;again auto incrementing the version number. If you don't want
+ to start at that commit id, you can specify any id or tag with:</para>
+<programlisting>
+&gbp-dch; <option>--since</option>=<replaceable>e76a6a180a57701ae4ae381f74523cacb3152780</replaceable> <option>--snapshot</option>
+</programlisting>
+ <para>
+ After testing, you can remove the snapshot header by a final &gbp-dch; call:
+ </para>
+<programlisting>
+&gbp-dch; <option>--release</option>
+</programlisting>
+ <para>
+ This will pick new commit if present and remove the specially
+ crafted version number and the snapshot header. If you want finer
+ control of what is being added you can again use the <option>--since</option>.
+ </para>
+ <sect2 id="gbp.release.numbers">
+ <title>Customizing snapshot numbers</title>
+ <para>If the auto incrementing of the snapshot number doesn't suite your needs, you
+ can give any Python expression that evaluates to a positive integer to
+ calculate the new snapshot number:</para>
+<programlisting>
+&gbp-dch; <option>-S</option> <option>-a</option> <option>--snapshot-number</option>=<replaceable>1</replaceable>
+&gbp-dch; <option>-S</option> <option>-a</option> <option>--snapshot-number</option>=<replaceable>'snapshot + 2'</replaceable>
+&gbp-dch; <option>-S</option> <option>-a</option> <option>--snapshot-number</option>=<replaceable>'os.popen("git-log --pretty=oneline | wc -l").readlines()[0]'</replaceable>
+&gbp-dch; <option>-S</option> <option>-a</option> <option>--snapshot-number</option>=<replaceable>`git-log --pretty=oneline debian/0.3.3 | wc -l`</replaceable>
+</programlisting>
+ <para>
+ You can also add the snapshot-number calculation to <filename>gbp.conf</filename>:
+ </para>
+<programlisting>
+[DEFAULT]
+snapshot-number = os.popen("git-log --pretty=oneline | wc -l").readlines()[0]
+</programlisting>
+ </sect2>
+ </sect1>
+
+ <sect1 id="gbp.release.commit">
+ <title>Tuning commit messages</title>
+ <para>
+ You can use <option>--full</option> to include the full commit
+ message in the changelog. If you want to tweak the formatting
+ there's a <option>--customizations</option>.
+ </para>
+ <para>
+ Additionally, there are tags <option>Closes:</option> and
+ <option>Thanks:</option> available to customize the commit message. Each tag
+ has to start at the beginning of a single line. For example, the git commit message
+<screen>
+New upstream version
+
+Closes: #1000
+Thanks: cool upstream
+</screen>
+ would result in a changelog entry:
+<screen>
+ * New upstream version (Closes: #1000) - thanks to cool upstream
+</screen>
+You can use the <option>Closes:</option> tag multiple times.
+ </para>
+ <para>
+ There are several tags to further customize what (and how) commit messages get
+ included into the changelog:
+ </para>
+ <para>
+ To exclude a commit from the generated changelog, use:
+ </para>
+ <para>
+<screen>
+Gbp-Dch: Ignore
+</screen>
+ This is e.g. useful if you're just fixing up a previous commit and couldn't
+ amend it, or for other janitorial commits that really don't need to end up in
+ the changelog. For example, the following git commit message
+<screen>
+Set correct branchnames in debian/gbp.conf
+
+Gbp-Dch: Ignore
+</screen>
+ will not show up in the generated changelog in any way.
+ </para>
+ <para>
+ To include the full commit message in the changelog, use:
+<screen>
+Gbp-Dch: Full
+</screen>
+ </para>
+ <para>
+ To only include the short description in the changelog and skip the body, use:
+<screen>
+Gbp-Dch: Short
+</screen>
+ The latter only takes effect when running &gbp-dch; with the
+ <option>--full</option> option since including only the short
+ description is the default.
+ </para>
+ <para>
+ Usually, changelog entries should correspond to a single &git;
+ commit. In this case, it's convenient to include the commit id
+ in the changelog entry. This has the advantage that it's easy
+ for people to identify changes without having to write very
+ extensive changelog messages&mdash;the link back to &git; can be
+ automated via the <option>Vcs-Browser</option> and
+ <option>Vcs-Git</option> fields in
+ <filename>debian/control</filename>. See <ulink
+ url="https://honk.sigxcpu.org/cl2vcs"> Cl2vcs</ulink> for how
+ this looks.
+ </para>
+ <para>
+ The inclusion of the commit id can be done automatically
+ via &gbp-dch;'s <option>--id-length</option> option. Using
+ <option>--id-length</option>=<replaceable>7</replaceable> would change the above example to:
+<screen>
+ * [571bfd4] New upstream version (Closes: #1000) - thanks to cool upstream
+</screen>
+ This makes it much easier to see which commit actually fixed bug #1000.
+ </para>
+</sect1>
+</chapter>
diff --git a/docs/chapters/special.xml b/docs/chapters/special.xml
new file mode 100644
index 0000000..8e4ec3f
--- /dev/null
+++ b/docs/chapters/special.xml
@@ -0,0 +1,250 @@
+ <chapter id="gbp.special">
+ <title>Special Use Cases</title>
+ <sect1 id="gbp.special.dfsgfree">
+ <title>Handling non-DFSG clean upstream sources</title>
+ <para>If you have to handle non-DFSG clean upstream sources, you can use a
+ different branch which you have to create once:
+ </para>
+<programlisting>
+ &gitcmd; branch dfsg_clean upstream
+</programlisting>
+ <para>
+ This creates the <emphasis>dfsg_clean</emphasis> branch from the tip of a
+ branch called <emphasis>upstream</emphasis>. Then, when importing a new
+ upstream version, you import the new version on the
+ <option>upstream-branch</option> (by default named
+ <emphasis>upstream</emphasis>) as usual and just don't merge to the
+ <emphasis>debian-branch</emphasis> (by default named
+ <emphasis>master</emphasis>):
+ </para>
+<programlisting>
+ &gbp-import-orig; --no-merge <filename>/path/to/nondfsg-clean-package_10.4.orig.tar.gz</filename>
+ &gitcmd; <option>tag</option> 10.4
+</programlisting>
+ <para>
+ After the import, you can switch to the <emphasis>dfsg_clean</emphasis>
+ branch and get the newly imported changes from the upstream branch:
+ </para>
+<programlisting>
+ &gitcmd; <option>checkout</option> dfsg_clean
+ &gitcmd; <option>pull</option> <filename>.</filename> upstream
+</programlisting>
+ <para>Now make this checkout dfsg clean (preferably by a cleanup script), commit
+ your changes and merge to your <option>debian-branch</option>:</para>
+<programlisting>
+ cleanup-script.sh
+ &gitcmd; commit -a -m "Make source dfsg clean"
+ &gitcmd; tag <replaceable>10.4.dfsg</replaceable>
+ &gitcmd; checkout <replaceable>master</replaceable>
+ &gitcmd; pull <replaceable>.</replaceable> <replaceable>dfsg_clean</replaceable>
+</programlisting>
+ </sect1>
+
+ <sect1 id="gbp.special.nmus">
+ <title>Importing NMUs</title>
+ <para>
+ First, create a branch that holds the NMUs from the tip of your
+ <option>debian-branch</option> (default is <emphasis>master</emphasis>) once:
+ </para>
+<programlisting>
+ &gitcmd; <option>branch</option> <replaceable>nmu</replaceable> <replaceable>master</replaceable>
+</programlisting>
+ <para>
+ To import an NMU, change into the git repository and use &gbp-import-dsc;:
+ </para>
+<programlisting>
+ &gitcmd; checkout <replaceable>master</replaceable>
+ &gbp-import-dsc; <option>--debian-branch</option>=<replaceable>nmu</replaceable> <filename>/path/to/package_1.0-1nmu0.dsc</filename>
+</programlisting>
+ <para>
+ This will import the NMU onto the branched named <emphasis>nmu</emphasis>
+ instead of the default <option>master</option>. This method can also
+ be used to import "old" releases into the &git; repository when migrating
+ to &git; from another VCS.
+ </para>
+ </sect1>
+
+ <sect1 id="gbp.special.pbuilder">
+ <title>Building with &cowbuilder;</title>
+ <para>
+ &cowbuilder; is nice tool to build Debian packages in a defined
+ environment. It makes sure all build-dependencies are specified
+ correctly by building the package in a clean chroot. As its
+ cousin &pbuilder; it can be extended via hooks to (e.g. run
+ autopkg tests) but doesn't need a tarball unpacked but uses copy
+ on write tree to speed up the build.
+ </para>
+
+ <para>
+ Since &cowbuilder; uses different command line arguments than
+ &debuild; and &dpkg-buildpackage;, we can't simply pass the
+ options to run it on the command line. To simplifiy the
+ integration we use a separate helper named &git-pbuilder;.
+ &gbp; has it's own command line option for this:
+ </para>
+<programlisting>
+ &gbp-buildpackage; <option>--git-pbuilder</option>
+</programlisting>
+ <para>
+ This will set the build command to run &git-pbuilder; (which
+ invokes &cowbuilder; by default) and the clean command
+ to <command>/bin/true</command>. It also activates the parsing
+ of several &git-pbuilder; related options
+ like <option>--git-dist</option>, <option>--git-arch</option> and
+ <option>--git-pbuilder-options</option>.
+ </para>
+
+ <para>
+ We can make &git-pbuilder; usage the default by adding it to
+ <filename>~/.gbp.conf</filename>:
+ </para>
+<programlisting>
+cat &lt;&lt;EOF &gt; <filename>~/.gbp.conf</filename>
+[DEFAULT]
+# We invoke cowbuilder via git-pbuilder. Arguments passed to &gbp-buildpackage;
+# will be passed to dpkg-buildpackage in the chroot
+pbuilder = True
+EOF
+</programlisting>
+ <para>
+ <command>git-pbuilder</command> defaults to building a package for the
+ <envar>sid</envar> distribution. If you want to build for another
+ distribution, pass this in the <option>--git-dist</option> option:
+
+<programlisting>
+ &gbp-buildpackage; --git-pbuilder --git-dist=jessie
+</programlisting>
+
+ If you want to use <command>debuild</command> again (without
+ modifying <filename>~/.gbp.conf</filename>), you can use:
+ </para>
+<programlisting>
+ &gbp-buildpackage; --git-no-pbuilder
+</programlisting>
+ <para>
+ In order for all of the above to work you have to create a
+ base chroot first using &git-pbuilder;:
+ </para>
+<programlisting>
+ <command>git-pbuilder</command> create
+</programlisting>
+ <para>
+ This can later be updated using:
+ </para>
+<programlisting>
+ <command>git-pbuilder</command> update
+</programlisting>
+ </sect1>
+
+ <sect1 id="gbp.special.hacking">
+ <title>Working on random packages</title>
+ <para>
+ Whenever you need to work on an arbitrary &debian; package, you can check it
+ right into &git; with one command:
+<programlisting>
+ &gbp-import-dsc; apt:<filename>package</filename>
+ cd <filename>package</filename>
+ &gitcmd; branch debian
+</programlisting>
+ </para>
+ <para>
+ This uses <command>apt-get</command> to download the source package,
+ puts the orig tarball on the <option>upstream-branch</option> and the
+ &debian; changes on the <option>debian-branch</option> (by default
+ <emphasis>master</emphasis>). The second command
+ creates a branch called <emphasis>debian</emphasis>. Now you can easily
+ modify the package, revert changes you made, create other branches for
+ testing, see what changes you made, etc. When finished, just do</para>
+<programlisting>
+ &gitcmd; commit -a
+ &gitcmd; diff debian --
+</programlisting>
+ <para>
+ to get a nice patch that can be submitted to the &debian; BTS. You can also
+ fetch the source package from a URL:
+
+<programlisting>
+ &gbp-import-dsc; <filename>http://mentors.debian.net/debian/pool/main/i/ipsec-tools/ipsec-tools_0.7.3-9.dsc</filename>
+</programlisting>
+
+ The import works incrementally; you can import new versions on top of
+ already imported ones for e.g. easy review of changes.
+ </para>
+
+ </sect1>
+ <sect1 id="gbp.special.sloppytarball">
+ <title>Sloppy tarball creation</title>
+ <para>
+ By default &gbp-buildpackage; uses <filename>debian/changelog</filename> to detect
+ the upstream version and build the corrsponding tarball either via &pristine-tar; or
+ by using <command>git archive</command> directly. This ensures that the tarball matches
+ what's in the Debian archive already.
+ </para>
+ <para>
+ But there might be cases where you don't want to follow these
+ packaging practice and create a fresh <emphasis>dirty</emphasis>
+ tarball that contains additional changes. Such tarballs are not
+ suitable for upload into the archive but might be helpful in local testing.
+ </para>
+ <para>
+ Especially when getting started and when you first want to get a
+ package built and dive into &gbp-dch;, &git-pbuilder; and the 3.0
+ (quilt) format later to find out what exactly
+<programlisting>
+dpkg-source: info: local changes detected, the modified files are:
+ hello-debhelper/configure
+dpkg-source: error: aborting due to unexpected upstream changes, see /tmp/hello-debhelper_2.8-1.diff.har2Xx
+dpkg-source: info: you can integrate the local changes with dpkg-source --commit
+</programlisting>
+means.
+ </para>
+ <para>
+ For that you can force &gbp-buildpackage; to create a tarball
+ from the <emphasis>debian-branch</emphasis> dropping
+ the <filename>debian/</filename> directory. This will give you a
+ tarball that is very close to your current working copy except
+ for the packaging.
+ </para>
+<programlisting>
+ &gbp-buildpackage; --git-ignore-new --git-force-create --git-upstream-tree=SLOPPY --git-no-pristine-tar
+</programlisting>
+ <para>
+ The important part is the <emphasis>SLOPPY</emphasis> above. The
+ other options are there to prevent &gbp-buildpackage; from using
+ &pristine-tar;, to force the creation of a new tarball and to
+ not abort if you have changes in your current working copy.
+
+ If you're still getting an error about <emphasis>unexpected
+ upstream changes</emphasis> make sure you have no uncommitted
+ changes in you workig copy (changes in
+ the <filename>debian/</filename> directory are o.k.) and no
+ files that are ignored by git
+ via <filename>.gitignore</filename>. A <command>git clean
+ -dfx</command> can help here.
+ </para>
+ <para>
+ Please don't use this tarball to upload to the Debian archive
+ since changes outside <filename>debian/</filename> need to be
+ represented by patches in <filename>debian/patches</filename>.
+ See the <command>dpkg-source</command> manpage for
+ details.
+ </para>
+ </sect1>
+
+ <sect1 id="gbp.special.pk4">
+ <title>Integrating with pk4</title>
+ <para>
+ &pk4; provides an easy way to fetch the sources of packages
+ currently installed on the system. In order to let pk4 provide
+ these packages as &git; repositories you can enable &gbp;'s
+ unpack hook:
+ </para>
+ <programlisting>
+mkdir -p ~/.config/pk4/hooks-enabled/unpack/
+ln -s /usr/share/pk4/hooks-available/unpack/gbp ~/.config/pk4/hooks-enabled/unpack/
+ </programlisting>
+ <para>
+ This will make sure packages are imported into a git respository after download.
+ </para>
+ </sect1>
+</chapter>
diff --git a/docs/common.ent b/docs/common.ent
new file mode 100644
index 0000000..7f18006
--- /dev/null
+++ b/docs/common.ent
@@ -0,0 +1,79 @@
+ <!ENTITY dhconfsection "<manvolnum>5</manvolnum>">
+ <!ENTITY dhsection "<manvolnum>1</manvolnum>">
+ <!ENTITY rpm-mansection "<manvolnum>1</manvolnum>">
+
+ <!ENTITY dhemail "<email>agx@sigxcpu.org</email>">
+ <!ENTITY dhfirstname "<firstname>Guido</firstname>">
+ <!ENTITY dhsurname "<surname>Günther</surname>">
+ <!ENTITY dhusername "Guido Günther">
+ <!ENTITY rpm-email "<email>markus.lehtonen@linux.intel.com</email>">
+ <!ENTITY rpm-firstname "<firstname>Markus</firstname>">
+ <!ENTITY rpm-surname "<surname>Lehtonen</surname>">
+ <!ENTITY rpm-username "Markus Lehtonen">
+
+ <!ENTITY cowbuildercmd "<command>cowbuilder</command>">
+ <!ENTITY debuildcmd "<command>debuild</command>">
+ <!ENTITY gbp "<command>gbp</command>">
+ <!ENTITY gbp-builder-mock "<command>gbp-builder-mock</command>">
+ <!ENTITY gbp-buildpackage "<command>gbp&nbsp;buildpackage</command>">
+ <!ENTITY gbp-buildpackage-rpm "<command>gbp&nbsp;buildpackage-rpm</command>">
+ <!ENTITY gbp-clone "<command>gbp&nbsp;clone</command>">
+ <!ENTITY gbp-config "<command>gbp&nbsp;config</command>">
+ <!ENTITY gbp-create-remote-repo "<command>gbp&nbsp;create-remote-repo</command>">
+ <!ENTITY gbp-dch "<command>gbp&nbsp;dch</command>">
+ <!ENTITY gbp-export-orig "<command>gbp&nbsp;export-orig</command>">
+ <!ENTITY gbp-import-dsc "<command>gbp&nbsp;import-dsc</command>">
+ <!ENTITY gbp-import-dscs "<command>gbp&nbsp;import-dscs</command>">
+ <!ENTITY gbp-import-orig "<command>gbp&nbsp;import-orig</command>">
+ <!ENTITY gbp-import-srpm "<command>gbp&nbsp;import-srpm</command>">
+ <!ENTITY gbp-pq "<command>gbp&nbsp;pq</command>">
+ <!ENTITY gbp-pq-export "<command>gbp&nbsp;pq&nbsp;<option>export</option></command>">
+ <!ENTITY gbp-pq-import "<command>gbp&nbsp;pq&nbsp;<option>import</option></command>">
+ <!ENTITY gbp-pq-rebase "<command>gbp&nbsp;pq&nbsp;<option>rebase</option></command>">
+ <!ENTITY gbp-pq-rpm "<command>gbp&nbsp;pq-rpm</command>">
+ <!ENTITY gbp-pristine-tar "<command>gbp&nbsp;pristine-tar</command>">
+ <!ENTITY gbp-pull "<command>gbp&nbsp;pull</command>">
+ <!ENTITY gbp-push "<command>gbp&nbsp;push</command>">
+ <!ENTITY gbp-rpm-ch "<command>gbp rpm-ch</command>">
+ <!ENTITY gbp-tag "<command>gbp tag</command>">
+ <!ENTITY gbp.conf "<filename>gbp.conf</filename>">
+ <!ENTITY git-pbuilder "<command>git-pbuilder</command>">
+ <!ENTITY git-qemubuilder "<command>git-pbuilder</command>">
+ <!ENTITY gitcmd "<command>git</command>">
+ <!ENTITY gitkcmd "<command>gitk</command>">
+ <!ENTITY pbuildercmd "<command>pbuilder</command>">
+ <!ENTITY pdebuildcmd "<command>pdebuild</command>">
+ <!ENTITY quilt "<command>quilt</command>">
+ <!ENTITY rpmbuild "<command>rpmbuild</command>">
+ <!ENTITY uscan "<command>uscan</command>">
+ <!ENTITY wget "<command>wget</command>">
+
+ <!ENTITY debian-branch "<option>debian-branch</option>">
+ <!ENTITY upstream-branch "<option>upstream-branch</option>">
+
+ <!ENTITY apt-get "<productname>apt-get</productname>">
+ <!ENTITY cowbuilder "<productname>Cowbuilder</productname>">
+ <!ENTITY dch "<productname>dch</productname>">
+ <!ENTITY debian "<productname>Debian</productname>">
+ <!ENTITY debuild "<productname>Debuild</productname>">
+ <!ENTITY dget "<productname>dget</productname>">
+ <!ENTITY dpkg-buildpackage "<productname>Dpkg-buildpackage</productname>">
+ <!ENTITY dpkg-source "<productname>Dpkg-source</productname>">
+ <!ENTITY git "<productname>Git</productname>">
+ <!ENTITY gitloaddirs "<productname>Git_load_dirs</productname>">
+ <!ENTITY gnu "<acronym>GNU</acronym>">
+ <!ENTITY gpg "<productname>GPG</productname>">
+ <!ENTITY gpl "&gnu; <acronym>GPL</acronym>">
+ <!ENTITY lintian "<productname>lintian</productname>">
+ <!ENTITY mock "<productname>mock</productname>">
+ <!ENTITY pbuilder "<productname>Pbuilder</productname>">
+ <!ENTITY pk4 "<productname>pk4</productname>">
+ <!ENTITY pristine-tar "<productname>pristine-tar</productname>">
+ <!ENTITY svn-buildpackage "<productname>svn-buildpackage</productname>">
+
+ <!ENTITY changelog " <filename>debian/changelog</filename>">
+
+ <!ENTITY dep14 "<ulink url='http://dep.debian.net/deps/dep14/'><citetitle>DEP-14</citetitle></ulink>">
+ <!ENTITY manual "<ulink url='https://honk.sigxcpu.org/piki/projects/git-buildpackage'>here</ulink>">
+ <!ENTITY pyformat "<ulink url='https://docs.python.org/2/library/stdtypes.html#string-formatting'><citetitle>Python format string</citetitle></ulink>">
+
diff --git a/docs/copyright.xml b/docs/copyright.xml
new file mode 100644
index 0000000..59bf77f
--- /dev/null
+++ b/docs/copyright.xml
@@ -0,0 +1,22 @@
+ <para>
+ git-buildpackage, all associated scripts and programs, this manual,
+ and all build scripts are Copyright &copy; 2006-2017 Guido Günther.
+ </para>
+ <para>
+ 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; Version 2 of the License.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, please see
+ &lt;http://www.gnu.org/licenses/&gt;
+ </para>
diff --git a/docs/gbp.css b/docs/gbp.css
new file mode 100644
index 0000000..c3d017e
--- /dev/null
+++ b/docs/gbp.css
@@ -0,0 +1,132 @@
+body {
+ font-family: Verdana, "Bitstream Vera Sans", sans-serif;
+ font-size: large;
+}
+
+h1 {
+ font-weight: normal;
+ font-variant: small-caps;
+}
+
+h2, h3 {
+ font-weight: normal;
+}
+
+a {
+ color: #182C41 !important;
+ font-weight: normal;
+ transition: background-color 0.2s linear;
+}
+
+a:hover {
+ background-color: #eef;
+}
+
+a[name] {
+ background-color: #fff;
+}
+
+pre {
+ background: #eef;
+ border: solid 1px #aaf;
+ padding: 0.5em;
+}
+
+pre.programlisting .command, pre.programlisting .filename, pre.programlisting .option, pre.programlisting .replaceable {
+ background: none;
+}
+
+.command {
+ font-style: italic;
+ font-weight: normal;
+ padding: 1px;
+}
+
+.filename {
+ background: #eee;
+ padding: 1px;
+}
+
+.option {
+ background: rgba(150,150,150,0.2);
+ padding: 1px;
+}
+
+.replaceable {
+ background: rgba(150,150,150,0.2);
+ padding: 1px;
+}
+
+table.variablelist {
+ border: none;
+}
+
+.variablelist th,td {
+ border: solid 1px #ddd;
+}
+
+div.book div.toc {
+ background: url("gbp.svg") no-repeat;
+ background-position: center;
+}
+
+div .titlepage table p.title {
+ font-size: 300%;
+ font-weight: normal;
+ margin: 0.5em;
+}
+
+body.chapter table.navigation th {
+ font-weight: normal;
+ margin: 0.5em;
+}
+
+body.appendix table.navigation th {
+ font-weight: normal;
+ margin: 0.5em;
+}
+
+body.refentry table.navigation th {
+ font-weight: normal;
+ margin: 0.5em;
+}
+
+body.refentry code.option, body.refentry tt.replaceable, body.refentry tt.filename {
+ background: none;
+}
+
+table.warning {
+ border-style: solid;
+ border-width: 5px;
+ border-collapse: collapse;
+ background: rgba(252, 175, 62, 0.1);
+ border-color: rgba(252, 175, 62, 0.2);
+}
+
+table.warning td {
+ border-width: 2px;
+ border-color: rgba(252, 175, 62, 0.2);
+}
+
+table.footnotes td {
+ border: none;
+}
+
+div.mediaobject {
+ background: #f5f5f5;
+ border-width: 2px;
+ border-color: lightgrey;
+ border-style: solid;
+}
+
+div.mediaobject img {
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+ padding: 5px 0px 0px 0px;
+}
+
+div.mediaobject p {
+ text-align: center;
+ font-style: italic;
+}
diff --git a/docs/gbp.svg b/docs/gbp.svg
new file mode 100644
index 0000000..c32847f
--- /dev/null
+++ b/docs/gbp.svg
@@ -0,0 +1,433 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="127.27471mm"
+ height="121.55948mm"
+ viewBox="0 0 127.27471 121.55948"
+ version="1.1"
+ id="svg4919"
+ inkscape:version="0.92.0 r15299"
+ sodipodi:docname="gbp-greyscale.svg">
+ <title
+ id="title5724">Git-buildpackage Logo</title>
+ <defs
+ id="defs4913">
+ <filter
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Greyscale"
+ id="filter4703">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0 0 0 1 0 "
+ id="feColorMatrix4701" />
+ </filter>
+ <radialGradient
+ r="5"
+ fy="41.5"
+ fx="5"
+ cy="41.5"
+ cx="5"
+ gradientTransform="matrix(0.990017,0,0,1.1,32.1147,-5.15)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3718-7"
+ xlink:href="#linearGradient3681"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3681"
+ inkscape:collect="always">
+ <stop
+ id="stop3683"
+ offset="0"
+ style="stop-color:black;stop-opacity:1;" />
+ <stop
+ id="stop3685"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ r="5"
+ fy="41.5"
+ fx="5"
+ cy="41.5"
+ cx="5"
+ gradientTransform="matrix(0.99001,0,0,1.1,-14.88523,-86.15)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient3720-36"
+ xlink:href="#linearGradient3681"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(1.179548,0,0,1,-4.219389,0)"
+ y2="34.999718"
+ x2="17.554192"
+ y1="46.000275"
+ x1="17.554192"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3722-5"
+ xlink:href="#linearGradient3703"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3703">
+ <stop
+ id="stop3705"
+ offset="0"
+ style="stop-color:black;stop-opacity:0;" />
+ <stop
+ style="stop-color:black;stop-opacity:1;"
+ offset="0.5"
+ id="stop3711" />
+ <stop
+ id="stop3707"
+ offset="1"
+ style="stop-color:black;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4210"
+ id="linearGradient5912"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.329903,0,-8.66441)"
+ x1="24.990499"
+ y1="49.424099"
+ x2="23.451571"
+ y2="14.38251" />
+ <linearGradient
+ id="linearGradient4210">
+ <stop
+ style="stop-color:#eaba6f;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4212" />
+ <stop
+ style="stop-color:#b97a1b;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop4214" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="45.934479"
+ x2="43.00663"
+ y1="30.554907"
+ x1="23.451576"
+ id="linearGradient4565-1"
+ xlink:href="#linearGradient4559"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4559"
+ inkscape:collect="always">
+ <stop
+ id="stop4561"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop4563"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="17.4375"
+ x2="26.5625"
+ y1="17.5"
+ x1="35.1875"
+ id="linearGradient4538-3"
+ xlink:href="#linearGradient3681"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="19"
+ x2="23.9375"
+ y1="19"
+ x1="28.0625"
+ id="linearGradient4766-9"
+ xlink:href="#linearGradient4559"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="11.473516"
+ x2="30.007845"
+ y1="7.1424866"
+ x1="23.157747"
+ id="linearGradient4776-0"
+ xlink:href="#linearGradient4770"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4770"
+ inkscape:collect="always">
+ <stop
+ id="stop4772"
+ offset="0"
+ style="stop-color:#fcf3e6;stop-opacity:1;" />
+ <stop
+ id="stop4774"
+ offset="1"
+ style="stop-color:#fcf3e6;stop-opacity:0;" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Greyscale"
+ id="filter4699">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0 0 0 1 0 "
+ id="feColorMatrix4697" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Greyscale"
+ id="filter4695">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0 0 0 1 0 "
+ id="feColorMatrix4693" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Greyscale"
+ id="filter4691">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0 0 0 1 0 "
+ id="feColorMatrix4689" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Greyscale"
+ id="filter4687">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0 0 0 1 0 "
+ id="feColorMatrix4685" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Greyscale"
+ id="filter4683">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0 0 0 1 0 "
+ id="feColorMatrix4681" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Greyscale"
+ id="filter4679">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0.21 0.72 0.072 0 0 0 0 0 1 0 "
+ id="feColorMatrix4677" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.98994949"
+ inkscape:cx="52.745428"
+ inkscape:cy="200.19578"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata4916">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Git-buildpackage Logo</dc:title>
+ <dc:date>2017-02-05</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Guido Günther</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title></dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <cc:license
+ rdf:resource="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-37.614072,-80.242321)">
+ <g
+ id="g5718">
+ <g
+ style="opacity:0.4;filter:url(#filter4703)"
+ transform="matrix(2.8665475,0,0,2.3453609,33.88756,91.335301)"
+ id="g3713-0">
+ <path
+ inkscape:connector-curvature="0"
+ style="opacity:1;fill:url(#radialGradient3718-7);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1"
+ d="M 37.064781,35 H 42 v 11 h -4.935219 z"
+ id="rect1907-9" />
+ <path
+ inkscape:connector-curvature="0"
+ transform="scale(-1)"
+ style="opacity:1;fill:url(#radialGradient3720-36);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1"
+ d="M -9.9351835,-46 H -5 v 11 h -4.9351835 z"
+ id="rect3689-63" />
+ <path
+ inkscape:connector-curvature="0"
+ style="opacity:1;fill:url(#linearGradient3722-5);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.20000052;stroke-opacity:1"
+ d="M 9.9351835,35 H 37.064782 V 46 H 9.9351835 Z"
+ id="rect3693-85" />
+ </g>
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ sodipodi:nodetypes="ccccccccccccc"
+ id="rect3115-6"
+ d="M 11.76152,11.650434 23,10.962934 l 0.03125,0.6875 h 11.73537 c 1.342971,0 2.174135,0.505422 2.736635,1.894444 l 1.9375,5.830122 v 20.618876 c 0,1.326522 -1.081164,2.394444 -2.424135,2.394444 H 9.8865199 c -1.3429709,0 -2.4241352,-1.067922 -2.4241352,-2.394444 V 19.375 l 1.875,-5.955122 c 0.375,-1.076522 1.0811643,-1.769444 2.4241353,-1.769444 z"
+ style="color:#000000;display:block;overflow:visible;visibility:visible;fill:url(#linearGradient5912);fill-opacity:1;fill-rule:nonzero;stroke:#a0670c;stroke-width:1.00000083;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ style="color:#000000;display:block;overflow:visible;visibility:visible;opacity:0.50549454;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient4565-1);stroke-width:1.00000095;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ d="m 12.492646,12.612816 h 21.566315 c 1.258979,0 2.03816,0.473773 2.56548,1.775816 l 1.816325,5.465046 V 39.55642 c 0,1.243457 -0.638546,1.869507 -1.897524,1.869507 h -26.30833 c -1.2589786,0 -1.7725248,-0.68855 -1.7725248,-1.932007 V 19.853678 L 10.220121,14.27146 c 0.351547,-1.009112 1.013546,-1.658644 2.272525,-1.658644 z"
+ id="path3847-1"
+ sodipodi:nodetypes="ccccccccccc"
+ inkscape:r_cx="true"
+ inkscape:r_cy="true"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ sodipodi:nodetypes="ccccccccc"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4740-1"
+ d="m 8.375,19.875 14.6875,0.375 -0.03783,-4.687168 c 0,0 6.559069,-0.156768 6.559069,-0.156768 0,0 -6.565647,-0.218175 -6.565647,-0.218175 L 23,13.125 22.710804,13.093243 22.625,19 Z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.50549454;fill:#000000;fill-opacity:0.75568183;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ sodipodi:nodetypes="cccccc"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4742-5"
+ d="M 8.5,19.8125 22.625,19.03125 22.75,12.125 22.28125,18.53125 9.3125,19.4375 Z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f5ddb8;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ sodipodi:nodetypes="cccccc"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4744-9"
+ d="M 22.3125,18.5625 8,19.6875 l 1.875,-6.25 c 0.499807,-0.989872 1.396594,-1.384276 2.5625,-1.375 l 10.34375,-0.625 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#dcbd8e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ sodipodi:nodetypes="ccccc"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4746-8"
+ d="m 23,13.125 v 5.9375 l 14.6875,0.0625 -2.222881,-5.929687 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.42857145;fill:url(#linearGradient4538-3);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ sodipodi:nodetypes="cccccc"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4748-4"
+ d="M 39.3125,19.375 24.75,12.625 24.5,4.9375 36.07369,11.644276 c 0.430764,0.31509 0.903173,0.75525 1.17631,1.293224 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#c68b31;fill-opacity:1;fill-rule:evenodd;stroke:#a0670c;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4758-8"
+ d="m 23,19 v 1 h 15 l -0.4375,-1 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.23076926;fill:url(#linearGradient4766-9);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4768-1"
+ d="m 25.190679,12.246913 -0.176777,-6.4081549 9.987884,5.7894369 -9.501748,-5.2149127 0.176777,5.5242717 3.49134,2.165515 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#linearGradient4776-0);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="matrix(2.8665446,0,0,2.8665446,33.88757,70.227331)"
+ inkscape:r_cy="true"
+ inkscape:r_cx="true"
+ id="path4528-0"
+ d="m 23,19 -0.0625,-3.4375 6.875,-0.125 0.375,0.25 -7,0.0625 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.10989009;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;filter:url(#filter4703)"
+ inkscape:connector-curvature="0" />
+ <rect
+ id="minus-7"
+ width="18.184086"
+ height="6.0613618"
+ x="67.35537"
+ y="142.2375"
+ style="fill:none;stroke:#030000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter4699)" />
+ <use
+ style="fill:none;stroke:#030000;stroke-opacity:1;filter:url(#filter4695)"
+ xlink:href="#minus-7"
+ transform="translate(24.24545,-1.8e-5)"
+ id="use5-6"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <use
+ style="fill:none;stroke:#030000;stroke-opacity:1;filter:url(#filter4691)"
+ xlink:href="#minus-7"
+ transform="translate(48.4909,-1.8e-5)"
+ id="use7-3"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <path
+ id="plus-1"
+ d="m 73.41672,153.12009 v 6.06128 h -6.06137 v 6.06141 h 6.06137 v 6.0614 h 6.06137 v -6.0614 h 6.06135 v -6.06141 h -6.06135 v -6.06128 z"
+ inkscape:connector-curvature="0"
+ style="fill:#c00000;fill-opacity:1;stroke:#00e400;stroke-width:0.00000148;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;filter:url(#filter4687)" />
+ <use
+ style="fill:#c00000;fill-opacity:1;stroke:#00e400;stroke-width:0.00000049;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;filter:url(#filter4683)"
+ xlink:href="#plus-1"
+ transform="translate(24.24545,2e-6)"
+ id="use10-7"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <use
+ style="fill:#c00000;fill-opacity:1;stroke:#00e400;stroke-width:0.00000049;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;filter:url(#filter4679)"
+ xlink:href="#plus-1"
+ transform="translate(48.4909,2e-6)"
+ id="use12-5"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </g>
+ </g>
+</svg>
diff --git a/docs/images/pq-applied.png b/docs/images/pq-applied.png
new file mode 100644
index 0000000..caa606e
--- /dev/null
+++ b/docs/images/pq-applied.png
Binary files differ
diff --git a/docs/images/pq-export.png b/docs/images/pq-export.png
new file mode 100644
index 0000000..f1e10ee
--- /dev/null
+++ b/docs/images/pq-export.png
Binary files differ
diff --git a/docs/images/pq-rebase.png b/docs/images/pq-rebase.png
new file mode 100644
index 0000000..a280950
--- /dev/null
+++ b/docs/images/pq-rebase.png
Binary files differ
diff --git a/docs/images/pq-time-machine.png b/docs/images/pq-time-machine.png
new file mode 100644
index 0000000..035af89
--- /dev/null
+++ b/docs/images/pq-time-machine.png
Binary files differ
diff --git a/docs/images/pq-unapplied.png b/docs/images/pq-unapplied.png
new file mode 100644
index 0000000..d472fed
--- /dev/null
+++ b/docs/images/pq-unapplied.png
Binary files differ
diff --git a/docs/man.gbp.xml b/docs/man.gbp.xml
new file mode 100644
index 0000000..f77a133
--- /dev/null
+++ b/docs/man.gbp.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+ <!ENTITY % COMMON SYSTEM "common.ent">
+ %COMMON;
+ <!ENTITY % MANPAGES SYSTEM "manpages/manpages.ent">
+ %MANPAGES;
+]>
+
+<reference>
+<title>git-buildpackage Manual</title>
+&man.gbp;
+&man.gbp.buildpackage;
+&man.gbp.buildpackage.rpm;
+&man.gbp.clone;
+&man.gbp.conf;
+&man.gbp.config;
+&man.gbp.config-files;
+&man.gbp.create.remote.repo;
+&man.gbp.dch;
+&man.gbp.exportorig;
+&man.gbp.importdsc;
+&man.gbp.importdscs;
+&man.gbp.importorig;
+&man.gbp.import.srpm;
+&man.gbp.pq;
+&man.gbp.pq.rpm;
+&man.gbp.pristine.tar;
+&man.gbp.pull;
+&man.gbp.push;
+&man.gbp.rpm.ch;
+&man.gbp.tag;
+</reference>
diff --git a/docs/manpages/gbp-buildpackage-rpm.xml b/docs/manpages/gbp-buildpackage-rpm.xml
new file mode 100644
index 0000000..9f8f438
--- /dev/null
+++ b/docs/manpages/gbp-buildpackage-rpm.xml
@@ -0,0 +1,678 @@
+<refentry id="man.gbp.buildpackage.rpm">
+ <refentryinfo>
+ <address>
+ &rpm-email;
+ </address>
+ <author>
+ &rpm-firstname;
+ &rpm-surname;
+ </author>
+ </refentryinfo>
+ <refmeta><refentrytitle>gbp-buildpackage-rpm</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-buildpackage-rpm</refname>
+ <refpurpose>Build RPM packages from a Git repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-buildpackage-rpm;
+ <arg><option>--git-[no-]ignore-new</option></arg>
+ <arg><option>--git-tag</option></arg>
+ <arg><option>--git-verbose</option></arg>
+ <arg><option>--git-color=</option>[auto|on|off]</arg>
+ <arg><option>--git-color-scheme=</option><replaceable>COLOR_SCHEME</replaceable></arg>
+ <arg><option>--git-notify=</option>[auto|on|off]</arg>
+ <arg><option>--git-tmp-dir</option>=<replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-vendor</option>=<replaceable>VENDOR</replaceable></arg>
+ <arg><option>--git-native</option>[auto|on|off]</arg>
+ <arg><option>--git-upstream-branch=</option><replaceable>TREEISH</replaceable></arg>
+ <arg><option>--git-packaging-branch=</option><replaceable>BRANCH_NAME</replaceable></arg>
+ <arg><option>--git-ignore-branch</option></arg>
+ <arg><option>--git-[no-]submodules</option></arg>
+ <arg><option>--git-builder=</option><replaceable>BUILD_CMD</replaceable></arg>
+ <arg><option>--git-cleaner=</option><replaceable>CLEAN_CMD</replaceable></arg>
+ <arg><option>--git-[no-]sign-tags</option></arg>
+ <arg><option>--git-keyid=</option><replaceable>GPG-KEYID</replaceable></arg>
+ <arg><option>--git-posttag=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-postbuild=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-postexport=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-prebuild=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-[no-]build</option></arg>
+ <arg><option>--git-[no-]hooks</option></arg>
+ <arg><option>--git-packaging-tag=</option><replaceable>TAG-FORMAT</replaceable></arg>
+ <arg><option>--git-upstream-tag=</option><replaceable>TAG-FORMAT</replaceable></arg>
+ <arg><option>--git-force-create</option></arg>
+ <arg><option>--git-no-create-orig</option></arg>
+ <arg><option>--git-upstream-tree=</option><replaceable>[TAG|BRANCH|TREEISH]</replaceable></arg>
+ <arg><option>--git-tarball-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-compression-level=</option><replaceable>LEVEL</replaceable></arg>
+ <arg><option>--git-export-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-export=</option><replaceable>TREEISH</replaceable></arg>
+ <arg><option>--git-packaging-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-spec-file=</option><replaceable>FILEPATH</replaceable></arg>
+ <arg><option>--git-export-sourcedir</option>=<replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-export-specdir</option>=<replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-[no-]pristine-tar</option></arg>
+ <arg><option>--git-[no-]pristine-tar-commit</option></arg>
+ <arg><option>--git-tag-only</option></arg>
+ <arg><option>--git-retag</option></arg>
+ <arg><option>--git-mock</option></arg>
+ <arg><option>--git-dist</option>=<replaceable>DISTRIBUTION</replaceable></arg>
+ <arg><option>--git-arch</option>=<replaceable>ARCHITECTURE</replaceable></arg>
+ <arg><option>--git-mock-options</option>=<replaceable>OPTIONS</replaceable></arg>
+ <arg><option>--git-mock-root</option>=<replaceable>ROOT</replaceable></arg>
+ <arg><option>--git-spec-vcs-tag</option>=<replaceable>TAG_FORMAT</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-buildpackage-rpm; is used to build RPM packages from a &git;
+ repository. It is an RPM counterpart for the &gbp-buildpackage; tool that
+ is designed for building Debian packages.
+ </para>
+ <para>
+ &gbp-buildpackage-rpm; will, in order:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ Verify that it is being executed from the proper location.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Verify that the repository doesn't contain any uncommitted source
+ changes.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Verify that it is being executed from the correct branch.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Export packaging files to a separate build area.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Create an orig source tarball if it doesn't exist.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Call <application>rpmbuild</application>(1) (or the application
+ specified via <option>--git-builder</option>), passing along all
+ command line arguments that don't start with --git-.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) tag the tree after a successful build.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) call a post build hook - e.g. to run
+ <productname>rpmlint</productname>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) call a post tag hook - e.g. to push the results to a
+ remote repository after creating the tag.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-[no-]ignore-new</option>
+ </term>
+ <listitem>
+ <para>
+ Don't abort if there are uncommitted changes in the source tree or
+ the current branch doesn't match the
+ <replaceable>PACKAGING-BRANCH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-tag</option>
+ </term>
+ <listitem>
+ <para>
+ Add a git tag after a successful build.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-builder=<replaceable>BUILD_CMD</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Use <replaceable>BUILD_CMD</replaceable> instead of
+ <command>rpmbuild -ba</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-cleaner=<replaceable>CLEAN_CMD</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Use <replaceable>CLEAN_CMD</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-verbose</option>
+ </term>
+ <listitem>
+ <para>
+ Verbose execution
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-color=</option>[auto|on|off]
+ </term>
+ <listitem>
+ <para>
+ Whether to use colored output.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-color-scheme=</option><replaceable>COLOR_SCHEME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Colors to use in output (when color is enabled). The format for
+ COLOR_SCHEME is
+ '&lt;debug&gt;:&lt;info&gt;:&lt;warning&gt;:&lt;error&gt;'.
+ Numerical values and color names are accepted, empty fields imply
+ the default color. For example --git-color-scheme='cyan:34::' would
+ show debug messages in cyan, info messages in blue and other messages
+ in default (i.e. warning and error messages in red).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-notify=</option>[auto|on|off]
+ </term>
+ <listitem>
+ <para>
+ Whether to send a desktop notification after the build.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-tmp-dir</option>=<replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Base directory under which temporary directories are created.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-vendor</option>=<replaceable>VENDOR</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Distribution vendor name.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-native=</option>[auto|on|off]
+ </term>
+ <listitem>
+ <para>
+ Define the 'nativity' of a package. The default value
+ <replaceable>auto</replaceable> makes &gbp-buildpackage-rpm; to
+ guess. Guessing is based on the existence of upstream branch.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-upstream-branch</option>=<replaceable>BRANCH_NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Branch to build the orig tarball from if
+ <option>--git-upstream-tree</option> is set to
+ <replaceable>BRANCH</replaceable>. Default is
+ <replaceable>upstream</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-packaging-branch</option>=<replaceable>BRANCH_NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ If you're not on this branch when invoking &gbp-buildpackage-rpm; it
+ will fail. Default is <replaceable>master</replaceable>. This is done
+ to make sure you don't accidentally release from a topic branch. Not
+ being on this branch will be ignored when using
+ <option>--git-ignore-new</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-ignore-branch</option>
+ </term>
+ <listitem>
+ <para>
+ Don't check if the current branch matches
+ <replaceable>PACKAGING-BRANCH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]submodules</option>
+ </term>
+ <listitem>
+ <para>
+ Include git submodules in the orig tarball.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]sign-tags</option>
+ </term>
+ <listitem>
+ <para>
+ GPG sign all created tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-keyid=</option><replaceable>GPG-KEYID</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this keyid for gpg signing tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-posttag=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Excecute <replaceable>COMMAND</replaceable> after tagging a new
+ version.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_TAG</envar> (the name
+ of the generated tag), <envar>GBP_BRANCH</envar> (the branch the
+ package was build from) and <envar>GBP_SHA1</envar> (the sha1 of the
+ commit the tag was created at).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-postbuild=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> after successful
+ build.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_CHANGES_FILE</envar>
+ (the name of the generated changes file),
+ <envar>GBP_BUILD_DIR</envar> (the build dir).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-postexport=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> after exporting the source
+ tree.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_GIT_DIR</envar> (the
+ repository the package is being built from),
+ <envar>GBP_TMP_DIR</envar> (the temporary directory where the sources
+ have been initially exported).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-prebuild=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> from the build directory
+ before calling <application>rpmbuild</application> or the application
+ specified via <option>--git-builder</option>.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_GIT_DIR</envar> (the
+ repository the package is being built from),
+ <envar>GBP_BUILD_DIR</envar> (the build dir).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]build</option>
+ </term>
+ <listitem>
+ <para>
+ Enable builder. Note: <option>--git-no-build</option> causes the
+ postbuild hook to be disabled, too.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]hooks</option>
+ </term>
+ <listitem>
+ <para>
+ Enable running all (cleaner, postexport, prebuild, postbuild, and
+ posttag) hooks. Note: the <option>--git-builder</option> command is
+ not affected by this option.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-packaging-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging released versions of the package.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-upstream-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags of upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-force-create</option>
+ </term>
+ <listitem>
+ <para>
+ Force creation of an orig tarball (overwriting a pre-existing one if
+ present).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-no-create-orig</option>
+ </term>
+ <listitem>
+ <para>
+ Don't try to create any orig tarball.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-export-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Export the packaging files from the current branch head (or the
+ treeish object given via <option>--git-export</option> to
+ <replaceable>DIRECTORY</replaceable> before building.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-export-sourcedir</option>=<replaceable>DIRECTORY</replaceable>
+ </term>
+ <term><option>--git-export-specdir</option>=<replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Subdirectories under export directory where packaging files are
+ exported. The default build options of rpmbuild builder are also
+ adjusted accordingly.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-export=</option><replaceable>TREEISH</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Instead of exporting the current branch head, export the treeish
+ object <replaceable>TREEISH</replaceable>. The special name
+ <replaceable>INDEX</replaceable> exports the current index,
+ <replaceable>WC</replaceable>) exports all files in the
+ current working directory.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-packaging-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Subdirectory that contains the RPM packaging files.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-spec-file=</option><replaceable>FILEPATH</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Relative path to the spec file to use. Special value
+ <replaceable>auto</replaceable> causes &gbp-buildpackage-rpm; to
+ search and guess. Other values cause the
+ <option>--git-packaging-dir</option> option to be ignored: the
+ directory of the spec file is used, instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-upstream-tree=</option><replaceable>[TAG|BRANCH|TREEISH]</replaceable>
+ </term>
+ <listitem>
+ <para>
+ How to find the upstream sources used to generate the tarball.
+ <replaceable>TAG</replaceable> looks at a tag corresponding to the
+ version in the changelog. <replaceable>BRANCH</replaceable> looks at
+ the upstream branch given via the
+ <option>--git-upstream-branch</option> option. Other values are
+ interpreted as treeishs.
+ </para>
+ <para>
+ This doesn't have any effect if <option>--git-pristine-tar</option>
+ is being used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-tarball-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Search for original tarballs in <replaceable>DIRECTORY</replaceable>
+ instead of generating them.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-compression-level=</option><replaceable>LEVEL</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Specifies the upstream tarball compression level if an upstream
+ tarball needs to be built.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-tag-only</option>
+ </term>
+ <listitem>
+ <para>
+ Don't build, only tag and run post-tag hooks.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-retag</option>
+ </term>
+ <listitem>
+ <para>
+ Don't fail tag operations if a tag with the same version already
+ exists, but, overwrite the existing tag, instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-pristine-tar</option>
+ </term>
+ <listitem>
+ <para>
+ Use pristine-tar when generating the upstream tarball if it doesn't
+ exist.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-pristine-tar-commit</option>
+ </term>
+ <listitem>
+ <para>
+ Commit the pristine-tar delta to the pristine-tar branch if a new
+ tarball was generated and the pristine-tar data isn't already there.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-mock</option>
+ </term>
+ <listitem>
+ <para>
+ Use &mock; to build the rpms by invoking &gbp-builder-mock;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-dist</option>
+ </term>
+ <listitem>
+ <para>
+ Build for this distribution when using &mock; (e.g.: epel-6).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-arch</option>
+ </term>
+ <listitem>
+ <para>
+ Build for this architecture when using mock, default is to build
+ for the current host architecture.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-mock-root</option>
+ </term>
+ <listitem>
+ <para>
+ The mock root to use. Defaults
+ to <replaceable>DIST</replaceable>-<replaceable>ARCH</replaceable>
+ from above.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-mock-options</option>
+ </term>
+ <listitem>
+ <para>
+ Additional options to pass to mock. Default is to pass no
+ additional options.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-spec-vcs-tag</option>=<replaceable>TAG_FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ &gbp-buildpackage-rpm; always automatically sets/updates the 'VCS:'
+ tag in the spec file after exporting. This option defines the format
+ string for the 'VCS:' tag. An empty value causes no 'VCS:' tag to be
+ inserted and possible old 'VCS:' tag to be removed. Otherwise, the
+ old 'VCS:' tag is updated or a new 'VCS:' tag is added if one does
+ not exist. In the format string '%(tagname)s' expands to the long tag
+ name (from git-describe) and '%(commit)s' expans to the sha1 of the
+ exported commit.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ Only build a source RPM with &rpmbuild;
+ </para>
+ <screen>
+ &gbp-buildpackage-rpm; -bs
+ </screen>
+ <para>
+ Build an RPM package with &rpmbuild; on a custom branch with the uncommitted
+ changes included.
+ </para>
+ <screen>
+ &gbp-buildpackage-rpm; --git-ignore-branch --git-export=WC
+ </screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ <para>
+ All options in the config files are specified without the 'git-' prefix.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.import.srpm"/>,
+ <xref linkend="man.gbp.pq.rpm"/>,
+ <xref linkend="man.gbp.rpm.ch"/>,
+ <citerefentry>
+ <refentrytitle>rpmbuild</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>mock</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &rpm-username; &rpm-email;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-buildpackage.xml b/docs/manpages/gbp-buildpackage.xml
new file mode 100644
index 0000000..9b6bcce
--- /dev/null
+++ b/docs/manpages/gbp-buildpackage.xml
@@ -0,0 +1,838 @@
+<refentry id="man.gbp.buildpackage">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta><refentrytitle>gbp-buildpackage</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-buildpackage</refname>
+ <refpurpose>Build &debian; packages from a &git; repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-buildpackage;
+ <arg><option>--git-[no-]ignore-new</option></arg>
+ <arg><option>--git-tag</option></arg>
+ <arg><option>--git-verbose</option></arg>
+ <arg><option>--git-color=</option>[auto|on|off]</arg>
+ <arg><option>--git-color-scheme</option>=<replaceable>COLOR_SCHEME</replaceable></arg>
+ <arg><option>--git-notify=</option>[auto|on|off]</arg>
+ <arg><option>--git-upstream-branch=</option><replaceable>TREEISH</replaceable></arg>
+ <arg><option>--git-debian-branch=</option><replaceable>BRANCH_NAME</replaceable></arg>
+ <arg><option>--git-ignore-branch</option></arg>
+ <arg><option>--git-[no-]submodules</option></arg>
+ <arg><option>--git-builder=</option><replaceable>BUILD_CMD</replaceable></arg>
+ <arg><option>--git-cleaner=</option><replaceable>CLEAN_CMD</replaceable></arg>
+ <arg><option>--git-[no-]overlay</option></arg>
+ <arg><option>--git-[no-]pbuilder</option></arg>
+ <arg><option>--git-[no-]qemubuilder</option></arg>
+ <arg><option>--git-dist=</option><replaceable>DIST</replaceable></arg>
+ <arg><option>--git-arch=</option><replaceable>ARCH</replaceable></arg>
+ <arg><option>--git-[no-]pbuilder-autoconf</option></arg>
+ <arg><option>--git-pbuilder-options</option>=<replaceable>PBUILDER_OPTIONS</replaceable></arg>
+ <arg><option>--git-[no-]sign-tags</option></arg>
+ <arg><option>--git-keyid=</option><replaceable>GPG-KEYID</replaceable></arg>
+ <arg><option>--git-postexport=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-prebuild=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-postbuild=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-posttag=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--git-[no-]hooks</option></arg>
+ <arg><option>--git-debian-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--git-debian-tag-msg=</option><replaceable>tag-msg-format</replaceable></arg>
+ <arg><option>--git-upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--git-force-create</option></arg>
+ <arg><option>--git-no-create-orig</option></arg>
+ <arg><option>--git-upstream-tree=</option><replaceable>[BRANCH|SLOPPY|TAG|TREEISH]</replaceable></arg>
+ <arg><option>--git-tarball-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-compression=</option><replaceable>TYPE</replaceable></arg>
+ <arg><option>--git-compression-level=</option><replaceable>LEVEL</replaceable></arg>
+ <arg rep='repeat'><option>--git-component=</option><replaceable>component</replaceable></arg>
+ <arg><option>--git-export-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--git-export=</option><replaceable>TREEISH</replaceable></arg>
+ <arg><option>--git-[no-]pristine-tar</option></arg>
+ <arg><option>--git-[no-]pristine-tar-commit</option></arg>
+ <arg><option>--git-[no-]-purge</option></arg>
+ <arg><option>--git-tag-only</option></arg>
+ <arg><option>--git-retag</option></arg>
+ <arg rep="repeat"><option>OPTION_PASSED_TO_BUILD_CMD</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-buildpackage; is used to build &debian; source and .deb packages from
+ a &git; repository.
+ </para>
+ <para>
+ &gbp-buildpackage; will, in order:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ Verify that it is being executed from the proper location.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Verify that the repository doesn't contain any uncommitted source
+ changes.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Verify that it is being executed from the correct branch.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) run a clean command specified
+ with <option>--git-cleaner</option>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) export the source tree to a separate build area.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Build an orig tarball if it doesn't exist. Optionally using &pristine-tar;.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) call a pre build hook.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Call <application>debuild</application>(1) or &cowbuilder;
+ (via <option>--git-pbuilder</option>) or the application
+ specified via <option>--git-builder</option> passing along
+ all arguments given to &gbp-buildpackage; on the command
+ line that don't start with --git-.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) tag the current commit after a successful build.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) call a post build hook - e.g. to run &lintian;.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) call a post tag hook - e.g. to push the results to a
+ remote repository after creating the tag.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <para>
+ All options are prefixed with <option>git-</option> to
+ distinguish options for &gbp-buildpackage; from options passed
+ to the <replaceable>BUILD_CMD</replaceable>:
+ </para>
+ <refsect2>
+ <title>Upstream tarball creation options</title>
+ <para>When &gbp-buildpackage; doesn't find a suitable upstream
+ tarball it will create one either using &pristine-tar;
+ or <command>git archive</command>. These options determine how the tarball is created:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-pristine-tar</option>
+ </term>
+ <listitem>
+ <para>
+ Use pristine-tar when generating the upstream tarball if
+ it doesn't exist. If this mode is enabled
+ the <option>--git-upstream-tag</option>, <option>--git-upstream-tree</option>
+ options have no effect.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-upstream-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags of upstream
+ versions to build the upstream tarballs. Default
+ is <replaceable>upstream/%(version)s</replaceable>. This
+ must be set correctly if you don't want to pass any
+ other options. The default matches what
+ &gbp-import-orig; uses to create tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-upstream-tree=</option><replaceable>[BRANCH|SLOPPY|TAG|TREEISH]</replaceable>
+ </term>
+ <listitem>
+ <para>
+ How to find the upstream sources used to generate the tarball.
+ <replaceable>TAG</replaceable> (the default) looks at a tag corresponding to the
+ version in the changelog. <replaceable>BRANCH</replaceable> looks at
+ the upstream branch given via the
+ <option>--git-upstream-branch</option> option. The <replaceable>SLOPPY</replaceable>
+ option looks at the debian branch given via
+ the <option>--git-debian-branch</option> and drops
+ the <filename>debian/</filename> dir.
+ </para>
+ <para>
+ Other values are interpreted as treeishs.
+ </para>
+ <para>
+ This doesn't have any effect if <option>--git-pristine-tar</option>
+ is being used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-upstream-branch</option>=<replaceable>BRANCH_NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Branch to build the orig tarball from if
+ <option>--git-upstream-tree</option> is set to
+ <replaceable>BRANCH</replaceable>. Default is
+ <replaceable>upstream</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-tarball-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Search for upstream tarballs in <replaceable>DIRECTORY</replaceable>
+ instead of generating them. If a tarball is not found here it
+ will be generated nevertheless.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-component=</option><replaceable>COMPONENT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ When generating tarballs create an additional upstream
+ tarball of directory <replaceable>COMPONENT</replaceable>
+ in the source tree. Using additional upstream tarballs is
+ a feature of the 3.0 (quilt) source format. See
+ the <command>dpkg-source</command> manpage for details. Note that the
+ <replaceable>--git-pristine-tar-commit</replaceable>
+ option is currently incompatible with this option.
+ </para>
+ <para>
+ This is considered an experimental feature and might
+ change incompatibly.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-pristine-tar-commit</option>
+ </term>
+ <listitem>
+ <para>
+ Commit the pristine-tar delta to the pristine-tar branch if a new
+ tarball was generated and the pristine-tar data isn't already there.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-force-create</option>
+ </term>
+ <listitem>
+ <para>
+ Force creation of an upstream tarball (overwriting a pre-existing one if
+ present).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-no-create-orig</option>
+ </term>
+ <listitem>
+ <para>
+ Don't try to create any upstream tarballs or to create
+ symlinks to existing tarballs in <option>tarball-dir</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]submodules</option>
+ </term>
+ <listitem>
+ <para>
+ Include &git; submodules in the orig tarball if present.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-compression=</option><replaceable>TYPE</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Specifies the upstream tarball compression type. This will be used to
+ locate and build the upstream tarball if necessary. The default is
+ <replaceable>auto</replaceable> which derives the compression type
+ from the pristine-tar branch if available and falls back to gzip
+ otherwise. Other options are <replaceable>gzip</replaceable>,
+ <replaceable>bzip2</replaceable>, <replaceable>lzma</replaceable> and
+ <replaceable>xz</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-compression-level=</option><replaceable>LEVEL</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Specifies the upstream tarball compression level if an upstream
+ tarball needs to be built.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Package build options</title>
+ <para>&gbp-buildpackage; can invoke different types of builders
+ to produce the source and binary packages. These options
+ determine which builder is invoked and how.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-builder=<replaceable>BUILD_CMD</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Use <replaceable>BUILD_CMD</replaceable> instead of
+ <command>debuild -i -I</command>. If you want to use
+ &cowbuilder; or &pbuilder; see
+ the <option>--git-pbuilder</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-cleaner=<replaceable>CLEAN_CMD</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Use <replaceable>CLEAN_CMD</replaceable> to clean the
+ source tree before the build. The default
+ is <command>/bin/true</command> (no cleaning).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-pbuilder</option>
+ </term>
+ <listitem>
+ <para>
+ Build the package using <command>git-pbuilder</command> (which
+ uses <command>cowbuilder</command> by default). Note that this overwrites
+ any <option>--git-builder</option> and
+ <option>--git-cleaner</option> options.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-qemubuilder</option>
+ </term>
+ <listitem>
+ <para>
+ Build package using <command>git-pbuilder</command> with
+ <command>qemubuilder</command>. Note that this overwrites any
+ <option>--git-builder</option> and <option>--git-cleaner</option>
+ options.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-dist=<replaceable>DIST</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Build for distribution <replaceable>DIST</replaceable>
+ when using &git-pbuilder; (either
+ via <option>--git-pbuilder</option>, <option>--git-qemubuilder</option>
+ or the corresponding configuration file options). If
+ unset build for the unstable distribution. The special
+ value <symbol>DEP14</symbol> will set the distribution to
+ build for from the branch name. I.e. if you're starting
+ the build from a branch named
+ <replaceable>debian/wheezy-backports</replaceable> the
+ distribution is set
+ to <replaceable>wheezy-backports</replaceable>. If the
+ branch is named <replaceable>downstream/sid</replaceable>
+ the distribution is set
+ to <replaceable>downstream_sid</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-arch=<replaceable>ARCH</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Build for architecture <replaceable>ARCH</replaceable>
+ when using &git-pbuilder; (either
+ via <option>--git-pbuilder</option>, <option>--git-qemubuilder</option>
+ or the corresponding configuration file options). If
+ unset no architecture is passed
+ to <command>git-pbuilder</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-pbuilder-autoconf</option>
+ </term>
+ <listitem>
+ <para>
+ Whether to try to autoconfigure &git-pbuilder; or to rely on
+ the settings in .pbuilderrc. See the &git-pbuilder; manpage
+ for details. Only takes effect when using &git-pbuilder;
+ (either via <option>--git-pbuilder</option>, <option>--git-qemubuilder</option>
+ or the corresponding configuration file options).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-pbuilder-options</option>
+ </term>
+ <listitem>
+ <para>
+ Options to pass to pbuilder when building via &git-pbuilder; (either
+ via <option>--git-pbuilder</option>, <option>--git-qemubuilder</option>
+ or the corresponding configuration file options). It does so by
+ using the <envar>GIT_PBUILDER_OPTIONS</envar> environment
+ variable to pass options to &git-pbuilder;.
+ </para>
+ <para>
+ As an example, to tell pbuilder to use an alternate
+ .pbuilderrc file, you may run &gbp-buildpackage; with
+ <option>--git-pbuilder-options=<replaceable>"--configfile
+ /tmp/my/pbuilderrc"</replaceable></option>. See
+ <citerefentry>
+ <refentrytitle>pbuilder</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>
+ for more options to pass through here.
+ </para>
+ <para>
+ If unset the <envar>GIT_PBUILDER_OPTIONS</envar> environment
+ variable will be left untouched.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-notify=</option>[auto|on|off]
+ </term>
+ <listitem>
+ <para>
+ Whether to send a desktop notification after the
+ build. This needs python3-notify2 installed.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Hook options</title>
+ <para>
+ Hooks allow you to run arbitrary commands at different stages of the
+ build. These options configure what will be run:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-postexport=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> after exporting the source
+ tree. Valid only if --git-export-dir has been specified. The working
+ directory of this hook is the toplevel directory of
+ the exported source tree.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_GIT_DIR</envar> (the
+ repository the package is being built from),
+ <envar>GBP_TMP_DIR</envar> (the temporary directory where the sources
+ have been initially exported).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-prebuild=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> from the build directory
+ before calling <application>debuild</application> or the application
+ specified via <option>--git-builder</option>.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_GIT_DIR</envar> (the
+ repository the package is being built from),
+ <envar>GBP_BUILD_DIR</envar> (the build dir).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-postbuild=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> after successful
+ build. The working directory of this hook is the
+ directory the package was built in.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_CHANGES_FILE</envar>
+ (the name of the generated changes file),
+ <envar>GBP_BUILD_DIR</envar> (the build dir).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-posttag=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> after tagging a new
+ version. The working directory of this hook is the
+ toplevel directory of the &git; repository.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_TAG</envar> (the name
+ of the generated tag), <envar>GBP_BRANCH</envar> (the branch the
+ package was build from) and <envar>GBP_SHA1</envar> (the sha1 of the
+ commit the tag was created at).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]hooks</option>
+ </term>
+ <listitem>
+ <para>
+ Enable running all (cleaner, postexport, prebuild, postbuild, and
+ posttag) hooks. Note: the <option>--git-builder</option> command is
+ not affected by this option.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Tagging options</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-tag</option>
+ </term>
+ <listitem>
+ <para>
+ Add a git tag after a successful build. It tags the
+ currently checked out commit except when you're on a
+ patch-queue branch. In that case the corresponding debian
+ branch is tagged.
+ </para>
+ <para>
+ This is a command line only option that cannot be
+ specified via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]sign-tags</option>
+ </term>
+ <listitem>
+ <para>
+ GPG sign all created tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-keyid=</option><replaceable>GPG-KEYID</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this keyid for gpg signing tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-debian-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging &debian; versions, default is
+ <replaceable>debian/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-debian-tag-msg=</option><replaceable>tag-msg-format</replaceable>
+ </term>
+ <listitem>
+ <para>Use this tag message format when signing &debian; versions,
+ default is <replaceable>%(pkg)s Debian release %(version)s</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-retag</option>
+ </term>
+ <listitem>
+ <para>
+ Don't fail tag operations if a tag with the same version
+ already exists. This is a command line only option that
+ cannot be specified via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-tag-only</option>
+ </term>
+ <listitem>
+ <para>
+ Don't build, only tag and run post-tag hooks. This is a
+ command line only option that cannot be specified via
+ &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Build area options</title>
+ <para>
+ &gbp-buildpackage; can export the source tree to a different
+ build-area before performing the build. This options specify
+ if and how this is done:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-export-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Export the current branch head (or the treeish object given via
+ <option>--git-export</option> to <replaceable>DIRECTORY</replaceable>
+ before building. If unset the source will not be
+ exported before starting the build.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-export=</option><replaceable>TREEISH</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Instead of exporting the current branch head, export the treeish
+ object <replaceable>TREEISH</replaceable>. The special name
+ <replaceable>INDEX</replaceable> exports the current index whereas
+ the special name <replaceable>WC</replaceable> exports the current
+ working copy as is.
+ </para>
+ <para>
+ Note that using <replaceable>WC</replaceable> enables the
+ <option>--git-ignore-branch</option>
+ and <option>--git-ignore-new</option> options as well
+ since when exporting the working copy there's no point in
+ enforcing any branch or modification checks.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git[-no]-purge</option>
+ </term>
+ <listitem>
+ <para>
+ Purge (remove) temporary build area after build. This is
+ the default but it can be turned off for e.g. debugging
+ purposes.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]overlay</option>
+ </term>
+ <listitem>
+ <para>
+ Extract upstream tarball from <option>tarball-dir</option> when
+ using the <option>export-dir</option> option (in analogy to
+ mergeWithUpstream in svn-bp). Also remove debian/ if
+ contained in the upstream tarball in case of 2.0 and 3.0
+ source formats.
+ </para>
+ <para>
+ This optios allows one to keep only the debian/ dir in the version
+ control system.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Safety options</title>
+ <para>
+ In order to make sure what you build is what you upload
+ &gbp-buildpackage; performs several safety checks. These
+ options allow one to configure and disable them:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-debian-branch</option>=<replaceable>BRANCH_NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ If you're not on this branch when invoking &gbp-buildpackage; it will
+ fail. Default is <replaceable>master</replaceable>. This is done to
+ make sure you don't accidentally release from a topic branch. Not
+ being on this branch will be ignored when using
+ <option>--git-ignore-new</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-ignore-branch</option>
+ </term>
+ <listitem>
+ <para>
+ Don't check if the current branch matches
+ <replaceable>DEBIAN-BRANCH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-[no-]ignore-new</option>
+ </term>
+ <listitem>
+ <para>
+ Don't abort if there are uncommitted changes in the source tree or
+ the current branch doesn't match the
+ <replaceable>DEBIAN-BRANCH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Color and verbosity options</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--git-verbose</option>
+ </term>
+ <listitem>
+ <para>
+ Verbose execution. Useful for debugging and bug reports.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-color=</option>[auto|on|off]
+ </term>
+ <listitem>
+ <para>
+ Whether to use colored output.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-color-scheme</option>=<replaceable>COLOR_SCHEME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Colors to use in output (when color is enabled). The format for
+ COLOR_SCHEME is
+ '&lt;debug&gt;:&lt;info&gt;:&lt;warning&gt;:&lt;error&gt;'.
+ Numerical values and color names are accepted, empty fields imply
+ the default color. For example --git-color-scheme='cyan:34::' would
+ show debug messages in cyan, info messages in blue and other messages
+ in default (i.e. warning and error messages in red).
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ Build a &debian; package using &git-pbuilder; which in turn invokes
+ &cowbuildercmd;. Instruct cowbuilder to build within a Wheezy chroot for
+ i386.
+ </para>
+ <screen>
+ &gbp-buildpackage; --git-pbuilder --git-arch=i386 --git-dist=wheezy
+ </screen>
+ <para>
+ Note that the above needs a &cowbuildercmd; chroot already. This can be
+ created using:
+ </para>
+ <screen>
+ DIST=wheezy ARCH=i386 &git-pbuilder; create
+ </screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ <para>
+ All options in the config files must be specified without the 'git-'
+ prefix. So
+ e.g. <option>--git-debian-branch</option>=<replaceable>debian/sid</replaceable>
+ becomes in &gbp.conf;:
+ </para>
+ <programlisting>
+ [buildpackage]
+ debian-branch = debian/sid
+ </programlisting>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.import.dsc"/>,
+ <xref linkend="man.gbp.import.dscs"/>,
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.dch"/>,
+ <citerefentry>
+ <refentrytitle>git-pbuilder</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>cowbuilder</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>dpkg-source</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>git-submodule</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-clone.xml b/docs/manpages/gbp-clone.xml
new file mode 100644
index 0000000..11a9343
--- /dev/null
+++ b/docs/manpages/gbp-clone.xml
@@ -0,0 +1,202 @@
+<refentry id="man.gbp.clone">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-clone</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-clone</refname>
+
+ <refpurpose>Clone a repository from remote</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-clone;
+
+ &man.common.options.synopsis;
+ <arg><option>--all</option></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ <arg><option>--debian-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--upstream-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--depth=</option><replaceable>depth</replaceable></arg>
+ <arg><option>--reference=</option><replaceable>repository</replaceable></arg>
+ <arg><option>--postclone=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--[no-]hooks</option></arg>
+ <arg><option>--repo-user=</option><option>[GIT|DEBIAN]</option></arg>
+ <arg><option>--repo-email=</option><option>[GIT|DEBIAN]</option></arg>
+ <arg choice="plain"><replaceable>repository</replaceable></arg>
+ <arg><replaceable>directory</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-clone; clones a remote repository and sets up tracking branches for
+ the <emphasis>debian</emphasis>, <emphasis>upstream</emphasis> and
+ <emphasis>pristine-tar</emphasis> branches. This way you can easily update
+ later using &gbp-pull;.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--all</option>
+ </term>
+ <listitem>
+ <para>Track all branches, not only <replaceable>debian</replaceable>
+ and <replaceable>upstream</replaceable>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>The branch in the Git repository the Debian package is being
+ developed on, default is <replaceable>master</replaceable>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>The branch in the &git; repository the upstream sources are put
+ onto. Default is <replaceable>upstream</replaceable>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--depth</option>=<replaceable>depth</replaceable>
+ </term>
+ <listitem>
+ <para>Git history depth, for creating shallow git clones.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--reference</option>=<replaceable>repository</replaceable>
+ </term>
+ <listitem>
+ <para>Local repository to use as alternate instead of re-copying data from remote repository.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pristine-tar</option>
+ </term>
+ <listitem>
+ <para>Track pristine tar branch.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]hooks</option></term>
+ <listitem>
+ <para>
+ Enable running hooks.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--postclone=</option><replaceable>COMMAND</replaceable></term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> after cloning the source
+ from the remote.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_GIT_DIR</envar> (the
+ repository the package is being built from).
+ </para>
+ <para>Note that if you clone a repository that contains a
+ hook configuration in <filename>debian/gbp.conf</filename>
+ this hook will not be run automatically to prevent execution
+ of untrusted code.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--repo-email=</option><option>[GIT|DEBIAN]</option></term>
+ <listitem>
+ <para>
+ When set to <option>DEBIAN</option>
+ use the <envar>DEBEMAIL</envar> environment variable to set the
+ user.email &git; configuration otherwise use &git;'s
+ defaults.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--repo-user=</option><option>[GIT|DEBIAN]</option></term>
+ <listitem>
+ <para>
+ When set to <option>DEBIAN</option>
+ use the <envar>DEBUSER</envar> environment variable to set the
+ user.name &git; configuration otherwise use &git;'s
+ defaults.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>repository</replaceable></term>
+ <listitem>
+ <para>
+ The (possibly remote) repository to clone from. This is
+ usually a &git; URL but some shortcuts are supported (see below).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><replaceable>directory</replaceable></term>
+ <listitem>
+ <para>
+ The directory to clone to.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ Clone a repository and setup a tracking branch for pristine-tar
+ as well:
+ </para>
+ <screen>
+ &gbp-clone; --pristine-tar git://honk.sigxcpu.org/git/git-buildpackage.git</screen>
+ <para>
+ Clone from the <emphasis>Git-Vcs</emphasis> URL of a package:
+ </para>
+ <screen>
+ &gbp-clone; vcsgit:libvirt</screen>
+ <para>
+ Clone from a github repository:
+ </para>
+ <screen>
+ &gbp-clone; github:agx/git-buildpackage</screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.pull"/>,
+ <xref linkend="man.gbp.conf"/>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>&dhusername; &dhemail;</para>
+
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-config.xml b/docs/manpages/gbp-config.xml
new file mode 100644
index 0000000..5adf7c9
--- /dev/null
+++ b/docs/manpages/gbp-config.xml
@@ -0,0 +1,100 @@
+<refentry id="man.gbp.config">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-config</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-config</refname>
+
+ <refpurpose>Query configuration values</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-config;
+
+ &man.common.options.synopsis;
+ <group choice='req'>
+ <arg choice="plain"><replaceable>command.option</replaceable></arg>
+ <arg choice="plain"><replaceable>command</replaceable></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-config; prints values from the configuration files. It interpolates the
+ value for <replaceable>option</replaceable> of
+ <replaceable>command</replaceable>.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXIT CODES</title>
+ <para>
+ When &gbp-config; finishes, it indicates success or failure with its exit code:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>0</option></term>
+ <listitem>
+ <para>Success.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>1</option></term>
+ <listitem>
+ <para>Failed to parse command line</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>2</option></term>
+ <listitem>
+ <para>The value did not exist</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>Print the value <option>upstream-branch</option> that &gbp-buildpackage;
+ would use:</para>
+ <screen>
+ $ gbp config buildpackage.upstream-branch
+ buildpackage.upstream-branch=upstream</screen>
+ <para>Print the values of all of &gbp-buildpackage; options</para>
+ <screen>
+ $ gbp config buildpackage
+ buildpackage.upstream-branch=upstream
+ buildpackage.debian-branch=master
+ ...</screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.conf"/>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>&dhusername; &dhemail;</para>
+
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-create-remote-repo.xml b/docs/manpages/gbp-create-remote-repo.xml
new file mode 100644
index 0000000..a1167e2
--- /dev/null
+++ b/docs/manpages/gbp-create-remote-repo.xml
@@ -0,0 +1,162 @@
+<refentry id="man.gbp.create.remote.repo">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-create-remote-repo</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-create-remote-repo</refname>
+
+ <refpurpose>Create remote repositories</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-create-remote-repo;
+ &man.common.options.synopsis;
+ <arg><option>--remote-url-pattern=</option><replaceable>url-pattern</replaceable></arg>
+ <arg><option>--remote-name=</option><replaceable>name</replaceable></arg>
+ <arg><option>--template-dir=</option><replaceable>directory</replaceable></arg>
+ <arg><option>--remote-config=</option><replaceable>config</replaceable></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ <arg><option>--debian-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--upstream-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--[no-]track</option></arg>
+ <arg><option>--[no-]bare</option></arg>
+ <group choice="opt">
+ <arg><replaceable>create</replaceable></arg>
+ <arg><replaceable>list</replaceable></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-create-remote-repo; creates a repository at a remote location via ssh
+ and pushes the local repository into it. It then sets up remote branch
+ tracking so you can use &gbp-pull; to update your repository from there.
+ </para>
+ <para>
+ Before performing any action on the remote location, it will print the
+ remote URL and ask for confirmation.
+ </para>
+ <para>
+ Note: By default, the remote repositories are created in the <systemitem
+ class="groupname">collab-maint</systemitem> repository on <systemitem
+ class="systemname">git.debian.org</systemitem>.
+ </para>
+ <para>
+ When invoked with <option>list</option> it lists the available
+ remote config templates.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--remote-url-pattern</option>=<replaceable>pattern</replaceable>
+ </term>
+ <listitem>
+ <para>Where to create the remote repository. The part
+ <replaceable>%(pkg)s</replaceable> will be replaced by the package name.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--remote-name</option>=<replaceable>name</replaceable>
+ </term>
+ <listitem>
+ <para>What name git will use when referring to that repository, e.g.
+ 'origin'.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--template-dir</option>=<replaceable>directory</replaceable>
+ </term>
+ <listitem>
+ <para>Template directory to pass to <command>git init</command> on the remote
+ side. This can be used to customize the remote repository, e.g. to set up hooks.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--remote-config</option>=<replaceable>config</replaceable>
+ </term>
+ <listitem>
+ <para>Name of a config file section in <filename>gbp.conf</filename>
+ that specifies the above parameters. See <xref linkend="man.gbp.conf"/>
+ manpage for details.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>The branch in the &git; repository the &debian; package is being
+ developed on, default is <replaceable>master</replaceable>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>The branch in the &git; repository the upstream sources are put
+ onto. Default is <replaceable>upstream</replaceable>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pristine-tar</option>
+ </term>
+ <listitem>
+ <para>Whether to push the pristine tar branch.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]track</option>
+ </term>
+ <listitem>
+ <para>Whether to set up branch tracking for the debian, upstream and
+ pristine-tar branches.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]bare</option>
+ </term>
+ <listitem>
+ <para>Whether to the remote repository should be a bare
+ repository (this is the default).</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.pull"/>,
+ <xref linkend="man.gbp.conf"/>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>&dhusername; &dhemail;</para>
+
+ </refsect1>
+</refentry>
+
+<!-- LocalWords: ssh
+ -->
diff --git a/docs/manpages/gbp-dch.xml b/docs/manpages/gbp-dch.xml
new file mode 100644
index 0000000..82a9833
--- /dev/null
+++ b/docs/manpages/gbp-dch.xml
@@ -0,0 +1,650 @@
+<refentry id="man.gbp.dch">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-dch</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-dch</refname>
+ <refpurpose>Generate the &debian; changelog from git commit messages</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-dch;
+ &man.common.options.synopsis;
+ <arg><option>--debian-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--debian-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--upstream-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--ignore-branch</option></arg>
+ <group>
+ <arg><option>-s</option> <replaceable>commitish</replaceable></arg>
+ <arg><option>--since=</option><replaceable>commitish</replaceable></arg>
+ </group>
+ <group>
+ <group>
+ <arg><option>-S</option></arg>
+ <arg><option>--snapshot</option></arg>
+ </group>
+ <group>
+ <arg><option>-R</option></arg>
+ <arg><option>--release</option></arg>
+ </group>
+ </group>
+ <group>
+ <arg><option>-N</option> <replaceable>version</replaceable></arg>
+ <arg><option>--new-version=</option><replaceable>version</replaceable></arg>
+ </group>
+ <group>
+ <arg><option>--bpo</option></arg>
+ <arg><option>--nmu</option></arg>
+ <arg><option>--qa</option></arg>
+ <arg><option>--security</option></arg>
+ <arg><option>--team</option></arg>
+ </group>
+ <arg><option>--distribution=</option><replaceable>name</replaceable></arg>
+ <arg><option>--force-distribution</option></arg>
+ <group>
+ <arg><option>-U</option> <replaceable>level</replaceable></arg>
+ <arg><option>--urgency=</option><replaceable>level</replaceable></arg>
+ </group>
+ <arg><option>--[no-]full</option></arg>
+ <arg><option>--[no-]meta</option></arg>
+ <arg><option>--meta-closes=bug-close-tags</option></arg>
+ <arg><option>--meta-closes-bugnum=bug-number-format</option></arg>
+ <arg><option>--snapshot-number=</option><replaceable>expression</replaceable></arg>
+ <arg><option>--id-length=</option><replaceable>number</replaceable></arg>
+ <arg><option>--git-log=</option><replaceable>git-log-options</replaceable></arg>
+ <arg><option>--[no-]git-author</option></arg>
+ <arg><option>--[no-]multimaint</option></arg>
+ <arg><option>--[no-]multimaint-merge</option></arg>
+ <arg><option>--spawn-editor=[always|never|snapshot|release]</option></arg>
+ <arg><option>--commit-msg=</option><replaceable>msg-format</replaceable></arg>
+ <group>
+ <arg><option>-c</option></arg>
+ <arg><option>--commit</option></arg>
+ </group>
+ <arg><option>--customizations=</option><replaceable>customization-file</replaceable></arg>
+ <arg><option>--postedit=</option><replaceable>COMMAND</replaceable></arg>
+ <arg rep='repeat'><option>--dch-opt=</option><replaceable>dch-options</replaceable></arg>
+ <arg><option>--verbose</option></arg>
+ <arg choice="plain"><replaceable><optional>path1 path2</optional></replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-dch; reads git commit messages and generates the &debian;
+ changelog from it. It starts at a given commit specified by the
+ <option>--since</option> option up to the current
+ <emphasis>HEAD</emphasis>. For each commit found it adds the
+ commit message to the changelog. If <option>--since</option> is
+ not given the commit to start from is determined by the
+ following rules (first one matches):
+ </para>
+
+ <orderedlist numeration="arabic">
+ <listitem><para>The start commit is read from the snapshot banner (see below for
+ details)</para></listitem>
+ <listitem><para>If the topmost version of the
+ <filename>debian/changelog</filename> is alread tagged. Use the commit
+ the tag points to as start commit.</para></listitem>
+ <listitem><para>The last commit that modified <filename>debian/changelog</filename> is
+ used as start commit.</para></listitem>
+ </orderedlist>
+ <para>
+ This is called automatic mode.
+ </para>
+ <para>
+ If the distribution of the topmost section in
+ <filename>debian/changelog</filename> is
+ <emphasis>UNRELEASED</emphasis>, the changelog entries will be
+ inserted into this section. Otherwise a new section will be
+ created.
+ </para>
+ <para>
+ If one ore more paths are given as arguments &gbp-dch; will only
+ include changes in <filename>debian/changelog</filename> that
+ affect these paths. E.g. using
+ <emphasis>debian/</emphasis> is a good choice if upstream uses
+ &git; and you don't want the upstream history to end up in
+ <filename>debian/changelog</filename>.
+ </para>
+ <para>
+ To restrict the selected changes even further you can use
+ use the <option>--git-log</option> option which is passed
+ on verbatim to <command>git log</command>. E.g. by using
+ <option>--git-log=</option><replaceable>"--author=Foo
+ Bar"</replaceable>.
+ </para>
+ <para>
+ The above relies on the <option>--debian-branch</option> option
+ pointing to the current branch and
+ <option>--upstream-branch</option> pointing to the corresponding
+ upstream branch in order to find the right merge points of these
+ branches. Furthermore &gbp-dch; must be able to identify git
+ tags from upstream and Debian version numbers. If you're not
+ using the defaults check the <option>--upstream-tag</option> and
+ <option>--debian-tag</option> options and make sure they match
+ the tags created by e.g. &gbp-import-orig; (when using tarballs)
+ or upstream's tagging pattern (when using upstream's git
+ directly).
+ </para>
+ <para>
+ If not changelog exists yet it is created and the version number
+ is derived from the last upstream tag if found.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the Git repository the Debian package is being
+ developed on, default is <replaceable>master</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Branch to determine the upstream version from.
+ Default is <replaceable>upstream</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-upstream-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags of upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-branch</option>
+ </term>
+ <listitem>
+ <para>
+ Don't check if the current branch matches
+ <replaceable>debian-branch</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-tag=</option><replaceable>tag-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Tag format used, when tagging debian versions,
+ default is <replaceable>debian/%(version)s</replaceable>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--since=</option><replaceable>committish</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Start reading commit messages at
+ <replaceable>committish</replaceable>.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--auto</option>,
+ <option>-a</option></term>
+ <listitem>
+ <para>
+ This option is ignored for compatibility with older
+ versions. It used to trigger automatic mode.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]meta</option></term>
+ <listitem>
+ <para>
+ Parse meta tags like <option>Closes:</option>,
+ <option>Thanks:</option> and <option>Gbp-Dch:</option>. See META TAGS
+ below.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--meta-closes=</option><replaceable>bug-close-tags</replaceable>
+ </term>
+ <listitem>
+ <para>
+ What meta tags to look for to generate bug-closing changelog entries.
+ The default is <literal>'Closes|LP'</literal> to support Debian and Launchpad.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--meta-closes-bugnum=</option><replaceable>bug-number-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ What regular expression should be used to parse out the bug number.
+ The default is <literal>'(?:bug|issue)?\#?\s?\d+'</literal>. Note: the regex should
+ suppress all portions of the bug number that are not wanted using
+ <literal>"(?:)"</literal>, see Python regex manual for details.
+ </para>
+ <para>
+ Example:
+ <option>--meta-closes-bugnum=</option><literal>"(?:bug)?\s*ex-\d+"</literal>
+
+ would match all of the following:
+ <screen>
+ Possible Txt Match? Result
+ ------------ ------ ------
+ bug EX-12345 Y EX-12345
+ ex-01273 Y ex-01273
+ bug ex-1ab Y ex-1
+ EX--12345 N</screen>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]full</option>
+ </term>
+ <listitem>
+ <para>
+ Include the full commit message in the changelog output.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--snapshot</option>,
+ <option>-S</option></term>
+ <listitem>
+ <para>
+ Create a snapshot release entry. It uses a snapshot
+ release number which is smaller than the final release
+ number and adds a warning banner to the changelog
+ entry. The version number is being auto incremented with
+ every new snapshot release.
+ </para>
+ <para>
+ The snapshot banner is also used by &gbp-dch; to determine which
+ entries are already in the changelog. This prevents duplicated
+ entries in <filename>debian/changelog</filename> when you did
+ not commit the changelog yet.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--snapshot-number=</option><replaceable>expression</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Python expression that gets eval()ed to the new snapshot number.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--release</option>,
+ <option>-R</option></term>
+ <listitem>
+ <para>
+ Remove any snapshot release banners and version suffixes
+ (if any), set the current distribution to
+ <replaceable>unstable</replaceable>, and open the
+ changelog for final tweaking. This option can't be set
+ via &gbp.conf;. It's usually used to finalize the
+ changelog before making a release.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--new-version=</option><replaceable>version</replaceable>,
+ <option>-N</option> <replaceable>version</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Add a new changelog section with version
+ <replaceable>newversion</replaceable>. Together with
+ <option>--snapshot</option>, the snapshot number will be appended to
+ <replaceable>newversion</replaceable>.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--team</option>
+ </term>
+ <listitem>
+ <para>
+ Create a Team upload changelog entry.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--bpo</option>
+ </term>
+ <listitem>
+ <para>
+ Increment the Debian release number for an upload to backports, and
+ add a backport upload changelog comment.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--nmu</option>
+ </term>
+ <listitem>
+ <para>
+ Increment the Debian release number for a non-maintainer upload.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--qa</option>
+ </term>
+ <listitem>
+ <para>
+ Increment the Debian release number for a Debian QA Team upload, and
+ add a QA upload changelog comment.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--security</option>
+ </term>
+ <listitem>
+ <para>
+ Increment the Debian release number for a Debian Security
+ Team non-maintainer upload, and add a "Security Team
+ upload" changelog comment.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--distribution=</option><replaceable>name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Set the distribution field to <replaceable>name</replaceable>.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--force-distribution</option>
+ </term>
+ <listitem>
+ <para>
+ Force the distribution specified with <option>--distribution</option>
+ to be used, even if it doesn't match the list of known distributions.
+ This option can't be set via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--urgency=</option><replaceable>level</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Set the urgency field to <replaceable>level</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-log=</option><replaceable>git-log-options</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Options passed on verbatim to git-log(1).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--id-length=</option><replaceable>N</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Include <replaceable>N</replaceable> digits of the commit id in the
+ changelog entry. Default is to not include any commit ids at all.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-regex=</option><replaceable>regex</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Ignore commit lines matching <replaceable>regex</replaceable>
+ when generating the changelog.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-author</option>
+ </term>
+ <listitem>
+ <para>
+ Use user.name and user.email from
+ <application>git-config</application>(1) for changelog trailer.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]multimaint-merge</option>
+ </term>
+ <listitem>
+ <para>
+ Merge commits by maintainer.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--spawn-editor=<replaceable>[always|never|snapshot|release]</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Whether to spawn an editor: always, never, when doing snapshots or when
+ doing a release.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--commit-msg=</option><replaceable>msg-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this format string for the commit message when committing the
+ generated changelog file (when <option>--commit</option> is given).
+ Default is
+ <replaceable>Update changelog for %(version)s release</replaceable>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--commit</option>
+ </term>
+ <listitem>
+ <para>
+ Commit the generated changelog.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--customizations=</option><replaceable>customization-file</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Load Python code from <replaceable>customization-file</replaceable>.
+ At the moment, the only useful thing the code can do is define a
+ custom format_changelog_entry() function.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--postedit=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Run<replaceable>COMMAND</replaceable> after changes to the changelog
+ file have been finalized, That is, after dch has been run and
+ possible text editor has been exited, but, before changes are
+ (possibly) committed to git.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--dch-opt=</option><replaceable>dch-option</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Pass option to &dch; verbatim. Note that &gbp-dch; invokes &dch;
+ multiple times and the option is passed to all invocations so not all
+ &dch; options make sense here. Options may also conflict
+ with options picked by &gbp-dch;.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>Snapshot mode</title>
+ <para>
+ Snapshot mode can be used for quick test and install cycles without
+ having to worry about version numbers or changelog entries.
+ </para>
+ <para>
+ When using <option>--snapshot</option> or <option>-S</option>, &gbp-dch;
+ uses a pseudo header in the Debian changelog to remember the last git
+ commit it added a changelog entry for. It also sets a version number
+ ending in
+ <replaceable>~&lt;snaspshotnumber&gt;.gbp&lt;commitid&gt;</replaceable>.
+ It automatically increments the snapshot number on subsequent invocations
+ of &gbp-dch; <option>-S</option> so that later snapshots automatically
+ have a higher version number. To leave snapshot mode, invoke &gbp-dch;
+ with the <option>--release</option> option. This removes the pseudo
+ header and unmangles the version number so the released version has a
+ higher version number than the snapshots.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>META TAGS</title>
+ <para>
+ Additional to the above options, the formatting of the commit message
+ in <filename>debian/changelog</filename> can be modified by special tags
+ (called Meta Tags)
+ given in the git commit message. Meta Tag processing can be activated via
+ the <option>--meta</option> option. The tags must start at the first column of
+ a commit message but can appear on any line.
+ They are of the form <option>Tagname</option>:
+ <replaceable>value</replaceable>. Valid Meta Tags are:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>Gbp-Dch</option>: <replaceable>action</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Supported actions are: <replaceable>Ignore</replaceable>
+ which will ignore this commit when
+ generating <filename>debian/changelog</filename>,
+ <replaceable>Short</replaceable> which will only use the
+ description (the first line) of the commit message when
+ generating the changelog entry (useful
+ when <option>--full</option> is given), and
+ <replaceable>Full</replaceable> which will use the full
+ commit message when generating the changelog entry (useful
+ when <option>--full</option> is not given).
+ </para>
+ <para>
+ In addition to <option>Gbp-Dch</option>, the
+ deprecated <option>Git-Dch</option> is still supported.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>Thanks</option>: <replaceable>msg</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Add a thanks message after the commit message.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>Closes</option>: <replaceable>bugnumber</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Indicate in the <filename>debian/changelog</filename> that the bug
+ was closed by this commit. See the <option>--meta-closes</option> on
+ how to extend this for other bugtrackers.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ The following git commit message:
+ </para>
+ <screen>
+ Document meta tags
+
+ so one doesn't have to consult the manual
+
+ Gbp-Dch: Short
+ Closes: #636088
+ Thanks: Raphaël Hertzog for the suggestion</screen>
+ <para>
+ Results in this <filename>debian/changelog</filename> entry:
+ </para>
+ <screen>
+ * Document meta tags.
+ Thanks to Raphaël Hertzog for the suggestion (Closes: #636088)</screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.import.dsc"/>,
+ <xref linkend="man.gbp.import.dscs"/>,
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ <ulink url="https://honk.sigxcpu.org/cl2vcs">
+ <citetitle>Cl2vcs</citetitle></ulink>,
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-export-orig.xml b/docs/manpages/gbp-export-orig.xml
new file mode 100644
index 0000000..9a9fa84
--- /dev/null
+++ b/docs/manpages/gbp-export-orig.xml
@@ -0,0 +1,244 @@
+<refentry id="man.gbp.export.orig">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta><refentrytitle>gbp-export-orig</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-export-orig</refname>
+ <refpurpose>Export upstream tarballs from a &git; repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-export-orig;
+ <arg><option>--verbose</option></arg>
+ <arg><option>--color=</option>[auto|on|off]</arg>
+ <arg><option>--color-scheme</option>=<replaceable>COLOR_SCHEME</replaceable></arg>
+ <arg><option>--upstream-branch=</option><replaceable>BRANCH_NAME</replaceable></arg>
+ <arg><option>--[no-]submodules</option></arg>
+ <arg><option>--upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--upstream-tree=</option><replaceable>[TAG|BRANCH|TREEISH]</replaceable></arg>
+ <arg><option>--tarball-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--force-create</option></arg>
+ <arg><option>--compression=</option><replaceable>TYPE</replaceable></arg>
+ <arg><option>--compression-level=</option><replaceable>LEVEL</replaceable></arg>
+ <arg rep='repeat'><option>--component=</option><replaceable>component</replaceable></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-export-orig; is used to export upstream tarballs from a
+ &git; repository. Usually upstream tarballs are created by
+ &gbp-buildpackage; when needed but if you don't want to perform
+ a build you can use this command.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--verbose</option>
+ </term>
+ <listitem>
+ <para>
+ verbose execution
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--color=</option>[auto|on|off]
+ </term>
+ <listitem>
+ <para>
+ Whether to use colored output.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--color-scheme</option>=<replaceable>COLOR_SCHEME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Colors to use in output (when color is enabled). The format for
+ COLOR_SCHEME is
+ '&lt;debug&gt;:&lt;info&gt;:&lt;warning&gt;:&lt;error&gt;'.
+ Numerical values and color names are accepted, empty fields imply
+ the default color. For example --color-scheme='cyan:34::' would
+ show debug messages in cyan, info messages in blue and other messages
+ in default (i.e. warning and error messages in red).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>BRANCH_NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Branch to build the orig tarball from if
+ <option>--upstream-tree</option> is set to
+ <replaceable>BRANCH</replaceable>. Default is
+ <replaceable>upstream</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]submodules</option>
+ </term>
+ <listitem>
+ <para>
+ Include git submodules in the orig tarball.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags of upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--force-create</option>
+ </term>
+ <listitem>
+ <para>
+ Force creation of an orig tarball (overwriting a pre-existing one if
+ present).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tree=</option><replaceable>[BRANCH|TAG|TREEISH]</replaceable>
+ </term>
+ <listitem>
+ <para>
+ How to find the upstream sources used to generate the tarball.
+ <replaceable>TAG</replaceable> (the default) looks at a tag corresponding to the
+ version in the changelog. <replaceable>BRANCH</replaceable> looks at
+ the upstream branch given via the
+ <option>--upstream-branch</option> option.
+ </para>
+ <para>
+ Other values are interpreted as treeishs.
+ </para>
+ <para>
+ This doesn't have any effect if <option>--pristine-tar</option>
+ is being used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--tarball-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Search for original tarballs in <replaceable>DIRECTORY</replaceable>
+ instead of generating them.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--compression=</option><replaceable>TYPE</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Specifies the upstream tarball compression type. This will be used to
+ locate and build the upstream tarball if necessary. The default is
+ <replaceable>auto</replaceable> which derives the compression type
+ from the pristine-tar branch if available and falls back to gzip
+ otherwise. Other options are <replaceable>gzip</replaceable>,
+ <replaceable>bzip2</replaceable>, <replaceable>lzma</replaceable> and
+ <replaceable>xz</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--compression-level=</option><replaceable>LEVEL</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Specifies the upstream tarball compression level if an upstream
+ tarball needs to be built.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--component=</option><replaceable>COMPONENT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ When generating tarballs create an additional original
+ tarball of directory <replaceable>COMPONENT</replaceable>
+ in the source tree. Using additional original tarballs is
+ a feature of the 3.0 (quilt) source format. See
+ the <command>dpkg-source</command> manpage for details. Note that the
+ <replaceable>--pristine-tar-commit</replaceable>
+ option is currently incompatible with this option.
+ </para>
+ <para>
+ This is considered an experimental feature and might
+ change incompatibly.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pristine-tar</option>
+ </term>
+ <listitem>
+ <para>
+ Use pristine-tar when generating the upstream tarball if it doesn't
+ exist.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ Generate an upstream tarball of the version currently referenced
+ in the changelog using &pristine-tar;:
+ </para>
+ <screen>
+ &gbp-export-orig; --pristine-tar
+ </screen>
+ <para>
+ Same as above but generate and additional tarball for
+ directory <filename>foo</filename>.
+ </para>
+ <screen>
+ &gbp-export-orig; --pristine-tar --component=foo
+ </screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.pristine.tar"/>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-import-dsc.xml b/docs/manpages/gbp-import-dsc.xml
new file mode 100644
index 0000000..39a37ec
--- /dev/null
+++ b/docs/manpages/gbp-import-dsc.xml
@@ -0,0 +1,310 @@
+<refentry id="man.gbp.import.dsc">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-import-dsc</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-import-dsc</refname>
+ <refpurpose>Import &debian; packages into a &git; repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-import-dsc;
+ &man.common.options.synopsis;
+ <arg><option>--allow-same-versions</option></arg>
+ <arg><option>--author-date-is-committer-date</option></arg>
+ <arg><option>--author-is-committer</option></arg>
+ <arg><option>--debian-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--debian-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg rep='repeat'><option>--filter=</option><replaceable>pattern</replaceable></arg>
+ <arg><option>--keyid=</option><replaceable>gpg-keyid</replaceable></arg>
+ <arg><option>--[no-]create-missing-branches</option></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ <arg><option>--[no-]sign-tags</option></arg>
+ <arg><option>--skip-debian-tag</option></arg>
+ <arg><option>--upstream-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--repo-user=</option><option>[GIT|DEBIAN]</option></arg>
+ <arg><option>--repo-email=</option><option>[GIT|DEBIAN]</option></arg>
+ <arg choice="plain"><replaceable>debian-source.dsc</replaceable></arg>
+ <arg choice="opt"><replaceable>target</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ &gbp-import-dsc;
+ <arg><option>options</option></arg>
+ <arg><option>--[no-]allow-unauthenticated</option></arg>
+ <group choice="plain">
+ <arg><replaceable>URL</replaceable></arg>
+ </group>
+ <arg choice="opt"><replaceable>target</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-import-dsc; imports a &debian; source package into a &git; repository,
+ notes the package version in the commit logs, and commits the change. All
+ information, including package name, version, &debian; modifications and upstream
+ source, is automatically detected from the source package. After
+ import the repository can be used with the other &gbp; tools.
+ </para>
+ <para>
+ If the command is run from within an existing &git; repository, it will import
+ into it; if not, a new repository named as the Debian source package is
+ created. You can override the location of the new repository by specifying
+ the optional <replaceable>target</replaceable> argument.
+ </para>
+ <para>
+ When given a <replaceable>URL</replaceable> &gbp-import-dsc;
+ will download the source package prior to importing it. &dget;
+ is used for all schemes except for the special
+ scheme <replaceable>apt:///</replaceable> which uses &apt-get;
+ (and therefore needs <replaceable>deb-src</replaceable>
+ entries in your <filename>/etc/apt/sources.list</filename>).
+ The later can be abbreviated by using the pseudo URL
+ <replaceable>apt:</replaceable>.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the &git; repository the upstream sources are put
+ onto. Default is <replaceable>upstream</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the &git; repository the debian sources are put
+ onto. Default is <replaceable>master</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]sign-tags</option>
+ </term>
+ <listitem>
+ <para>
+ GPG sign all created tags
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--keyid=</option><replaceable>gpg-keyid</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this keyid for gpg signing tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-tag=</option><replaceable>tag-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging &debian; versions,
+ default is <replaceable>debian/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>tag-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--skip-debian-tag</option>
+ </term>
+ <listitem>
+ <para>
+ Don't create &debian; tag after importing the &debian; patch. This can be
+ useful if you already created a package but want to further work on
+ it after importing it into git.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--filter=</option><replaceable>pattern</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Filter out files glob-matching
+ <option><replaceable>pattern</replaceable></option> from
+ upstream tarballs and the debian tarball of 3.0(quilt)
+ packages. Note that the <emphasis>.diff.gz</emphasis> of 1.0
+ source format packages is currently not filtered.
+ </para>
+ <para>
+ This option can be given multiple times.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pristine-tar</option>
+ </term>
+ <listitem>
+ <para>
+ Generate pristine-tar delta file.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--allow-unauthenticated</option>
+ </term>
+ <listitem>
+ <para>
+ Whether to skip signature verification on
+ downloads. Passed on verbatim to &dget; and &apt-get;
+ respectively. Use with care.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--allow-same-version</option>
+ </term>
+ <listitem>
+ <para>
+ Allow one to import a package with the same debian version.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--author-is-committer</option>
+ </term>
+ <listitem>
+ <para>
+ When importing the &debian; patch, use the author identity as
+ committer identity.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--author-date-is-committer-date</option>
+ </term>
+ <listitem>
+ <para>
+ When importing the &debian; patch, use the author date as
+ committer date.
+ <warning>
+ <para>
+ &git; will subtly misbehave if the committer date of a commit is not
+ later than or equal to all its parents.
+ </para>
+ </warning>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]create-missing-branches</option>
+ </term>
+ <listitem>
+ <para>
+ Create upstream and debian branch if missing.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--repo-user=</option><option>[GIT|DEBIAN]</option></term>
+ <listitem>
+ <para>
+ When set to <option>DEBIAN</option>
+ use the <envar>DEBUSER</envar> environment variable to set the
+ user.name &git; configuration otherwise use &git;'s
+ defaults. Only affects newly created repos.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--repo-email=</option><option>[GIT|DEBIAN]</option></term>
+ <listitem>
+ <para>
+ When set to <option>DEBIAN</option>
+ use the <envar>DEBEMAIL</envar> environment variable to set the
+ user.email &git; configuration otherwise use &git;'s
+ defaults. Only affects newly created repos.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ Download and import a source package from a URL:
+ </para>
+ <screen>
+ &gbp-import-dsc; http://http.debian.net/debian/pool/main/h/hello/hello_2.10-1.dsc
+ </screen>
+ <para>
+ Download and import a source package via <command>apt-get
+ source</command> from unstable:
+ </para>
+ <screen>
+ &gbp-import-dsc; apt:hello/sid
+ </screen>
+ <para>
+ Import a source package in the local file system:
+ </para>
+ <screen>
+ &gbp-import-dsc; ../hello_2.10-1.dsc
+ </screen>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+ <xref linkend="man.gbp.import.dscs"/>,
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.dch"/>,
+ <citerefentry>
+ <refentrytitle>apt-get</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>sources.list</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>dget</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-import-dscs.xml b/docs/manpages/gbp-import-dscs.xml
new file mode 100644
index 0000000..d0d1f11
--- /dev/null
+++ b/docs/manpages/gbp-import-dscs.xml
@@ -0,0 +1,96 @@
+<refentry id="man.gbp.import.dscs">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-import-dscs</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-import-dscs</refname>
+ <refpurpose>Import multiple versions of a Debian source packages into a Git repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-import-dscs;
+ <arg><option>options</option></arg>
+ <arg><option>gbp import-dsc options</option></arg>
+ <arg choice="plain"><replaceable>pkg_1.dsc</replaceable></arg>
+ <arg choice="plain"><replaceable>pkg_2.dsc</replaceable></arg>
+ <arg choice="plain"><replaceable>...</replaceable></arg>
+ </cmdsynopsis>
+
+ <para>or</para>
+
+ <cmdsynopsis>
+ &gbp-import-dscs;
+ <arg choice="req">--debsnap</arg>
+ <arg><option>options</option></arg>
+ <arg><option>gbp import-dsc options</option></arg>
+ <arg choice="req"><replaceable>package</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-import-dscs; imports several versions of a Debian source package into
+ a &git; repository. To do so, it sorts the packages by their versions first,
+ and then imports them via calling &gbp-import-dsc; on each package.
+ </para>
+
+ <para>
+ If the current directory isn't a &git; repository already, the repository is
+ created in a subdir of the current working directory, named after the first
+ imported package, otherwise the &git; repository in the current working
+ directory is being used. This allows for incremental imports.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--debsnap</option>
+ </term>
+ <listitem>
+ <para>Fetch snapshots from snapshots.debian.org using debsnap.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-repo-config</option>
+ </term>
+ <listitem>
+ <para>Ignore <filename>gbp.conf</filename> files stored in the git
+repository itself. This can be useful to ignore branch information and other
+options shipped in the package source.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ All other options are passed on verbatim to &gbp-import-dsc;.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+ <xref linkend="man.gbp.import.dsc"/>,
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.dch"/>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>&dhusername; &dhemail;</para>
+
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-import-orig.xml b/docs/manpages/gbp-import-orig.xml
new file mode 100644
index 0000000..7eccf6a
--- /dev/null
+++ b/docs/manpages/gbp-import-orig.xml
@@ -0,0 +1,403 @@
+<refentry id="man.gbp.import.orig">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-import-orig</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-import-orig</refname>
+ <refpurpose>Import an upstream source into a git repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-import-orig;
+
+ &man.common.options.synopsis;
+ <arg><option>--upstream-version=</option><replaceable>version</replaceable></arg>
+ <arg><option>--[no-]merge</option></arg>
+ <arg><option>--merge-mode=</option><replaceable>[auto|merge|replace]</replaceable></arg>
+ <arg><option>--upstream-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--debian-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--upstream-vcs-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--[no-]sign-tags</option></arg>
+ <arg><option>--keyid=</option><replaceable>gpg-keyid</replaceable></arg>
+ <arg><option>--upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg rep='repeat'><option>--filter=</option><replaceable>pattern</replaceable></arg>
+ <arg rep='repeat'><option>--component=</option><replaceable>component</replaceable></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ <arg><option>--[no-]filter-pristine-tar</option></arg>
+ <arg><option>--[no-]symlink-orig</option></arg>
+ <arg><option>--postimport=cmd</option></arg>
+ <arg><option>--[no-]interactive</option></arg>
+ <arg><option>--[no-]rollback</option></arg>
+ <group choice="plain">
+ <arg choice="plain"><replaceable>filename</replaceable></arg>
+ <arg choice="plain"><replaceable>url</replaceable></arg>
+ <arg><option>--uscan</option></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-import-orig; imports upstream sources into a &git;
+ repository. It can import from three sources:
+ </para>
+ <orderedlist>
+ <listitem>
+ <para>
+ <replaceable>filename</replaceable>: A file in the local
+ file system. Gzip, bzip2, lzma and xz compressed tar
+ archives, zip archives and already unpacked source trees are
+ supported.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <replaceable>url</replaceable>: The tarball is downloaded
+ from a <replaceable>http</replaceable>
+ or <replaceable>https</replaceable> <replaceable>url</replaceable>.
+ This needs the python-request package installed.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <option>--uscan</option>: The latest upstream version is fetched
+ via &uscan; relying on <filename>debian/watch</filename>.
+ </para>
+ </listitem>
+ </orderedlist>
+ <para>
+ If the tarballs name is already of the form
+ <replaceable>package-name_version.orig.tar.gz</replaceable>, the
+ version information is determined from the tarball's filename,
+ otherwise it can be given on the command line
+ via <option>--upstream-version</option>. If the source package
+ name or version can't be determined, &gbp-import-orig; will
+ prompt for it unless <option>--no-interactive</option> is given.
+ </para>
+ <para>
+ The sources are placed on the upstream branch (default:
+ <replaceable>upstream</replaceable>), tagged and merged onto the
+ debian branch (default: <replaceable>master</replaceable>). This
+ is either done using plain <command>git merge</command>
+ or by creating a new tree that consists of the new
+ upstream version plus the <filename>debian/</filename>
+ directory. The later is used for source format 3.0
+ (quilt) packages since direct modifications of the upstream
+ sources are not allowed in that format and so a 1:1 replacement
+ of the upstream sources is almost always desired. It can
+ be tweaked via the <option>--merge-mode</option>.
+ </para>
+ <para>In case of an error &gbp-import-orig; will rollback (undo)
+ all changes it has done to the repository (see
+ the <option>--rollback</option> option).
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--upstream-version</option>=<replaceable>version</replaceable></term>
+ <term><option>-u</option><replaceable>version</replaceable></term>
+ <listitem>
+ <para>
+ The upstream version number
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]merge</option></term>
+ <listitem>
+ <para>
+ Merge the upstream branch to the &debian; branch after import
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--merge-mode=</option><replaceable>[auto|merge|replace]</replaceable></term>
+ <listitem>
+ <para>
+ How to fold the newly imported upstream source to the
+ &debian; packaging branch after import.
+ </para>
+ <para>
+ <replaceable>merge</replaceable> does a
+ &git; <command>merge</command> leaving you on your own in
+ case of merge conflict resolution.
+ </para>
+ <para>
+ <replaceable>replace</replaceable> mode on the
+ other hand makes the head of the &debian; packaging branch
+ identical to the newly imported tree but preserves the
+ content of the <filename>debian/</filename> directory
+ while keeping the current head as well as the newly
+ imported tree as parents of the generated commit. This is
+ similar to a <option>theirs</option> merge strategy while
+ preserving <filename>debian/</filename>.
+ </para>
+ <para>
+ The default is <replaceable>auto</replaceable> which
+ uses <replaceable>replace</replaceable> for 3.0 (quilt) packages
+ and <replaceable>merge</replaceable> otherwise.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the &git; repository the upstream sources are put
+ onto. Default is <replaceable>upstream</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the &git; repository the &debian; package is being
+ developed on, default is <replaceable>master</replaceable>. After
+ importing the new sources on the upstream branch, &gbp-import-orig;
+ will try to merge the new version onto this branch.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-vcs-tag</option>=<replaceable>tag-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Add <replaceable>tag-format</replaceable> as additional parent to the
+ commit of the upstream tarball. Useful when upstream uses git and you
+ want to link to its revision history. The
+ <replaceable>tag-format</replaceable> can be a pattern similar to
+ what <option>--upstream-tag</option> supports.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]sign-tags</option>
+ </term>
+ <listitem>
+ <para>
+ GPG sign all created tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--keyid=</option><replaceable>gpg-keyid</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this <option><replaceable>keyid</replaceable></option>
+ for gpg signing tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>tag-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--import-msg=</option><replaceable>msg-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this format string for the commit message when importing upstream
+ versions, default is
+ <replaceable>New upstream version %(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--filter=</option><replaceable>pattern</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Filter out files glob-matching
+ <option><replaceable>pattern</replaceable></option>. This
+ option can be given multiple times.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--component=</option><replaceable>COMPONENT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ When importing the upstream tarball also look for an additional tarball
+ with component name <replaceable>COMPONENT</replaceable>. E.g. in
+ <filename>hello-debhelper_1.0.orig-foo.tar.gz</filename>
+ the component would be <replaceable>foo</replaceable>. The additional
+ tarball is expected to be in the same directory than the upstream tarball
+ and to use the same compression type.
+ </para>
+ <para>
+ Using additional original tarballs is a feature of the 3.0
+ (quilt) source format. See
+ the <command>dpkg-source</command> manpage for
+ details. This is currently considered an experimental
+ feature and might change incompatibly.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]pristine-tar</option>
+ </term>
+ <listitem>
+ <para>
+ Generate <command>pristine-tar</command> delta file.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]filter-pristine-tar</option>
+ </term>
+ <listitem>
+ <para>
+ If using a filter, also filter the files out of the tarball
+ passed to <command>pristine-tar</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]symlink-orig</option></term>
+ <listitem>
+ <para>
+ Whether to create and keep a symlink from the upstream tarball
+ to a &debian; policy conformant upstream tarball name located in
+ <filename class="directory">../</filename>.
+ </para>
+ <para>
+ This is a good idea if not using <command>pristine-tar</command>
+ since it avoids creating a new tarball with a different md5sum.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--postimport=<replaceable>cmd</replaceable></option></term>
+ <listitem>
+ <para>
+ Run <replaceable>cmd</replaceable> after the import. The
+ hook gets the following environment variables passed:
+ <variablelist>
+ <varlistentry>
+ <term><envar>GBP_BRANCH</envar></term>
+ <listitem><para>
+ The name of the Debian packaging branch
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><envar>GBP_TAG</envar></term>
+ <listitem><para>
+ The name of the just created upstream tag
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><envar>GBP_UPSTREAM_VERSION</envar></term>
+ <listitem><para>
+ The just imported upstream version
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><envar>GBP_DEBIAN_VERSION</envar></term>
+ <listitem><para>
+ The Debian version of the package with a Debian
+ revision of '-1'
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--uscan</option></term>
+ <listitem>
+ <para>
+ Use &uscan; to fetch new upstream version.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]interactive</option></term>
+ <listitem>
+ <para>
+ Run command interactively, i.e. ask package name and version if
+ needed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]rollback</option></term>
+ <listitem>
+ <para>
+ Rollback changes in case of an error.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ Download and import a new upstream version using the information from <filename>debian/watch</filename>
+ </para>
+ <screen>
+ &gbp-import-orig; --uscan</screen>
+ <para>
+ Fetch tarball from an URL
+ </para>
+ <screen>
+ &gbp-import-orig; https://debian.example.com/sid/upstream-tarball-0.1.tar.gz</screen>
+ <para>
+ Import a local tarball
+ </para>
+ <screen>
+ &gbp-import-orig; ../upstream-tarball-0.1.tar.gz</screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.import.dsc"/>,
+ <xref linkend="man.gbp.import.dscs"/>,
+ <xref linkend="man.gbp.dch"/>,
+ <xref linkend="man.gbp.conf"/>,
+ <citerefentry>
+ <refentrytitle>uscan</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
+<!-- LocalWords: xz lzma bzip gzip tarball
+ -->
diff --git a/docs/manpages/gbp-import-srpm.xml b/docs/manpages/gbp-import-srpm.xml
new file mode 100644
index 0000000..11f4acb
--- /dev/null
+++ b/docs/manpages/gbp-import-srpm.xml
@@ -0,0 +1,286 @@
+<refentry id="man.gbp.import.srpm">
+ <refentryinfo>
+ <address>
+ &rpm-email;
+ </address>
+ <author>
+ &rpm-firstname;
+ &rpm-surname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-import-srpm</refentrytitle>
+ &rpm-mansection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-import-srpm</refname>
+ <refpurpose>Import source RPM packages into a Git repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-import-srpm;
+ &man.common.options.synopsis;
+ <arg><option>--vendor</option>=<replaceable>VENDOR</replaceable></arg>
+ <arg><option>--allow-same-versions</option></arg>
+ <arg><option>--author-is-committer</option></arg>
+ <arg><option>--packaging-branch=</option><replaceable>BRANCH-NAME</replaceable></arg>
+ <arg><option>--packaging-tag=</option><replaceable>TAG-FORMAT</replaceable></arg>
+ <arg><option>--skip-packaging-tag</option></arg>
+ <arg><option>--packaging-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--filter=</option><replaceable>PATTERN</replaceable></arg>
+ <arg><option>--keyid=</option><replaceable>GPG-KEYID</replaceable></arg>
+ <arg><option>--[no-]create-missing-branches</option></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ <arg><option>--[no-]sign-tags</option></arg>
+ <arg><option>--upstream-branch=</option><replaceable>BRANCH-NAME</replaceable></arg>
+ <arg><option>--upstream-tag=</option><replaceable>TAG-FORMAT</replaceable></arg>
+ <arg><option>--upstream-vcs-tag=</option><replaceable>TAG-FORMAT</replaceable></arg>
+ <arg><option>--native</option></arg>
+ <arg><option>--repo-user=</option><option>[GIT|DEBIAN]</option></arg>
+ <arg><option>--repo-email=</option><option>[GIT|DEBIAN]</option></arg>
+ <group choice="plain">
+ <arg><replaceable>SRPM</replaceable></arg>
+ <arg><replaceable>DIRECTORY</replaceable></arg>
+ </group>
+ <arg choice="opt"><replaceable>target</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ &gbp-import-srpm;
+ <arg><option>options</option></arg>
+ <group choice="plain">
+ <arg><replaceable>URL</replaceable></arg>
+ </group>
+ <arg choice="opt"><replaceable>target</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-import-srpm; imports an RPM source package into a &git; repository,
+ notes the package version in the commit logs, and commits the change. All
+ information, including package name, version and upstream source is
+ automatically detected from the source package but you can override the
+ location of the new repository by optionally specifying the
+ <replaceable>target</replaceable> argument. The tool supports importing
+ both archived (src.rpm files) or unpacked (directory) source RPMs. It also imports
+ from http(s)-URLs.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--vendor</option>=<replaceable>VENDOR</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Distribution vendor name.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>BRANCH-NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the &git; repository the upstream sources are put
+ onto. Default is <replaceable>upstream</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--packaging-branch</option>=<replaceable>BRANCH-NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the &git; repository the packaging files are put
+ onto. Default is <replaceable>master</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]sign-tags</option>
+ </term>
+ <listitem>
+ <para>
+ GPG sign all created tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--keyid=</option><replaceable>GPG-KEYID</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this keyid for gpg signing tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--packaging-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging released versions,
+ default is <replaceable>%(vendor)s/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--skip-packaging-tag</option>
+ </term>
+ <listitem>
+ <para>
+ Do not create packaging tag after importing the packaging files.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-vcs-tag</option>=<replaceable>TAG-NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Add <replaceable>TAG-FORMAT</replaceable> as an additional parent of the
+ commit of the upstream tarball. Useful when upstream uses git and you
+ want to link to its revision history.
+ <replaceable>TAG-FORMAT</replaceable> can be a pattern similar to
+ what <option>--upstream-tag</option> supports.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-packaging-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Subdirectory where to put the RPM packaging files.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--filter=</option><replaceable>PATTERN</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Filter out files glob-matching pattern. Can be given multiple times.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pristine-tar</option>
+ </term>
+ <listitem>
+ <para>
+ Generate pristine-tar delta file.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--allow-same-version</option>
+ </term>
+ <listitem>
+ <para>
+ Allow one to re-import a package with an already existing version.
+ This will not re-import the upstream sources - only packaging files
+ will be re-imported.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--author-is-committer</option>
+ </term>
+ <listitem>
+ <para>
+ Use the author identity as the committer when importing upstream
+ sources and packaging files.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]create-missing-branches</option>
+ </term>
+ <listitem>
+ <para>
+ Create missing upstream and/or packaging branch if missing.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--orphan-packaging</option>
+ </term>
+ <listitem>
+ <para>
+ Import packaging files into an orphan branch that will not be based
+ on the upstream branch. Useful if you want to maintain (non-native)
+ package using the 'orphan-packaging' model. This option have no
+ effect if <option>--native</option> is used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--native</option>
+ </term>
+ <listitem>
+ <para>
+ Treat the package as native package. No separate upstream branch or
+ upstream tags will be created.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--repo-user=</option><option>[GIT|DEBIAN]</option></term>
+ <listitem>
+ <para>
+ When set to <option>DEBIAN</option>
+ use the <envar>DEBUSER</envar> environment variable to set the
+ user.name &git; configuration otherwise use &git;'s
+ defaults. Only affects newly created repos.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--repo-email=</option><option>[GIT|DEBIAN]</option></term>
+ <listitem>
+ <para>
+ When set to <option>DEBIAN</option>
+ use the <envar>DEBEMAIL</envar> environment variable to set the
+ user.email &git; configuration otherwise use &git;'s
+ defaults. Only affects newly created repos.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+ <xref linkend="man.gbp.buildpackage.rpm"/>,
+ <xref linkend="man.gbp.pq.rpm"/>,
+ <xref linkend="man.gbp.rpm.ch"/>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &rpm-username; &rpm-email;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-pq-rpm.xml b/docs/manpages/gbp-pq-rpm.xml
new file mode 100644
index 0000000..9406aa3
--- /dev/null
+++ b/docs/manpages/gbp-pq-rpm.xml
@@ -0,0 +1,275 @@
+<refentry id="man.gbp.pq.rpm">
+ <refentryinfo>
+ <address>
+ &rpm-email;
+ </address>
+ <author>
+ &rpm-firstname;
+ &rpm-surname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-pq-rpm</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-pq-rpm</refname>
+ <refpurpose>Manage patches and development branches in Git</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-pq-rpm;
+ &man.common.options.synopsis;
+ <arg><option>--packaging-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--spec-file=</option><replaceable>FILEPATH</replaceable></arg>
+ <arg><option>--upstream-tag=</option><replaceable>TAG-FORMAT</replaceable></arg>
+ <arg><option>--abbrev=</option><replaceable>num</replaceable></arg>
+ <arg><option>--force</option></arg>
+ <arg><option>--[no-]drop</option></arg>
+ <arg><option>--[no-]patch-numbers</option></arg>
+ <group choice="plain">
+ <arg><option>drop</option></arg>
+ <arg><option>export</option></arg>
+ <arg><option>import</option></arg>
+ <arg><option>rebase</option></arg>
+ <arg><option>switch</option></arg>
+ <arg><option>apply</option></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-pq-rpm; helps in managing patches and development branch(es) for
+ packages that are maintained with &gbp;. It designed to be used for
+ packages that are maintained using the "orphan-packaging" model.
+ &gbp-pq-rpm; has multiple subcommands, or actions, for working with the
+ branches and patches.
+ </para>
+ <para>
+ &gbp-pq-rpm; makes it easy to do source code development on a separate
+ development branch (patch-queue branch in Debian git-buildpackage terms).
+ For example, if the packaging files would be stored in
+ <replaceable>master</replaceable> the associated development branch would
+ be <replaceable>development/master</replaceable>.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>ACTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>import</option>
+ </term>
+ <listitem>
+ <para>
+ Create a development (patch-queue) branch by applying all patches
+ from the packaging branch on top of the upstream version. The patches
+ must apply without fuzz.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>export</option>
+ </term>
+ <listitem>
+ <para>
+ Export patches from the development branch into the packaging branch.
+ It generates patches (one-per-commit) from the development branch and
+ updates the spec file accordingly. It doesn't automatically commit
+ the changes though - they need to verified and committed manually.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>rebase</option>
+ </term>
+ <listitem>
+ <para>
+ Switch to the development branch and rebase it against the current
+ upstream version (indicated in the spec file of the associated
+ packaging branch).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>drop</option>
+ </term>
+ <listitem>
+ <para>
+ Drop (delete) the development branch associated to the current
+ branch. For example, you're on branch <replaceable>foo</replaceable>
+ this would drop branch <replaceable>development/foo</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>apply</option>
+ </term>
+ <listitem>
+ <para>
+ Add a single patch to the development branch - similar to using
+ <command>git-am</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>switch</option>
+ </term>
+ <listitem>
+ <para>
+ Switch between the development branch and the associated packaging
+ branch.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+ <varlistentry>
+ <term><option>--packaging-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Subdirectory that contains the RPM packaging files.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--spec-file=</option><replaceable>FILEPATH</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Relative path to the spec file to use. Special value
+ <replaceable>auto</replaceable> causes &gbp; to search and guess.
+ Other values cause the <option>--packaging-dir</option> option to be
+ ignored: the directory of the spec file is used, instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags of upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--abbrev=</option><replaceable>NUM</replaceable>
+ </term>
+ <listitem>
+ <para>
+ When exporting a patch queue abbreviate commit, instead of showing the
+ full 40-byte hexadecimal object name in header lines, show only a
+ partial prefix of length <replaceable>NUM</replaceable>. This is
+ useful when existing patches were not generated by &gbp-pq;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Import even if the development (patch-queue) branch already exists.
+ Only valid for the import action.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]drop</option></term>
+ <listitem>
+ <para>
+ Whether to drop (delete) the patch queue branch after a successful
+ export.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]patch-numbers</option>
+ </term>
+ <listitem>
+ <para>
+ Whether the patch files should start with a number or not.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>META TAGS</title>
+ <para>
+ When exporting patches from a patch-queue branch &gbp-pq-rpm; will look at
+ the commit message for special tags it recognizes. All tags need to start
+ at the first column and require at least one whitespace after the colon.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>Gbp-Rpm: Ignore</option>
+ </term>
+ <listitem>
+ <para>
+ Ignores the commit, no patch is generated out of it.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>Gbp-Rpm: If &lt;expression&gt;</option>
+ </term>
+ <listitem>
+ <para>
+ Conditional patch. Put patch inside "%if &lt;expression&gt;" in the
+ spec file.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>Gbp-Rpm: IfArch&lt;expression&gt;</option>
+ </term>
+ <listitem>
+ <para>
+ Conditional patch. Put patch inside "%ifarch &lt;expression&gt;" in
+ the spec file.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ For example, the following commit message:
+ </para>
+ <screen>
+ Fix around a problem in Fedora
+
+ which is not applicable elsewhere.
+
+ Gbp-Rpm: If 0%{?fedora}
+ </screen>
+ <para>
+ Will result something like this in the spec file:
+ </para>
+ <screen>
+ # 0001-Fix-around-a-problem-in-Fedora.patch
+ %if 0%{?fedora}
+ %patch0 -p1
+ %endif
+ </screen>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage.rpm"/>,
+ <xref linkend="man.gbp.import.srpm"/>,
+ <xref linkend="man.gbp.conf"/>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &rpm-username; &rpm-email;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-pq.xml b/docs/manpages/gbp-pq.xml
new file mode 100644
index 0000000..b75a669
--- /dev/null
+++ b/docs/manpages/gbp-pq.xml
@@ -0,0 +1,357 @@
+<refentry id="man.gbp.pq">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-pq</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-pq</refname>
+ <refpurpose>Manage quilt patches on patch queue branches in git</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-pq;
+ &man.common.options.synopsis;
+ <arg><option>--[no-]patch-numbers</option></arg>
+ <arg><option>--patch-num-format=</option><replaceable>format</replaceable></arg>
+ <arg><option>--[no-]renumber</option></arg>
+ <arg><option>--topic=</option><replaceable>topic</replaceable></arg>
+ <arg><option>--time-machine=</option><replaceable>num</replaceable></arg>
+ <arg><option>--[no-]drop</option></arg>
+ <arg><option>--abbrev=</option><replaceable>num</replaceable></arg>
+ <arg><option>--force</option></arg>
+ <arg><option>--commit</option></arg>
+ <arg><option>--meta-closes=bug-close-tags</option></arg>
+ <arg><option>--meta-closes-bugnum=bug-number-format</option></arg>
+ <arg><option>--pq-from=</option><replaceable>[DEBIAN|TAG]</replaceable></arg>
+ <arg><option>--upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <group choice="plain">
+ <arg><option>drop</option></arg>
+ <arg><option>export</option></arg>
+ <arg><option>import</option></arg>
+ <arg><option>rebase</option></arg>
+ <arg><option>switch</option></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-pq; helps one to manage quilt patches in &debian; packages that are
+ maintained with &gbp;. This is especially useful with packages using the
+ 3.0 (quilt) source format. With &gbp-pq;, you can maintain the quilt patches
+ that should be applied to a package on a separate branch called patch-queue
+ branch. So if your &debian; package lives on
+ <replaceable>master</replaceable>, the associated patch-queue branch will be
+ called <replaceable>patch-queue/master</replaceable>.
+ </para>
+ <para>
+ See <ulink url="https://honk.sigxcpu.org/piki/development/debian_packages_in_git/"></ulink>
+ for example workflows.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>ACTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>import</option>
+ </term>
+ <listitem>
+ <para>
+ Create a patch queue branch from quilt patches in debian/patches/
+ that are listed in debian/patches/series. The patches must apply
+ without fuzz.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>export</option>
+ </term>
+ <listitem>
+ <para>
+ Export the patches on the patch-queue branch associated to the
+ current branch into a quilt patch series in debian/patches/ and
+ update the series file.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>rebase</option>
+ </term>
+ <listitem>
+ <para>
+ Switch to the patch-queue branch associated to the current branch and
+ rebase it against the current branch.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>drop</option>
+ </term>
+ <listitem>
+ <para>
+ Drop (delete) the patch queue associated to the current branch. So if
+ you're on branch <replaceable>foo</replaceable>, this would drop
+ branch <replaceable>patch-queue/foo</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>apply</option>
+ </term>
+ <listitem>
+ <para>
+ Add a single patch to the patch-queue similar to using
+ <command>git-am</command>. Use <option>--topic</option> if you want
+ the patch to appear in a separate subdir when exporting the patch queue
+ using <option>export</option>. This can be used to separate upstream
+ patches from &debian; specific patches.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>switch</option>
+ </term>
+ <listitem>
+ <para>
+ Switch to the patch-queue branch if on the base branch and switch
+ to base branch if on patch-queue branch.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--[no-]patch-numbers</option>
+ </term>
+ <listitem>
+ <para>
+ Whether or not the patch files should be prefixed with a number.
+ The default is to export patches with patch numbers. Note,
+ however, that this normally affects patches whose names are
+ automatically generated, and has no effect on exporting patches
+ which have a <option>Gbp[-Pq]: Name</option> tag, since the
+ name specified is preserved unless the <option>--renumber</option>
+ option is used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--patch-num-format=</option><replaceable>format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The format specifier for patch number prefixes. The default format is '%04d-'.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]renumber</option>
+ </term>
+ <listitem>
+ <para>
+ Whether or not to renumber patches exported from the patch queue,
+ instead of preserving numbers specified in
+ <option>Gbp-Pq: Name</option> tags. The default is not to renumber
+ patches. Useful when patches need to be renamed for the sake of
+ uniformity. For example, using <option>--renumber</option> with
+ <option>--no-patch-num</option> will strip all numeric prefixes from
+ exported patches.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--topic=</option><replaceable>topic</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Topic to use when importing a single patch
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--time-machine=</option><replaceable>NUM</replaceable>
+ </term>
+ <listitem>
+ <para>
+ When importing a patch queue fails, go back commit-by-commit on the
+ current branch to check if the patch-queue applies there. Do this at
+ most <replaceable>NUM</replaceable> times. This can be useful if the
+ patch-queue doesn't apply to the current branch HEAD anymore, e.g.
+ after importing a new upstream version.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]drop</option></term>
+ <listitem>
+ <para>Whether to automatically drop (delete) the patch queue branch after
+ a successful export</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--abbrev=</option><replaceable>NUM</replaceable>
+ </term>
+ <listitem>
+ <para>
+ When exporting a patch queue abbreviate commit, instead of showing the
+ full 40-byte hexadecimal object name in header lines, show only a
+ partial prefix of length <replaceable>NUM</replaceable>. This is
+ useful when existing patches were not generated by &gbp-pq;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>In case of <option>import</option>, import even if the
+ patch-queue branch already exists and overwrite its
+ content with <filename>debian/patches</filename>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--export</option></term>
+ <listitem>
+ <para>
+ In case of <option>export</option>, commit
+ <filename>debian/patches</filename>the changes to &git; after
+ exporting the patches.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--meta-closes=</option><replaceable>bug-close-tags</replaceable>
+ </term>
+ <listitem>
+ <para>
+ What meta tags to look for to generate a commit message when
+ using <option>export</option> <option>--commit</option>.
+ The default is <literal>'Closes|LP'</literal> to support Debian and Launchpad.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--meta-closes-bugnum=</option><replaceable>bug-number-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ What regular expression should be used to parse out the
+ bug number when using
+ <option>export</option> <option>--commit</option>. The
+ default is <literal>'(?:bug|issue)?\#?\s?\d+'</literal>.
+ See <xref linkend="man.gbp.dch"/> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pq-from=</option><replaceable>[DEBIAN|TAG]</replaceable>
+ </term>
+ <listitem>
+ <para>
+ How to find the starting point for the patch queue base. The options are DEBIAN, that will use the Debian branch as the base for the patch queue branch, and TAG, that will use the corresponding upstream tag as a base for the patch queue branch.
+ </para>
+ <para>
+ This is only needed if your upstream branch is not merged in the Debian branch.
+ The default is <replaceable>DEBIAN</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags of upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>TAGS</title>
+ <para>
+ When exporting patches from a patch-queue branch, &gbp-pq; will look at the
+ patch header for special tags it recognizes. All tags need to start at the
+ first column and require at least one whitespace after the colon.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>Gbp[-Pq]: Ignore</option>
+ </term>
+ <listitem>
+ <para>
+ Ignores the commit, no patch is generated out of it.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>Gbp[-Pq]: Name</option> <replaceable>name</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The name to use for the patch when running
+ <screen>&gbp-pq; export</screen>
+ If unset, it will be formatted like
+ <command>git am</command> would format it.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>Gbp[-Pq]: Topic</option> <replaceable>topic</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Moves the patch into a subdir called <replaceable>topic</replaceable>
+ when running <screen>&gbp-pq; export</screen> This allows for some
+ structure below <filename>debian/patches</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>Gbp-Pq-Topic:</option> <replaceable>topic</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Deprecated: use <option>Gbp[-Pq]: Topic</option>
+ <replaceable>topic</replaceable> instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <citerefentry>
+ <refentrytitle>dpkg-source</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>quilt</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <xref linkend="man.gbp.conf"/>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-pristine-tar.xml b/docs/manpages/gbp-pristine-tar.xml
new file mode 100644
index 0000000..93a1db3
--- /dev/null
+++ b/docs/manpages/gbp-pristine-tar.xml
@@ -0,0 +1,112 @@
+<refentry id="man.gbp.pristine.tar">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-pristine-tar</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-pristine-tar</refname>
+ <refpurpose>Manage pristine-tar commits in a git repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-pristine-tar;
+
+ &man.common.options.synopsis;
+ <arg><option>--upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg rep='repeat'><option>--component=</option><replaceable>component</replaceable></arg>
+ <group choice="plain">
+ <arg><option>commit</option></arg>
+ </group>
+ <group choice="plain">
+ <arg choice="plain"><replaceable>filename</replaceable></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-pristine-tar; adds the pristine-tar commit for a given
+ upstream tarball to a &git; repository. This can be useful if a
+ original tarball has been already imported and the pristine-tar
+ commits should be added at a later time or if you're tracking
+ upstream git and want to create pristine-tar commits
+ nevertheless.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>tag-format</replaceable>
+ </term>
+ <listitem>
+ <para>
+ use this tag format when tagging upstream versions,
+ default is <replaceable>upstream/%(version)s</replaceable>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--component=</option><replaceable>COMPONENT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ When creating the pristine-tar commits also look for an additional tarball
+ with component name <replaceable>COMPONENT</replaceable>. E.g. in
+ <filename>hello-debhelper_1.0.orig-foo.tar.gz</filename>
+ the component name would be <replaceable>foo</replaceable>. The additional
+ tarball is expected to be in the same directory than the upstream tarball
+ and to use the same compression type. This option can be
+ given multiple times to add multiple additional tarballs.
+ </para>
+ <para>
+ Using additional original tarballs is a feature of the 3.0
+ (quilt) source format. See
+ the <command>dpkg-source</command> manpage for
+ details. This is currently considered an experimental
+ feature and might change incompatibly.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>Add pristine-tar commits for an upstream tarball:</para>
+ <screen>
+ &gbp-pristine-tar; ../upstream-tarball-0.1.tar.gz</screen>
+ <para>Same as above with an additional
+ tarball <filename>../upstream-tarball-foo-0.1.tar.gz:</filename></para>
+ <screen>
+ &gbp-pristine-tar; --component-tarball=foo ../upstream-tarball-0.1.tar.gz</screen>
+</refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
+<!-- LocalWords: xz lzma bzip gzip tarball
+ -->
diff --git a/docs/manpages/gbp-pull.xml b/docs/manpages/gbp-pull.xml
new file mode 100644
index 0000000..1662383
--- /dev/null
+++ b/docs/manpages/gbp-pull.xml
@@ -0,0 +1,178 @@
+<refentry id="man.gbp.pull">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-pull</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-pull</refname>
+
+ <refpurpose>Safely update a repository from remote</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-pull;
+
+ &man.common.options.synopsis;
+ <arg><option>--force</option></arg>
+ <arg><option>--all</option></arg>
+ <arg><option>--redo-pq</option></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ <arg><option>--ignore-branch</option></arg>
+ <arg><option>--debian-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--upstream-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--track-missing</option></arg>
+ <arg><option>--depth=</option><replaceable>depth</replaceable></arg>
+ <arg><replaceable>repository</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-pull; updates the <emphasis>debian</emphasis>,
+ <emphasis>upstream</emphasis> and <emphasis>pristine-tar</emphasis>
+ branches from remote repositories in one go. It checks if the update is safe (would
+ result in a <emphasis>fast-forward</emphasis> merge) and aborts otherwise.
+ </para>
+ <para>
+ If given on the command line the changes are fetched from the
+ given repository otherwise the default
+ for <replaceable>repository</replaceable> is read from
+ the <replaceable>remote</replaceable> configuration for each
+ branch (in git's configuration).
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>Force a branch update even if this results in a non fast
+ forward update. <warning><para>Forcing a branch update
+ makes you lose your modifications.</para></warning></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--all</option></term>
+ <listitem>
+ <para>Update all remote-tracking branches that have identical name in the
+ remote repository.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--redo-pq</option></term>
+ <listitem>
+ <para>Also rebuild the corresponding patch-queue using &gbp-pq;.
+ <warning><para>This drops the patch-queue branch if it exists.</para></warning></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-branch</option>
+ </term>
+ <listitem>
+ <para>Don't care if we're on a branch or in detached head state.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>The branch in the Git repository the Debian package is being
+ developed on, default is <replaceable>master</replaceable>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>branch_name</replaceable>
+ </term>
+ <listitem>
+ <para>The branch in the &git; repository the upstream sources are put
+ onto. Default is <replaceable>upstream</replaceable>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--track-missing</option></term>
+ <listitem>
+ <para>
+ If a branch is missing in the local repository try to
+ fetch it from the remote side and (if it exists) fetch it and set up
+ branch tracking for it. This ensures that branches that are added at a
+ later point to the remote side (e.g.
+ <replaceable>pristine-tar</replaceable>) are picked up automatically.
+ Note that it's not an error for the branch to not exist on the remote
+ side.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--depth</option>=<replaceable>depth</replaceable>
+ </term>
+ <listitem>
+ <para>Git history depth, for deepening shallow git clones.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pristine-tar</option>
+ </term>
+ <listitem>
+ <para>Whether to update the pristine-tar branch too.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXIT CODES</title>
+ <para>
+ When &gbp-pull; finishes, it indicates success or failure with its exit code:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>0</option></term>
+ <listitem>
+ <para>Success.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>1</option></term>
+ <listitem>
+ <para>An error occurred during the pull, see the printed error message for details.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>2</option></term>
+ <listitem>
+ <para>At least one branch couldn't be fast forwarded.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.clone"/>,
+ <xref linkend="man.gbp.pq"/>,
+ <xref linkend="man.gbp.conf"/>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>&dhusername; &dhemail;</para>
+
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-push.xml b/docs/manpages/gbp-push.xml
new file mode 100644
index 0000000..5c8abc1
--- /dev/null
+++ b/docs/manpages/gbp-push.xml
@@ -0,0 +1,176 @@
+<refentry id="man.gbp.push">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-push</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-push</refname>
+
+ <refpurpose>Push &debian; packaging changes to a &git; remote</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-push;
+
+ &man.common.options.synopsis;
+ <arg><option>--ignore-branch</option></arg>
+ <arg><option>--debian-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--upstream-branch=</option><replaceable>branch_name</replaceable></arg>
+ <arg><option>--debian-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--upstream-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--[no-]pristine-tar</option></arg>
+ <arg><option>--dry-run</option></arg>
+ <arg><replaceable>repository</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &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
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ Verify that it is being executed from the &debian-branch;.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Determine the debian tag from <filename>debian/changelog</filename>
+ and add it the list of things to push if the changelog does not indicate
+ an unreleased package.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Determine the upstream tag from <filename>debian/changelog</filename>
+ and add it to the list of things to push if it's not a native package.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ 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.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Determine if the pristine-tar branch needs to be pushed and if so adds it
+ to the list of things to push.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Finally, if not in dry-run mode, pushes the above changes to the remote side.
+ </para>
+ </listitem>
+ </itemizedlist>
+ <para>
+ If a <replaceable>remote</replaceable> 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 <emphasis>origin</emphasis>.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+ <varlistentry>
+ <term><option>--ignore-branch</option></term>
+ <listitem>
+ <para>
+ Don't fail if the &debian-branch; does not match the currently checked out
+ branach and push the current branch instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>branch_name</replaceable></term>
+ <listitem>
+ <para>
+ The branch in the Git repository the Debian package is being
+ developed on. If set to the empty value the branch will not be pushed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-tag=</option><replaceable>TAG-FORMAT</replaceable></term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags corresponding to a &debian;
+ version. Default is <replaceable>debian/%(version)s</replaceable>.
+ If set to the empty value the tag will not be pushed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-branch</option>=<replaceable>branch_name</replaceable></term>
+ <listitem>
+ <para>
+ The branch in the &git; repository the upstream sources are put
+ onto. If set to the empty value the branch will not be pushed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--upstream-tag=</option><replaceable>TAG-FORMAT</replaceable></term>
+ <listitem>
+ <para>
+ Use this tag format when looking for tags of upstream
+ versions. Default
+ is <replaceable>upstream/%(version)s</replaceable>.
+ If set to the empty value the tag will not be pushed.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--pristine-tar</option></term>
+ <listitem>
+ <para>Whether to update the pristine-tar branch too.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--dry-run</option></term>
+ <listitem>
+ <para>
+ Pass the <option>--dry-run</option> to &gitcmd; <option>push</option>. So don't
+ push anything, only check if things are pushable.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.clone"/>,
+ <xref linkend="man.gbp.pull"/>,
+ <xref linkend="man.gbp.conf"/>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>&dhusername; &dhemail;</para>
+
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-rpm-ch.xml b/docs/manpages/gbp-rpm-ch.xml
new file mode 100644
index 0000000..e0f0d9d
--- /dev/null
+++ b/docs/manpages/gbp-rpm-ch.xml
@@ -0,0 +1,355 @@
+<refentry id="man.gbp.rpm.ch">
+ <refentryinfo>
+ <address>
+ &rpm-email;
+ </address>
+ <author>
+ &rpm-firstname;
+ &rpm-surname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp-rpm-ch</refentrytitle>
+ &rpm-mansection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-rpm-ch;</refname>
+ <refpurpose>Generate the RPM changelog from git commit messages</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-rpm-ch;
+ &man.common.options.synopsis;
+ <arg><option>--tmp-dir</option>=<replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--vendor</option>=<replaceable>VENDOR</replaceable></arg>
+ <arg><option>--packaging-branch=</option><replaceable>BRANCH-NAME</replaceable></arg>
+ <arg><option>--packaging-tag=</option><replaceable>TAG-FORMAT</replaceable></arg>
+ <arg><option>--ignore-branch</option></arg>
+ <arg><option>--packaging-dir=</option><replaceable>DIRECTORY</replaceable></arg>
+ <arg><option>--changelog-file=</option><replaceable>FILEPATH</replaceable></arg>
+ <arg><option>--spec-file=</option><replaceable>FILEPATH</replaceable></arg>
+ <arg><option>--since=</option><replaceable>COMMITISH</replaceable></arg>
+ <arg><option>--no-release</option></arg>
+ <arg><option>--[no-]git-author</option></arg>
+ <arg><option>--[no-]full</option></arg>
+ <arg><option>--id-length=</option><replaceable>NUMBER</replaceable></arg>
+ <arg><option>--ignore-regex=</option><replaceable>REGEX</replaceable></arg>
+ <arg><option>--changelog-revision=</option><replaceable>REV-FORMAT</replaceable></arg>
+ <arg><option>--git-log=</option><replaceable>GIT-LOG-OPTIONS</replaceable></arg>
+ <arg><option>--spawn-editor=<replaceable>[always|release|no]</replaceable></option></arg>
+ <arg><option>--editor-cmd=</option><replaceable>EDITOR</replaceable></arg>
+ <arg><option>--customizations=</option><replaceable>CUSTOMIZATION-FILE</replaceable></arg>
+ <arg choice="plain"><replaceable>[PATH1 PATH2]</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-rpm-ch; reads git commit messages up to the current tip of the current
+ branch and updates the RPM changelog from them.
+ </para>
+ <para>
+ By default, &gbp-rpm-ch; tries to guess the last &git; commit documented in
+ the changelog. Alternatively, <option>--since</option> can be used to
+ tell &gbp-rpm-ch; at which point it should start in the &git; history, or,
+ <option>--all</option> to use all commits from the &git; history.
+ </para>
+ <para>
+ The additional path arguments can be used to restrict the repository paths
+ &gbp-rpm-ch; looks at. For even more detailed control, you can use
+ <option>--git-log</option> to restrict the generated changelog entries
+ further. E.g. by using
+ <option>--git-log=</option><replaceable>"--author=Foo Bar"</replaceable>.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+
+ <varlistentry>
+ <term><option>--git-tmp-dir</option>=<replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Base directory under which temporary directories are created.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--vendor</option>=<replaceable>VENDOR</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Distribution vendor name.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--packaging-branch</option>=<replaceable>BRANCH-NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ The branch in the Git repository the package is being developed on,
+ default is <replaceable>master</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-branch</option>
+ </term>
+ <listitem>
+ <para>
+ Don't check if the current branch matches
+ <replaceable>PACKAGING-BRANCH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--packaging-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Tag format used, when tagging releases,
+ default is <replaceable>%(vendor)s/%(version)s</replaceable>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--packaging-dir=</option><replaceable>DIRECTORY</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Subdirectory that contains the RPM packaging files.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--changelog-file=</option><replaceable>FILEPATH</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Relative path to the changelog file to use. Special value
+ <replaceable>auto</replaceable> causes &gbp; to guess,
+ <replaceable>SPEC</replaceable> uses the spec file,
+ <replaceable>CHANGES</replaceable> uses a separate changelog file
+ (name derived spec file name with .spec suffix replaced by .changes).
+ Guessing logic is simple: use separate changelog file if it is found,
+ otherwise use the spec file.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--spec-file=</option><replaceable>FILEPATH</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Relative path to the spec file to use. Special value
+ <replaceable>auto</replaceable> causes &gbp; to search and guess.
+ Other values cause the <option>--packaging-dir</option> option to be
+ ignored: the directory of the spec file is used, instead.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--since=</option><replaceable>COMMITTISH</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Start reading commit messages at
+ <replaceable>COMMITTISH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--no-release</option>
+ </term>
+ <listitem>
+ <para>
+ Do not create a new changelog section, just update the last
+ changelog section.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]full</option>
+ </term>
+ <listitem>
+ <para>
+ Include the full commit message in the changelog output.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--git-log=</option><replaceable>GIT-LOG-OPTIONS</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Options passed on verbatim to git-log(1).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--id-length=</option><replaceable>N</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Include <replaceable>N</replaceable> digits of the commit id in the
+ changelog entry. Default is to not include any commit ids at all.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-regex=</option><replaceable>REGEX</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Ignore lines in commit message matching
+ <replaceable>REGEX</replaceable> when generating the changelog.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--changelog-revision=</option><replaceable>REV-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Format string to use for revision field in the changelog header. The
+ following string fields are accepted:
+ <replaceable>%(upstreamversion)s</replaceable> the upstream version;
+ <replaceable>%(release)s</replaceable> the rpm patchlevel, i.e.
+ Release; <replaceable>%(version)s</replaceable> full rpm package
+ version; <replaceable>%(tagname)s</replaceable> tag/commit, i.e.
+ basically what <command>git-describe</command> would give.
+ If empty or not defined the default from packaging policy is used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-regex=</option><replaceable>REGEX</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Ignore commit lines matching <replaceable>REGEX</replaceable>
+ when generating the changelog.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]git-author</option>
+ </term>
+ <listitem>
+ <para>
+ Use user.name and user.email from
+ <application>git-config</application>(1) for the changelog header.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--spawn-editor=<replaceable>[always|release|no]</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Whether to spawn an editor: always, when doing a release or never.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--editor-cmd=<replaceable>EDITOR</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ The editor to use for editing the changelog.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--customizations=</option><replaceable>CUSTOMIZATION-FILE</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Load Python code from <replaceable>CUSTOMIZATION-FILE</replaceable>.
+ At the moment, the only useful thing the code can do is define a
+ custom ChangelogEntryFormatter class.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>META TAGS</title>
+ <para>
+ Additional to the above options the formatting of the new changelog entries
+ (one-per-commit) in the changelog can be modified by special tags (called
+ Meta Tags) given in the git commit message. The tags must start at the
+ first column of a commit message but can appear on any line. They are of
+ the form <option>Tagname</option>: <replaceable>VALUE</replaceable>. Valid
+ Meta Tags are:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>Git-Rpm-Ch</option>: <replaceable>ACTION</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Supported actions are: <replaceable>Ignore</replaceable> which will
+ ignore this commit when generating new changelog entries.
+ <replaceable>Short</replaceable> which will only use the description
+ (the first line) of the commit message when generating the changelog
+ entry (useful when <option>--full</option> is given) and
+ <replaceable>Full</replaceable> which will use the full commit
+ message when generating the changelog entry (useful when
+ <option>--full</option> is not given).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>[Close|Closes|...]</option>: <replaceable>BUGNUMBER</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Indicate in the changelog entry that bug
+ <replaceable>BUGNUMBER</replaceable> was addressed in this commit.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ The following git commit message:
+ </para>
+ <screen>
+ Document meta tags
+
+ so one doesn't have to consult the manual
+
+ Git-Rpm-Ch: Short
+ Closes: #636088
+ </screen>
+ <para>
+ Results in this changelog entry:
+ </para>
+ <screen>
+ - Document meta tags (Closes: #636088)
+ </screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage.rpm"/>,
+ <xref linkend="man.gbp.import.srpm"/>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ <ulink url="https://honk.sigxcpu.org/cl2vcs">
+ <citetitle>Cl2vcs</citetitle></ulink>,
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &rpm-username; &rpm-email;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp-tag.xml b/docs/manpages/gbp-tag.xml
new file mode 100644
index 0000000..56da6db
--- /dev/null
+++ b/docs/manpages/gbp-tag.xml
@@ -0,0 +1,197 @@
+<refentry id="man.gbp.tag">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta><refentrytitle>gbp-tag</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>gbp-tag</refname>
+ <refpurpose>Tag a &debian; packages in a &git; repository</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp-tag;
+
+ &man.common.options.synopsis;
+ <arg><option>--ignore-branch</option></arg>
+ <arg><option>--[no-]ignore-new</option></arg>
+ <arg><option>--[no-]sign-tags</option></arg>
+ <arg><option>--keyid=</option><replaceable>GPG-KEYID</replaceable></arg>
+ <arg><option>--debian-branch=</option><replaceable>BRANCH_NAME</replaceable></arg>
+ <arg><option>--debian-tag=</option><replaceable>tag-format</replaceable></arg>
+ <arg><option>--debian-tag-msg=</option><replaceable>tag-msg-format</replaceable></arg>
+ <arg><option>--posttag=</option><replaceable>COMMAND</replaceable></arg>
+ <arg><option>--retag</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp-tag; tags the current head commit appropriately. It will in order:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ Verify that it is being executed from the Debian branch.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Verify that the repository doesn't contain any uncommitted source
+ changes.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Create a git tag using the information from <filename>debian/changelog</filename>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ (Optionally) call a post tag hook.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ &man.common.options.description;
+ <varlistentry>
+ <term><option>--posttag=</option><replaceable>COMMAND</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Execute <replaceable>COMMAND</replaceable> after creating the tag.
+ </para>
+ <para>
+ Exported environment variables are: <envar>GBP_TAG</envar> (the name
+ of the generated tag), <envar>GBP_BRANCH</envar> (the branch the
+ package was build from) and <envar>GBP_SHA1</envar> (the sha1 of the
+ commit the tag was created at).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]sign-tags</option>
+ </term>
+ <listitem>
+ <para>
+ GPG sign all created tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--keyid=</option><replaceable>GPG-KEYID</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this keyid for gpg signing tags.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-tag=</option><replaceable>TAG-FORMAT</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Use this tag format when tagging &debian; versions, default is
+ <replaceable>debian/%(version)s</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-tag-msg=</option><replaceable>tag-msg-format</replaceable>
+ </term>
+ <listitem>
+ <para>Use this tag message format when signing &debian; versions,
+ default is <replaceable>%(pkg)s Debian release %(version)s</replaceable></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--retag</option>
+ </term>
+ <listitem>
+ <para>
+ Don't fail tag operations if a tag with the same version
+ already exists. This is a command line only option that
+ cannot be specified via &gbp.conf;.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debian-branch</option>=<replaceable>BRANCH_NAME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ If you're not on this branch when invoking &gbp-tag; it will
+ fail. Default is <replaceable>master</replaceable>. This is done to
+ make sure you don't accidentally tag on the wrong branch. Not
+ being on this branch will be ignored when using
+ <option>--ignore-branch</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--ignore-branch</option>
+ </term>
+ <listitem>
+ <para>
+ Don't check if the current branch matches
+ <replaceable>DEBIAN-BRANCH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--[no-]ignore-new</option>
+ </term>
+ <listitem>
+ <para>
+ Don't abort if there are uncommitted changes in the source tree or
+ the current branch doesn't match the
+ <replaceable>DEBIAN-BRANCH</replaceable>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ Create a tag with the current defaults
+ </para>
+ <screen>
+ &gbp-tag;
+ </screen>
+ <para>
+ Create using a more upstreamish tag format:
+ </para>
+ <screen>
+ &gbp-tag; --debian-tag='v%(version)s'
+ </screen>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.push"/>,
+ <xref linkend="man.gbp.conf"/>,
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ &dhusername; &dhemail;
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp.conf.xml b/docs/manpages/gbp.conf.xml
new file mode 100644
index 0000000..4f018c4
--- /dev/null
+++ b/docs/manpages/gbp.conf.xml
@@ -0,0 +1,327 @@
+<refentry id="man.gbp.conf">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp.conf</refentrytitle>
+ &dhconfsection;
+ </refmeta>
+ <refnamediv>
+ <refname>&gbp.conf;</refname>
+ <refpurpose>Gbp configuration file</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <variablelist>
+ <varlistentry>
+ <term>
+ /etc/git-buildpackage/gbp.conf
+ </term>
+ <listitem>
+ <para>
+ system wide
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ ~/.gbp.conf
+ </term>
+ <listitem>
+ <para>
+ per user
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ .gbp.conf
+ </term>
+ <listitem>
+ <para>
+ per branch, can be published with the repository
+ (deprecated)
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ debian/gbp.conf
+ </term>
+ <listitem>
+ <para>
+ per branch, can be published with the repository
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ .git/gbp.conf
+ </term>
+ <listitem>
+ <para>
+ per repository
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ The <filename>gbp.conf</filename> configuration files provide
+ default global options and specific options for individual &gbp;
+ commands. All files have the same format as described below and
+ are parsed in the above order from top to bottom with increasing
+ precedence. Non existing files will be skipped.
+ </para>
+ <para>
+ Each file consists of either zero or one default section, and
+ zero or one sections for each &gbp; command. Additionally, there
+ can be an arbitrary number of
+ <option>remote-config</option> sections. Comments start with a
+ hash sign (<option>#</option>). The overall layout is:
+ </para>
+ <programlisting>
+ [DEFAULT]
+ # This section is for global settings. Affects all commands.
+ # Options set here have the lowest priority.
+ key = value
+
+ [&lt;command&gt;]
+ # Specific sections for each command, like <command>buildpackage</command>
+ # Options set here have lower priority than command line options
+ key = value
+
+ [remote-config &lt;name&gt;]
+ # Specific sections for a remote configuration. This can be used several
+ # times to set up remote configuration for <command>gbp create-remote-repo</command>
+ key = value
+ </programlisting>
+ <para>
+ The sections for each command are named like the command (without &gbp;) surrounded
+ by square brackets (e.g. <option>[buildpackage]</option>).
+ For backwards compatibility, command sections starting with <filename>git-</filename> or
+ <filename>gbp-</filename> are also supported but will provoke a warning when parsed by
+ &gbp;.
+ </para>
+
+ <para>
+ The keys in the
+ <option>key</option>=<parameter>value</parameter> pairs are named
+ like the command-line options of the corresponding command (with the
+ '--' stripped off) and can hold the same values, but see below for
+ details. In case of &gbp-buildpackage; and &gbp-buildpackage-rpm;
+ the key needs '--git-' instead of '--' stripped off.
+ </para>
+ <para>
+ For example,
+ the <xref linkend="man.gbp.buildpackage"/> manual page documents
+ the <option>--git-export-dir</option>=<parameter>directory</parameter>
+ option which can be turned into configuration file setting by
+ dropping the
+ <option>--git-</option> prefix:
+ </para>
+
+ <programlisting>
+ [buildpackage]
+ export-dir = directory
+ </programlisting>
+
+ <para>
+ Options that can be repeated on the command line take Python-like
+ lists in the config file. For example,
+ the <xref linkend="man.gbp.import.orig"/> command has the
+ <option>--filter</option>=<parameter>pattern</parameter> option
+ which can be turned into a configuration file option like this:
+ </para>
+
+ <programlisting>
+ [import-orig]
+ filter = [ '.svn', '.hg' ]
+ </programlisting>
+
+ <para>
+ Boolean options can be either <option>True</option> or <option>False</option>. For example,
+ <xref linkend="man.gbp.import.orig"/> has the <option>--pristine-tar</option> and
+ <option>--no-pristine-tar</option> options which translate to:
+ </para>
+ <programlisting>
+ [import-orig]
+ pristine-tar = True
+ </programlisting>
+
+ <para>and</para>
+
+ <programlisting>
+ [import-orig]
+ pristine-tar = False
+ </programlisting>
+ <para>respectively.</para>
+
+ <refsect2>
+ <title>Remote site configuration</title>
+ <para>
+ <xref linkend="man.gbp.create.remote.repo"/> can additionally parse remote site
+ configurations from <filename>gbp.conf</filename>. For example, a configuration like:
+ </para>
+
+ <programlisting>
+ [remote-config pkg-libvirt]
+ # Location of the repository
+ remote-url-pattern = ssh://git.debian.org/git/pkg-libvirt/%(pkg)s
+ # Template dir to passed to git-init
+ template-dir = /srv/alioth.debian.org/chroot/home/groups/pkg-libvirt/git-template
+ </programlisting>
+
+ <para>
+ can be used to create remote repositories for the pkg-libvirt project using:
+ </para>
+
+ <programlisting>
+ gbp-create-remote-repo --remote-config=pkg-libvirt
+ </programlisting>
+
+ <para>
+ This can be useful if you're often creating new remote repositories for
+ different projects.
+ </para>
+ </refsect2>
+
+ <refsect2>
+ <title>Notes</title>
+ <note>
+ <para>
+ To see the current set of values that would be applied after parsing the
+ configuration files, use <xref linkend="man.gbp.config"/>.
+ </para>
+ </note>
+
+ <note>
+ <para>
+ <command>gbp import-dscs</command> and <command>git-pbuilder</command>
+ can't be configured via <filename>gbp.conf</filename>.
+ </para>
+ </note>
+ </refsect2>
+
+ </refsect1>
+ <refsect1>
+ <title>EXAMPLES</title>
+ <para>
+ An example set up for packaging work:
+ </para>
+
+ <programlisting>
+ # $HOME/.gbp.conf
+
+ [DEFAULT]
+ pristine-tar = True
+ cleaner = fakeroot debian/rules clean
+
+ [buildpackage]
+ export-dir = ../build-area/
+
+ [import-orig]
+ dch = False
+ filter = [
+ '.svn',
+ '.hg',
+ '.bzr',
+ 'CVS',
+ 'debian/*',
+ '*/debian/*'
+ ]
+ filter-pristine-tar = True
+
+ [import-dsc]
+ filter = [
+ 'CVS',
+ '.cvsignore',
+ '.hg',
+ '.hgignore'
+ '.bzr',
+ '.bzrignore',
+ '.gitignore'
+ ]
+
+ # End of file
+ </programlisting>
+ </refsect1>
+
+ <refsect1>
+ <title>ENVIRONMENT</title>
+ <para>
+ The following environment variables can be used to modify &gbp;'s
+ configuration file handling:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><envar>GBP_CONF_FILES</envar></term>
+ <listitem>
+ <para>
+ A colon separated list of configuration files to parse. If
+ unset or empty the default list of files is parsed (see above).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><envar>GBP_DISABLE_SECTION_DEPRECATION</envar></term>
+ <listitem>
+ <para>
+ When set to a non-empty value don't print a deprecation
+ warning in case &gbp; encounters a section starting with
+ <emphasis>git-</emphasis> or <emphasis>gbp-</emphasis>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><envar>GBP_DISABLE_GBP_CONF_DEPRECATION</envar></term>
+ <listitem>
+ <para>
+ When set to a non-empty value don't print a deprecation
+ warning in case &gbp; encounters a configuration file in
+ a deprecated location.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>FILES</title>
+ <para>
+ See <filename>/etc/git-buildpackage/gbp.conf</filename> for an example.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.config"/>,
+ <xref linkend="man.gbp.clone"/>,
+ <xref linkend="man.gbp.create.remote.repo"/>,
+ <xref linkend="man.gbp.pq"/>,
+ <xref linkend="man.gbp.pull"/>,
+ <xref linkend="man.gbp.dch"/>,
+ <xref linkend="man.gbp.import.dsc"/>,
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.buildpackage"/>,
+ <ulink url="file:///usr/share/doc/git-buildpackage/manual-html/index.html">
+ <citetitle>The Git-Buildpackage Manual</citetitle></ulink>
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHORS</title>
+ <para>&dhusername; &dhemail;</para>
+ <para>This manual page is based on a POD version by Jari Aalto <email>jari.aalto@cante.net</email>.
+ Released under license GNU GPL version 2 or (at your option) any later.
+ version.
+ </para>
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/gbp.xml b/docs/manpages/gbp.xml
new file mode 100644
index 0000000..40da9ba
--- /dev/null
+++ b/docs/manpages/gbp.xml
@@ -0,0 +1,211 @@
+<refentry id="man.gbp">
+ <refentryinfo>
+ <address>
+ &dhemail;
+ </address>
+ <author>
+ &dhfirstname;
+ &dhsurname;
+ </author>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>gbp</refentrytitle>
+ &dhsection;
+ </refmeta>
+ <refnamediv>
+ <refname>&gbp;</refname>
+ <refpurpose>Maintain &debian; packages in &git;</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ &gbp;
+ <group choice='req'>
+ <arg><option>--help</option></arg>
+ <arg><option>--version</option></arg>
+ <arg><option>--list-cmds</option></arg>
+ <arg><option>command</option><arg choice='opt' rep='repeat'><option>args</option></arg></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ &gbp; is used to maintain &debian; source packages in the &git; version control system.
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>OPTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--help</option>
+ </term>
+ <listitem>
+ <para>Print help</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--version</option>
+ </term>
+ <listitem>
+ <para>Print the programs version</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--list-cmds</option>
+ </term>
+ <listitem>
+ <para>List all available commands</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ <title>GBP COMMANDS</title>
+ <para>
+ These are the possible commands. For possible arguments to these commands, please
+ see the corresponding man pages.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>buildpackage
+ </term>
+ <listitem>
+ <para>Build source and binary packages from a &git; repository</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>import-orig
+ </term>
+ <listitem>
+ <para>Import a new upstream version into a &git; repository</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>export-orig
+ </term>
+ <listitem>
+ <para>Recreate upstream tarballs from a &git; repository</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>import-dsc
+ </term>
+ <listitem>
+ <para>Import a &debian; source package into a &git; repository</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>import-dscs
+ </term>
+ <listitem>
+ <para>Import several &debian; source packages into a &git; repository, sorted
+ by version number</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>dch
+ </term>
+ <listitem>
+ <para>Generate the <filename>debian/changelog</filename> from &git; commit history</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>pq
+ </term>
+ <listitem>
+ <para>Manage <filename>debian/patches</filename> using &git; <option>rebase</option></para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>pull
+ </term>
+ <listitem>
+ <para>Update a &git; repository from a remote</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>clone
+ </term>
+ <listitem>
+ <para>Clone a &git; repository from a remote and set up the necessary branch tracking.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>create-remote-repo
+ </term>
+ <listitem>
+ <para>Create a remote &git; repository</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>tag
+ </term>
+ <listitem>
+ <para>Tag a &debian; release in a &git; repository</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <variablelist>
+ <varlistentry>
+ <term>push
+ </term>
+ <listitem>
+ <para>Push packaging branches to a remote &git; repository</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+ <refsect1>
+ &man.gbp.config-files;
+ </refsect1>
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <xref linkend="man.gbp.buildpackage"/>,
+ <xref linkend="man.gbp.clone"/>,
+ <xref linkend="man.gbp.create.remote.repo"/>,
+ <xref linkend="man.gbp.dch"/>,
+ <xref linkend="man.gbp.export.orig"/>,
+ <xref linkend="man.gbp.import.dsc"/>,
+ <xref linkend="man.gbp.import.dscs"/>,
+ <xref linkend="man.gbp.import.orig"/>,
+ <xref linkend="man.gbp.pq"/>,
+ <xref linkend="man.gbp.pristine.tar"/>,
+ <xref linkend="man.gbp.pull"/>,
+ <xref linkend="man.gbp.push"/>,
+ <xref linkend="man.gbp.tag"/>,
+ <citerefentry>
+ <refentrytitle>git-pbuilder</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <xref linkend="man.gbp.conf"/>,
+ &man.seealso.common;
+ </para>
+ </refsect1>
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>&dhusername; &dhemail;</para>
+
+ </refsect1>
+</refentry>
diff --git a/docs/manpages/man.common-options.ent b/docs/manpages/man.common-options.ent
new file mode 100644
index 0000000..2a70581
--- /dev/null
+++ b/docs/manpages/man.common-options.ent
@@ -0,0 +1,66 @@
+<!ENTITY man.common.options.synopsis
+"
+ <arg><option>--version</option></arg>
+ <arg><option>--help</option></arg>
+ <arg><option>--verbose</option></arg>
+ <arg><option>--color=</option>[auto|on|off]</arg>
+ <arg><option>--color-scheme=</option><replaceable>COLOR_SCHEME</replaceable></arg>
+"
+>
+<!ENTITY man.common.options.description
+"
+ <varlistentry>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>
+ Print version of the program, i.e. version of the git-buildpackage
+ suite
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-v</option></term>
+ <term><option>--verbose</option></term>
+ <listitem>
+ <para>
+ Verbose execution
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>
+ Print help and exit
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--color=</option>[auto|on|off]
+ </term>
+ <listitem>
+ <para>
+ Whether to use colored output.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--color-scheme=</option><replaceable>COLOR_SCHEME</replaceable>
+ </term>
+ <listitem>
+ <para>
+ Colors to use in output (when color is enabled). The format for
+ COLOR_SCHEME is
+ '&lt;debug&gt;:&lt;info&gt;:&lt;warning&gt;:&lt;error&gt;'.
+ Numerical values and color names are accepted, empty fields imply
+ the default color. For example,
+ <option>--git-color-scheme=</option><literal>'cyan:34::'</literal> would
+ show debug messages in cyan, info messages in blue and other messages
+ in default (i.e. warning and error messages in red).
+ </para>
+ </listitem>
+ </varlistentry>
+"
+>
+
diff --git a/docs/manpages/man.conffiles.xml b/docs/manpages/man.conffiles.xml
new file mode 100644
index 0000000..17dcd1c
--- /dev/null
+++ b/docs/manpages/man.conffiles.xml
@@ -0,0 +1,4 @@
+ <title>CONFIGURATION FILES</title>
+ <para>Several <filename>gbp.conf</filename> files are parsed
+ to set defaults for the above command-line arguments. See the
+ <xref linkend="man.gbp.conf"/> manpage for details.</para>
diff --git a/docs/manpages/man.seealso.xml b/docs/manpages/man.seealso.xml
new file mode 100644
index 0000000..b7af133
--- /dev/null
+++ b/docs/manpages/man.seealso.xml
@@ -0,0 +1,14 @@
+ <citerefentry>
+ <refentrytitle>debuild</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>git</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>pristine-tar</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ <ulink url="file:///usr/share/doc/git-buildpackage/manual-html/index.html">
+ <citetitle>The Git-Buildpackage Manual</citetitle></ulink>
diff --git a/docs/manpages/manpages.ent b/docs/manpages/manpages.ent
new file mode 100644
index 0000000..452f83a
--- /dev/null
+++ b/docs/manpages/manpages.ent
@@ -0,0 +1,25 @@
+<!ENTITY man.gbp SYSTEM "gbp.xml">
+<!ENTITY man.gbp.buildpackage SYSTEM "gbp-buildpackage.xml">
+<!ENTITY man.gbp.buildpackage.rpm SYSTEM "gbp-buildpackage-rpm.xml">
+<!ENTITY man.gbp.clone SYSTEM "gbp-clone.xml">
+<!ENTITY man.gbp.conf SYSTEM "gbp.conf.xml">
+<!ENTITY man.gbp.config SYSTEM "gbp-config.xml">
+<!ENTITY man.gbp.config-files SYSTEM "man.conffiles.xml">
+<!ENTITY man.gbp.create.remote.repo SYSTEM "gbp-create-remote-repo.xml">
+<!ENTITY man.gbp.dch SYSTEM "gbp-dch.xml">
+<!ENTITY man.gbp.exportorig SYSTEM "gbp-export-orig.xml">
+<!ENTITY man.gbp.import.srpm SYSTEM "gbp-import-srpm.xml">
+<!ENTITY man.gbp.importdsc SYSTEM "gbp-import-dsc.xml">
+<!ENTITY man.gbp.importdscs SYSTEM "gbp-import-dscs.xml">
+<!ENTITY man.gbp.importorig SYSTEM "gbp-import-orig.xml">
+<!ENTITY man.gbp.pq SYSTEM "gbp-pq.xml">
+<!ENTITY man.gbp.pq.rpm SYSTEM "gbp-pq-rpm.xml">
+<!ENTITY man.gbp.pristine.tar SYSTEM "gbp-pristine-tar.xml">
+<!ENTITY man.gbp.pull SYSTEM "gbp-pull.xml">
+<!ENTITY man.gbp.push SYSTEM "gbp-push.xml">
+<!ENTITY man.gbp.rpm.ch SYSTEM "gbp-rpm-ch.xml">
+<!ENTITY man.gbp.tag SYSTEM "gbp-tag.xml">
+<!ENTITY man.seealso.common SYSTEM "man.seealso.xml">
+
+<!ENTITY % COMMON.OPTIONS SYSTEM "man.common-options.ent">
+%COMMON.OPTIONS;
diff --git a/docs/manual.xml b/docs/manual.xml
new file mode 100644
index 0000000..35a4e41
--- /dev/null
+++ b/docs/manual.xml
@@ -0,0 +1,58 @@
+<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+ <!ENTITY % COMMON SYSTEM "common.ent">
+ %COMMON;
+ <!ENTITY % VERSION SYSTEM "version.ent">
+ %VERSION;
+ <!ENTITY % MANPAGES SYSTEM "manpages/manpages.ent">
+ %MANPAGES;
+ <!ENTITY % CHAPTERS SYSTEM "chapters/chapters.ent">
+ %CHAPTERS;
+ <!ENTITY gbp.copyright SYSTEM "copyright.xml">
+]>
+
+<book id="gbp">
+ <bookinfo>
+ <title>Building Debian Packages with git-buildpackage</title>
+ <author>&dhfirstname; &dhsurname;</author>
+ <address>&dhemail;</address>
+ <subtitle>Version: &gbp-version;</subtitle>
+ </bookinfo>
+
+ &ch.intro;
+ &ch.import;
+ &ch.building;
+ &ch.patches;
+ &ch.releases;
+ &ch.cfgfile;
+ &ch.special;
+
+ <appendix id="man.reference">
+ <title>Command Reference</title>
+ &man.gbp;
+ &man.gbp.buildpackage;
+ &man.gbp.clone;
+ &man.gbp.config;
+ &man.gbp.create.remote.repo;
+ &man.gbp.dch;
+ &man.gbp.exportorig;
+ &man.gbp.importdsc;
+ &man.gbp.importdscs;
+ &man.gbp.importorig;
+ &man.gbp.pq;
+ &man.gbp.pristine.tar;
+ &man.gbp.pull;
+ &man.gbp.push;
+ &man.gbp.tag;
+ &man.gbp.conf;
+ &man.gbp.buildpackage.rpm;
+ &man.gbp.import.srpm;
+ &man.gbp.pq.rpm;
+ &man.gbp.rpm.ch;
+ </appendix>
+ <appendix id="gbp.copyleft">
+ <title>Copyright</title>
+ &gbp.copyright;
+ </appendix>
+</book>
+
diff --git a/examples/README.source b/examples/README.source
new file mode 100644
index 0000000..90e1f00
--- /dev/null
+++ b/examples/README.source
@@ -0,0 +1,29 @@
+This package is maintained with git-buildpackage(1). It follows DEP-14
+for branch naming (e.g. using debian/sid for the current version
+in Debian unstable).
+
+It uses pristine-tar(1) to store enough information in git to generate
+bit identical tarballs when building the package without having
+downloaded an upstream tarball first.
+
+When working with patches it is recommended to use "gbp pq import" to
+import the patches, modify the source and then use "gbp pq export
+--commit" to commit the modifications.
+
+The changelog is generated using "gbp dch" so if you submit any
+changes don't bother to add changelog entries but rather provide
+a nice git commit message that can then end up in the changelog.
+
+It is recommended to build the package with pbuilder using:
+
+ gbp buildpackage --git-pbuilder
+
+For information on how to set up a pbuilder environment see the
+git-pbuilder(1) manpage. In short:
+
+ DIST=sid git-pbuilder create
+ gbp clone <project-url>
+ cd <project>
+ gbp buildpackage --git-pbuilder
+
+ -- Guido Günther <agx@sigxcpu.org>, Wed, 2 Dec 2015 18:51:15 +0100
diff --git a/examples/gbp-add-patch b/examples/gbp-add-patch
new file mode 100755
index 0000000..806ed94
--- /dev/null
+++ b/examples/gbp-add-patch
@@ -0,0 +1,120 @@
+#!/usr/bin/python3 -u
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010,2015 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Add a patch from debian/patches and autocreate the commit message from the
+patch info
+
+running
+
+ gbp-add-patch debian/patches/0010-bla-fasel
+
+commits debian/patches/0010-bla-fasel with this changelog message:
+
+ New patch 0010-bla-fasel
+
+ <patch summary>
+ Closes: <bugs>
+ Thanks: <author>
+"""
+
+import re
+import sys
+import os
+import os.path
+from gbp.command_wrappers import (Command)
+from gbp.config import GbpOptionParserDebian
+from gbp.errors import GbpError
+from gbp.git import (GitRepositoryError, GitRepository)
+from gbp.patch_series import Patch
+
+
+def build_commit_msg(repo, patch, options):
+ bug_r = r'(?:bug)?\#?\s?\d+'
+ bts_closes = re.compile(r'(?P<bts>%s):\s+%s' % (options.meta_closes, bug_r), re.I)
+ thanks = ''
+ closes = ''
+
+ author = repo.get_author_info()
+ if author.name != patch.author:
+ thanks = "Thanks: %s" % patch.author
+
+ for line in patch.long_desc.split('\n'):
+ if bts_closes.match(line):
+ closes += line + '\n'
+
+ patch_name = os.path.basename(patch.path)
+ msg = """New patch %s
+
+%s
+%s
+%s""" % (patch_name, patch.subject, thanks, closes)
+ return msg
+
+
+def main(argv):
+ retval = 0
+
+ parser = GbpOptionParserDebian(command=os.path.basename(argv[0]), prefix='',
+ usage='%prog [options] - add a new patch')
+ parser.add_config_file_option(option_name="meta-closes", dest="meta_closes",
+ help="Meta tags for the bts close commands, default is '%(meta-closes)s'")
+ parser.add_option("-e", "--edit", action="store_true", dest="edit", default=False,
+ help="edit commit message before committing")
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
+ help="verbose command execution")
+
+ (options, args) = parser.parse_args(argv)
+
+ if options.verbose:
+ Command.verbose = True
+
+ try:
+ repo = GitRepository(os.path.curdir)
+ except GitRepositoryError:
+ print("%s is not a git repository" % os.path.abspath('.'),
+ file=sys.stderr)
+ return 1
+
+ try:
+ if len(args) != 2:
+ parser.print_help()
+ raise GbpError
+ else:
+ patchfile = args[1]
+
+ patch = Patch(patchfile)
+
+ repo.add_files(patchfile)
+ msg = build_commit_msg(repo, patch, options)
+ repo.commit_staged(edit=options.edit, msg=msg)
+ # FIXME: handle the series file
+
+ except GitRepositoryError:
+ retval = 1
+ except GbpError as err:
+ if str(err):
+ print(err, file=sys.stderr)
+ 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/examples/gbp-configure-unpatched-source b/examples/gbp-configure-unpatched-source
new file mode 100755
index 0000000..276e310
--- /dev/null
+++ b/examples/gbp-configure-unpatched-source
@@ -0,0 +1,54 @@
+#!/bin/bash
+#
+#
+# Setup dpkg-source and git to unpatch the source after the build and ignore
+# the .pc directory.
+
+GIT_EXCLUDE=.git/info/exclude
+LOCAL_OPTIONS=debian/source/local-options
+
+help ()
+{
+ cat << EOF >/dev/stdout
+
+Modifies "$LOCAL_OPTIONS" and "$GIT_EXCLUDE"
+to ignore .pc and unpatch the source after the build.
+
+EOF
+
+ exit $1
+}
+
+
+case $1 in
+ -h|--help)
+ help 0
+ ;;
+esac
+
+
+if [ ! -d .git ]; then
+ echo "Not the top of a git repository - aborting."
+ help 1
+fi
+
+if ! grep -qs '^3.*\(quilt\)' debian/source/format; then
+ echo "Not a source format 3 (quilt) package - aborting."
+ help 1
+fi
+
+if ! grep -qs '^unapply-patches' $LOCAL_OPTIONS; then
+ echo "Setting unapply-patches in $LOCAL_OPTIONS"
+ echo "unapply-patches" >> $LOCAL_OPTIONS
+ git add $LOCAL_OPTIONS
+ git commit -m "Unapply patches from source" $LOCAL_OPTIONS
+else
+ echo "unapply-patches already configured"
+fi
+
+if ! grep -qs "^\.pc/" $GIT_EXCLUDE; then
+ echo "Adding .pc/ to $GIT_EXCLUDE"
+ echo ".pc/" >> $GIT_EXCLUDE
+else
+ echo "$GIT_EXCLUDE already configured"
+fi
diff --git a/examples/gbp-svn-tag b/examples/gbp-svn-tag
new file mode 100755
index 0000000..08313ed
--- /dev/null
+++ b/examples/gbp-svn-tag
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Helper script to tag a version in SVN if using git-svn(1)
+
+DIST=$(dpkg-parsechangelog | awk "/^Distribution:/{ print \$2; }")
+VERSION=$(dpkg-parsechangelog | awk "/^Version:/{ print \$2; }")
+PKG=$(dpkg-parsechangelog | awk "/^Source:/{ print \$2; }")
+
+
+if [ "$DIST" = "UNRELEASED" ]; then
+ echo "Distribution is unreleased"
+ exit 1
+elif [ -z "$VERSION" ]; then
+ echo "Cant read package version"
+ exit 1
+fi
+
+git svn tag -m"Tagging $PKG ($VERSION)" $VERSION
+
diff --git a/examples/gbp-try-ff b/examples/gbp-try-ff
new file mode 100755
index 0000000..725ea3e
--- /dev/null
+++ b/examples/gbp-try-ff
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# Try to update a package to a new upstream version
+
+set -e
+
+DB=$(gbp config buildpackage.debian-branch | sed 's/\(.*debian-branch=\)\(.*\)/\2/')
+MAINTAINER=$(dpkg-parsechangelog -SMaintainer)
+SOURCE=$(dpkg-parsechangelog -SSource)
+OLD_VERSION=$(dpkg-parsechangelog -SVersion)
+
+git checkout "${DB}"
+gbp pq import --force
+git checkout "${DB}"
+
+set +e
+gbp import-orig --uscan --no-interactive --no-pristine-tar
+ret=$?
+set -e
+# no new version found
+if [ $ret = 4 ]; then
+ exit 0
+# all other errors
+elif [ $ret != 0 ]; then
+ exit $ret
+fi
+
+if ! gbp pq rebase; then
+ echo "Automatic rebase failed"
+ git rebase --abort
+ exit 1
+fi
+
+gbp pq export --commit
+gbp dch -S -a
+
+NEW_VERSION=$(dpkg-parsechangelog -SVersion)
+git commit -m"Snapshot build of ${SOURCE} $NEW_VERSION" debian/changelog
+
+gbp buildpackage --git-pbuilder \
+ --git-no-pristine-tar \
+ --git-postbuild='lintian $GBP_CHANGES_FILE' \
+ -nc \
+ ${GBP_BUILDPACKAGE_ARGS}
+
+MSG="Fast forward of ${SOURCE} from ${OLD_VERSION} to ${NEW_VERSION} successful"
+echo "${MSG}"
+MAILADDR=$(echo $MAINTAINER | sed -e 's/.*<\(.*\)>/\1/')
+echo "$MSG" | mail -s "Update of ${SOURCE} to ${NEW_VERSION}" "${MAILADDR}"
diff --git a/examples/jenkins-scratchbuilder b/examples/jenkins-scratchbuilder
new file mode 100755
index 0000000..c2e32fe
--- /dev/null
+++ b/examples/jenkins-scratchbuilder
@@ -0,0 +1,41 @@
+#!/bin/bash
+#
+# - Use "execute shell" step in Jenkins like
+#
+# export ARCH=i386
+# export DIST=sid
+# jenkins-scratchbuilder
+#
+# - jenkins must be be able to invoke cowbuilder/pbuilder via sudo:
+#
+# %pbuilder ALL = SETENV: NOPASSWD: /usr/sbin/cowbuilder, /usr/sbin/pbuilder
+#
+# - It assumes you checked out the sources into scratchbuild/
+
+set +e
+
+# Remove old build artifacts from workspace
+rm -f *.deb *.changes *.build *.dsc
+
+# We assume jenkins was told to checkout into a specific subdir
+# named scratchbuild
+cd scratchbuild/
+
+# Make sure we have an uptodate cowbuilder environment
+# Note that git-pbuilder will pick up $DIST and $ARCH from the environment
+[ -d /var/cache/pbuilder/base.cow ] || git-pbuilder create
+git-pbuilder update
+
+# Cleanup any leftovers
+git clean -dfx
+
+# Reset the changelog
+git checkout -f debian/changelog
+
+# Create a monotonically increasing changelog by including the build number
+gbp dch -S -a --ignore-branch --snapshot-number=${BUILD_NUMBER}
+
+# Build the package
+[ -z ARCH ] || GBP_OPTS="$GBP_OPTS --git-arch=$ARCH"
+[ -z DIST ] || GBP_OPTS="$GBP_OPTS --git-dist=$DIST"
+gbp buildpackage ${GBP_OPTS} --git-ignore-branch --git-pbuilder --git-cleaner=/bin/true --git-ignore-new --git-pristine-tar -b
diff --git a/examples/wrap_cl.py b/examples/wrap_cl.py
new file mode 100644
index 0000000..c4c6f8b
--- /dev/null
+++ b/examples/wrap_cl.py
@@ -0,0 +1,15 @@
+# Simple changelog entry formatter
+#
+# It simply uses the built in formatter and linewraps the text
+#
+# Use git-dch --customizations=/usr/share/doc/git-buildpackage/examples/wrap_cl.py
+# or set it via gbp.conf
+
+import textwrap
+import gbp.dch
+
+
+def format_changelog_entry(commit_info, options, last_commit=False):
+ entry = gbp.dch.format_changelog_entry(commit_info, options, last_commit)
+ if entry:
+ return textwrap.wrap(" ".join(entry))
diff --git a/examples/zeitgeist-git.py b/examples/zeitgeist-git.py
new file mode 100755
index 0000000..b62e7bd
--- /dev/null
+++ b/examples/zeitgeist-git.py
@@ -0,0 +1,104 @@
+#! /usr/bin/python3
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 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, please see
+# <http://www.gnu.org/licenses/>
+#
+# Simple Zeitgeist Git data source
+
+"""Post-commit hook to submit the commit to Zeitgeist (http://www.zeitgeist-project.com)
+
+copy as post-commit to
+
+ .git/hooks/post-commit
+
+in existing repositories or to
+
+ /usr/share/git-core/templates
+
+so it get's used for new ones.
+"""
+
+
+import os
+import subprocess
+import sys
+import time
+
+CLIENT = None
+
+try:
+ from zeitgeist.client import ZeitgeistClient
+ from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation
+except ImportError:
+ pass
+else:
+ try:
+ CLIENT = ZeitgeistClient()
+ except RuntimeError as e:
+ print("Unable to connect to Zeitgeist, won't send events. Reason: '%s'" % e)
+
+
+def get_repo():
+ """Get uri of remote repository and its name"""
+ repo = None
+ uri = subprocess.Popen(['git', 'config', '--get', 'remote.origin.url'],
+ stdout=subprocess.PIPE).communicate()[0]
+
+ if uri:
+ uri = uri.strip().decode(sys.getfilesystemencoding())
+ if '/' in uri:
+ sep = '/'
+ else:
+ sep = ':'
+ try:
+ repo = unicode(uri.rsplit(sep, 1)[1])
+ except IndexError: # no known separator
+ repo = uri
+ repo = repo.rsplit(u'.git', 1)[0]
+ return repo, uri
+
+
+def main(argv):
+ interpretation = Interpretation.MODIFY_EVENT.uri
+
+ # FIXME: I'd be great if zeitgeist would allow for more detail:
+ # * branch
+ # * log summary (git log -1 --format=%s HEAD)
+ curdir = os.path.abspath(os.curdir).decode(sys.getfilesystemencoding())
+ uri = u"file://%s" % curdir
+
+ repo, origin = get_repo()
+ if not repo:
+ repo = unicode(curdir.rsplit('/', 1)[1])
+ origin = uri
+
+ subject = Subject.new_for_values(
+ uri=uri,
+ interpretation=Interpretation.DOCUMENT.TEXT_DOCUMENT.PLAIN_TEXT_DOCUMENT.SOURCE_CODE.uri,
+ manifestation=Manifestation.FILE_DATA_OBJECT.uri,
+ text=repo,
+ origin=origin)
+ event = Event.new_for_values(
+ timestamp=int(time.time() * 1000),
+ interpretation=interpretation,
+ manifestation=Manifestation.USER_ACTIVITY.uri,
+ actor="application://gitg.desktop",
+ subjects=[subject])
+ CLIENT.insert_event(event)
+
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/gbp.conf b/gbp.conf
new file mode 100644
index 0000000..d104a50
--- /dev/null
+++ b/gbp.conf
@@ -0,0 +1,141 @@
+# Configuration file for "gbp <command>"
+
+[DEFAULT]
+# the default build command:
+#builder = debuild -i -I
+# the default clean command:
+#cleaner = debuild clean
+# the default branch for upstream sources:
+#upstream-branch = upstream
+# the default branch for the debian patch:
+#debian-branch = master
+# the default tag formats used:
+#upstream-tag = upstream/%(version)s
+#debian-tag = debian/%(version)s
+#debian-tag-msg = %(pkg)s Debian release %(version)s
+# use pristine-tar:
+#pristine-tar = True
+# don't check if debian-branch == current branch:
+#ignore-branch = True
+# Use color when on a terminal, alternatives: on/true, off/false or auto
+#color = auto
+
+# Options only affecting gbp buildpackage
+[buildpackage]
+# Look for a tag matching the upstream version when creating a tarball
+#upstream-tree = tag
+# uncomment this to automatically GPG sign tags:
+#sign-tags = True
+# keyid to GPG sign tags with:
+#keyid = 0xdeadbeef
+# push to a remote repository after a successful tag:
+#posttag = git-push git.example.com
+# call lintian after a successful build:
+#postbuild = lintian $GBP_CHANGES_FILE
+# let package generate upstream changelog before build:
+#prebuild = GIT_DIR=$GBP_GIT_DIR debian/autogen.sh
+# use this for more svn-buildpackage like behaviour:
+#export-dir = ../build-area/
+#tarball-dir = ../tarballs/
+#ignore-new = True
+#export = HEAD
+# compress with bzip2
+#compression = bzip2
+# use best compression
+#compression-level = best
+# Don't send notifications, alternatives: on/true, off/false or auto
+#notify = off
+# Transparently handle submodules
+# submodules = True
+# Whether to use cowbuilder via git-pbuilder(1)
+#pbuilder = True
+# Which distribution to use with git-pbuilder
+#dist = testing
+# Options to pass to pbuilder when using git-pbuilder
+#git-pbuilder-options = '--hookdir /etc/pbuilder/hooks'
+
+# Options only affecting gbp import-orig
+[import-orig]
+# set a different upstream branch to import to:
+#upstream-branch = newupstream
+# set a different branch to merge to:
+#debian-branch = dfsgclean
+# don't merge to debian branch by default:
+#merge = False
+# import filter:
+#filter = .svn
+# filter out files from tarball passed to pristine tar:
+#filter-pristine-tar = True
+# run hook after the import:
+#postimport = git-dch -N%(version)s -S -a --debian-branch=$GBP_BRANCH
+# emulate old behaviour of calling dch:
+#postimport = dch -v%(version)s New Upstream Version
+# commit message:
+#import-msg = New upstream version %(version)s
+
+# Options only affecting gbp import-dsc
+[import-dsc]
+# set a different upstream branch:
+#upstream-branch = svn-upstream
+# import filter:
+#filter = [ 'CVS', '.cvsignore' ]
+#force committer to be the same as author
+#author-is-committer = True
+#same for the date
+#author-date-is-committer-date = True
+
+# Options only affecting gbp dch
+[dch]
+# options passed to git-log:
+#git-log = --no-merges
+# next snapshot number:
+#snapshot-number = snapshot + 1
+# include 7 digits of the commit id in the changelog enty:
+#id-length = 7
+# don't include information from meta tags:
+#meta = False
+# what tags to look for to generate bug-closing changelog entries:
+#meta-closes = Closes|LP
+# what regex should be used to parse the bug number
+#meta-closes-bugnum = '(?:bug|issue)?\#?\s?\d+'
+# include the full commit message in the changelog:
+#full = True
+# ignore Signed-off-by: lines:
+#ignore-regex=(Signed-off|Acked)-by:
+# use author name and email from git-config:
+#git-author = True
+# Customizatons can e.g. be used for line wrapping
+#customizations=/usr/share/doc/git-buildpackage/examples/wrap_cl.py
+# Options to pass to dch verbatim
+#dch-opt = ['--mainttrailer']
+
+# Options only affecting gbp pq
+[pq]
+#patch-numbers = False
+# The format specifier for patch number prefixes
+#patch-num-format = '%04d-'
+# Whether to renumber patches when exporting patch queues
+#renumber = False
+# Whether to drop patch queue after export
+#drop = False
+
+# Options only affecting gbp clone
+[clone]
+#pristine-tar = True
+
+# Options only affecting gbp pull
+[pull]
+#pristine-tar = True
+
+# Options only affecting gbp create remote repo
+[create-remote-repo]
+# disable remote branch tracking
+#track = False
+
+# Sample config to create remote repositore using gbp create-remote-repo:
+[remote-config pkg-libvirt]
+# Location of the repository
+remote-url-pattern = ssh://git.debian.org/git/pkg-libvirt/%(pkg)s
+# Template dir to passed to git-init
+template-dir = /srv/alioth.debian.org/chroot/home/groups/pkg-libvirt/git-template
+
diff --git a/gbp.egg-info/PKG-INFO b/gbp.egg-info/PKG-INFO
new file mode 100644
index 0000000..2c611d3
--- /dev/null
+++ b/gbp.egg-info/PKG-INFO
@@ -0,0 +1,44 @@
+Metadata-Version: 1.2
+Name: gbp
+Version: 0.9.9
+Summary: Suite to help with Debian packages in Git repositories
+Home-page: https://honk.sigxcpu.org/piki/projects/git-buildpackage/
+Author: Guido Günther
+Author-email: agx@sigxcpu.org
+License: GPLv2+
+Description: Git-buildpackage
+ ----------------
+ Tools to ease the development of Debian and (partially) RPM packages in git
+ repositories. For documentation see the manual online at:
+
+ https://gbp.sigxcpu.org/manual/
+
+ On Debian systems, the documentation can be found in
+ /usr/share/doc/git-buildpackage/manual-html.
+
+ The API documentation of the gbp module can be found at:
+
+ https://gbp.sigxcpu.org/apidocs/
+
+ The mailing list is at:
+
+ * https://lists.sigxcpu.org/mailman/listinfo/git-buildpackage
+ * git-buildpackage at lists.sigxcpu.org
+
+ See the HACKING document for details on contributing to gbp development. The
+ package is also available on Pypi at:
+
+ https://pypi.python.org/pypi/gbp/
+
+ ![gbp logo](docs/gbp.svg)
+
+ [![Build Status](https://travis-ci.org/agx/git-buildpackage.svg?branch=master)](https://travis-ci.org/agx/git-buildpackage)
+ [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1311/badge)](https://bestpractices.coreinfrastructure.org/projects/1311)
+
+Platform: UNKNOWN
+Classifier: Environment :: Console
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Software Development :: Version Control
+Classifier: Operating System :: POSIX :: Linux
+Requires: dateutil
+Requires-Python: >=3.5
diff --git a/gbp.egg-info/SOURCES.txt b/gbp.egg-info/SOURCES.txt
new file mode 100644
index 0000000..1f152b4
--- /dev/null
+++ b/gbp.egg-info/SOURCES.txt
@@ -0,0 +1,85 @@
+README.md
+gbp.conf
+setup.cfg
+setup.py
+bin/gbp-builder-mock
+bin/git-pbuilder
+gbp/__init__.py
+gbp/command_wrappers.py
+gbp/config.py
+gbp/dch.py
+gbp/errors.py
+gbp/format.py
+gbp/log.py
+gbp/notifications.py
+gbp/patch_series.py
+gbp/paths.py
+gbp/tmpfile.py
+gbp/tristate.py
+gbp/version.py
+gbp.egg-info/PKG-INFO
+gbp.egg-info/SOURCES.txt
+gbp.egg-info/dependency_links.txt
+gbp.egg-info/entry_points.txt
+gbp.egg-info/requires.txt
+gbp.egg-info/top_level.txt
+gbp/deb/__init__.py
+gbp/deb/changelog.py
+gbp/deb/control.py
+gbp/deb/dscfile.py
+gbp/deb/format.py
+gbp/deb/git.py
+gbp/deb/policy.py
+gbp/deb/pristinetar.py
+gbp/deb/rollbackgit.py
+gbp/deb/source.py
+gbp/deb/upstreamsource.py
+gbp/deb/uscan.py
+gbp/git/__init__.py
+gbp/git/args.py
+gbp/git/commit.py
+gbp/git/errors.py
+gbp/git/fastimport.py
+gbp/git/modifier.py
+gbp/git/repository.py
+gbp/git/vfs.py
+gbp/pkg/__init__.py
+gbp/pkg/archive.py
+gbp/pkg/compressor.py
+gbp/pkg/git.py
+gbp/pkg/pkgpolicy.py
+gbp/pkg/pristinetar.py
+gbp/pkg/upstreamsource.py
+gbp/rpm/__init__.py
+gbp/rpm/changelog.py
+gbp/rpm/git.py
+gbp/rpm/lib_rpm.py
+gbp/rpm/linkedlist.py
+gbp/rpm/policy.py
+gbp/scripts/__init__.py
+gbp/scripts/buildpackage.py
+gbp/scripts/buildpackage_rpm.py
+gbp/scripts/clone.py
+gbp/scripts/config.py
+gbp/scripts/create_remote_repo.py
+gbp/scripts/dch.py
+gbp/scripts/export_orig.py
+gbp/scripts/import_dsc.py
+gbp/scripts/import_dscs.py
+gbp/scripts/import_orig.py
+gbp/scripts/import_ref.py
+gbp/scripts/import_srpm.py
+gbp/scripts/pq.py
+gbp/scripts/pq_rpm.py
+gbp/scripts/pristine_tar.py
+gbp/scripts/pull.py
+gbp/scripts/push.py
+gbp/scripts/rpm_ch.py
+gbp/scripts/supercommand.py
+gbp/scripts/tag.py
+gbp/scripts/common/__init__.py
+gbp/scripts/common/buildpackage.py
+gbp/scripts/common/hook.py
+gbp/scripts/common/import_orig.py
+gbp/scripts/common/pq.py
+gbp/scripts/common/repo_setup.py \ No newline at end of file
diff --git a/gbp.egg-info/dependency_links.txt b/gbp.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/gbp.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/gbp.egg-info/entry_points.txt b/gbp.egg-info/entry_points.txt
new file mode 100644
index 0000000..94a44e7
--- /dev/null
+++ b/gbp.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+gbp = gbp.scripts.supercommand:supercommand
+
diff --git a/gbp.egg-info/requires.txt b/gbp.egg-info/requires.txt
new file mode 100644
index 0000000..0f08daa
--- /dev/null
+++ b/gbp.egg-info/requires.txt
@@ -0,0 +1 @@
+python-dateutil
diff --git a/gbp.egg-info/top_level.txt b/gbp.egg-info/top_level.txt
new file mode 100644
index 0000000..b608721
--- /dev/null
+++ b/gbp.egg-info/top_level.txt
@@ -0,0 +1 @@
+gbp
diff --git a/gbp/__init__.py b/gbp/__init__.py
new file mode 100755
index 0000000..800dbae
--- /dev/null
+++ b/gbp/__init__.py
@@ -0,0 +1,19 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,2008 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, please see
+# <http://www.gnu.org/licenses/>
+"""The various things needed by git-buildpackage and friends"""
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/command_wrappers.py b/gbp/command_wrappers.py
new file mode 100644
index 0000000..609f3b4
--- /dev/null
+++ b/gbp/command_wrappers.py
@@ -0,0 +1,366 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2007,2009,2015,2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""
+Simple class wrappers for the various external commands needed by
+git-buildpackage and friends
+"""
+
+import subprocess
+import os
+import signal
+import sys
+from contextlib import contextmanager
+from tempfile import TemporaryFile
+
+import gbp.log as log
+
+
+class CommandExecFailed(Exception):
+ """Exception raised by the Command class"""
+ pass
+
+
+@contextmanager
+def proxy_stdf():
+ """
+ Circulate stdout/stderr via a proper file object. Designed to work around a
+ problem where Python nose replaces sys.stdout/stderr with a custom 'Tee'
+ object that is not a file object (compatible) and thus causes a crash with
+ Popen.
+ """
+ tmp_stdout = sys.stdout
+ try:
+ tmp_stdout.fileno()
+ except Exception:
+ tmp_stdout = TemporaryFile()
+
+ tmp_stderr = sys.stderr
+ try:
+ tmp_stderr.fileno()
+ except Exception:
+ tmp_stderr = TemporaryFile()
+
+ try:
+ yield tmp_stdout, tmp_stderr
+ finally:
+ if tmp_stdout != sys.stdout:
+ tmp_stdout.seek(0)
+ sys.stdout.write(tmp_stdout.read().decode())
+ if tmp_stderr != sys.stderr:
+ tmp_stderr.seek(0)
+ sys.stderr.write(tmp_stderr.read().decode())
+
+
+class Command(object):
+ """
+ Wraps a shell command, so we don't have to store any kind of command
+ line options in one of the git-buildpackage commands
+
+ Note that it does not do any shell quoting even with shell=True so
+ you have to quote arguments yourself if necessary.
+
+ If cmd doesn't contain a path component it will be looked up in $PATH.
+ """
+ def __init__(self, cmd, args=[], shell=False, extra_env=None, cwd=None,
+ capture_stderr=False,
+ capture_stdout=False):
+ self.cmd = cmd
+ self.args = args
+ self.run_error = self._f("'%s' failed: {err_reason}",
+ (" ".join([self.cmd] + self.args)))
+ self.shell = shell
+ self.capture_stdout = capture_stdout
+ self.capture_stderr = capture_stderr
+ self.cwd = cwd
+ if extra_env is not None:
+ self.env = os.environ.copy()
+ self.env.update(extra_env)
+ else:
+ self.env = None
+ self._reset_state()
+
+ @staticmethod
+ def _f(format, *args):
+ """Build error string template
+
+ '%' expansion is performed while curly braces in args are
+ quoted so we don't accidentally try to expand them when
+ printing an error message later that uses one of our
+ predefined error variables stdout, stderr, stderr_or_reason
+ and self.err_reason.
+
+ >>> Command._f("foo %s", "bar")
+ 'foo bar'
+ >>> Command._f("{foo} %s %s", "bar", "baz")
+ '{foo} bar baz'
+ >>> Command._f("{foo} bar")
+ '{foo} bar'
+
+ """
+ def _q(arg):
+ return arg.replace('{', '{{').replace('}', '}}')
+ return format % tuple([_q(arg) for arg in args])
+
+ def _reset_state(self):
+ self.retcode = 1
+ self.stdout, self.stderr, self.err_reason = [''] * 3
+
+ def __call(self, args):
+ """
+ Wraps subprocess.call so we can be verbose and fix Python's
+ SIGPIPE handling
+ """
+ def default_sigpipe():
+ "Restore default signal handler (http://bugs.python.org/issue1652)"
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+ log.debug("%s %s %s" % (self.cmd, self.args, args))
+ self._reset_state()
+ cmd = [self.cmd] + self.args + args
+ if self.shell:
+ # subprocess.call only cares about the first argument if shell=True
+ cmd = " ".join(cmd)
+ with proxy_stdf() as (stdout, stderr):
+ stdout_arg = subprocess.PIPE if self.capture_stdout else stdout
+ stderr_arg = subprocess.PIPE if self.capture_stderr else stderr
+
+ try:
+ popen = subprocess.Popen(cmd,
+ cwd=self.cwd,
+ shell=self.shell,
+ env=self.env,
+ preexec_fn=default_sigpipe,
+ stdout=stdout_arg,
+ stderr=stderr_arg)
+ (self.stdout, self.stderr) = popen.communicate()
+ if self.stdout is not None:
+ self.stdout = self.stdout.decode()
+ if self.stderr is not None:
+ self.stderr = self.stderr.decode()
+ except OSError as err:
+ self.err_reason = "execution failed: %s" % str(err)
+ self.retcode = 1
+ raise
+
+ self.retcode = popen.returncode
+ if self.retcode < 0:
+ self.err_reason = "it was terminated by signal %d" % -self.retcode
+ elif self.retcode > 0:
+ self.err_reason = "it exited with %d" % self.retcode
+ return self.retcode
+
+ def _log_err(self):
+ "Log an error message"
+ log.err(self._format_err())
+
+ def _format_err(self):
+ """Log an error message
+
+ This allows to replace stdout, stderr and err_reason in
+ the self.run_error.
+ """
+ stdout = self.stdout.rstrip() if self.stdout else self.stdout
+ stderr = self.stderr.rstrip() if self.stderr else self.stderr
+ stderr_or_reason = self.stderr.rstrip() if self.stderr else self.err_reason
+ return self.run_error.format(stdout=stdout,
+ stderr=stderr,
+ stderr_or_reason=stderr_or_reason,
+ err_reason=self.err_reason)
+
+ def __call__(self, args=[], quiet=False):
+ """Run the command and raise exception on errors
+
+ If run quietly it will not print an error message via the
+ L{gbp.log} logging API.
+
+ Whether the command prints anything to stdout/stderr depends on
+ the I{capture_stderr}, I{capture_stdout} instance variables.
+
+ All errors will be reported as subclass of the
+ L{CommandExecFailed} exception including a non zero exit
+ status of the run command.
+
+ @param args: additional command line arguments
+ @type args: C{list} of C{strings}
+ @param quiet: don't log failed execution to stderr. Mostly useful during
+ unit testing
+ @type quiet: C{bool}
+
+ >>> Command("/bin/true")(["foo", "bar"])
+ >>> Command("/foo/bar")(quiet=True) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ gbp.command_wrappers.CommandExecFailed
+ """
+ try:
+ ret = self.__call(args)
+ except OSError:
+ ret = 1
+ if ret:
+ if not quiet:
+ self._log_err()
+ raise CommandExecFailed(self._format_err())
+
+ def call(self, args, quiet=True):
+ """Like L{__call__} but let the caller handle the return status.
+
+ Only raise L{CommandExecFailed} if we failed to launch the command
+ at all (i.e. if it does not exist) not if the command returned
+ nonzero.
+
+ Logs errors using L{gbp.log} by default.
+
+ @param args: additional command line arguments
+ @type args: C{list} of C{strings}
+ @param quiet: don't log failed execution to stderr. Mostly useful during
+ unit testing
+ @type quiet: C{bool}
+ @returns: the exit status of the run command
+ @rtype: C{int}
+
+ >>> Command("/bin/true").call(["foo", "bar"])
+ 0
+ >>> Command("/foo/bar").call(["foo", "bar"]) # doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ gbp.command_wrappers.CommandExecFailed: execution failed: ...
+ >>> c = Command("/bin/true", capture_stdout=True,
+ ... extra_env={'LC_ALL': 'C'})
+ >>> c.call(["--version"])
+ 0
+ >>> c.stdout.startswith('true')
+ True
+ >>> c = Command("/bin/false", capture_stdout=True,
+ ... extra_env={'LC_ALL': 'C'})
+ >>> c.call(["--help"])
+ 1
+ >>> c.stdout.startswith('Usage:')
+ True
+ """
+ ret = 1
+ try:
+ ret = self.__call(args)
+ except OSError:
+ raise CommandExecFailed(self.err_reason)
+ finally:
+ if ret and not quiet:
+ self._log_err()
+ return ret
+
+
+class RunAtCommand(Command):
+ """Run a command in a specific directory"""
+ def __call__(self, dir='.', *args):
+ curdir = os.path.abspath(os.path.curdir)
+ try:
+ os.chdir(dir)
+ Command.__call__(self, list(*args))
+ finally:
+ os.chdir(curdir)
+
+
+class UnpackTarArchive(Command):
+ """Wrap tar to unpack a compressed tar archive"""
+ def __init__(self, archive, dir, filters=[], compression=None):
+ self.archive = archive
+ self.dir = dir
+ exclude = [("--exclude=%s" % _filter) for _filter in filters]
+
+ if not compression:
+ compression = '-a'
+
+ Command.__init__(self, 'tar', exclude +
+ ['-C', dir, compression, '-xf', archive])
+ self.run_error = self._f("Couldn't unpack '%s': {err_reason}", self.archive)
+
+
+class PackTarArchive(Command):
+ """Wrap tar to pack a compressed tar archive"""
+ def __init__(self, archive, dir, dest, filters=[], compression=None):
+ self.archive = archive
+ self.dir = dir
+ exclude = [("--exclude=%s" % _filter) for _filter in filters]
+
+ if not compression:
+ compression = '-a'
+
+ Command.__init__(self, 'tar', exclude +
+ ['-C', dir, compression, '-cf', archive, dest])
+ self.run_error = self._f("Couldn't repack '%s': {err_reason}", self.archive)
+
+
+class CatenateTarArchive(Command):
+ """Wrap tar to catenate a tar file with the next"""
+ def __init__(self, archive, **kwargs):
+ self.archive = archive
+ Command.__init__(self, 'tar', ['-A', '-f', archive], **kwargs)
+
+ def __call__(self, target):
+ Command.__call__(self, [target])
+
+
+class RemoveTree(Command):
+ "Wrap rm to remove a whole directory tree"
+ def __init__(self, tree):
+ self.tree = tree
+ Command.__init__(self, 'rm', ['-rf', tree])
+ self.run_error = self._f("Couldn't remove '%s': {err_reason}", self.tree)
+
+
+class DpkgSourceExtract(Command):
+ """
+ Wrap dpkg-source to extract a Debian source package into a certain
+ directory
+ """
+ def __init__(self):
+ Command.__init__(self, 'dpkg-source', ['-x'])
+
+ def __call__(self, dsc, output_dir):
+ self.run_error = self._f("Couldn't extract '%s': {err_reason}", dsc)
+ Command.__call__(self, [dsc, output_dir])
+
+
+class UnpackZipArchive(Command):
+ """Wrap zip to Unpack a zip file"""
+ def __init__(self, archive, dir):
+ self.archive = archive
+ self.dir = dir
+
+ Command.__init__(self, 'unzip', ["-q", archive, '-d', dir])
+ self.run_error = self._f("Couldn't unpack '%s': {err_reason}", self.archive)
+
+
+class CatenateZipArchive(Command):
+ """Wrap zipmerge tool to catenate a zip file with the next"""
+ def __init__(self, archive, **kwargs):
+ self.archive = archive
+ Command.__init__(self, 'zipmerge', [archive], **kwargs)
+
+ def __call__(self, target):
+ self.run_error = self._f("Couldn't append '%s' to '%s': {err_reason}",
+ target, self.archive)
+ Command.__call__(self, [target])
+
+
+class GitCommand(Command):
+ "Mother/Father of all git commands"
+ def __init__(self, cmd, args=[], **kwargs):
+ Command.__init__(self, 'git', [cmd] + args, **kwargs)
+ self.run_error = self._f("Couldn't run git %s: {err_reason}", cmd)
+
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/config.py b/gbp/config.py
new file mode 100644
index 0000000..95b0244
--- /dev/null
+++ b/gbp/config.py
@@ -0,0 +1,872 @@
+# vim: set fileencoding=utf-8:
+#
+# (C) 2006,2007,2010-2012,2015,2016,2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""handles command line and config file option parsing for the gbp commands"""
+
+from optparse import OptionParser, OptionGroup, Option, OptionValueError
+import configparser
+from copy import copy
+import errno
+import os.path
+import sys
+
+from gbp.errors import GbpError
+
+try:
+ from gbp.version import gbp_version
+except ImportError:
+ gbp_version = "[Unknown version]"
+import gbp.tristate
+import gbp.log
+from gbp.git import GitRepositoryError, GitRepository
+
+no_upstream_branch_msg = """
+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.
+"""
+
+
+def expand_path(option, opt, value):
+ value = os.path.expandvars(value)
+ return os.path.expanduser(value)
+
+
+def check_tristate(option, opt, value):
+ try:
+ val = gbp.tristate.Tristate(value)
+ except TypeError:
+ raise OptionValueError(
+ "option %s: invalid value: %r" % (opt, value))
+ else:
+ return val
+
+
+def save_option(f):
+ """save options on the underlying parser"""
+ def _decorator(self, *args, **kwargs):
+ obj = self
+ option_name = kwargs.get('option_name')
+ if not option_name and len(args):
+ option_name = args[0]
+
+ # We're decorating GbpOption but store valid_options on
+ # GbpOptionParser
+ if not hasattr(obj, 'valid_options'):
+ if not hasattr(obj, 'parser'):
+ raise ValueError("Can only decorete GbpOptionParser and GbpOptionGroup not %s" % obj)
+ else:
+ obj = obj.parser
+
+ if option_name and not option_name.startswith('no-'):
+ obj.valid_options.append(option_name)
+ return f(self, *args, **kwargs)
+ return _decorator
+
+
+class GbpOption(Option):
+ TYPES = Option.TYPES + ('path', 'tristate')
+ TYPE_CHECKER = copy(Option.TYPE_CHECKER)
+ TYPE_CHECKER['path'] = expand_path
+ TYPE_CHECKER['tristate'] = check_tristate
+
+
+class GbpOptionParser(OptionParser):
+ """
+ Handles commandline options and parsing of config files
+ @ivar command: the gbp command we store the options for
+ @type command: string
+ @ivar prefix: prefix to prepend to all commandline options
+ @type prefix: string
+ @ivar config: current configuration parameters
+ @type config: dict
+ @cvar defaults: defaults value of an option if not in the config file or
+ given on the command line
+ @type defaults: dict
+ @cvar help: help messages
+ @type help: dict
+ @cvar def_config_files: config files we parse
+ @type def_config_files: dict (type, path)
+ """
+ defaults = {'abbrev': 7,
+ 'allow-unauthenticated': 'False',
+ 'arch': '',
+ 'author-date-is-committer-date': 'False',
+ 'author-is-committer': 'False',
+ 'bare': 'True',
+ 'cleaner': '/bin/true',
+ 'color': 'auto',
+ 'color-scheme': '',
+ 'commit': 'False',
+ 'commit-msg': 'Update changelog for %(version)s release',
+ 'component': [],
+ 'compression': 'auto',
+ 'compression-level': '',
+ 'create-missing-branches': 'False',
+ 'customizations': '',
+ 'dch-opt': [],
+ 'debian-branch': 'master',
+ 'debian-tag': 'debian/%(version)s',
+ 'debian-tag-msg': '%(pkg)s Debian release %(version)s',
+ 'dist': 'sid',
+ 'drop': 'False',
+ 'export': 'HEAD',
+ 'export-dir': '',
+ 'filter': [],
+ 'filter-pristine-tar': 'False',
+ 'force-create': 'False',
+ 'full': 'False',
+ 'git-author': 'False',
+ 'git-log': '--no-merges',
+ 'hooks': 'True',
+ 'id-length': '0',
+ 'ignore-branch': 'False',
+ 'ignore-new': 'False',
+ 'ignore-regex': '',
+ 'import-msg': 'New upstream version %(version)s',
+ 'interactive': 'True',
+ 'keyid': '',
+ 'merge': 'True',
+ 'merge-mode': 'auto',
+ 'meta': 'True',
+ 'meta-closes': 'Closes|LP',
+ 'meta-closes-bugnum': r'(?:bug|issue)?\#?\s?\d+',
+ 'multimaint': 'True',
+ 'multimaint-merge': 'False',
+ 'no-create-orig': 'False',
+ 'notify': 'auto',
+ 'overlay': 'False',
+ 'patch-num-format': '%04d-',
+ 'patch-numbers': 'True',
+ 'pbuilder': 'False',
+ 'pbuilder-autoconf': 'True',
+ 'pbuilder-options': '',
+ 'postbuild': '',
+ 'postclone': '',
+ 'postedit': '',
+ 'postexport': '',
+ 'postimport': '',
+ 'posttag': '',
+ 'pq-from': 'DEBIAN',
+ 'prebuild': '',
+ 'pristine-tar': 'False',
+ 'pristine-tar-commit': 'False',
+ 'purge': 'True',
+ 'qemubuilder': 'False',
+ 'remote-config': '',
+ 'remote-url-pattern': 'ssh://git.debian.org/git/collab-maint/%(pkg)s.git',
+ 'renumber': 'False',
+ 'repo-email': 'DEBIAN',
+ 'repo-user': 'DEBIAN',
+ 'rollback': 'True',
+ 'sign-tags': 'False',
+ 'snapshot-number': 'snapshot + 1',
+ 'spawn-editor': 'release',
+ 'submodules': 'False',
+ 'symlink-orig': 'True',
+ 'tarball-dir': '',
+ 'template-dir': '',
+ 'time-machine': 1,
+ 'track': 'True',
+ 'track-missing': 'False',
+ 'upstream-branch': 'upstream',
+ 'upstream-tag': 'upstream/%(version)s',
+ 'upstream-tree': 'TAG',
+ 'upstream-vcs-tag': '',
+ 'urgency': 'medium',
+ }
+ help = {
+ 'debian-branch':
+ "Branch the Debian package is being developed on, "
+ "default is '%(debian-branch)s'",
+ 'upstream-branch':
+ "Upstream branch, default is '%(upstream-branch)s'",
+ 'upstream-tree':
+ "Where to generate the upstream tarball from "
+ "(tag or branch), default is '%(upstream-tree)s'",
+ 'pq-from':
+ "How to find the patch queue base. DEBIAN or TAG, "
+ "the default is '%(pq-from)s'",
+ 'debian-tag':
+ "Format string for debian tags, "
+ "default is '%(debian-tag)s'",
+ 'debian-tag-msg':
+ "Format string for signed debian-tag messages, "
+ "default is '%(debian-tag-msg)s'",
+ 'upstream-tag':
+ "Format string for upstream tags, "
+ "default is '%(upstream-tag)s'",
+ 'sign-tags':
+ "Whether to sign tags, default is '%(sign-tags)s'",
+ 'keyid':
+ "GPG keyid to sign tags with, default is '%(keyid)s'",
+ 'import-msg':
+ "Format string for commit message used to commit "
+ "the upstream tarball, default is '%(import-msg)s'",
+ 'commit-msg':
+ "Format string for commit message used to commit, "
+ "the changelog, default is '%(commit-msg)s'",
+ 'pristine-tar':
+ "Use pristine-tar to create orig tarball, "
+ "default is '%(pristine-tar)s'",
+ 'pristine-tar-commit':
+ "When generating a tarball commit it to the pristine-tar branch '%(pristine-tar-commit)s' "
+ "default is '%(pristine-tar-commit)s'",
+ 'filter-pristine-tar':
+ "Filter pristine-tar when filter option is used, default is '%(filter-pristine-tar)s'",
+ 'filter':
+ "Files to filter out during import (can be given multiple times), default is %(filter)s",
+ 'git-author':
+ "Use name and email from git-config for changelog trailer, default is '%(git-author)s'",
+ 'full':
+ "Include the full commit message instead of only the first line, default is '%(full)s'",
+ 'meta':
+ "Parse meta tags in commit messages, default is '%(meta)s'",
+ 'meta-closes':
+ "Meta tags for the bts close commands, default is '%(meta-closes)s'",
+ 'meta-closes-bugnum':
+ "Meta bug number format, default is '%(meta-closes-bugnum)s'",
+ 'ignore-new':
+ "Build with uncommitted changes in the source tree, default is '%(ignore-new)s'",
+ 'ignore-branch':
+ "Build although debian-branch != current branch, "
+ "default is '%(ignore-branch)s'",
+ 'overlay':
+ "extract orig tarball when using export-dir option, "
+ "default is '%(overlay)s'",
+ 'remote-url-pattern':
+ "Remote url pattern to create the repo at, "
+ "default is '%(remote-url-pattern)s'",
+ 'multimaint':
+ "Note multiple maintainers, default is '%(multimaint)s'",
+ 'multimaint-merge':
+ "Merge commits by maintainer, "
+ "default is '%(multimaint-merge)s'",
+ 'pbuilder':
+ "Invoke git-pbuilder for building, "
+ "default is '%(pbuilder)s'",
+ 'dist':
+ "Build for this distribution when using git-pbuilder, "
+ "default is '%(dist)s'",
+ 'arch':
+ "Build for this architecture when using git-pbuilder, "
+ "default is '%(arch)s'",
+ 'qemubuilder':
+ "Invoke git-pbuilder with qemubuilder for building, "
+ "default is '%(qemubuilder)s'",
+ 'interactive':
+ "Run command interactively, default is '%(interactive)s'",
+ 'color':
+ "Whether to use colored output, default is '%(color)s'",
+ 'color-scheme':
+ "Colors to use in output (when color is enabled), format "
+ "is '<debug>:<info>:<warning>:<error>', e.g. "
+ "'cyan:34::'. Numerical values and color names are "
+ "accepted, empty fields indicate using the default.",
+ 'spawn-editor':
+ "Whether to spawn an editor after adding the "
+ "changelog entry, default is '%(spawn-editor)s'",
+ 'patch-numbers':
+ "Whether to number patch files, "
+ "default is %(patch-numbers)s",
+ 'patch-num-format':
+ "The format specifier for patch number prefixes, "
+ "default is %(patch-num-format)s",
+ 'renumber':
+ "Whether to renumber patches exported from patch queues, "
+ "instead of preserving the number specified in "
+ "'Gbp-Pq: Name' tags, default is %(renumber)s",
+ 'notify':
+ "Whether to send a desktop notification after the build, "
+ "default is '%(notify)s'",
+ 'merge':
+ "After the import merge the result to the debian branch, "
+ "default is '%(merge)s'",
+ 'merge-mode':
+ "Howto merge the new upstream sources onto the debian branch, "
+ "default is '%(merge-mode)s'",
+ 'track':
+ "Set up tracking for remote branches, "
+ "default is '%(track)s'",
+ 'track-missing':
+ "Track missing remote branches, "
+ "default is '%(track-missing)s'",
+ 'author-is-committer':
+ "Use the authors's name also as the committer's name, "
+ "default is '%(author-is-committer)s'",
+ 'author-date-is-committer-date':
+ "Use the authors's date as the committer's date, "
+ "default is '%(author-date-is-committer-date)s'",
+ 'create-missing-branches':
+ "Create missing branches automatically, "
+ "default is '%(create-missing-branches)s'",
+ 'submodules':
+ "Transparently handle submodules in the upstream tree, "
+ "default is '%(submodules)s'",
+ 'postimport':
+ "hook run after a successful import, "
+ "default is '%(postimport)s'",
+ 'hooks':
+ "Enable running all hooks, default is %(hooks)s",
+ 'time-machine':
+ "don't try to apply patch queue to head commit only. "
+ "Try at most TIME_MACHINE commits back, "
+ "default is '%(time-machine)d'",
+ 'pbuilder-autoconf':
+ "Whether to configure pbuilder automatically, "
+ "default is '%(pbuilder-autoconf)s'",
+ 'pbuilder-options':
+ "Options to pass to pbuilder, "
+ "default is '%(pbuilder-options)s'",
+ 'template-dir':
+ "Template directory used by git init, "
+ "default is '%(template-dir)s'",
+ 'remote-config':
+ "Remote definition in gbp.conf used to create the remote "
+ "repository, default is '%(remote-config)s'",
+ 'allow-unauthenticated':
+ "Don't verify integrity of downloaded source, "
+ "default is '%(allow-unauthenticated)s'",
+ 'symlink-orig':
+ "Whether to create a symlink from the upstream tarball "
+ "to the orig.tar.gz if needed, default is "
+ "'%(symlink-orig)s'",
+ 'purge':
+ "Purge exported package build directory. Default is '%(purge)s'",
+ 'drop':
+ "In case of 'export' drop the patch-queue branch "
+ "after export. Default is '%(drop)s'",
+ 'abbrev':
+ "abbreviate commits to this length. default is '%(abbrev)s'",
+ 'commit':
+ "commit changes after export, Default is '%(commit)s'",
+ 'rollback':
+ "Rollback repository changes when encountering an error",
+ 'component':
+ 'component name for additional tarballs',
+ 'bare':
+ "wether to create a bare repository on the remote side. "
+ "'Default is '%(bare)s'.",
+ 'urgency':
+ "Set urgency level, default is '%(urgency)s'",
+ 'repo-user':
+ "Set repo username from the DEBFULLNAME and DEBEMAIL "
+ "environment variables ('DEBIAN') or fallback to the "
+ "git configuration ('GIT'), default is '%(repo-user)s'",
+ 'repo-email':
+ "Set repo email from the DEBFULLNAME and DEBEMAIL "
+ "environment variables ('DEBIAN') or fallback to the "
+ "git configuration ('GIT'), default is '%(repo-email)s'"
+ }
+
+ short_opts = {
+ 'urgency': '-U',
+ }
+
+ def_config_files = [('/etc/git-buildpackage/gbp.conf', 'system'),
+ ('~/.gbp.conf', 'global'),
+ ('%(top_dir)s/.gbp.conf', None),
+ ('%(top_dir)s/debian/gbp.conf', 'debian'),
+ ('%(git_dir)s/gbp.conf', None)]
+
+ list_opts = ['filter', 'component', 'dch-opt']
+
+ @classmethod
+ def get_config_files(cls, no_local=False):
+ """
+ Get list of config files from the I{GBP_CONF_FILES} environment
+ variable.
+
+ @param no_local: don't return the per-repo configuration files
+ @type no_local: C{bool}
+ @return: list of config files we need to parse
+ @rtype: C{list}
+
+ >>> import re
+ >>> conf_backup = os.getenv('GBP_CONF_FILES')
+ >>> if conf_backup is not None: del os.environ['GBP_CONF_FILES']
+ >>> homedir = os.path.expanduser("~")
+ >>> files = GbpOptionParser.get_config_files()
+ >>> files_mangled = [re.sub("^%s" % homedir, 'HOME', file) for file in files]
+ >>> sorted(files_mangled)
+ ['%(git_dir)s/gbp.conf', '%(top_dir)s/.gbp.conf', '%(top_dir)s/debian/gbp.conf', '/etc/git-buildpackage/gbp.conf', 'HOME/.gbp.conf']
+ >>> files = GbpOptionParser.get_config_files(no_local=True)
+ >>> files_mangled = [re.sub("^%s" % homedir, 'HOME', file) for file in files]
+ >>> sorted(files_mangled)
+ ['/etc/git-buildpackage/gbp.conf', 'HOME/.gbp.conf']
+ >>> os.environ['GBP_CONF_FILES'] = 'test1:test2'
+ >>> GbpOptionParser.get_config_files()
+ ['test1', 'test2']
+ >>> del os.environ['GBP_CONF_FILES']
+ >>> if conf_backup is not None: os.environ['GBP_CONF_FILES'] = conf_backup
+ """
+ envvar = os.environ.get('GBP_CONF_FILES')
+ files = envvar.split(':') if envvar else [f for (f, _) in cls.def_config_files]
+ files = [os.path.expanduser(fname) for fname in files]
+ if no_local:
+ files = [fname for fname in files if fname.startswith('/')]
+ return files
+
+ def _read_config_file(self, repo, filename):
+ """Read config file"""
+ str_fields = {}
+ if repo:
+ str_fields['git_dir'] = repo.git_dir
+ if not repo.bare:
+ str_fields['top_dir'] = repo.path
+ try:
+ filename = filename % str_fields
+ except KeyError:
+ # Skip if filename wasn't expanded, i.e. we're not in git repo
+ return
+ if (repo and
+ filename == os.path.join(repo.path, '.gbp.conf') and
+ os.path.exists(filename)):
+ self._warn_old_gbp_conf(filename)
+ self.config_parser.read(filename)
+
+ def _warn_old_config_section(self, oldcmd, cmd):
+ if not os.getenv("GBP_DISABLE_SECTION_DEPRECATION"):
+ gbp.log.warn("Old style config section [%s] found "
+ "please rename to [%s]" % (oldcmd, cmd))
+
+ def _warn_old_gbp_conf(self, gbp_conf):
+ if (not os.getenv("GBP_DISABLE_GBP_CONF_DEPRECATION") and
+ not self._warned_old_gbp_conf):
+ gbp.log.warn("Deprecated configuration file found at %s, "
+ "check gbp.conf(5) for alternatives" % gbp_conf)
+ self._warned_old_gbp_conf = True
+
+ @property
+ def config_file_sections(self):
+ """List of all found config file sections"""
+ return self.config_parser.sections()
+
+ @staticmethod
+ def _listify(value):
+ """
+ >>> GbpOptionParser._listify(None)
+ []
+ >>> GbpOptionParser._listify('string')
+ ['string']
+ >>> GbpOptionParser._listify('["q", "e", "d"] ')
+ ['q', 'e', 'd']
+ >>> GbpOptionParser._listify('[')
+ Traceback (most recent call last):
+ ...
+ configparser.Error: [ is not a proper list
+ """
+ # filter can be either a list or a string, always build a list:
+ if value:
+ if value.startswith('['):
+ try:
+ return eval(value)
+ except SyntaxError:
+ raise configparser.Error("%s is not a proper list" % value)
+ else:
+ return [value]
+ else:
+ return []
+
+ def parse_lists(self):
+ """
+ Parse options that can be given as lists
+
+ Since they take multiple arguments they can also be given in plural form
+ e.g. components instead of component.
+ """
+ for opt in self.list_opts:
+ try:
+ plural_opt = opt + 's'
+ valp = self._listify(self.config.get(plural_opt, None))
+ vals = self._listify(self.config[opt])
+ if valp and vals:
+ raise configparser.Error("Found %s and %s - use only one" % (valp, vals))
+ self.config[opt] = valp or vals
+ except ValueError:
+ raise configparser.Error("Failed to parse %s: %s" % (opt, self.config[opt]))
+
+ def parse_config_files(self):
+ """
+ Parse the possible config files and set appropriate values
+ default values
+ """
+ parser = self.config_parser
+ # Fill in the built in values
+ self.config = dict(self.__class__.defaults)
+ config_files = self.get_config_files()
+ try:
+ repo = GitRepository(".", toplevel=False)
+ except GitRepositoryError:
+ repo = None
+ # Read all config files
+ for filename in config_files:
+ self._read_config_file(repo, filename)
+ # Update with the values from the defaults section. This is needed
+ # in case the config file doesn't have a [<command>] section at all
+ self.config.update(dict(parser.defaults()))
+
+ # Make sure we read any legacy sections prior to the real subcommands
+ # section i.e. read [gbp-pull] prior to [pull]
+ if (self.command.startswith('gbp-') or
+ self.command.startswith('git-')):
+ cmd = self.command[4:]
+ oldcmd = self.command
+ if parser.has_section(oldcmd):
+ self.config.update(dict(parser.items(oldcmd, raw=True)))
+ self._warn_old_config_section(oldcmd, cmd)
+ else:
+ cmd = self.command
+ for prefix in ['gbp', 'git']:
+ oldcmd = '%s-%s' % (prefix, self.command)
+ if parser.has_section(oldcmd):
+ self.config.update(dict(parser.items(oldcmd, raw=True)))
+ self._warn_old_config_section(oldcmd, cmd)
+
+ # Update with command specific settings
+ if parser.has_section(cmd):
+ # Don't use items() until we got rid of the compat sections
+ # since this pulls in the defaults again
+ self.config.update(dict(parser._sections[cmd].items()))
+
+ for section in self.sections:
+ if parser.has_section(section):
+ self.config.update(dict(parser._sections[section].items()))
+ else:
+ raise configparser.NoSectionError(
+ "Mandatory section [%s] does not exist." % section)
+
+ self.parse_lists()
+
+ def __init__(self, command, prefix='', usage=None, sections=[]):
+ """
+ @param command: the command to build the config parser for
+ @type command: C{str}
+ @param prefix: A prefix to add to all command line options
+ @type prefix: C{str}
+ @param usage: a usage description
+ @type usage: C{str}
+ @param sections: additional (non optional) config file sections
+ to parse
+ @type sections: C{list} of C{str}
+ """
+ self.command = command[:-3] if command.endswith('.py') else command
+ self.sections = sections
+ self.prefix = prefix
+ self.config = {}
+ self.valid_options = []
+ self.config_parser = configparser.SafeConfigParser()
+ self._warned_old_gbp_conf = False
+
+ try:
+ self.parse_config_files()
+ except configparser.ParsingError as err:
+ raise GbpError(str(err) + "\nSee 'man gbp.conf' for the format.")
+
+ OptionParser.__init__(self, option_class=GbpOption,
+ prog="gbp %s" % self.command,
+ usage=usage, version='%s %s' % (self.command,
+ gbp_version))
+
+ def _is_boolean(self, dummy, *unused, **kwargs):
+ """is option_name a boolean option"""
+ ret = False
+ try:
+ if kwargs['action'] in ['store_true', 'store_false']:
+ ret = True
+ except KeyError:
+ ret = False
+ return ret
+
+ def _get_bool_default(self, option_name):
+ """
+ get default for boolean options
+ this way we can handle no-foo=True and foo=False
+ """
+ if option_name.startswith('no-'):
+ pos = option_name[3:]
+ neg = option_name
+ else:
+ pos = option_name
+ neg = "no-%s" % option_name
+
+ try:
+ default = self.config[pos]
+ except KeyError:
+ default = self.config[neg]
+
+ if default.lower() in ["true", "1"]:
+ val = 'True'
+ elif default.lower() in ["false", "0"]:
+ val = 'False'
+ else:
+ raise ValueError("Boolean options must be True or False")
+ return eval(val)
+
+ def get_default(self, option_name, **kwargs):
+ """get the default value"""
+ if self._is_boolean(self, option_name, **kwargs):
+ default = self._get_bool_default(option_name)
+ else:
+ default = self.config[option_name]
+ return default
+
+ def get_opt_names(self, option_name):
+ names = ["--%s%s" % (self.prefix, option_name)]
+ if option_name in self.short_opts:
+ if self.prefix:
+ raise ValueError("Options with prefix cannot have a short option")
+ names.insert(0, self.short_opts[option_name])
+ return names
+
+ @save_option
+ def add_config_file_option(self, option_name, dest, help=None, **kwargs):
+ """
+ set a option for the command line parser, the default is read from the config file
+ param option_name: name of the option
+ type option_name: string
+ param dest: where to store this option
+ type dest: string
+ param help: help text
+ type help: string
+ """
+ if not help:
+ help = self.help[option_name]
+ opt_names = self.get_opt_names(option_name)
+ default = kwargs.pop('default', None)
+ if default is None:
+ default = self.get_default(option_name, **kwargs)
+ OptionParser.add_option(self, *opt_names, dest=dest,
+ default=default,
+ help=help % self.config, **kwargs)
+
+ def add_boolean_config_file_option(self, option_name, dest):
+ self.add_config_file_option(option_name=option_name, dest=dest, action="store_true")
+ neg_help = "negates '--%s%s'" % (self.prefix, option_name)
+ self.add_config_file_option(option_name="no-%s" % option_name, dest=dest, help=neg_help, action="store_false")
+
+ def get_config_file_value(self, option_name):
+ """
+ Query a single interpolated config file value.
+
+ @param option_name: the config file option to look up
+ @type option_name: string
+ @returns: The config file option value or C{None} if it doesn't exist
+ @rtype: C{str} or C{None}
+ """
+ return self.config.get(option_name)
+
+ def print_help(self, file=None):
+ """
+ Print an extended help message, listing all options and any
+ help text provided with them, to 'file' (default stdout).
+ """
+ if file is None:
+ file = sys.stdout
+
+ encoding = file.encoding if hasattr(file, 'encoding') else 'utf-8'
+ try:
+ msg = self.format_help()
+ if hasattr(file, 'mode') and 'b' in file.mode:
+ msg = msg.encode(encoding, "replace")
+ file.write(msg)
+ except OSError as e:
+ if e.errno != errno.EPIPE:
+ raise
+
+ @classmethod
+ def _name_to_filename(cls, name):
+ """
+ Translate a name like 'system' to a config file name
+
+ >>> GbpOptionParser._name_to_filename('foo')
+ >>> GbpOptionParser._name_to_filename('system')
+ '/etc/git-buildpackage/gbp.conf'
+ >>> GbpOptionParser._name_to_filename('global')
+ '~/.gbp.conf'
+ >>> GbpOptionParser._name_to_filename('debian')
+ '%(top_dir)s/debian/gbp.conf'
+ """
+ for k, v in cls.def_config_files:
+ if name == v:
+ return k
+ else:
+ return None
+
+ @classmethod
+ def _set_config_file_value(cls, section, option, value, name=None, filename=None):
+ """
+ Write a config value to a file creating it if needed
+
+ On errors a ConfigParserError is raised
+ """
+ if not name and not filename:
+ raise configparser.Error("Either 'name' or 'filename' must be given")
+ if not filename:
+ filename = os.path.expanduser(cls._name_to_filename(name))
+
+ # Create e new config parser since we only operate on a single file
+ cfg = configparser.RawConfigParser()
+ cfg.read(filename)
+ if not cfg.has_section(section):
+ cfg.add_section(section)
+ cfg.set(section, option, value)
+ with open(filename, 'w') as fp:
+ cfg.write(fp)
+
+
+class GbpOptionGroup(OptionGroup):
+ @save_option
+ def add_config_file_option(self, option_name, dest, help=None, **kwargs):
+ """
+ set a option for the command line parser, the default is read from the config file
+ param option_name: name of the option
+ type option_name: string
+ param dest: where to store this option
+ type dest: string
+ param help: help text
+ type help: string
+ """
+ if not help:
+ help = self.parser.help[option_name]
+ opt_names = self.parser.get_opt_names(option_name)
+ default = kwargs.pop('default', None)
+ if default is None:
+ default = self.parser.get_default(option_name, **kwargs)
+ OptionGroup.add_option(self, *opt_names, dest=dest,
+ default=default,
+ help=help % self.parser.config, **kwargs)
+
+ def add_boolean_config_file_option(self, option_name, dest):
+ self.add_config_file_option(option_name=option_name, dest=dest, action="store_true")
+ neg_help = "negates '--%s%s'" % (self.parser.prefix, option_name)
+ self.add_config_file_option(option_name="no-%s" % option_name, dest=dest, help=neg_help, action="store_false")
+
+
+class GbpOptionParserDebian(GbpOptionParser):
+ """
+ Handles commandline options and parsing of config files for Debian tools
+ """
+ defaults = dict(GbpOptionParser.defaults)
+ defaults.update({
+ 'builder': 'debuild -i -I',
+ })
+
+ def _warn_old_gbp_conf(self, gbp_conf):
+ if os.path.exists("debian/control"):
+ GbpOptionParser._warn_old_gbp_conf(self, gbp_conf)
+
+
+class GbpOptionParserRpm(GbpOptionParser):
+ """
+ Handles commandline options and parsing of config files for rpm tools
+ """
+ defaults = dict(GbpOptionParser.defaults)
+ defaults.update({
+ 'tmp-dir': '/var/tmp/gbp/',
+ 'vendor': 'Downstream',
+ 'packaging-branch': 'master',
+ 'packaging-dir': '',
+ 'packaging-tag-msg': '%(pkg)s (vendor)s release %(version)s',
+ 'packaging-tag': 'packaging/%(version)s',
+ 'export-sourcedir': 'SOURCES',
+ 'export-specdir': 'SPECS',
+ 'export-dir': '../rpmbuild',
+ 'builder': 'rpmbuild',
+ 'spec-file': '',
+ 'mock': 'False',
+ 'dist': '',
+ 'arch': '',
+ 'mock-root': '',
+ 'mock-options': '',
+ 'native': 'auto',
+ 'changelog-file': 'auto',
+ 'changelog-revision': '',
+ 'spawn-editor': 'always',
+ 'editor-cmd': 'vim',
+ 'spec-vcs-tag': '',
+ })
+
+ help = dict(GbpOptionParser.help)
+ help.update(
+ {
+ 'tmp-dir':
+ "Base directory under which temporary directories are "
+ "created, default is '%(tmp-dir)s'",
+ 'vendor':
+ "Distribution vendor name, default is '%(vendor)s'",
+ 'packaging-branch':
+ "Branch the packaging is being maintained on, rpm counterpart "
+ "of the 'debian-branch' option, default is "
+ "'%(packaging-branch)s'",
+ 'packaging-dir':
+ "Subdir for RPM packaging files, default is "
+ "'%(packaging-dir)s'",
+ 'packaging-tag':
+ "Format string for packaging tags, RPM counterpart of the "
+ "'debian-tag' option, default is '%(packaging-tag)s'",
+ 'packaging-tag-msg':
+ "Format string for packaging tag messages, "
+ "default is '%(packaging-tag-msg)s'",
+ 'spec-file':
+ "Spec file to use, causes the packaging-dir option to be "
+ "ignored, default is '%(spec-file)s'",
+ 'export-sourcedir':
+ "Subdir (under EXPORT_DIR) where packaging sources (other than "
+ "the spec file) are exported, default is "
+ "'%(export-sourcedir)s'",
+ 'export-specdir':
+ "Subdir (under EXPORT_DIR) where package spec file is "
+ "exported default is '%(export-specdir)s'",
+ 'mock':
+ "Invoke mock for building using gbp-builder-mock, "
+ "default is '%(mock)s'",
+ 'dist':
+ "Build for this distribution when using mock. E.g.: epel-6, "
+ "default is '%(dist)s'",
+ 'arch':
+ "Build for this architecture when using mock, "
+ "default is '%(arch)s'",
+ 'mock-root':
+ "The mock root (-r) name for building with mock: <dist>-<arch>, "
+ "default is '%(mock-root)s'",
+ 'mock-options':
+ "Options to pass to mock, "
+ "default is '%(mock-options)s'",
+ 'native':
+ "Treat this package as native, default is '%(native)s'",
+ 'changelog-file':
+ "Changelog file to be used, default is '%(changelog-file)s'",
+ 'changelog-revision':
+ "Format string for the revision field in the changelog header. "
+ "If empty or not defined the default from packaging policy is "
+ "used.",
+ 'editor-cmd':
+ "Editor command to use",
+ 'git-author':
+ "Use name and email from git-config for the changelog header, "
+ "default is '%(git-author)s'",
+ 'spec-vcs-tag':
+ "Set/update the 'VCS:' tag in the spec file, empty value "
+ "removes the tag entirely, default is '%(spec-vcs-tag)s'",
+ })
+
+ def _warn_old_gbp_conf(self, gbp_conf):
+ # The rpm based tools use $repo/.gbp.conf a lot, don't
+ # warn there yet
+ pass
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/dch.py b/gbp/dch.py
new file mode 100644
index 0000000..d72152b
--- /dev/null
+++ b/gbp/dch.py
@@ -0,0 +1,141 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 Rob Browning <rlb@defaultvalue.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, please see
+# <http://www.gnu.org/licenses/>
+"""provides git-dch helpers"""
+
+import re
+
+MAX_CHANGELOG_LINE_LENGTH = 76
+
+
+def extract_git_dch_cmds(lines, options):
+ """Return a dictionary of all Git-Dch: commands found in lines.
+ The command keys will be lowercased, i.e. {'ignore' : True,
+ 'short': True}. For now, all the options are binary. Also return
+ all of the lines that do not contain Git-Dch: commands."""
+ commands = {}
+ other_lines = []
+ for line in lines:
+ if line.startswith('Git-Dch: ') or line.startswith('Gbp-Dch: '):
+ cmd = line.split(' ', 1)[1].strip().lower()
+ commands[cmd] = True
+ else:
+ other_lines.append(line)
+ return (commands, other_lines)
+
+
+def filter_ignore_rx_matches(lines, options):
+ """Filter any lines that match options.ignore_regex
+ (i.e. --ignore-regex)."""
+ if options.ignore_regex:
+ ignore_re = re.compile(options.ignore_regex)
+ return [line for line in lines if not ignore_re.match(line)]
+ else:
+ return lines
+
+
+def extract_bts_cmds(lines, opts):
+ """Return a dictionary of the bug tracking system commands
+ contained in the the given lines. i.e. {'closed' : [1], 'fixed':
+ [3, 4]}. Right now, this will only notice a single directive
+ clause on a line. Also return all of the lines that do not
+ contain bug tracking system commands."""
+ _bug_re = re.compile(opts.meta_closes_bugnum, re.I)
+ bts_rx = re.compile(r'(?P<bts>%s):\s+%s' % (opts.meta_closes, opts.meta_closes_bugnum), re.I)
+ commands = {}
+ other_lines = []
+ for line in lines:
+ m = bts_rx.match(line)
+ if m:
+ bug_nums = [bug.strip() for bug in _bug_re.findall(line, re.I)]
+ try:
+ commands[m.group('bts')] += bug_nums
+ except KeyError:
+ commands[m.group('bts')] = bug_nums
+ else:
+ other_lines.append(line)
+ return (commands, other_lines)
+
+
+def extract_thanks_info(lines, options):
+ """Return a list of all of the Thanks: entries, and a list of all
+ of the lines that do not contain Thanks: entries."""
+ thanks = []
+ thanks_re = re.compile(r'thanks:\s+', re.I)
+ other_lines = []
+ for line in lines:
+ if thanks_re.match(line):
+ thanks.append(line.split(' ', 1)[1].strip())
+ else:
+ other_lines.append(line)
+ return (thanks, other_lines)
+
+
+def _ispunct(ch):
+ return not ch.isalnum() and not ch.isspace()
+
+
+def terminate_first_line_if_needed(lines):
+ """Terminate the first line of lines with a '.' if multi-line."""
+ # Don't add a period to empty or one line commit messages.
+ if len(lines) < 2:
+ return lines
+ if lines[0] and _ispunct(lines[0][-1]):
+ return lines
+ if lines[1] and (_ispunct(lines[1][0]) or lines[1][0].islower()):
+ return lines
+ return [lines[0] + "."] + lines[1:]
+
+
+def format_changelog_entry(commit_info, options, last_commit=False):
+ """Return a list of lines (without newlines) as the changelog
+ entry for commit_info (generated by
+ GitRepository.get_commit_info()). If last_commit is not False,
+ then this entry is the last one in the series."""
+ entry = [commit_info['subject']]
+ body = commit_info['body'].splitlines()
+ commitid = commit_info['id']
+ (git_dch_cmds, body) = extract_git_dch_cmds(body, options)
+
+ if 'ignore' in git_dch_cmds:
+ return None
+ if options.idlen:
+ entry[0] = '[%s] ' % commitid[0:options.idlen] + entry[0]
+
+ bts_cmds = {}
+ thanks = []
+ if options.meta:
+ (bts_cmds, body) = extract_bts_cmds(body, options)
+ (thanks, body) = extract_thanks_info(body, options)
+ body = filter_ignore_rx_matches(body, options)
+
+ if 'full' in git_dch_cmds or (options.full and 'short' not in git_dch_cmds):
+ # Add all non-blank body lines.
+ entry.extend([line for line in body if line.strip()])
+ if thanks:
+ # Last wins for now (match old behavior).
+ thanks_msg = 'Thanks to %s' % thanks[-1]
+ entry.extend([thanks_msg])
+ for bts in bts_cmds:
+ bts_msg = '(%s: %s)' % (bts, ', '.join(bts_cmds[bts]))
+ if len(entry[-1]) + len(bts_msg) >= MAX_CHANGELOG_LINE_LENGTH:
+ entry.extend([''])
+ else:
+ entry[-1] += " "
+ entry[-1] += bts_msg
+
+ entry = terminate_first_line_if_needed(entry)
+ return entry
diff --git a/gbp/deb/__init__.py b/gbp/deb/__init__.py
new file mode 100644
index 0000000..c275734
--- /dev/null
+++ b/gbp/deb/__init__.py
@@ -0,0 +1,119 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,2011,2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""provides some debian source package related helpers"""
+
+import os
+import subprocess
+
+import gbp.command_wrappers as gbpc
+from gbp.errors import GbpError
+from gbp.git import GitRepositoryError
+
+# Make sure these are available with 'import gbp.deb'
+from gbp.deb.changelog import ChangeLog, NoChangeLogError
+from gbp.deb.policy import DebianPkgPolicy # noqa: F401
+
+
+Releases = ("buzz",
+ "rez",
+ "bo",
+ "hamm",
+ "slink",
+ "potato",
+ "woody",
+ "sarge",
+ "etch",
+ "lenny",
+ "squeeze",
+ "wheezy",
+ "jessie",
+ "stretch",
+ "buster",
+ "bullseye",
+ "sid")
+
+
+class DpkgCompareVersions(gbpc.Command):
+ dpkg = '/usr/bin/dpkg'
+
+ def __init__(self):
+ if not os.access(self.dpkg, os.X_OK):
+ raise GbpError("%s not found - cannot use compare versions" % self.dpkg)
+ gbpc.Command.__init__(self, self.dpkg, ['--compare-versions'], capture_stderr=True)
+
+ def __call__(self, version1, version2):
+ """
+ Compare two package versions. Return 0 if the versions are equal, -1 1 if version1 < version2,
+ and 1 oterwise.
+
+ @raises CommandExecFailed: if the version comparison fails
+ """
+ self.run_error = "Couldn't compare %s with %s" % (version1, version2)
+ res = self.call([version1, 'lt', version2])
+ if res not in [0, 1]:
+ if self.stderr:
+ self.run_error += ' (%s)' % self.stderr
+ raise gbpc.CommandExecFailed("%s: bad return code %d" % (self.run_error, res))
+ if res == 0:
+ return -1
+ elif res == 1:
+ res = self.call([version1, 'gt', version2])
+ if res not in [0, 1]:
+ if self.stderr:
+ self.run_error += ' (%s)' % self.stderr
+ raise gbpc.CommandExecFailed("%s: bad return code %d" % (self.run_error, res))
+ if res == 0:
+ return 1
+ return 0
+
+
+def parse_changelog_repo(repo, branch, filename):
+ """
+ Parse the changelog file from given branch in the git
+ repository.
+
+ FIXME: this should use *Vfs methods
+ """
+ try:
+ # Note that we could just pass in the branch:filename notation
+ # to show as well, but we want to check if the branch / filename
+ # exists first, so we can give a separate error from other
+ # repository errors.
+ sha = repo.rev_parse("%s:%s" % (branch, filename))
+ except GitRepositoryError:
+ raise NoChangeLogError("Changelog %s not found in branch %s" % (filename, branch))
+
+ return ChangeLog(repo.show(sha))
+
+
+def get_arch():
+ pipe = subprocess.Popen(["dpkg", "--print-architecture"], shell=False, stdout=subprocess.PIPE)
+ arch = pipe.stdout.readline().strip()
+ return arch.decode('ascii')
+
+
+def compare_versions(version1, version2):
+ """compares to Debian version numbers suitable for sort()"""
+ return DpkgCompareVersions()(version1, version2)
+
+
+def get_vendor():
+ pipe = subprocess.Popen(["dpkg-vendor", "--query", "Vendor"], shell=False, stdout=subprocess.PIPE)
+ vendor = pipe.stdout.readline().strip()
+ return vendor.decode('ascii')
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/deb/changelog.py b/gbp/deb/changelog.py
new file mode 100644
index 0000000..dda9b75
--- /dev/null
+++ b/gbp/deb/changelog.py
@@ -0,0 +1,368 @@
+# 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, please see
+# <http://www.gnu.org/licenses/>
+"""A Debian Changelog"""
+
+import email
+import os
+import subprocess
+from gbp.command_wrappers import Command
+
+
+class NoChangeLogError(Exception):
+ """No changelog found"""
+ pass
+
+
+class ParseChangeLogError(Exception):
+ """Problem parsing changelog"""
+ pass
+
+
+class ChangeLogSection(object):
+ """A section in the changelog describing one particular version"""
+ def __init__(self, package, version):
+ self._package = package
+ self._version = version
+
+ @property
+ def package(self):
+ return self._package
+
+ @property
+ def version(self):
+ return self._version
+
+ @classmethod
+ def parse(cls, section):
+ """
+ Parse one changelog section
+
+ @param section: a changelog section
+ @type section: C{str}
+ @returns: the parse changelog section
+ @rtype: L{ChangeLogSection}
+ """
+ header = section.split('\n')[0]
+ package = header.split()[0]
+ version = header.split()[1][1:-1]
+ return cls(package, version)
+
+
+class ChangeLog(object):
+ """A Debian changelog"""
+
+ def __init__(self, contents=None, filename=None):
+ """
+ @param contents: the contents of the changelog
+ @type contents: C{str}
+ @param filename: the filename of the changelog
+ @param filename: C{str}
+ """
+ self._contents = ''
+ self._cp = None
+ self._filename = filename
+
+ # Check that either contents or filename is passed (but not both)
+ if (not filename and not contents) or (filename and contents):
+ raise ValueError("Either filename or contents must be passed")
+
+ if filename and not os.access(filename, os.F_OK):
+ raise NoChangeLogError("Changelog %s not found" % (filename, ))
+
+ if contents:
+ self._contents = contents[:]
+ else:
+ self._read()
+ self._parse()
+
+ def _run_parsechangelog(self, options=None):
+ options = options if options is not None else []
+ cmd = subprocess.Popen(['dpkg-parsechangelog', '-l-'] + options,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (stdout, stderr) = cmd.communicate(self._contents.encode('utf-8'))
+ if cmd.returncode:
+ raise ParseChangeLogError("Failed to parse changelog. "
+ "dpkg-parsechangelog said:\n%s" % stderr.decode().strip())
+ return stdout.decode()
+
+ def _parse(self):
+ """Parse a changelog based on the already read contents."""
+ output = self._run_parsechangelog()
+ # Parse the result of dpkg-parsechangelog (which looks like
+ # email headers)
+ cp = email.message_from_string(output)
+ try:
+ if ':' in cp['Version']:
+ cp['Epoch'], cp['NoEpoch-Version'] = cp['Version'].split(':', 1)
+ else:
+ cp['NoEpoch-Version'] = cp['Version']
+ if '-' in cp['NoEpoch-Version']:
+ cp['Upstream-Version'], cp['Debian-Version'] = cp['NoEpoch-Version'].rsplit('-', 1)
+ else:
+ cp['Debian-Version'] = cp['NoEpoch-Version']
+
+ # py3's email.message_from_string() saves dpkg-parsechangelog's
+ # first newline from the "Changes" field.
+ changes = cp['Changes'].lstrip("\n")
+ del cp['Changes']
+ cp['Changes'] = changes
+ except TypeError:
+ raise ParseChangeLogError(output.split('\n')[0])
+
+ self._cp = cp
+
+ def _read(self):
+ try:
+ with open(self.filename, encoding='utf-8') as f:
+ self._contents = f.read()
+ except UnicodeDecodeError:
+ with open(self.filename, encoding='iso-8859-1') as f:
+ self._contents = f.read()
+
+ def __getitem__(self, item):
+ return self._cp[item]
+
+ def __setitem__(self, item, value):
+ self._cp[item] = value
+
+ @property
+ def filename(self):
+ """The filename (path) of the changelog"""
+ return self._filename
+
+ @property
+ def name(self):
+ """The packages name"""
+ return self._cp['Source']
+
+ @property
+ def version(self):
+ """The full version string"""
+ return self._cp['Version']
+
+ @property
+ def distribution(self):
+ return self._cp['Distribution']
+
+ @property
+ def upstream_version(self):
+ """The upstream version"""
+ return self._cp['Upstream-Version']
+
+ @property
+ def debian_version(self):
+ """The Debian part of the version number"""
+ return self._cp['Debian-Version']
+
+ @property
+ def epoch(self):
+ """The package's epoch"""
+ return self._cp['Epoch']
+
+ @property
+ def noepoch(self):
+ """The version string without the epoch"""
+ return self._cp['NoEpoch-Version']
+
+ def has_epoch(self):
+ """
+ Whether the version has an epoch
+
+ @return: C{True} if the version has an epoch, C{False} otherwise
+ @rtype: C{bool}
+ """
+ return 'Epoch' in self._cp
+
+ @property
+ def author(self):
+ """
+ The author of the last modification
+ """
+
+ return self._parse_maint(self._cp['Maintainer'])[0]
+
+ @property
+ def email(self):
+ """
+ The author's email
+ """
+ return self._parse_maint(self._cp['Maintainer'])[1]
+
+ @property
+ def date(self):
+ """
+ The date of the last modification as rfc822 date
+ """
+ return self._cp['Date']
+
+ @property
+ def sections_iter(self):
+ """
+ Iterate over sections in the changelog
+ """
+ section = ''
+ for line in self._contents.split('\n'):
+ if line and line[0] not in [' ', '\t']:
+ section += line
+ else:
+ if section:
+ yield ChangeLogSection.parse(section)
+ section = ''
+
+ @property
+ def sections(self):
+ """
+ Get sections in the changelog
+ """
+ return list(self.sections_iter)
+
+ @staticmethod
+ def spawn_dch(msg=[], author=None, email=None, newversion=False, version=None,
+ release=False, distribution=None, dch_options=None):
+ """
+ Spawn dch
+
+ @param author: committers name
+ @type author: C{str}
+ @param email: committers email
+ @type email: C{str}
+ @param newversion: start a new version
+ @type newversion: C{bool}
+ @param version: the verion to use
+ @type version: C{str}
+ @param release: finalize changelog for releaze
+ @type release: C{bool}
+ @param distribution: distribution to use
+ @type distribution: C{str}
+ @param dch_options: options passed verbatim to dch
+ @type dch_options: C{list}
+ """
+ env = {}
+ args = ['--no-auto-nmu']
+ if newversion:
+ if version:
+ try:
+ args.append(version['increment'])
+ except KeyError:
+ args.append('--newversion=%s' % version['version'])
+ else:
+ args.append('-i')
+ elif release:
+ args.extend(["--release", "--no-force-save-on-release"])
+ msg = None
+
+ if author:
+ env['DEBFULLNAME'] = author.encode('utf-8')
+ if email:
+ env['DEBEMAIL'] = email.encode('utf-8')
+
+ if distribution:
+ args.append("--distribution=%s" % distribution)
+
+ args.extend(dch_options or [])
+
+ if '--create' in args:
+ env['EDITOR'] = env['VISUAL'] = '/bin/true'
+
+ args.append('--')
+ if msg:
+ args.append('[[[insert-git-dch-commit-message-here]]]')
+ else:
+ args.append('')
+ dch = Command('debchange', args, extra_env=env, capture_stderr=True)
+ dch.run_error = Command._f("Dch failed: {stderr_or_reason}")
+ dch([], quiet=True)
+ if msg:
+ old_cl = open("debian/changelog", "r", encoding='utf-8')
+ new_cl = open("debian/changelog.bak", "w", encoding='utf-8')
+ for line in old_cl:
+ if line == " * [[[insert-git-dch-commit-message-here]]]\n":
+ print(" * " + msg[0], file=new_cl)
+ for line in msg[1:]:
+ print(" " + line, file=new_cl)
+ else:
+ print(line, end='', file=new_cl)
+ os.rename("debian/changelog.bak", "debian/changelog")
+
+ def add_entry(self, msg, author=None, email=None, dch_options=[]):
+ """Add a single changelog entry
+
+ @param msg: log message to add
+ @type msg: C{str}
+ @param author: name of the author of the log message
+ @type author: C{str}
+ @param email: email of the author of the log message
+ @type email: C{str}
+ @param dch_options: options passed verbatim to dch
+ @type dch_options: C{list}
+ """
+ self.spawn_dch(msg=msg, author=author, email=email, dch_options=dch_options)
+
+ def add_section(self, msg, distribution, author=None, email=None,
+ version={}, dch_options=[]):
+ """Add a new section to the changelog
+
+ @param msg: log message to add
+ @type msg: C{str}
+ @param distribution: distribution to set for the new changelog entry
+ @type distribution: C{str}
+ @param author: name of the author of the log message
+ @type author: C{str}
+ @param email: email of the author of the log message
+ @type email: C{str}
+ @param version: version to set for the new changelog entry
+ @param version: C{dict}
+ @param dch_options: options passed verbatim to dch
+ @type dch_options: C{list}
+ """
+ self.spawn_dch(msg=msg, newversion=True, version=version, author=author,
+ email=email, distribution=distribution, dch_options=dch_options)
+
+ def get_changes(self, since='0~'):
+ return self._run_parsechangelog(['-v%s' % since, '-SChanges'])
+
+ @staticmethod
+ def _parse_maint(maintainer):
+ """
+ Parse maintainer
+
+ Mostly rfc822 but we allow for commas
+ """
+ def _quote(u):
+ return u.replace(',', '##comma##')
+
+ def _unquote(q):
+ return q.replace('##comma##', ',')
+
+ name, mail = email.utils.parseaddr(_quote(maintainer or ''))
+ return (_unquote(name), _unquote(mail))
+
+ @classmethod
+ def create(cls, package=None, version=None):
+ """
+ Create a new, empty changelog
+ """
+ dch_options = ['--create']
+ if package:
+ dch_options.extend(['--package', package])
+ if version:
+ dch_options.extend(['--newversion', version])
+
+ cls.spawn_dch(dch_options=dch_options)
+ return cls(filename='debian/changelog')
diff --git a/gbp/deb/control.py b/gbp/deb/control.py
new file mode 100644
index 0000000..efa92ca
--- /dev/null
+++ b/gbp/deb/control.py
@@ -0,0 +1,81 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Daniel Dehennin <daniel.dehennin@baby-gnu.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, please see
+# <http://www.gnu.org/licenses/>
+"""A Debian Control file"""
+
+import email
+import os
+
+
+class NoControlError(Exception):
+ """No control found"""
+ pass
+
+
+class ParseControlError(Exception):
+ """Problem parsing control"""
+ pass
+
+
+class Control(object):
+ """A Debian control"""
+
+ def __init__(self, contents=None, filename="debian/control"):
+ """
+ Parse an existing control file.
+
+ @param contents: content of a control file
+ @type contents: C{str}
+ @param filename: name of the control file
+ @type filename: C{str}
+ @return: Control object
+ @rtype: C{gbp.deb.conrol.Control} object
+ """
+ if contents:
+ control = email.message_from_string(contents)
+ else:
+ if not os.access(filename, os.F_OK):
+ raise NoControlError("Control file %s does not exist" % filename)
+
+ with open(filename) as f:
+ control = email.message_from_file(f)
+
+ if not control.items():
+ raise ParseControlError("Empty or invalid control file or contents")
+
+ self._control = control
+ self.filename = filename
+
+ def __getitem__(self, item):
+ return self._control[item]
+
+ def __setitem__(self, item, value):
+ self._control[item] = value
+
+ @property
+ def name(self):
+ """The name of the package"""
+ return self._control['Source']
+
+ @property
+ def section(self):
+ """The section of the package"""
+ return self._control['Section']
+
+ @property
+ def priority(self):
+ """The priority of the package"""
+ return self._control['Priority']
diff --git a/gbp/deb/dscfile.py b/gbp/deb/dscfile.py
new file mode 100644
index 0000000..76676fc
--- /dev/null
+++ b/gbp/deb/dscfile.py
@@ -0,0 +1,146 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,2011,2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""provides some debian source package related helpers"""
+
+import os
+import re
+
+from gbp.errors import GbpError
+from gbp.deb.upstreamsource import DebianUpstreamSource
+from gbp.deb.policy import DebianPkgPolicy
+
+
+class DscFile(object):
+ """Keeps data read from a dscfile"""
+ compressions = r"(%s)" % '|'.join(DebianUpstreamSource.known_compressions())
+ pkg_re = re.compile(r'Source:\s+(?P<pkg>.+)\s*')
+ version_re = re.compile(r'Version:\s((?P<epoch>\d+)\:)?'
+ '(?P<version>[%s]+)\s*$'
+ % DebianPkgPolicy.debianversion_chars)
+ tar_re = re.compile(r'^\s\w+\s\d+\s+(?P<tar>[^_]+_[^_]+'
+ '(\.orig)?\.tar\.%s)$' % compressions)
+ add_tar_re = re.compile(r'^\s\w+\s\d+\s+(?P<tar>[^_]+_[^_]+'
+ '\.orig-(?P<dir>[a-zA-Z0-9-]+)\.tar\.%s)$' % compressions)
+ diff_re = re.compile(r'^\s\w+\s\d+\s+(?P<diff>[^_]+_[^_]+'
+ '\.diff.(gz|bz2))$')
+ deb_tgz_re = re.compile(r'^\s\w+\s\d+\s+(?P<deb_tgz>[^_]+_[^_]+'
+ '\.debian.tar.%s)$' % compressions)
+ format_re = re.compile(r'Format:\s+(?P<format>[0-9.]+)\s*')
+ sig_re = re.compile(r'^\s\w+\s\d+\s+(?P<sig>[^_]+_[^_]+'
+ '\.orig(-[a-z0-9-]+)?\.tar\.%s.asc)$' % compressions)
+
+ def __init__(self, dscfile):
+ self.pkg = ""
+ self.tgz = ""
+ self.diff = ""
+ self.deb_tgz = ""
+ self.pkgformat = "1.0"
+ self.debian_version = ""
+ self.upstream_version = ""
+ self.native = False
+ self.dscfile = os.path.abspath(dscfile)
+ sigs = []
+ add_tars = []
+
+ f = open(self.dscfile, encoding='utf-8')
+ fromdir = os.path.dirname(os.path.abspath(dscfile))
+ for line in f:
+ m = self.version_re.match(line)
+ if m and not self.upstream_version:
+ if '-' in m.group('version'):
+ self.debian_version = m.group('version').split("-")[-1]
+ self.upstream_version = "-".join(m.group('version').split("-")[0:-1])
+ self.native = False
+ else:
+ self.native = True # Debian native package
+ self.upstream_version = m.group('version')
+ if m.group('epoch'):
+ self.epoch = m.group('epoch')
+ else:
+ self.epoch = ""
+ continue
+ m = self.pkg_re.match(line)
+ if m:
+ self.pkg = m.group('pkg')
+ continue
+ m = self.deb_tgz_re.match(line)
+ if m:
+ self.deb_tgz = os.path.join(fromdir, m.group('deb_tgz'))
+ continue
+ m = self.add_tar_re.match(line)
+ if m:
+ add_tars.append((m.group('dir'),
+ os.path.join(fromdir, m.group('tar'))))
+ continue
+ m = self.tar_re.match(line)
+ if m:
+ self.tgz = os.path.join(fromdir, m.group('tar'))
+ continue
+ m = self.sig_re.match(line)
+ if m:
+ sigs.append(os.path.join(fromdir, m.group('sig')))
+ continue
+ m = self.diff_re.match(line)
+ if m:
+ self.diff = os.path.join(fromdir, m.group('diff'))
+ continue
+ m = self.format_re.match(line)
+ if m:
+ self.pkgformat = m.group('format')
+ continue
+ f.close()
+
+ # Source format 1.0 can have non-native packages without a Debian revision:
+ # e.g. http://snapshot.debian.org/archive/debian/20090801T192339Z/pool/main/l/latencytop/latencytop_0.5.dsc
+ if self.pkgformat == "1.0" and self.diff:
+ self.native = False
+ elif not self.native and not self.debian_version:
+ raise GbpError("Cannot parse Debian version number from '%s'" % self.dscfile)
+
+ if not self.pkg:
+ raise GbpError("Cannot parse package name from '%s'" % self.dscfile)
+ elif not self.tgz:
+ raise GbpError("Cannot parse archive name from '%s'" % self.dscfile)
+ if not self.upstream_version:
+ raise GbpError("Cannot parse version number from '%s'" % self.dscfile)
+ self.additional_tarballs = dict(add_tars)
+ self.sigs = list(set(sigs))
+
+ @property
+ def version(self):
+ version = ["", self.epoch + ":"][len(self.epoch) > 0]
+ if self.native:
+ version += self.upstream_version
+ else:
+ if self.debian_version != '':
+ version += "%s-%s" % (self.upstream_version, self.debian_version)
+ else: # possible in 1.0
+ version += "%s" % self.upstream_version
+ return version
+
+ def __str__(self):
+ return "<%s object %s>" % (self.__class__.__name__, self.dscfile)
+
+ @classmethod
+ def parse(cls, filename):
+ try:
+ dsc = cls(filename)
+ except IOError as err:
+ raise GbpError("Error reading dsc file: %s" % err)
+ return dsc
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/deb/format.py b/gbp/deb/format.py
new file mode 100644
index 0000000..412bb93
--- /dev/null
+++ b/gbp/deb/format.py
@@ -0,0 +1,118 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 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, please see
+# <http://www.gnu.org/licenses/>
+"""Parse debian/source/format"""
+
+
+class DebianSourceFormatError(Exception):
+ pass
+
+
+class DebianSourceFormat(object):
+ """
+ Contents of debian/source/format
+
+ >>> d = DebianSourceFormat("3.0 (quilt)")
+ >>> d.type
+ 'quilt'
+ >>> d.version
+ '3.0'
+ >>> d = DebianSourceFormat("3.0 (native)")
+ >>> d.type
+ 'native'
+ >>> d = DebianSourceFormat("1.0")
+ >>> d.type
+ >>> d.version
+ '1.0'
+ >>> d = DebianSourceFormat("1.0 broken")
+ Traceback (most recent call last):
+ ...
+ gbp.deb.format.DebianSourceFormatError: Cannot get source format from '1.0 broken'
+ """
+ format_file = 'debian/source/format'
+
+ def _parse(self, content):
+ parts = content.split()
+
+ self._version = parts[0]
+ if len(parts) == 2:
+ if (parts[1][0] == '(' and parts[1][-1] == ')'):
+ self._type = parts[1][1:-1]
+ else:
+ raise DebianSourceFormatError("Cannot get source format from "
+ "'%s'" % content)
+
+ def __init__(self, content):
+ self._version = None
+ self._type = None
+ self._parse(content)
+
+ @property
+ def version(self):
+ """The source format version number"""
+ return self._version
+
+ @property
+ def type(self):
+ """The 'type' (e.g. git, native)"""
+ return self._type
+
+ def __str__(self):
+ return "%s (%s)" % (self._version, self._type)
+
+ @classmethod
+ def parse_file(cls, filename):
+ """
+ Parse debian/source/format file
+
+ @param filename: the file to parse
+ @type filename: C{str}
+ @returns: a debisn/source/format object
+ @rtype: L{DebianSourceFormat}
+
+ >>> import tempfile, os
+ >>> with tempfile.NamedTemporaryFile(delete=False) as t:
+ ... ret = t.write(b"3.0 (quilt)")
+ >>> d = DebianSourceFormat.parse_file(t.name)
+ >>> d.version
+ '3.0'
+ >>> d.type
+ 'quilt'
+ >>> os.unlink(t.name)
+ """
+ with open(filename) as f:
+ return cls(f.read())
+
+ @classmethod
+ def from_content(cls, version, type, format_file=None):
+ """
+ Write a format file from I{type} and I{format} at
+ I{format_file}
+
+ @param version: the source package format version
+ @param type: the format type
+ @param format_file: the format file to create with
+ the above parameters
+ """
+ format_file = format_file or cls.format_file
+ with open(cls.format_file, 'w') as f:
+ f.write("%s (%s)" % (version, type))
+ return cls.parse_file(cls.format_file)
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/gbp/deb/git.py b/gbp/deb/git.py
new file mode 100644
index 0000000..7e87eda
--- /dev/null
+++ b/gbp/deb/git.py
@@ -0,0 +1,379 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011,2014 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, please see
+# <http://www.gnu.org/licenses/>
+"""A Git Repository that keeps a Debian Package"""
+
+import os
+import re
+
+from gbp.command_wrappers import CommandExecFailed
+from gbp.git import GitRepositoryError
+from gbp.deb.pristinetar import DebianPristineTar
+from gbp.format import format_str
+from gbp.paths import to_bin
+from gbp.pkg.git import PkgGitRepository
+
+import gbp.log
+
+
+class DebianGitRepository(PkgGitRepository):
+ """A git repository that holds the source of a Debian package"""
+
+ version_mangle_re = (r'%\(version'
+ '%(?P<M>[^%])'
+ '%(?P<R>([^%]|\\%))+'
+ '\)s')
+
+ def __init__(self, *args, **kwargs):
+ super(DebianGitRepository, self).__init__(*args, **kwargs)
+ self.pristine_tar = DebianPristineTar(self)
+
+ def tree_drop_dirs(self, tree, dirs):
+ """
+ Drop the given top level dirs from the given git tree
+ returning a new tree object.
+ """
+ objs = self.list_tree(tree)
+ new_tree_objs = []
+ dirs = [to_bin(d) for d in dirs]
+
+ for m, t, s, n in objs:
+ if not (n in dirs and t == 'tree'):
+ new_tree_objs.append((m, t, s, n))
+ new_tree = self.make_tree(new_tree_objs)
+ return new_tree
+
+ def tree_get_dir(self, tree, dir):
+ """
+ Get the SHA1 of directory in a given tree
+ """
+ dir = to_bin(dir)
+ toplevel = self.list_tree(tree)
+ for m, t, s, n in toplevel:
+ if n == dir and t == 'tree':
+ return s
+ return None
+
+ def find_version(self, format, version):
+ """
+ Check if a certain version is stored in this repo and return the SHA1
+ of the related commit. That is, an annotated tag is dereferenced to the
+ commit object it points to.
+
+ For legacy tags don't only check the tag itself but also the commit
+ message, since the former wasn't injective until release 0.5.5. You
+ only need to use this function if you also need to check for legacy
+ tags.
+
+ @param format: tag pattern
+ @type format: C{str}
+ @param version: debian version number
+ @type version: C{str}
+ @return: sha1 of the commit the tag references to
+ @rtype: C{str}
+ """
+ tag = self.version_to_tag(format, version)
+ legacy_tag = self._build_legacy_tag(format, version)
+ if self.has_tag(tag): # new tags are injective
+ # dereference to a commit object
+ return self.rev_parse("%s^0" % tag)
+ elif self.has_tag(legacy_tag):
+ out, ret = self._git_getoutput('cat-file', args=['-p', legacy_tag])
+ if ret:
+ return None
+ for line in out:
+ line = line.decode()
+ if line.endswith(" %s\n" % version):
+ # dereference to a commit object
+ return self.rev_parse("%s^0" % legacy_tag)
+ elif line.startswith('---'): # GPG signature start
+ return None
+ return None
+
+ def debian_version_from_upstream(self, upstream_tag_format,
+ upstream_branch, commit='HEAD',
+ epoch=None, debian_release=True):
+ """
+ Build the Debian version that a package based on upstream commit
+ I{commit} would carry taking into account a possible epoch.
+
+ @param upstream_tag_format: the tag format on the upstream branch
+ @type upstream_tag_format: C{str}
+ @param upstream_branch: the upstream branch
+ @type upstream_branch: C{str}
+ @param commit: the commit to search for the latest upstream version
+ @param epoch: an epoch to use
+ @param debian_release: If set to C{False} don't append a Debian release
+ number to the version number
+ @returns: a new debian version
+ @raises GitRepositoryError: if no upstream tag was found
+ """
+ pattern = self._unmangle_format(upstream_tag_format) % dict(version='*')
+ tag = self.find_branch_tag(commit, upstream_branch, pattern=pattern)
+ version = self.tag_to_version(tag, upstream_tag_format)
+
+ if debian_release:
+ version += "-1"
+
+ if epoch:
+ version = "%s:%s" % (epoch, version)
+ return version
+
+ @staticmethod
+ def _build_legacy_tag(format, version):
+ """
+ Legacy tags (prior to 0.5.5) dropped epochs and didn't honor the '~'
+
+ >>> DebianGitRepository._build_legacy_tag('upstream/%(version)s', '1:2.0~3')
+ 'upstream/2.0.3'
+ """
+ if ':' in version: # strip of any epochs
+ version = version.split(':', 1)[1]
+ version = version.replace('~', '.')
+ return format % dict(version=version)
+
+ @classmethod
+ def version_to_tag(cls, format, version):
+ """Generate a tag from a given format and a version
+
+ %(version)s provides a clean version that works as a git tag.
+
+ %(hversion)s provides the same thing, but with '.' replaced with '-'.
+ hversion is useful for upstreams with tagging policies that prohibit .
+ characters.
+
+ %(version%A%B)s provides %(version)s with string 'A' replaced by 'B'.
+ This way, simple version mangling is possible via substitution.
+ Inside the substition string, '%' needs to be escaped. See the
+ examples below.
+
+ >>> DebianGitRepository.version_to_tag("debian/%(version)s", "0:0~0")
+ 'debian/0%0_0'
+ >>> DebianGitRepository.version_to_tag("libfoo-%(hversion)s", "1.8.1")
+ 'libfoo-1-8-1'
+ >>> DebianGitRepository.version_to_tag("v%(version%.%_)s", "1.2.3")
+ 'v1_2_3'
+ >>> DebianGitRepository.version_to_tag("%(version%-%\%)s", "0-1.2.3")
+ '0%1.2.3'
+ """
+ f, v = cls._mangle_version(format, version)
+ return format_str(f, dict(version=cls._sanitize_version(v),
+ hversion=cls._sanitize_version(v).replace('.', '-')))
+
+ @classmethod
+ def _mangle_version(cls, format, version):
+ """
+ Basic version mangling to replce single characters
+
+ >>> DebianGitRepository._mangle_version("%(version%-%\%)s", "0-1.2.3")
+ ('%(version)s', '0%1.2.3')
+ """
+ r = re.search(cls.version_mangle_re, format)
+ if r:
+ f = re.sub(cls.version_mangle_re, "%(version)s", format)
+ v = version.replace(r.group('M'), r.group('R').replace('\%', '%'))
+ return f, v
+ else:
+ return format, version
+
+ @classmethod
+ def _unmangle_format(cls, format):
+ """
+ Reverse of _mangle_version for format
+ """
+ r = re.search(cls.version_mangle_re, format)
+ if r:
+ return re.sub(cls.version_mangle_re, "%(version)s", format)
+ else:
+ return format
+
+ @classmethod
+ def _unmangle_version(cls, format, tag):
+ """
+ Reverse of _mangle_version for version
+ """
+ r = re.search(cls.version_mangle_re, format)
+ if r:
+ v = tag.replace(r.group('R').replace('\%', '%'), r.group('M'))
+ return v
+ else:
+ return tag
+
+ @staticmethod
+ def _sanitize_version(version):
+ """sanitize a version so git accepts it as a tag
+
+ as described in DEP14
+
+ >>> DebianGitRepository._sanitize_version("0.0.0")
+ '0.0.0'
+ >>> DebianGitRepository._sanitize_version("0.0~0")
+ '0.0_0'
+ >>> DebianGitRepository._sanitize_version("0:0.0")
+ '0%0.0'
+ >>> DebianGitRepository._sanitize_version("0%0~0")
+ '0%0_0'
+ >>> DebianGitRepository._sanitize_version("0....0")
+ '0.#.#.#.0'
+ >>> DebianGitRepository._sanitize_version("0.lock")
+ '0.#lock'
+ """
+ v = re.sub('\.(?=\.|$|lock$)', '.#', version)
+ return v.replace('~', '_').replace(':', '%')
+
+ @staticmethod
+ def _unsanitize_version(tag):
+ """Reverse _sanitize_version
+
+ as described in DEP14
+
+ >>> DebianGitRepository._unsanitize_version("1%0_bpo3")
+ '1:0~bpo3'
+ >>> DebianGitRepository._unsanitize_version("1%0_bpo3.#.")
+ '1:0~bpo3..'
+ """
+ return tag.replace('_', '~').replace('%', ':').replace('#', '')
+
+ @classmethod
+ def tag_to_version(cls, tag, format):
+ """Extract the version from a tag
+
+ >>> DebianGitRepository.tag_to_version("upstream/1%2_3-4", "upstream/%(version)s")
+ '1:2~3-4'
+ >>> DebianGitRepository.tag_to_version("foo/2.3.4", "foo/%(version)s")
+ '2.3.4'
+ >>> DebianGitRepository.tag_to_version("v1-2-3", "v%(version%.%-)s")
+ '1.2.3'
+ >>> DebianGitRepository.tag_to_version("v1.#.2", "v%(version%.%-)s")
+ '1..2'
+ >>> DebianGitRepository.tag_to_version("foo/2.3.4", "upstream/%(version)s")
+ """
+ f = cls._unmangle_format(format)
+ version_re = f.replace('%(version)s', '(?P<version>[\w_%+-.#]+)')
+ r = re.match(version_re, tag)
+ if r:
+ v = cls._unsanitize_version(r.group('version'))
+ return cls._unmangle_version(format, v)
+ return None
+
+ @property
+ def pristine_tar_branch(self):
+ """
+ The name of the pristine-tar branch, whether it already exists or
+ not.
+ """
+ return DebianPristineTar.branch
+
+ def has_pristine_tar_branch(self):
+ """
+ Whether the repo has a I{pristine-tar} branch.
+
+ @return: C{True} if the repo has pristine-tar commits already, C{False}
+ otherwise
+ @rtype: C{Bool}
+ """
+ return True if self.has_branch(self.pristine_tar_branch) else False
+
+ def create_pristine_tar_commits(self, upstream_tree, tarball, component_tarballs):
+ """
+ Create pristine-tar commits for a package with main tarball
+ and (optional) component tarballs based on upstream_tree
+
+ @param tarball: path to main tarball
+ @param component_tarballs: C{list} of C{tuple}s of component
+ name and path to additional tarball
+ @param upstream_tree: the treeish in the git repo to create the commits against
+ """
+ components = [c for (c, t) in component_tarballs]
+ main_tree = self.tree_drop_dirs(upstream_tree, components)
+
+ try:
+ for component, name in component_tarballs:
+ subtree = self.tree_get_dir(upstream_tree, component)
+ if not subtree:
+ raise GitRepositoryError("No tree for '%s' found in '%s' to create "
+ "pristine tar commit from" % (component, upstream_tree))
+ gbp.log.debug("Creating pristine tar commit '%s' from '%s'" % (component, subtree))
+ self.pristine_tar.commit(name, subtree, quiet=True)
+ self.pristine_tar.commit(tarball, main_tree, quiet=True)
+ 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:
+ self.pristine_tar.checkout(source.name, source.upstream_version, comp.type, output_dir,
+ component=component, quiet=True)
+ except Exception as e:
+ raise GitRepositoryError("Error creating %s: %s" % (output, e))
+ return True
+
+ def create_upstream_tarball_via_git_archive(self, source, output_dir, treeish,
+ comp, with_submodules, component=None):
+ """
+ Create a compressed orig tarball in output_dir using git archive
+
+ @param source: debian source package
+ @type source: L{DebianSource}
+ @param output_dir: output directory
+ @param type: C{str}
+ @param treeish: git treeish
+ @type treeish: C{str}
+ @param comp: compressor
+ @type comp: L{Compressor}
+ @param with_submodules: wether to add submodules
+ @type with_submodules: C{bool}
+ @param component: component to add to tarball name
+ @type component: C{str}
+
+ Raises GitRepositoryError in case of an error
+ """
+ submodules = False
+ output = os.path.join(output_dir,
+ source.upstream_tarball_name(comp.type, component=component))
+ prefix = "%s-%s" % (source.name, source.upstream_version)
+
+ try:
+ if self.has_submodules() and with_submodules:
+ submodules = True
+ self.update_submodules()
+ self.archive_comp(treeish, output, prefix, comp, submodules=submodules)
+ except Exception as e:
+ raise GitRepositoryError("Error creating %s: %s" % (output, e))
+ return True
+
+ def vcs_tag_parent(self, vcs_tag_format, version):
+ """If linking to the upstream VCS get the commit id"""
+ if vcs_tag_format:
+ try:
+ tag = "%s^{}" % self.version_to_tag(vcs_tag_format, version)
+ return [self.rev_parse(tag)]
+ except GitRepositoryError:
+ raise GitRepositoryError("Can't find upstream vcs tag at '%s'" % tag)
+ else:
+ return None
+
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/deb/policy.py b/gbp/deb/policy.py
new file mode 100644
index 0000000..d9b27ac
--- /dev/null
+++ b/gbp/deb/policy.py
@@ -0,0 +1,98 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,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, please see
+# <http://www.gnu.org/licenses/>
+"""
+Debian Packaging policies
+
+like allowed characters in version numbers, etc.
+"""
+
+import os
+import re
+
+from gbp.errors import GbpError
+from gbp.pkg.pkgpolicy import PkgPolicy
+from gbp.pkg.compressor import Compressor
+
+
+class DebianPkgPolicy(PkgPolicy):
+ """
+ Packaging policy for Debian Source Packages
+
+ >>> DebianPkgPolicy.is_valid_upstreamversion('1:9.8.4.dfsg.P1-6')
+ True
+ >>> DebianPkgPolicy.is_valid_upstreamversion('-1')
+ False
+ """
+
+ # Valid package names according to Debian Policy Manual 5.6.1:
+ # "Package names (both source and binary, see Package, Section 5.6.7)
+ # must consist only of lower case letters (a-z), digits (0-9), plus (+)
+ # and minus (-) signs, and periods (.). They must be at least two
+ # characters long and must start with an alphanumeric character."
+ packagename_re = re.compile("^[a-zA-Z0-9][a-zA-Z0-9\.\+\-~]+$")
+ packagename_msg = """Package names must be at least two characters long, start with an
+ alphanumeric and can only containg letters (a-z,A-Z), digits
+ (0-9), plus signs (+), minus signs (-), periods (.) and hyphens (~)"""
+
+ # Valid upstream versions according to Debian Policy Manual 5.6.12:
+ # "The upstream_version may contain only alphanumerics[32] and the
+ # characters . + - : ~ (full stop, plus, hyphen, colon, tilde) and
+ # should start with a digit. If there is no debian_revision then hyphens
+ # are not allowed; if there is no epoch then colons are not allowed."
+ # Since we don't know about any epochs and debian revisions yet, the
+ # last two conditions are not checked.
+ upstreamversion_re = re.compile("^[0-9][a-zA-Z0-9\.\+\-\:\~]*$")
+ upstreamversion_msg = """Upstream version numbers must start with a digit and can only containg lower case
+ letters (a-z), digits (0-9), full stops (.), plus signs (+), minus signs
+ (-), colons (:) and tildes (~)"""
+
+ # Valid characters in a debian version
+ debianversion_chars = 'a-zA-Z\\d.~+-'
+
+ @staticmethod
+ def build_tarball_name(name, version, compression, dir=None, component=None):
+ """
+ Given a source package's I{name}, I{version} and I{compression}
+ return the name of the corresponding upstream tarball.
+
+ >>> DebianPkgPolicy.build_tarball_name('foo', '1.0', 'bzip2')
+ 'foo_1.0.orig.tar.bz2'
+ >>> DebianPkgPolicy.build_tarball_name('bar', '0.0~git1234', 'xz')
+ 'bar_0.0~git1234.orig.tar.xz'
+ >>> DebianPkgPolicy.build_tarball_name('bar', '0.0~git1234', 'xz', component="foo")
+ 'bar_0.0~git1234.orig-foo.tar.xz'
+
+ @param name: the source package's name
+ @type name: C{str}
+ @param version: the upstream version
+ @type version: C{str}
+ @param compression: the desired compression
+ @type compression: C{str}
+ @param dir: a directory to prepend
+ @type dir: C{str}
+ @return: the tarballs name corresponding to the input parameters
+ @rtype: C{str}
+ """
+ try:
+ ext = Compressor.Exts[compression]
+ except KeyError:
+ raise GbpError("Unknown compression type '%s'" % compression)
+ sub = '-{0}'.format(component) if component else ''
+ tarball = "%s_%s.orig%s.tar.%s" % (name, version, sub, ext)
+ if dir:
+ tarball = os.path.join(dir, tarball)
+ return tarball
diff --git a/gbp/deb/pristinetar.py b/gbp/deb/pristinetar.py
new file mode 100644
index 0000000..2f52f3a
--- /dev/null
+++ b/gbp/deb/pristinetar.py
@@ -0,0 +1,67 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 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, please see
+# <http://www.gnu.org/licenses/>
+"""Handle checkin and checkout of archives from the pristine-tar branch"""
+
+from gbp.pkg.compressor import Compressor
+from gbp.pkg.pristinetar import PristineTar
+from gbp.deb import DebianPkgPolicy
+
+
+class DebianPristineTar(PristineTar):
+ """The pristine-tar branch in a Debian git repository"""
+ def has_commit(self, package, version, comp_type=None):
+ """
+ Do we have a pristine-tar commit for package I{package} at version
+ {version} with compression type I{comp_type}?
+
+ @param package: the package to look for
+ @type package: C{str}
+ @param version: the upstream version to look for
+ @type version: C{str}
+ @param comp_type: the compression type
+ @type comp_type: C{str}
+ """
+ if not comp_type:
+ ext = '\w\+'
+ else:
+ ext = Compressor.Exts[comp_type]
+
+ name_regexp = '%s_%s\.orig\.tar\.%s' % (package, version, ext)
+
+ return super(DebianPristineTar, self).has_commit(name_regexp)
+
+ def checkout(self, package, version, comp_type, output_dir, component=None,
+ quiet=False):
+ """
+ Checkout the orig tarball for package I{package} of I{version} and
+ compression type I{comp_type} to I{output_dir}
+
+ @param package: the package to generate the orig tarball for
+ @type package: C{str}
+ @param version: the version to check generate the orig tarball for
+ @type version: C{str}
+ @param comp_type: the compression type of the tarball
+ @type comp_type: C{str}
+ @param output_dir: the directory to put the tarball into
+ @type output_dir: C{str}
+ """
+ name = DebianPkgPolicy.build_tarball_name(package,
+ version,
+ comp_type,
+ output_dir,
+ component=component)
+ super(DebianPristineTar, self).checkout(name, quiet=quiet)
diff --git a/gbp/deb/rollbackgit.py b/gbp/deb/rollbackgit.py
new file mode 100644
index 0000000..74a0b93
--- /dev/null
+++ b/gbp/deb/rollbackgit.py
@@ -0,0 +1,134 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2018 Guido Günther <agx@sigxcpu.org>
+"""A git repository for Debian packages that can roll back operations"""
+
+
+from .. import log
+from .. git import GitRepositoryError
+from . git import DebianGitRepository
+
+
+class RollbackError(GitRepositoryError):
+ """
+ Error raised if the rollback failed
+ """
+ def __init__(self, errors):
+ self.msg = "Automatic rollback failed"
+ super(RollbackError, self).__init__(self.msg)
+ self.errors = errors
+
+ def __str__(self):
+ return "%s %s" % (self.msg, self.errors)
+
+
+class RollbackDebianGitRepository(DebianGitRepository):
+ """
+ Like a DebianGitRepository but can also perform rollbacks and knows
+ about some of the inner workings upstream vcs_tag, …
+ """
+ def __init__(self, *args, **kwargs):
+ self.rollbacks = []
+ self.rollback_errors = []
+ DebianGitRepository.__init__(self, *args, **kwargs)
+
+ def has_rollbacks(self):
+ return len(self.rollbacks) > 0
+
+ def rrr(self, refname, action, reftype):
+ """
+ Remember ref for rollback
+
+ @param refname: ref to roll back
+ @param action: the rollback action (delete, reset, ...)
+ @param reftype: the reference type (tag, branch, ...)
+ """
+ sha = None
+
+ if action == 'reset':
+ try:
+ sha = self.rev_parse(refname)
+ except GitRepositoryError as err:
+ log.warn("Failed to rev-parse '%s': %s" % (refname, err))
+ elif action == 'delete':
+ pass
+ elif action == 'abortmerge':
+ pass
+ else:
+ raise GitRepositoryError("Unknown action '%s' for %s '%s'" % (action, reftype, refname))
+ self.rollbacks.append((refname, reftype, action, sha))
+
+ def rrr_branch(self, branchname, action='reset-or-delete'):
+ if action == 'reset-or-delete':
+ if self.has_branch(branchname):
+ return self.rrr(branchname, 'reset', 'branch')
+ else:
+ return self.rrr(branchname, 'delete', 'branch')
+ else:
+ return self.rrr(branchname, action, 'branch')
+
+ def rrr_tag(self, tagname, action='delete'):
+ return self.rrr(tagname, action, 'tag')
+
+ def rrr_merge(self, commit, action='abortmerge'):
+ return self.rrr(commit, action, 'commit')
+
+ def rollback(self):
+ """
+ Perform a complete rollback
+
+ Try to roll back as much as possible and remember what failed.
+ """
+ for (name, reftype, action, sha) in self.rollbacks:
+ try:
+ if action == 'delete':
+ log.info("Rolling back %s '%s' by deleting it" % (reftype, name))
+ if reftype == 'tag':
+ self.delete_tag(name)
+ elif reftype == 'branch':
+ self.delete_branch(name)
+ else:
+ raise GitRepositoryError("Don't know how to delete %s '%s'" % (reftype, name))
+ elif action == 'reset' and reftype == 'branch':
+ log.info('Rolling back branch %s by resetting it to %s' % (name, sha))
+ self.update_ref("refs/heads/%s" % name, sha, msg="gbp import-orig: failure rollback of %s" % name)
+ elif action == 'abortmerge':
+ if self.is_in_merge():
+ log.info('Rolling back failed merge of %s' % name)
+ self.abort_merge()
+ else:
+ log.info("Nothing to rollback for merge of '%s'" % name)
+ else:
+ raise GitRepositoryError("Don't know how to %s %s '%s'" % (action, reftype, name))
+ except GitRepositoryError as e:
+ self.rollback_errors.append((name, reftype, action, sha, e))
+ if self.rollback_errors:
+ raise RollbackError(self.rollback_errors)
+
+ # Wrapped methods for rollbacks
+ def create_tag(self, *args, **kwargs):
+ name = kwargs['name']
+ ret = super(RollbackDebianGitRepository, self).create_tag(*args, **kwargs)
+ self.rrr_tag(name)
+ return ret
+
+ def commit_dir(self, *args, **kwargs):
+ import_branch = kwargs['branch']
+ self.rrr_branch(import_branch)
+ return super(RollbackDebianGitRepository, self).commit_dir(*args, **kwargs)
+
+ def create_branch(self, *args, **kwargs):
+ branch = kwargs['branch']
+ ret = super(RollbackDebianGitRepository, self).create_branch(*args, **kwargs)
+ self.rrr_branch(branch, 'delete')
+ return ret
+
+ def merge(self, *args, **kwargs):
+ commit = args[0] if args else kwargs['commit']
+ try:
+ return super(RollbackDebianGitRepository, self).merge(*args, **kwargs)
+ except GitRepositoryError:
+ # Only cleanup in the error case to undo working copy
+ # changes. Resetting the refs handles the other cases.
+ self.rrr_merge(commit)
+ raise
diff --git a/gbp/deb/source.py b/gbp/deb/source.py
new file mode 100644
index 0000000..f1e2c56
--- /dev/null
+++ b/gbp/deb/source.py
@@ -0,0 +1,163 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""provides some debian source package related helpers"""
+
+import os
+from gbp.deb import DebianPkgPolicy as Policy
+from gbp.deb.format import DebianSourceFormat
+from gbp.deb.changelog import ChangeLog
+from gbp.deb.control import Control
+
+
+class FileVfs(object):
+ def __init__(self, dir):
+ """
+ Access files in an unpacked Debian source package.
+
+ @param dir: the toplevel of the source tree
+ @type dir: C{str}
+ """
+ self._dir = dir
+
+ def open(self, path, flags=None):
+ flags = flags or 'r'
+ return open(os.path.join(self._dir, path), flags)
+
+
+class DebianSourceError(Exception):
+ pass
+
+
+class DebianSource(object):
+ """
+ A debianized source tree
+
+ Querying/setting information in a debianized source tree
+ involves several files. This class provides a common interface.
+ """
+ def __init__(self, vfs):
+ """
+ @param vfs: a class that implements L{GitVfs} interface or a directory
+ (which will use the L{FileVfs} class. The directory must be the
+ toplevel of a Debian source package.
+ """
+ self._changelog = None
+ self._control = None
+
+ if isinstance(vfs, str):
+ self._vfs = FileVfs(vfs)
+ else:
+ self._vfs = vfs
+
+ def is_native(self):
+ """
+ Whether this is a native Debian package
+ """
+ try:
+ with self._vfs.open('debian/source/format') as ff:
+ f = DebianSourceFormat(ff.read())
+ if f.type:
+ return f.type == 'native'
+ except IOError as e:
+ pass # Fall back to changelog parsing
+
+ try:
+ return '-' not in self.changelog.version
+ except IOError as e:
+ raise DebianSourceError("Failed to determine source format: %s" % e)
+
+ def is_releasable(self):
+ """
+ Check if package is releasable
+
+ Debian's current practice is to check for UNRELEASED in the distribution.
+ """
+ return self.changelog.distribution != 'UNRELEASED'
+
+ @property
+ def changelog(self):
+ """
+ Return the L{gbp.deb.ChangeLog}
+ """
+ if not self._changelog:
+ try:
+ with self._vfs.open('debian/changelog', 'rb') as clf:
+ self._changelog = ChangeLog(clf.read().decode('utf-8'))
+ except IOError as err:
+ raise DebianSourceError('Failed to read changelog: %s' % err)
+ return self._changelog
+
+ @property
+ def control(self):
+ """
+ Return the L{gbp.deb.Control}
+ """
+ if not self._control:
+ try:
+ with self._vfs.open('debian/control', 'rb') as cf:
+ self._control = Control(cf.read().decode('utf-8'))
+ except IOError as err:
+ raise DebianSourceError('Failed to read control file: %s' % err)
+ return self._control
+
+ @property
+ def sourcepkg(self):
+ """
+ The source package's name
+ """
+ return self.changelog['Source']
+
+ @property
+ def name(self):
+ return self.sourcepkg
+
+ @property
+ def version(self):
+ return self.changelog.version
+
+ @property
+ def upstream_version(self):
+ return self.changelog.upstream_version
+
+ @property
+ def debian_version(self):
+ return self.changelog.debian_version
+
+ def upstream_tarball_name(self, compression, component=None):
+ """
+ Possible upstream tarball name for this source package
+
+ Gives the name of the main tarball if component is None
+ """
+ if self.is_native():
+ return None
+ return Policy.build_tarball_name(self.name,
+ self.upstream_version,
+ compression=compression,
+ component=component)
+
+ def upstream_tarball_names(self, comp_type, components=None):
+ """
+ Possible upstream tarballs names for this source package
+
+ This includes component tarballs names. with the given
+ component names
+ """
+ names = [self.upstream_tarball_name(comp_type)]
+ for component in (components or []):
+ names += [self.upstream_tarball_name(comp_type, c) for c in components]
+ return names
diff --git a/gbp/deb/upstreamsource.py b/gbp/deb/upstreamsource.py
new file mode 100644
index 0000000..04c3774
--- /dev/null
+++ b/gbp/deb/upstreamsource.py
@@ -0,0 +1,56 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 Intel Corporation <markus.lehtonen@linux.intel.com>
+# (C) 2016 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, please see
+# <http://www.gnu.org/licenses/>
+"""Debian-specific upstream sources"""
+
+from gbp.pkg import UpstreamSource
+from gbp.deb.policy import DebianPkgPolicy
+import gbp.command_wrappers
+
+import os
+import shutil
+import tempfile
+
+
+class DebianUpstreamSource(UpstreamSource):
+ """Upstream source class for Debian"""
+ def __init__(self, name, unpacked=None):
+ super(DebianUpstreamSource, self).__init__(name,
+ unpacked,
+ DebianPkgPolicy)
+
+
+def unpack_component_tarball(dest, component, tarball, filters):
+ """
+ Unpack the tarball I{tarball} into dest naming it I{component}.
+ Apply filters during unpack.
+ """
+ olddir = os.path.abspath(os.path.curdir)
+ tmpdir = None
+ try:
+ tmpdir = os.path.abspath(tempfile.mkdtemp(dir=os.path.join(dest, '..')))
+ source = DebianUpstreamSource(tarball)
+ source.unpack(tmpdir, filters)
+
+ newdest = os.path.join(dest, component)
+ if os.path.exists(newdest):
+ shutil.rmtree(newdest)
+ shutil.move(source.unpacked, newdest)
+ finally:
+ os.chdir(olddir)
+ if tmpdir is not None:
+ gbp.command_wrappers.RemoveTree(tmpdir)()
diff --git a/gbp/deb/uscan.py b/gbp/deb/uscan.py
new file mode 100644
index 0000000..0416587
--- /dev/null
+++ b/gbp/deb/uscan.py
@@ -0,0 +1,199 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012,2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""Interface to uscan"""
+
+import os
+import re
+import subprocess
+
+
+class UscanError(Exception):
+ pass
+
+
+class Uscan(object):
+ cmd = '/usr/bin/uscan'
+
+ def __init__(self, dir='.'):
+ self._uptodate = False
+ self._tarball = None
+ self._dir = os.path.abspath(dir)
+
+ @property
+ def uptodate(self):
+ return self._uptodate
+
+ @property
+ def tarball(self):
+ return self._tarball
+
+ def _parse(self, out):
+ r"""
+ Parse the uscan output return and update the object's properties
+
+ @param out: uscan output
+ @type out: string
+
+ >>> u = Uscan('http://example.com/')
+ >>> u._parse('<target>virt-viewer_0.4.0.orig.tar.gz</target>')
+ >>> u.tarball
+ '../virt-viewer_0.4.0.orig.tar.gz'
+ >>> u.uptodate
+ False
+ >>> u._parse('')
+ Traceback (most recent call last):
+ ...
+ gbp.deb.uscan.UscanError: Couldn't find 'upstream-url' in uscan output
+ """
+ source = None
+ self._uptodate = False
+
+ # Check if uscan downloaded something
+ for row in out.split("\n"):
+ # uscan >= 2.10.70 has a target element:
+ m = re.match(r"<target>(.*)</target>", row)
+ if m:
+ source = '../%s' % m.group(1)
+ break
+ elif row.startswith('<messages>'):
+ m = re.match(r".*symlinked ([^\s]+) to it", row)
+ if m:
+ source = "../%s" % m.group(1)
+ break
+ m = re.match(r"Successfully downloaded updated package "
+ "([^<]+)", row)
+ if m:
+ source = "../%s" % m.group(1)
+ break
+ # Try to determine the already downloaded sources name
+ else:
+ d = {}
+
+ try:
+ for row in out.split("\n"):
+ for n in ('package',
+ 'upstream-version',
+ 'upstream-url'):
+ m = re.match("<%s>(.*)</%s>" % (n, n), row)
+ if m:
+ d[n] = m.group(1)
+ d["ext"] = os.path.splitext(d['upstream-url'])[1]
+ # We want the name of the orig tarball if possible
+ source = ("../%(package)s_%(upstream-version)s."
+ "orig.tar%(ext)s" % d)
+
+ # Fall back to the upstream source name otherwise
+ if not os.path.exists(source):
+ source = "../%s" % d['upstream-url'].rsplit('/', 1)[1]
+ if not os.path.exists(source):
+ raise UscanError("Couldn't find tarball at '%s'" %
+ source)
+ except KeyError as e:
+ raise UscanError("Couldn't find '%s' in uscan output" %
+ e.args[0])
+ self._tarball = source
+
+ def _parse_uptodate(self, out):
+ """
+ Check if the uscan reports that we're up to date.
+
+ @param out: uscan output
+ @type out: string
+ @returns: C{True} if package is uptodate
+
+ >>> u = Uscan('http://example.com/')
+ >>> u._parse_uptodate('<status>up to date</status>')
+ True
+ >>> u.tarball
+ >>> u.uptodate
+ True
+ >>> u._parse_uptodate('')
+ False
+ >>> u.tarball
+ >>> u.uptodate
+ False
+ """
+ if "<status>up to date</status>" in out:
+ self._uptodate = True
+ else:
+ self._uptodate = False
+ return self._uptodate
+
+ def _raise_error(self, out):
+ r"""
+ Parse the uscan output for errors and warnings and raise
+ a L{UscanError} exception based on this. If no error detail
+ is found a generic error message is used.
+
+ @param out: uscan output
+ @type out: string
+ @raises UscanError: exception raised
+
+ >>> u = Uscan('http://example.com/')
+ >>> u._raise_error("<warnings>uscan warning: "
+ ... "In watchfile debian/watch, reading webpage\n"
+ ... "http://a.b/ failed: 500 Cant connect "
+ ... "to example.com:80 (Bad hostname)</warnings>")
+ Traceback (most recent call last):
+ ...
+ gbp.deb.uscan.UscanError: Uscan failed: uscan warning: In watchfile debian/watch, reading webpage
+ http://a.b/ failed: 500 Cant connect to example.com:80 (Bad hostname)
+ >>> u._raise_error("<errors>uscan: Can't use --verbose if "
+ ... "you're using --dehs!</errors>")
+ Traceback (most recent call last):
+ ...
+ gbp.deb.uscan.UscanError: Uscan failed: uscan: Can't use --verbose if you're using --dehs!
+ >>> u = u._raise_error('')
+ Traceback (most recent call last):
+ ...
+ gbp.deb.uscan.UscanError: Uscan failed - debug by running 'uscan --verbose'
+ """
+ msg = None
+
+ for n in ('errors', 'warnings'):
+ m = re.search("<%s>(.*)</%s>" % (n, n), out, re.DOTALL)
+ if m:
+ msg = "Uscan failed: %s" % m.group(1)
+ break
+
+ if not msg:
+ msg = "Uscan failed - debug by running 'uscan --verbose'"
+ raise UscanError(msg)
+
+ def scan(self, destdir='..'):
+ """
+ Invoke uscan to fetch a new upstream version
+
+ @returns: C{True} if a new version was downloaded
+ """
+ p = subprocess.Popen(['uscan', '--symlink', '--destdir=%s' % destdir,
+ '--dehs'],
+ cwd=self._dir,
+ stdout=subprocess.PIPE)
+ out = p.communicate()[0].decode()
+ # uscan exits with 1 in case of uptodate and when an error occurred.
+ # Don't fail in the uptodate case:
+ if self._parse_uptodate(out):
+ return False
+
+ if p.returncode:
+ self._raise_error(out)
+
+ self._parse(out)
+ return True
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/errors.py b/gbp/errors.py
new file mode 100644
index 0000000..d1f6e8d
--- /dev/null
+++ b/gbp/errors.py
@@ -0,0 +1,24 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,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, please see
+# <http://www.gnu.org/licenses/>
+"""Errors raised in gbp commands"""
+
+
+class GbpError(Exception):
+ """Generic exception raised in git-buildpackage commands"""
+ pass
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/format.py b/gbp/format.py
new file mode 100644
index 0000000..ce29a66
--- /dev/null
+++ b/gbp/format.py
@@ -0,0 +1,66 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2014 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, please see
+# <http://www.gnu.org/licenses/>
+"""Format a message"""
+
+from gbp.errors import GbpError
+
+
+def format_str(msg, args):
+ """
+ Format a string with the given dict. Be a bit more verbose than
+ default python about the error cause.
+
+ >>> format_str("%(foo)", {})
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: Failed to format %(foo): Missing value 'foo' in {}
+ >>> format_str("%(foo)", {'foo': 'bar'})
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: Failed to format %(foo) with {'foo': 'bar'}: incomplete format
+ >>> format_str("A %(foo)s is a %(bar)s", {'foo': 'dog', 'bar': 'mamal'})
+ 'A dog is a mamal'
+ """
+ try:
+ return msg % args
+ except ValueError as e:
+ raise GbpError("Failed to format %s with %s: %s" % (msg, args, e))
+ except KeyError as e:
+ raise GbpError("Failed to format %s: Missing value %s in %s" %
+ (msg, e, args))
+
+
+def format_b(fmtstr, *args):
+ """String-like interpolation for bytes objects.
+
+ NOTE: This is a compatibility wrapper for older versions (<3.5) of Python 3
+ which do not support the percent operator ('%') for bytes objects. This
+ function should be removed (and replaced by simple '%') when Python 3.5
+ has gained wide enough adoption.
+
+ >>> format_b(b'%s %d', b'foo', 123)
+ b'foo 123'
+ >>> format_b(b'foo 123')
+ b'foo 123'
+ >>> format_b('%s %d', b'foo', 123)
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'str' object has no attribute 'decode'
+ """
+ fmtstr = fmtstr.decode()
+ strargs = tuple([(a.decode() if isinstance(a, bytes) else a) for a in args])
+ return (fmtstr % strargs).encode()
diff --git a/gbp/git/__init__.py b/gbp/git/__init__.py
new file mode 100644
index 0000000..b3bc599
--- /dev/null
+++ b/gbp/git/__init__.py
@@ -0,0 +1,53 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,2008,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, please see
+# <http://www.gnu.org/licenses/>
+"""Accessing Git from python"""
+
+import calendar
+import dateutil.parser
+
+from gbp.git.modifier import GitModifier # noqa: F401
+from gbp.git.commit import GitCommit # noqa: F401
+from gbp.git.errors import GitError # noqa: F401
+from gbp.git.repository import ( # noqa: F401
+ GitRepository, GitRepositoryError)
+from gbp.git.fastimport import FastImport # noqa: F401
+from gbp.git.args import GitArgs # noqa: F401
+from gbp.git.vfs import GitVfs # noqa: F401
+
+
+def rfc822_date_to_git(rfc822_date, fuzzy=False):
+ """Parse a date in RFC822 format, and convert to a 'seconds tz' C{str}ing.
+
+ >>> rfc822_date_to_git('Thu, 1 Jan 1970 00:00:01 +0000')
+ '1 +0000'
+ >>> rfc822_date_to_git('Thu, 20 Mar 2008 01:12:57 -0700')
+ '1206000777 -0700'
+ >>> rfc822_date_to_git('Sat, 5 Apr 2008 17:01:32 +0200')
+ '1207407692 +0200'
+ >>> rfc822_date_to_git('So, 26 Feb 1998 8:50:00 +0100')
+ Traceback (most recent call last):
+ ...
+ ValueError: Unknown string format
+ >>> rfc822_date_to_git('So, 26 Feb 1998 8:50:00 +0100', fuzzy=True)
+ '888479400 +0100'
+ """
+ d = dateutil.parser.parse(rfc822_date, fuzzy=fuzzy)
+ seconds = calendar.timegm(d.utctimetuple())
+ tz = d.strftime("%z")
+ return '%d %s' % (seconds, tz)
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/git/args.py b/gbp/git/args.py
new file mode 100644
index 0000000..88ad40b
--- /dev/null
+++ b/gbp/git/args.py
@@ -0,0 +1,108 @@
+# 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, please see
+# <http://www.gnu.org/licenses/>
+"""
+Git command argument handling helpers
+"""
+
+import collections
+
+
+class GitArgs(object):
+ """
+ Handle arguments to git commands
+
+ >>> GitArgs('-h', '--no-foo').args
+ ['-h', '--no-foo']
+ >>> GitArgs('-n', 123).args
+ ['-n', '123']
+ >>> GitArgs().add('--more-foo', '--less-bar').args
+ ['--more-foo', '--less-bar']
+ >>> GitArgs().add(['--foo', '--bar']).args
+ ['--foo', '--bar']
+ >>> GitArgs().add_cond(1 > 2, '--opt', '--no-opt').args
+ ['--no-opt']
+ >>> GitArgs().add_true(True, '--true').args
+ ['--true']
+ >>> GitArgs().add_false(True, '--true').args
+ []
+ >>> GitArgs().add_false(False, '--false').args
+ ['--false']
+ """
+
+ def __init__(self, *args):
+ self._args = []
+ self.add(args)
+
+ @property
+ def args(self):
+ return self._args
+
+ def add(self, *args):
+ """
+ Add arguments to argument list
+ """
+ for arg in args:
+ if isinstance(arg, str):
+ self._args.append(arg)
+ elif isinstance(arg, collections.Iterable):
+ for i in iter(arg):
+ self._args.append(str(i))
+ else:
+ self._args.append(str(arg))
+
+ return self
+
+ def add_true(self, condition, *args):
+ """
+ Add I{args} if I{condition} is C{True}
+
+ @param condition: the condition to test
+ @type condition: C{bool}
+ @param args: arguments to add
+ """
+ if condition:
+ self.add(*args)
+ return self
+
+ def add_false(self, condition, *args):
+ """
+ Add I{args} if I{condition} is C{False}
+
+ @param condition: the condition to test
+ @type condition: C{bool}
+ @param args: arguments to add
+ """
+ self.add_true(not condition, *args)
+ return self
+
+ def add_cond(self, condition, opt, noopt=[]):
+ """
+ Add option I{opt} to I{alist} if I{condition} is C{True}
+ else add I{noopt}.
+
+ @param condition: condition
+ @type condition: C{bool}
+ @param opt: option to add if I{condition} is C{True}
+ @type opt: C{str} or C{list}
+ @param noopt: option to add if I{condition} is C{False}
+ @type noopt: C{str} or C{list}
+ """
+ if condition:
+ self.add(opt)
+ else:
+ self.add(noopt)
+ return self
diff --git a/gbp/git/commit.py b/gbp/git/commit.py
new file mode 100644
index 0000000..9437a5a
--- /dev/null
+++ b/gbp/git/commit.py
@@ -0,0 +1,45 @@
+# 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, please see
+# <http://www.gnu.org/licenses/>
+"""Git commit class and helpers"""
+
+import re
+
+
+class GitCommit(object):
+ """A git commit"""
+ sha1_re = re.compile(r'[0-9a-f]{40}$')
+
+ @staticmethod
+ def is_sha1(value):
+ """
+ Is I{value} a valid 40 digit SHA1?
+
+ >>> GitCommit.is_sha1('asdf')
+ False
+ >>> GitCommit.is_sha1('deadbeef')
+ False
+ >>> GitCommit.is_sha1('17975594b2d42f2a3d144a9678fdf2c2c1dd96a0')
+ True
+ >>> GitCommit.is_sha1('17975594b2d42f2a3d144a9678fdf2c2c1dd96a0toolong')
+ False
+
+ @param value: the value to check
+ @type value: C{str}
+ @return: C{True} if I{value} is a 40 digit SHA1, C{False} otherwise.
+ @rtype: C{bool}
+ """
+ return True if GitCommit.sha1_re.match(value) else False
diff --git a/gbp/git/errors.py b/gbp/git/errors.py
new file mode 100644
index 0000000..3a94865
--- /dev/null
+++ b/gbp/git/errors.py
@@ -0,0 +1,22 @@
+# 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, please see
+# <http://www.gnu.org/licenses/>
+"""Git base error exception"""
+
+
+class GitError(Exception):
+ """Exception thrown by Git related classes"""
+ pass
diff --git a/gbp/git/fastimport.py b/gbp/git/fastimport.py
new file mode 100644
index 0000000..3c113c1
--- /dev/null
+++ b/gbp/git/fastimport.py
@@ -0,0 +1,144 @@
+
+# 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, please see
+# <http://www.gnu.org/licenses/>
+"""Git fast import class"""
+
+import subprocess
+import time
+from gbp.errors import GbpError
+from gbp.format import format_b
+from gbp.paths import to_bin
+
+
+class FastImport(object):
+ """Add data to a git repository using I{git fast-import}"""
+ _bufsize = 1024
+
+ m_regular = 644
+ m_exec = 755
+ m_symlink = 120000
+
+ def __init__(self, repo):
+ """
+ @param repo: the git repository L{FastImport} acts on
+ @type repo: L{GitRepository}
+ """
+ self._repo = repo
+ try:
+ self._fi = subprocess.Popen(['git', 'fast-import', '--quiet'],
+ stdin=subprocess.PIPE, cwd=repo.path)
+ self._out = self._fi.stdin
+ except OSError as err:
+ raise GbpError("Error spawning git fast-import: %s" % err)
+ except ValueError as err:
+ raise GbpError(
+ "Invalid argument when spawning git fast-import: %s" % err)
+
+ def _do_data(self, fd, size):
+ self._out.write(format_b(b"data %d\n", size))
+ while True:
+ data = fd.read(self._bufsize)
+ self._out.write(data)
+ if len(data) != self._bufsize:
+ break
+ self._out.write(b"\n")
+
+ def _do_file(self, filename, mode, fd, size):
+ name = b"/".join(to_bin(filename).split(b'/')[1:])
+ self._out.write(format_b(b"M %d inline %s\n", mode, name))
+ self._do_data(fd, size)
+
+ def add_file(self, filename, fd, size, mode=m_regular):
+ """
+ Add a file
+
+ @param filename: the name of the file to add
+ @type filename: C{str}
+ @param fd: stream to read data from
+ @type fd: C{File} like object
+ @param size: size of the file to add
+ @type size: C{int}
+ @param mode: file mode, default is L{FastImport.m_regular}.
+ @type mode: C{int}
+ """
+ self._do_file(filename, mode, fd, size)
+
+ def add_symlink(self, linkname, linktarget):
+ """
+ Add a symlink
+
+ @param linkname: the symbolic link's name
+ @param linkname: C{str}
+ @param linktarget: the target the symlink points to
+ @type linktarget: C{str}
+ """
+ linktarget = to_bin(linktarget)
+ linkname = to_bin(linkname)
+ self._out.write(format_b(b"M %d inline %s\n", self.m_symlink, linkname))
+ self._out.write(format_b(b"data %d\n", len(linktarget)))
+ self._out.write(format_b(b"%s\n", linktarget))
+
+ def start_commit(self, branch, committer, msg):
+ """
+ Start a fast import commit
+
+ @param branch: branch to commit on
+ @type branch: C{str}
+ @param committer: the committer information
+ @type committer: L{GitModifier}
+ @param msg: the commit message
+ @type msg: C{str}
+ """
+ length = len(msg)
+ if not committer.date:
+ committer.date = "%d %s" % (time.time(),
+ time.strftime("%z"))
+
+ if self._repo.has_branch(branch):
+ from_ = "from refs/heads/%(branch)s^0\n"
+ else:
+ from_ = ''
+
+ s = """commit refs/heads/%(branch)s
+committer %(name)s <%(email)s> %(time)s
+data %(length)s
+%(msg)s%(from)s""" % {'branch': branch,
+ 'name': committer.name,
+ 'email': committer.email,
+ 'time': committer.date,
+ 'length': length,
+ 'msg': msg,
+ 'from': from_}
+ self._out.write(s.encode())
+
+ def deleteall(self):
+ """
+ Issue I{deleteall} to fastimport so we start from a empty tree
+ """
+ self._out.write(b"deleteall\n")
+
+ def close(self):
+ """
+ Close fast-import issuing all pending actions
+ """
+ if self._out:
+ self._out.close()
+ if self._fi:
+ self._fi.wait()
+
+ def __del__(self):
+ self.close()
diff --git a/gbp/git/modifier.py b/gbp/git/modifier.py
new file mode 100644
index 0000000..d93086d
--- /dev/null
+++ b/gbp/git/modifier.py
@@ -0,0 +1,174 @@
+# 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, please see
+# <http://www.gnu.org/licenses/>
+"""
+Someone who modifiers something in git
+
+like committing changes or authoring a patch
+"""
+
+import calendar
+import datetime
+
+from gbp.git.errors import GitError
+
+
+class GitModifierError(GitError):
+ """Exception thrown by L{GitModifier}"""
+ pass
+
+
+class GitTz(datetime.tzinfo):
+ """Simple class to store the utc offset only"""
+ def __init__(self, offset_sec=0, *args, **kwargs):
+ super(GitTz, self).__init__(*args, **kwargs)
+ self._offset = datetime.timedelta(seconds=offset_sec)
+
+ def utcoffset(self, dt):
+ return self._offset
+
+ def dst(self, dt):
+ return datetime.timedelta(0)
+
+
+class GitModifier(object):
+ """Stores authorship/committer information"""
+ def __init__(self, name=None, email=None, date=None):
+ """
+ @param name: the modifier's name
+ @type name: C{str}
+ @param email: the modifier's email
+ @type email: C{str}
+ @param date: the date of the modification
+ @type date: C{str} (git raw date), C{int} (timestamp) or I{datetime} object
+ """
+ self.name = name
+ self.email = email
+ self._parse_date(date)
+
+ def _parse_date(self, date):
+ self._date = None
+ tz = GitTz(0)
+
+ if isinstance(date, str):
+ timestamp, offset = date.split()
+ offset_h = int(offset[:-2])
+ offset_m = int(offset[-2:])
+ tz = GitTz(offset_h * 3600 + offset_m * 60)
+ self._date = datetime.datetime.fromtimestamp(int(timestamp), tz)
+ elif type(date) in [type(0), type(0.0)]:
+ self._date = datetime.datetime.fromtimestamp(date, tz)
+ elif isinstance(date, datetime.datetime):
+ if date.tzinfo:
+ self._date = date
+ else:
+ self._date = date.replace(tzinfo=tz)
+ elif date is not None:
+ raise ValueError("Date '%s' not timestamp, "
+ "datetime object or git raw date" % date)
+
+ def _get_env(self, who):
+ """Get author or committer information as env var dictionary"""
+ who = who.upper()
+ if who not in ['AUTHOR', 'COMMITTER']:
+ raise GitModifierError("Neither committer nor author")
+
+ extra_env = {}
+ if self.name:
+ extra_env['GIT_%s_NAME' % who] = self.name
+ if self.email:
+ extra_env['GIT_%s_EMAIL' % who] = self.email
+ if self.date:
+ extra_env['GIT_%s_DATE' % who] = self.date
+ return extra_env
+
+ def get_date(self):
+ """Return date as a git raw date"""
+ if self._date:
+ return "%s %s" % (calendar.timegm(self._date.utctimetuple()),
+ self._date.strftime('%z'))
+ else:
+ return None
+
+ def set_date(self, date):
+ """Set date from timestamp, git raw date or datetime object"""
+ self._parse_date(date)
+
+ date = property(get_date, set_date)
+
+ @property
+ def datetime(self):
+ """Return the date as datetime object"""
+ return self._date
+
+ @property
+ def tz_offset(self):
+ """Return the date's UTC offset"""
+ return self._date.strftime('%z')
+
+ def get_author_env(self):
+ """
+ Get env vars for authorship information
+
+ >>> g = GitModifier("Joey Ramone", "joey@example.com")
+ >>> g.get_author_env()['GIT_AUTHOR_EMAIL']
+ 'joey@example.com'
+ >>> g.get_author_env()['GIT_AUTHOR_NAME']
+ 'Joey Ramone'
+
+ @return: Author information suitable to use as environment variables
+ @rtype: C{dict}
+ """
+ return self._get_env('author')
+
+ def get_committer_env(self):
+ """
+ Get env vars for committer information
+
+ >>> g = GitModifier("Joey Ramone", "joey@example.com")
+ >>> g.get_committer_env()['GIT_COMMITTER_EMAIL']
+ 'joey@example.com'
+ >>> g.get_committer_env()['GIT_COMMITTER_NAME']
+ 'Joey Ramone'
+
+ @return: Committer information suitable to use as environment variables
+ @rtype: C{dict}
+ """
+ return self._get_env('committer')
+
+ def get(self, key, default=None):
+ if key in self.keys():
+ return self.__getitem__(key)
+ else:
+ return default
+
+ def __getitem__(self, key):
+ if key == 'date':
+ return self.date
+ else:
+ return self.__dict__[key]
+
+ @staticmethod
+ def keys():
+ return ['name', 'email', 'date']
+
+ def items(self):
+ items = []
+ for key in self.keys():
+ val = self.__getitem__(key)
+ if val:
+ items.append((key, val))
+ return items
diff --git a/gbp/git/repository.py b/gbp/git/repository.py
new file mode 100644
index 0000000..6cc3cd9
--- /dev/null
+++ b/gbp/git/repository.py
@@ -0,0 +1,2098 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,2008,2011,2013,2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""A Git repository"""
+
+import subprocess
+import os.path
+import re
+import sys
+from collections import defaultdict
+
+import gbp.log as log
+from gbp.errors import GbpError
+from gbp.format import format_b
+from gbp.git.modifier import GitModifier
+from gbp.git.commit import GitCommit
+from gbp.git.errors import GitError
+from gbp.git.args import GitArgs
+from gbp.paths import to_bin
+
+
+class GitRepositoryError(GitError):
+ """Exception thrown by L{GitRepository}"""
+ pass
+
+
+class GitRemote(object):
+ """Class representing a remote repository"""
+ def __init__(self, name, fetch_url, push_urls):
+ self._name = name
+ self._fetch_url = fetch_url
+ if isinstance(push_urls, str):
+ self._push_urls = [push_urls]
+ else:
+ self._push_urls = [url for url in push_urls]
+
+ def __str__(self):
+ return self.name
+
+ @property
+ def name(self):
+ """Name of the remote"""
+ return self._name
+
+ @property
+ def fetch_url(self):
+ """Fetch URL"""
+ return self._fetch_url
+
+ @property
+ def push_urls(self):
+ """List of push URLs"""
+ return self._push_urls
+
+
+class GitRepository(object):
+ """
+ Represents a git repository at I{path}. It's currently assumed that the git
+ repository is stored in a directory named I{.git/} below I{path}.
+
+ @ivar _path: The path to the working tree
+ @type _path: C{str}
+ @ivar _bare: Whether this is a bare repository
+ @type _bare: C{bool}
+ @raises GitRepositoryError: on git errors GitRepositoryError is raised by
+ all methods.
+ """
+
+ def _check_bare(self):
+ """Check whether this is a bare repository"""
+ out, dummy, ret = self._git_inout('rev-parse', ['--is-bare-repository'],
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError(
+ "Failed to get repository state at '%s'" % self.path)
+ self._bare = False if out.decode().strip() != 'true' else True
+
+ def _get_git_dir(self):
+ out, dummy, ret = self._git_inout('rev-parse', ['--git-dir'],
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError(
+ "Failed to determine repos git-dir at '%s'" % self.path)
+ git_dir = out.strip().decode(sys.getfilesystemencoding())
+ if os.path.isabs(git_dir):
+ self._git_dir = git_dir
+ else:
+ self._git_dir = os.path.abspath(os.path.join(self.path, git_dir))
+
+ def _check_repo(self, path, toplevel):
+ try:
+ out, dummy, ret = self._git_inout('rev-parse', ['--show-cdup'],
+ cwd=path,
+ capture_stderr=True)
+ cdup = out.strip().decode(sys.getfilesystemencoding())
+ if ret:
+ raise GitRepositoryError("No Git repository at '%s': '%s'" % (path, cdup))
+ if toplevel and cdup:
+ raise GitRepositoryError("Not the toplevel of a Git repository at '%s': '%s'" % (path, cdup))
+ ret = os.path.abspath(os.path.join(path, cdup or '.'))
+ except GitRepositoryError:
+ raise # We already have a useful error message
+ except Exception:
+ raise GitRepositoryError("No Git repository at '%s'" % path)
+ return ret
+
+ def __init__(self, path, toplevel=True):
+ """
+ @param path: path to git repo (or subdir)
+ @type path: C{str}
+ @param toplevel: whether path points to the toplevel dir of
+ git repository
+ @type toplevel: C{bool}
+ """
+ self._bare = False
+ self._path = self._check_repo(path, toplevel)
+ self._check_bare()
+ self._get_git_dir()
+
+ @staticmethod
+ def __build_env(extra_env):
+ """Prepare environment for subprocess calls"""
+ env = None
+ if extra_env is not None:
+ env = os.environ.copy()
+ env.update(extra_env)
+ return env
+
+ def _git_getoutput(self, command, args=[], extra_env=None, cwd=None):
+ """
+ Run a git command and return the output
+
+ @param command: git command to run
+ @type command: C{str}
+ @param args: list of arguments
+ @type args: C{list}
+ @param extra_env: extra environment variables to pass
+ @type extra_env: C{dict}
+ @param cwd: directory to switch to when running the command, defaults to I{self.path}
+ @type cwd: C{str}
+ @return: stdout, return code
+ @rtype: C{tuple} of C{list} of C{bytestr} and C{int}
+
+ @deprecated: use L{gbp.git.repository.GitRepository._git_inout} instead.
+ """
+ output = []
+
+ if not cwd:
+ cwd = self.path
+
+ env = self.__build_env(extra_env)
+ cmd = ['git', command] + args
+ log.debug(cmd)
+ popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env, cwd=cwd)
+ while popen.poll() is None:
+ output += popen.stdout.readlines()
+ output += popen.stdout.readlines()
+ return output, popen.returncode
+
+ def _git_inout(self, command, args, input=None, extra_env=None, cwd=None,
+ capture_stderr=False, config_args=None):
+ """
+ Run a git command with input and return output
+
+ @param command: git command to run
+ @type command: C{str}
+ @param input: input to pipe to command
+ @type input: C{str}
+ @param args: list of arguments
+ @type args: C{list}
+ @param extra_env: extra environment variables to pass
+ @type extra_env: C{dict}
+ @param cwd: directory to switch to when running the command, defaults to I{self.path}
+ @type cwd: C{str}
+ @param capture_stderr: whether to capture stderr
+ @type capture_stderr: C{bool}
+ @return: stdout, stderr, return code
+ @rtype: C{tuple} of C{bytestr}, C{bytestr}, C{int}
+ """
+ if not cwd:
+ cwd = self.path
+ return self.__git_inout(command, args, input, extra_env, cwd, capture_stderr, config_args)
+
+ @classmethod
+ def __git_inout(cls, command, args, input, extra_env, cwd, capture_stderr, config_args=None):
+ """
+ As _git_inout but can be used without an instance
+ """
+ config_opts = []
+ config_args = config_args or []
+ for arg in config_args:
+ config_opts.extend(['-c', arg])
+
+ cmd = ['git'] + config_opts + [command] + args
+ env = cls.__build_env(extra_env)
+ stderr_arg = subprocess.PIPE if capture_stderr else None
+ stdin_arg = subprocess.PIPE if input is not None else None
+
+ log.debug(cmd)
+ popen = subprocess.Popen(cmd,
+ stdin=stdin_arg,
+ stdout=subprocess.PIPE,
+ stderr=stderr_arg,
+ env=env,
+ close_fds=True,
+ cwd=cwd)
+ (stdout, stderr) = popen.communicate(input)
+ return stdout, stderr, popen.returncode
+
+ def _git_command(self, command, args=[], extra_env=None):
+ """
+ Execute git command with arguments args and environment env
+ at path.
+
+ @param command: git command
+ @type command: C{str}
+ @param args: command line arguments
+ @type args: C{list}
+ @param extra_env: extra environment variables to set when running command
+ @type extra_env: C{dict}
+ """
+ try:
+ stdout, stderr, ret = self._git_inout(command=command,
+ args=args,
+ input=None,
+ extra_env=extra_env,
+ capture_stderr=True)
+ except Exception as excobj:
+ raise GitRepositoryError("Error running git %s: %s" % (command, excobj))
+ if ret:
+ detail = stderr or stdout
+ raise GitRepositoryError("Error running git %s: %s" % (command, detail.decode().strip()))
+
+ def _cmd_has_feature(self, command, feature):
+ """
+ Check if the git command has certain feature enabled.
+
+ @param command: git command
+ @type command: C{str}
+ @param feature: feature / command option to check
+ @type feature: C{str}
+ @return: True if feature is supported
+ @rtype: C{bool}
+ """
+ args = GitArgs(command, '-m')
+ help, stderr, ret = self._git_inout('help',
+ args.args,
+ extra_env={'LC_ALL': 'C'},
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Invalid git command '%s': %s"
+ % (command, stderr.decode().strip()))
+
+ # Parse git command man page
+ section_re = re.compile(r'^(?P<section>[A-Z].*)')
+ option_re = re.compile(r'--?(?P<name>[a-zA-Z\-]+).*')
+ optopt_re = re.compile(r'--\[(?P<prefix>[a-zA-Z\-]+)\]-?')
+ man_section = None
+ for line in help.decode().splitlines():
+ if man_section == "OPTIONS" and line.startswith(' -'):
+ opts = line.split(',')
+ for opt in opts:
+ opt = opt.strip()
+ match = optopt_re.match(opt)
+ if match:
+ opts.append(re.sub(optopt_re, '--', opt))
+ prefix = match.group('prefix').strip('-')
+ opt = re.sub(optopt_re, '--%s-' % prefix, opt)
+ match = option_re.match(opt)
+ if match and match.group('name') == feature:
+ return True
+ # Check man section
+ match = section_re.match(line)
+ if match:
+ man_section = match.group('section')
+ return False
+
+ @property
+ def path(self):
+ """The absolute path to the repository"""
+ return self._path
+
+ @property
+ def git_dir(self):
+ """The absolute path to git's metadata"""
+ return os.path.join(self.path, self._git_dir)
+
+ @property
+ def bare(self):
+ """Whether this is a bare repository"""
+ return self._bare
+
+ @property
+ def tags(self):
+ """List of all tags in the repository"""
+ return self.get_tags()
+
+ @property
+ def branch(self):
+ """The currently checked out branch"""
+ try:
+ return self.get_branch()
+ except GitRepositoryError:
+ return None
+
+ @property
+ def head(self):
+ """SHA1 of the current HEAD"""
+ return self.rev_parse('HEAD')
+
+#{ Branches and Merging
+ def rename_branch(self, branch, newbranch):
+ """
+ Rename branch
+
+ @param branch: name of the branch to be renamed
+ @param newbranch: new name of the branch
+ """
+ args = GitArgs("-m", branch, newbranch)
+ self._git_command("branch", args.args)
+
+ def create_branch(self, branch, rev=None, force=False):
+ """
+ Create a new branch
+
+ @param branch: the branch's name
+ @param rev: where to start the branch from
+ @param force: reset branch HEAD to start point, if it already exists
+
+ If rev is None the branch starts from the current HEAD.
+ """
+ args = GitArgs(branch)
+ args.add_true(force, '--force')
+ args.add_true(rev, rev)
+ self._git_command("branch", args.args)
+
+ def delete_branch(self, branch, remote=False):
+ """
+ Delete branch I{branch}
+
+ @param branch: name of the branch to delete
+ @type branch: C{str}
+ @param remote: delete a remote branch
+ @param remote: C{bool}
+ """
+ if not self.has_branch(branch):
+ return
+
+ args = GitArgs('-D')
+ args.add_true(remote, '-r')
+ args.add(branch)
+
+ if self.branch != branch:
+ self._git_command("branch", args.args)
+ else:
+ raise GitRepositoryError("Can't delete the branch you're on")
+
+ def get_branch(self):
+ """
+ On what branch is the current working copy
+
+ @return: current branch or C{None} in an empty repo
+ @rtype: C{str}
+ @raises GitRepositoryError: if HEAD is not a symbolic ref
+ (e.g. when in detached HEAD state)
+ """
+ out, _, ret = self._git_inout('symbolic-ref', ['HEAD'],
+ capture_stderr=True)
+ if ret:
+ # We don't append stderr since
+ # "fatal: ref HEAD is not a symbolic ref" confuses people
+ raise GitRepositoryError("Currently not on a branch")
+ ref = out.decode().split('\n')[0]
+
+ # Check if ref really exists
+ try:
+ self._git_command('show-ref', [ref])
+ branch = ref[11:] # strip /refs/heads
+ except GitRepositoryError:
+ branch = None # empty repo
+ return branch
+
+ def has_branch(self, branch, remote=False):
+ """
+ Check if the repository has branch named I{branch}.
+
+ @param branch: branch to look for
+ @param remote: only look for remote branches
+ @type remote: C{bool}
+ @return: C{True} if the repository has this branch, C{False} otherwise
+ @rtype: C{bool}
+ """
+ args = GitArgs('--verify')
+
+ branch_pattern = 'refs/remotes/%s' if remote else 'refs/heads/%s'
+ args.add(branch_pattern % branch)
+ try:
+ self._git_command('show-ref', args.args)
+ except GitRepositoryError:
+ return False
+ return True
+
+ def set_branch(self, branch):
+ """
+ Switch to branch I{branch}
+
+ @param branch: name of the branch to switch to
+ @type branch: C{str}
+ """
+ if self.branch == branch:
+ return
+
+ if self.bare:
+ self._git_command("symbolic-ref",
+ ['HEAD', 'refs/heads/%s' % branch])
+ else:
+ self._git_command("checkout", [branch])
+
+ def get_merge_branch(self, branch):
+ """
+ Get the branch we'd merge from
+
+ @return: repo and branch we would merge from
+ @rtype: C{str}
+ """
+ try:
+ remote = self.get_config("branch.%s.remote" % branch)
+ merge = self.get_config("branch.%s.merge" % branch)
+ except KeyError:
+ return None
+ remote += merge.replace("refs/heads", "", 1)
+ return remote
+
+ def get_merge_base(self, commit1, commit2):
+ """
+ Get the common ancestor between two commits
+
+ @param commit1: commit SHA1 or name of a branch or tag
+ @type commit1: C{str}
+ @param commit2: commit SHA1 or name of a branch or tag
+ @type commit2: C{str}
+ @return: SHA1 of the common ancestor
+ @rtype: C{str}
+ """
+ args = GitArgs()
+ args.add(commit1)
+ args.add(commit2)
+ sha1, stderr, ret = self._git_inout('merge-base',
+ args.args,
+ extra_env={'LC_ALL': 'C'},
+ capture_stderr=True)
+ if not ret:
+ return self.strip_sha1(sha1.decode())
+ else:
+ raise GitRepositoryError("Failed to get common ancestor: %s" % stderr.decode().strip())
+
+ def merge(self, commit, verbose=False, edit=False):
+ """
+ Merge changes from the named commit into the current branch
+
+ @param commit: the commit to merge from (usually a branch name or tag)
+ @type commit: C{str}
+ @param verbose: whether to print a summary after the merge
+ @type verbose: C{bool}
+ @param edit: whether to invoke an editor to edit the merge message
+ @type edit: C{bool}
+ """
+ args = GitArgs()
+ args.add_cond(verbose, '--summary', '--no-summary')
+ if (self._cmd_has_feature('merge', 'edit')):
+ args.add_cond(edit, '--edit', '--no-edit')
+ else:
+ log.debug("Your git suite doesn't support --edit/--no-edit "
+ "option for git-merge ")
+ args.add(commit)
+ self._git_command("merge", args.args)
+
+ def abort_merge(self):
+ """
+ Abort a merge
+ """
+ self._git_command("merge", ["--abort"])
+
+ def is_in_merge(self):
+ return os.path.exists(os.path.join(self.git_dir, 'MERGE_HEAD'))
+
+ def is_fast_forward(self, from_branch, to_branch):
+ """
+ Check if an update I{from from_branch} to I{to_branch} would be a fast
+ forward or if the branch is up to date already.
+
+ @return: can_fast_forward, up_to_date
+ @rtype: C{tuple}
+ """
+ has_local = False # local repo has new commits
+ has_remote = False # remote repo has new commits
+ out = self._git_getoutput('rev-list',
+ ["--left-right",
+ "%s...%s" % (from_branch, to_branch),
+ "--"])[0]
+
+ if not out: # both branches have the same commits
+ return True, True
+
+ for line in (l.decode() for l in out):
+ if line.startswith("<"):
+ has_local = True
+ elif line.startswith(">"):
+ has_remote = True
+
+ if has_local and has_remote:
+ return False, False
+ elif has_local:
+ return False, True
+ elif has_remote:
+ return True, False
+
+ def _get_branches(self, remote=False):
+ """
+ Get a list of branches
+
+ @param remote: whether to list local or remote branches
+ @type remote: C{bool}
+ @return: local or remote branches
+ @rtype: C{list}
+ """
+ args = ['--format=%(refname:short)']
+ args += ['refs/remotes/'] if remote else ['refs/heads/']
+ out = self._git_getoutput('for-each-ref', args)[0]
+ return [ref.decode().strip() for ref in out]
+
+ def get_local_branches(self):
+ """
+ Get a list of local branches
+
+ @return: local branches
+ @rtype: C{list}
+ """
+ return self._get_branches(remote=False)
+
+ def get_remote_branches(self):
+ """
+ Get a list of remote branches
+
+ @return: remote branches
+ @rtype: C{list}
+ """
+ return self._get_branches(remote=True)
+
+ def update_ref(self, ref, new, old=None, msg=None):
+ """
+ Update ref I{ref} to commit I{new} if I{ref} currently points to
+ I{old}
+
+ @param ref: the ref to update
+ @type ref: C{str}
+ @param new: the new value for ref
+ @type new: C{str}
+ @param old: the old value of ref
+ @type old: C{str}
+ @param msg: the reason for the update
+ @type msg: C{str}
+ """
+ args = GitArgs()
+ args.add_true(msg, '-m', msg)
+ args.add(ref, new)
+ args.add_true(old, old)
+ self._git_command("update-ref", args.args)
+
+ def branch_contains(self, branch, commit, remote=False):
+ """
+ Check if branch I{branch} contains commit I{commit}
+
+ @param branch: the branch the commit should be on
+ @type branch: C{str}
+ @param commit: the C{str} commit to check
+ @type commit: C{str}
+ @param remote: whether to check remote instead of local branches
+ @type remote: C{bool}
+ """
+ args = GitArgs()
+ args.add_true(remote, '-r')
+ args.add('--contains')
+ args.add(commit)
+
+ out, ret = self._git_getoutput('branch', args.args)
+ for line in [l.decode() for l in out]:
+ # remove prefix '*' for current branch before comparing
+ line = line.replace('*', '')
+ if line.strip() == branch:
+ return True
+ return False
+
+ def set_upstream_branch(self, local_branch, upstream):
+ """
+ Set upstream branches for local branch
+
+ @param local_branch: name of the local branch
+ @type local_branch: C{str}
+ @param upstream: Remote branch in the form remote/branch, e.g. origin/master
+ @type upstream: C{str}
+ """
+
+ # check if both branches exist
+ for branch, remote in [(local_branch, False), (upstream, True)]:
+ if not self.has_branch(branch, remote=remote):
+ raise GitRepositoryError("Branch %s doesn't exist!" % branch)
+
+ if self._cmd_has_feature('branch', 'set-upstream-to'):
+ args = ['--set-upstream-to=%s' % upstream, local_branch]
+ else:
+ args = ["--set-upstream", local_branch, upstream]
+
+ dummy, err, ret = self._git_inout('branch',
+ args,
+ extra_env={'LC_ALL': 'C'},
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError(
+ "Failed to set upstream branch '%s' for '%s': %s" %
+ (upstream, local_branch, err.strip()))
+
+ def get_upstream_branch(self, local_branch):
+ """
+ Get upstream branch for the local branch
+
+ @param local_branch: name fo the local branch
+ @type local_branch: C{str}
+ @return: upstream (remote/branch) or '' if no upstream found
+ @rtype: C{str}
+
+ """
+ args = GitArgs('--format=%(upstream:short)')
+ if self.has_branch(local_branch, remote=False):
+ args.add('refs/heads/%s' % local_branch)
+ else:
+ raise GitRepositoryError("Branch %s doesn't exist!" % local_branch)
+
+ out = self._git_getoutput('for-each-ref', args.args)[0]
+
+ return out[0].decode().strip()
+
+#{ Tags
+
+ def create_tag(self, name, msg=None, commit=None, sign=False, keyid=None):
+ """
+ Create a new tag.
+
+ @param name: the tag's name
+ @type name: C{str}
+ @param msg: The tag message.
+ @type msg: C{str}
+ @param commit: the commit or object to create the tag at, default
+ is I{HEAD}
+ @type commit: C{str}
+ @param sign: Whether to sing the tag
+ @type sign: C{bool}
+ @param keyid: the GPG keyid used to sign the tag
+ @type keyid: C{str}
+ """
+ args = []
+ args += ['-m', msg] if msg else []
+ if sign:
+ args += ['-s']
+ args += ['-u', keyid] if keyid else []
+ args += [name]
+ args += [commit] if commit else []
+ self._git_command("tag", args)
+
+ def delete_tag(self, tag):
+ """
+ Delete a tag named I{tag}
+
+ @param tag: the tag to delete
+ @type tag: C{str}
+ """
+ if self.has_tag(tag):
+ self._git_command("tag", ["-d", tag])
+
+ def move_tag(self, old, new):
+ self._git_command("tag", [new, old])
+ self.delete_tag(old)
+
+ def has_tag(self, tag):
+ """
+ Check if the repository has a tag named I{tag}.
+
+ @param tag: tag to look for
+ @type tag: C{str}
+ @return: C{True} if the repository has that tag, C{False} otherwise
+ @rtype: C{bool}
+ """
+ out, ret = self._git_getoutput('tag', ['-l', tag])
+ return [False, True][len(out)]
+
+ def describe(self, commitish, pattern=None, longfmt=False, always=False,
+ abbrev=None, tags=False, exact_match=False):
+ """
+ Describe commit, relative to the latest tag reachable from it.
+
+ @param commitish: the commit-ish to describe
+ @type commitish: C{str}
+ @param pattern: only look for tags matching I{pattern}
+ @type pattern: C{str}
+ @param longfmt: describe the commit in the long format
+ @type longfmt: C{bool}
+ @param always: return commit sha1 as fallback if no tag is found
+ @type always: C{bool}
+ @param abbrev: abbreviate sha1 to given length instead of the default
+ @type abbrev: None or C{long}
+ @param tags: enable matching a lightweight (non-annotated) tag
+ @type tags: C{bool}
+ @param exact_match: only output exact matches (a tag directly
+ references the supplied commit)
+ @type exact_match: C{bool}
+ @return: tag name plus/or the abbreviated sha1
+ @rtype: C{str}
+ """
+ args = GitArgs()
+ args.add_true(pattern, ['--match', pattern])
+ args.add_true(longfmt, '--long')
+ # 'long' and 'abbrev=0' are incompatible, behave similar to
+ # 'always' and 'abbrev=0'
+ if longfmt and abbrev == 0:
+ args.add('--abbrev=40')
+ elif abbrev is not None:
+ args.add('--abbrev=%s' % abbrev)
+ args.add_true(always, '--always')
+ args.add_true(tags, '--tags')
+ args.add_true(exact_match, '--exact-match')
+ args.add(commitish)
+
+ tag, err, ret = self._git_inout('describe', args.args,
+ extra_env={'LC_ALL': 'C'},
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Can't describe %s. Git error: %s" %
+ (commitish, err.decode().strip()))
+ return tag.decode().strip()
+
+ def find_tag(self, commit, pattern=None):
+ """
+ Find the closest tag to a given commit
+
+ @param commit: the commit to describe
+ @type commit: C{str}
+ @param pattern: only look for tags matching I{pattern}
+ @type pattern: C{str}
+ @return: the found tag
+ @rtype: C{str}
+ """
+ return self.describe(commit, pattern, abbrev=0)
+
+ def find_branch_tag(self, commit, branch, pattern=None):
+ """
+ Find the closest tag on a certain branch to a given commit
+
+ @param commit: the commit to describe
+ @type commit: C{str}
+ @type branch: C{str}
+ @param pattern: only look for tags matching I{pattern}
+ @type pattern: C{str}
+ @return: the found tag
+ @rtype: C{str}
+ """
+ base_commit = self.get_merge_base(commit, branch)
+ return self.describe(base_commit, pattern, abbrev=0)
+
+ def get_tags(self, pattern=None):
+ """
+ List tags
+
+ @param pattern: only list tags matching I{pattern}
+ @type pattern: C{str}
+ @return: tags
+ @rtype: C{list} of C{str}
+ """
+ args = ['-l', pattern] if pattern else []
+ return [line.decode().strip() for line in self._git_getoutput('tag', args)[0]]
+
+ def verify_tag(self, tag):
+ """
+ Verify a signed tag
+
+ @param tag: the tag's name
+ @type tag: C{str}
+ @return: Whether the signature on the tag could be verified
+ @rtype: C{bool}
+ """
+ args = GitArgs('-v', tag)
+
+ try:
+ self._git_command('tag', args.args)
+ except GitRepositoryError:
+ return False
+ return True
+
+#}
+ def force_head(self, commit, hard=False):
+ """
+ Force HEAD to a specific commit
+
+ @param commit: commit to move HEAD to
+ @param hard: also update the working copy
+ @type hard: C{bool}
+ """
+ if not GitCommit.is_sha1(commit):
+ commit = self.rev_parse(commit)
+
+ if self.bare:
+ ref = "refs/heads/%s" % self.get_branch()
+ self._git_command("update-ref", [ref, commit])
+ else:
+ args = GitArgs('--quiet')
+ args.add_true(hard, '--hard')
+ args.add(commit, '--')
+ self._git_command("reset", args.args)
+
+ def _status(self, porcelain, ignore_untracked, paths):
+ args = GitArgs()
+ args.add_true(ignore_untracked, '-uno')
+ args.add_true(porcelain, '--porcelain')
+
+ if paths is None:
+ paths = []
+ elif isinstance(paths, str):
+ paths = [paths]
+
+ out, ret = self._git_getoutput('status',
+ args.args + paths,
+ extra_env={'LC_ALL': 'C'})
+ if ret:
+ raise GitRepositoryError("Can't get repository status")
+ return out
+
+ def is_clean(self, ignore_untracked=False, paths=None):
+ """
+ Does the repository contain any uncommitted modifications?
+
+ @param ignore_untracked: whether to ignore untracked files when
+ checking the repository status
+ @type ignore_untracked: C{bool}
+ @param paths: only check changes on paths
+ @type paths: C{list} of C{stings}
+ @return: C{True} if the repository is clean, C{False} otherwise
+ and Git's status message
+ @rtype: C{tuple}
+ """
+ if self.bare:
+ return (True, '')
+
+ out = self._status(porcelain=True,
+ ignore_untracked=ignore_untracked,
+ paths=paths)
+ if out:
+ # Get a more helpful error message.
+ out = self._status(porcelain=False,
+ ignore_untracked=ignore_untracked,
+ paths=paths)
+ return (False, "".join([e.decode() for e in out]))
+ else:
+ return (True, '')
+
+ def clean(self, directories=False, force=False, dry_run=False):
+ """
+ Remove untracked files from the working tree.
+
+ @param directories: remove untracked directories, too
+ @type directories: C{bool}
+ @param force: satisfy git configuration variable clean.requireForce
+ @type force: C{bool}
+ @param dry_run: don’t actually remove anything
+ @type dry_run: C{bool}
+ """
+ options = GitArgs()
+ options.add_true(directories, '-d')
+ options.add_true(force, '-f')
+ options.add_true(dry_run, '-n')
+
+ _out, err, ret = self._git_inout('clean', options.args,
+ extra_env={'LC_ALL': 'C'})
+ if ret:
+ raise GitRepositoryError("Can't execute repository clean: %s" % err)
+
+ def status(self, pathlist=None):
+ """
+ Check status of repository.
+
+ @param pathlist: List of paths to check status for
+ @type pathlist: C{list}
+ @return C{dict} of C{lists} of paths, where key is a git status flag.
+ @rtype C{dict}
+ """
+ options = GitArgs('--porcelain', '-z')
+ if pathlist:
+ for path in pathlist:
+ options.add(path)
+
+ out, err, ret = self._git_inout('status', options.args,
+ extra_env={'LC_ALL': 'C'})
+ if ret:
+ raise GitRepositoryError("Can't get repository status: %s" % err)
+
+ elements = out.split(b'\x00')
+ result = defaultdict(list)
+
+ while elements[0] != b'':
+ element = elements.pop(0)
+ status = element[:2].decode()
+ filepath = element[3:]
+ # Expect to have two filenames for renames and copies
+ if status[0] in ['R', 'C']:
+ filepath = elements.pop(0) + b'\x00' + filepath
+ result[status].append(filepath)
+
+ return result
+
+ def is_empty(self):
+ """
+ Is the repository empty?
+
+ @return: True if the repositorydoesn't have any commits,
+ False otherwise
+ @rtype: C{bool}
+ """
+ # an empty repo has no branches:
+ return len(self.get_local_branches()) == 0
+
+ def rev_parse(self, name, short=0):
+ """
+ Find the SHA1 of a given name
+
+ @param name: the name to look for
+ @type name: C{str}
+ @param short: try to abbreviate SHA1 to given length
+ @type short: C{int}
+ @return: the name's sha1
+ @rtype: C{str}
+ """
+ args = GitArgs("--quiet", "--verify")
+ args.add_cond(short, '--short=%d' % short)
+ args.add(name)
+ sha, ret = self._git_getoutput('rev-parse', args.args)
+ if ret:
+ raise GitRepositoryError("revision '%s' not found" % name)
+ return self.strip_sha1(sha[0].decode(), short)
+
+ @staticmethod
+ def strip_sha1(sha1, length=0):
+ """
+ Strip a given sha1 and check if the resulting
+ hash has the expected length.
+
+ >>> GitRepository.strip_sha1(' 58ef37dbeb12c44b206b92f746385a6f61253c0a\\n')
+ '58ef37dbeb12c44b206b92f746385a6f61253c0a'
+ >>> GitRepository.strip_sha1('58ef37d', 10)
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: '58ef37d' is not a valid sha1 of length 10
+ >>> GitRepository.strip_sha1('58ef37d', 7)
+ '58ef37d'
+ >>> GitRepository.strip_sha1('123456789', 7)
+ '123456789'
+ >>> GitRepository.strip_sha1('foobar')
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: 'foobar' is not a valid sha1
+ """
+ maxlen = 40
+ s = sha1.strip()
+
+ sl = length or maxlen
+
+ if len(s) < sl or len(s) > maxlen:
+ raise GitRepositoryError("'%s' is not a valid sha1%s" %
+ (s, " of length %d" % sl if length else ""))
+ return s
+
+#{ Trees
+ def checkout(self, treeish):
+ """
+ Checkout treeish
+
+ @param treeish: the treeish to check out
+ @type treeish: C{str}
+ """
+ self._git_command("checkout", ["--quiet", treeish])
+
+ def has_treeish(self, treeish):
+ """
+ Check if the repository has the treeish object I{treeish}.
+
+ @param treeish: treeish object to look for
+ @type treeish: C{str}
+ @return: C{True} if the repository has that tree, C{False} otherwise
+ @rtype: C{bool}
+ """
+ _out, _err, ret = self._git_inout('ls-tree', [treeish],
+ capture_stderr=True)
+ return [True, False][ret != 0]
+
+ def write_tree(self, index_file=None):
+ """
+ Create a tree object from the current index
+
+ @param index_file: alternate index file to read changes from
+ @type index_file: C{str}
+ @return: the new tree object's sha1
+ @rtype: C{str}
+ """
+ if index_file:
+ extra_env = {'GIT_INDEX_FILE': index_file}
+ else:
+ extra_env = None
+
+ tree, stderr, ret = self._git_inout('write-tree', [],
+ extra_env=extra_env,
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Can't write out current index: %s" % stderr.decode().strip())
+ return tree.decode().strip()
+
+ def make_tree(self, contents):
+ """
+ Create a tree based on contents.
+
+ @param contents: same format as I{GitRepository.list_tree} output.
+ @type contents: C{list} of C{str}
+ """
+ objs = b''
+ args = GitArgs('-z')
+
+ for mode, type_, sha1, name in contents:
+ name = to_bin(name)
+ objs += format_b(b'%s %s %s\t%s\0', mode.encode(), type_.encode(), sha1.encode(), name)
+
+ sha1, err, ret = self._git_inout('mktree',
+ args.args,
+ objs,
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Failed to mktree: '%s'" % err)
+ return self.strip_sha1(sha1.decode())
+
+ def get_obj_type(self, obj):
+ """
+ Get type of a git repository object
+
+ @param obj: repository object
+ @type obj: C{str}
+ @return: type of the repository object
+ @rtype: C{str}
+ """
+ out, ret = self._git_getoutput('cat-file', args=['-t', obj])
+ if ret:
+ raise GitRepositoryError("Not a Git repository object: '%s'" % obj)
+ return out[0].decode().strip()
+
+ def list_tree(self, treeish, recurse=False, paths=None):
+ """
+ Get a trees content. It returns a list of objects that match the
+ 'ls-tree' output: [mode, type, sha1, path].
+
+ @param treeish: the treeish object to list
+ @type treeish: C{str}
+ @param recurse: whether to list the tree recursively
+ @type recurse: C{bool}
+ @return: the tree
+ @rtype: C{list} of objects. See above.
+ """
+ args = GitArgs('-z')
+ args.add_true(recurse, '-r')
+ args.add(treeish)
+ args.add("--")
+ args.add_cond(paths, paths)
+
+ out, err, ret = self._git_inout('ls-tree', args.args, capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Failed to ls-tree '%s': '%s'" % (treeish, err.decode().strip()))
+
+ tree = []
+ for line in out.split(b'\0'):
+ if line:
+ parts = line.split(None, 3)
+ # decode everything but the file name
+ for i in range(len(parts) - 1):
+ parts[i] = parts[i].decode()
+ tree.append(parts)
+ return tree
+
+#}
+
+ def get_config(self, name):
+ """
+ Gets the config value associated with I{name}
+
+ @param name: config value to get
+ @return: fetched config value
+ @rtype: C{str}
+ """
+ value, ret = self._git_getoutput('config', [name])
+ if ret:
+ raise KeyError("'%s' not found in git config")
+ return value[0].decode()[:-1] # first line with \n ending removed
+
+ def set_config(self, name, value):
+ """
+ Set a git config value in this repository
+ """
+ args = GitArgs(name, value)
+ self._git_command("config", args.args)
+
+ def set_user_name(self, name):
+ """
+ Sets the full name to use for git commits.
+
+ @param name: full name to use
+ """
+ self.set_config('user.name', name)
+
+ def set_user_email(self, email):
+ """
+ Sets the email address to use for git commits.
+
+ @param email: email address to use
+ """
+ self.set_config('user.email', email)
+
+ def get_author_info(self):
+ """
+ Determine a sane values for author name and author email from git's
+ config and environment variables.
+
+ @return: name and email
+ @rtype: L{GitModifier}
+ """
+ try:
+ name = self.get_config("user.name")
+ except KeyError:
+ name = os.getenv("USER")
+ try:
+ email = self.get_config("user.email")
+ except KeyError:
+ email = os.getenv("EMAIL")
+ email = os.getenv("GIT_AUTHOR_EMAIL", email)
+ name = os.getenv("GIT_AUTHOR_NAME", name)
+ return GitModifier(name, email)
+
+#{ Remote Repositories
+
+ def get_remotes(self):
+ """
+ Get a list of remote repositories
+
+ @return: remote repositories
+ @rtype: C{dict} of C{GitRemote}
+ """
+ out, err, ret = self._git_inout('remote', [],
+ extra_env={'LC_ALL': 'C'},
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError('Failed to get list of remotes: %s' % err.decode().strip())
+
+ # Get information about all remotes
+ remotes = {}
+ for remote in out.decode().splitlines():
+ out, err, _ret = self._git_inout('remote', ['show', '-n', remote],
+ extra_env={'LC_ALL': 'C'},
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError('Failed to get information for remote '
+ '%s: %s' % (remote, err.decode().strip()))
+ fetch_url = None
+ push_urls = []
+ for line in out.decode().splitlines():
+ match = re.match('\s*Fetch\s+URL:\s*(\S.*)', line)
+ if match:
+ fetch_url = match.group(1)
+ match = re.match('\s*Push\s+URL:\s*(\S.*)', line)
+ if match:
+ push_urls.append(match.group(1))
+ remotes[remote] = GitRemote(remote, fetch_url, push_urls)
+
+ return remotes
+
+ def get_remote_repos(self):
+ """
+ Get all remote repositories
+
+ @deprecated: Use get_remotes() instead
+
+ @return: remote repositories
+ @rtype: C{list} of C{str}
+ """
+ out = self._git_getoutput('remote')[0]
+ return [remote.decode().strip() for remote in out]
+
+ def has_remote_repo(self, name):
+ """
+ Do we know about a remote named I{name}?
+
+ @param name: name of the remote repository
+ @type name: C{str}
+ @return: C{True} if the remote repositore is known, C{False} otherwise
+ @rtype: C{bool}
+ """
+ if name in self.get_remotes():
+ return True
+ else:
+ return False
+
+ def add_remote_repo(self, name, url, tags=True, fetch=False):
+ """
+ Add a tracked remote repository
+
+ @param name: the name to use for the remote
+ @type name: C{str}
+ @param url: the url to add
+ @type url: C{str}
+ @param tags: whether to fetch tags
+ @type tags: C{bool}
+ @param fetch: whether to fetch immediately from the remote side
+ @type fetch: C{bool}
+ """
+ args = GitArgs('add')
+ args.add_false(tags, '--no-tags')
+ args.add_true(fetch, '--fetch')
+ args.add(name, url)
+ self._git_command("remote", args.args)
+
+ def remove_remote_repo(self, name):
+ args = GitArgs('rm', name)
+ self._git_command("remote", args.args)
+
+ def fetch(self, repo=None, tags=False, depth=0, refspec=None,
+ all_remotes=False):
+ """
+ Download objects and refs from another repository.
+
+ @param repo: repository to fetch from
+ @type repo: C{str}
+ @param tags: whether to fetch all tag objects
+ @type tags: C{bool}
+ @param depth: deepen the history of (shallow) repository to depth I{depth}
+ @type depth: C{int}
+ @param refspec: refspec to use instead of the default from git config
+ @type refspec: C{str}
+ @param all_remotes: fetch all remotes
+ @type all_remotes: C{bool}
+ """
+ args = GitArgs('--quiet')
+ args.add_true(tags, '--tags')
+ args.add_cond(depth, '--depth=%s' % depth)
+ if all_remotes:
+ args.add_true(all_remotes, '--all')
+ else:
+ args.add_cond(repo, repo)
+ args.add_cond(refspec, refspec)
+
+ self._git_command("fetch", args.args)
+
+ def pull(self, repo=None, ff_only=False, all_remotes=False):
+ """
+ Fetch and merge from another repository
+
+ @param repo: repository to fetch from
+ @type repo: C{str}
+ @param ff_only: only merge if this results in a fast forward merge
+ @type ff_only: C{bool}
+ @param all_remotes: fetch all remotes
+ @type all_remotes: C{bool}
+ """
+ args = GitArgs()
+ args.add_true(ff_only, '--ff-only')
+ if all_remotes:
+ args.add_true(all_remotes, '--all')
+ else:
+ args.add_true(repo, repo)
+ self._git_command("pull", args.args)
+
+ def push(self, repo=None, src=None, dst=None, ff_only=True, force=False,
+ tags=False, dry_run=False):
+ """
+ Push changes to the remote repo
+
+ @param repo: repository to push to
+ @type repo: C{str}
+ @param src: the source ref to push
+ @type src: C{str}
+ @param dst: the name of the destination ref to push to
+ @type dst: C{str}
+ @param ff_only: only push if it's a fast forward update
+ @type ff_only: C{bool}
+ @param force: force push, can cause the remote repository to lose
+ commits; use it with care
+ @type force: C{bool}
+ @param tags: push all refs under refs/tags, in addition to other refs
+ @type tags: C{bool}
+ @param dry_run: dry run
+ @type dry_run: C{bool}
+ """
+ args = GitArgs()
+ args.add_cond(repo, repo)
+ args.add_true(force, "-f")
+ args.add_true(tags, "--tags")
+ args.add_true(dry_run, "--dry-run")
+
+ # Allow for src == '' to delete dst on the remote
+ if src is not None:
+ refspec = src
+ if dst:
+ refspec += ':%s' % dst
+ if not ff_only:
+ refspec = '+%s' % refspec
+ args.add(refspec)
+
+ self._git_command("push", args.args)
+
+ def push_tag(self, repo, tag, dry_run=False):
+ """
+ Push a tag to the remote repo
+
+ @param repo: repository to push to
+ @type repo: C{str}
+ @param tag: the name of the tag
+ @type tag: C{str}
+ @param dry_run: dry run
+ @type dry_run: C{bool}
+ """
+ args = GitArgs(repo, 'tag', tag)
+ args.add_true(dry_run, "--dry-run")
+ self._git_command("push", args.args)
+
+#{ Files
+
+ def add_files(self, paths, force=False, index_file=None, work_tree=None):
+ """
+ Add files to a the repository
+
+ @param paths: list of files to add
+ @type paths: list or C{str}
+ @param force: add files even if they would be ignored by .gitignore
+ @type force: C{bool}
+ @param index_file: alternative index file to use
+ @param work_tree: alternative working tree to use
+ """
+ extra_env = {}
+
+ if isinstance(paths, str):
+ paths = [paths]
+
+ args = ['-f'] if force else []
+
+ if index_file:
+ extra_env['GIT_INDEX_FILE'] = index_file
+
+ if work_tree:
+ extra_env['GIT_WORK_TREE'] = work_tree
+
+ self._git_command("add", args + paths, extra_env)
+
+ def remove_files(self, paths, verbose=False):
+ """
+ Remove files from the repository
+
+ @param paths: list of files to remove
+ @param paths: C{list} or C{str}
+ @param verbose: be verbose
+ @type verbose: C{bool}
+ """
+ if isinstance(paths, str):
+ paths = [paths]
+
+ args = [] if verbose else ['--quiet']
+ self._git_command("rm", args + paths)
+
+ def list_files(self, types=['cached']):
+ """
+ List files in index and working tree
+
+ @param types: list of types to show
+ @type types: C{list}
+ @return: list of files as byte string
+ @rtype: C{list} of C{str}
+ """
+ all_types = ['cached', 'deleted', 'others', 'ignored', 'stage'
+ 'unmerged', 'killed', 'modified']
+ args = ['-z']
+
+ for t in types:
+ if t in all_types:
+ args += ['--%s' % t]
+ else:
+ raise GitRepositoryError("Unknown type '%s'" % t)
+ out, ret = self._git_getoutput('ls-files', args)
+ if ret:
+ raise GitRepositoryError("Error listing files: '%d'" % ret)
+ if out:
+ return [file for file in out[0].split(b'\0') if file]
+ else:
+ return []
+
+ def write_file(self, filename, filters=True):
+ """
+ Hash a single file and write it into the object database
+
+ @param filename: the filename to the content of the file to hash
+ @type filename: C{bytestr}
+ @param filters: whether to run filters
+ @type filters: C{bool}
+ @return: the hash of the file
+ @rtype: C{str}
+ """
+ args = GitArgs('-w', '-t', 'blob')
+ args.add_false(filters, '--no-filters')
+ args.add(filename)
+
+ sha1, stderr, ret = self._git_inout('hash-object',
+ args.args,
+ capture_stderr=True)
+ if not ret:
+ return self.strip_sha1(sha1.decode())
+ else:
+ raise GbpError("Failed to hash %s: %s" % (filename, stderr.decode().strip()))
+
+ def rename_file(self, old, new):
+ """
+ Rename file, directory, or symlink
+ """
+ args = GitArgs(old, new)
+ _, stderr, ret = self._git_inout('mv',
+ args.args,
+ capture_stderr=True)
+ if ret:
+ raise GbpError("Failed to move '%s' to '%s': %s" % (old, new, stderr.decode().rstrip()))
+#}
+
+#{ Comitting
+
+ def _commit(self, msg, args=[], author_info=None):
+ extra_env = author_info.get_author_env() if author_info else None
+ self._git_command("commit", ['-q', '-m', msg] + args, extra_env=extra_env)
+
+ def commit_staged(self, msg, author_info=None, edit=False):
+ """
+ Commit currently staged files to the repository
+
+ @param msg: commit message
+ @type msg: C{str}
+ @param author_info: authorship information
+ @type author_info: L{GitModifier}
+ @param edit: whether to spawn an editor to edit the commit info
+ @type edit: C{bool}
+ """
+ args = GitArgs()
+ args.add_true(edit, '--edit')
+ self._commit(msg=msg, args=args.args, author_info=author_info)
+
+ def commit_all(self, msg, author_info=None, edit=False):
+ """
+ Commit all changes to the repository
+ @param msg: commit message
+ @type msg: C{str}
+ @param author_info: authorship information
+ @type author_info: L{GitModifier}
+ """
+ args = GitArgs('-a')
+ args.add_true(edit, '--edit')
+ self._commit(msg=msg, args=args.args, author_info=author_info)
+
+ def commit_files(self, files, msg, author_info=None):
+ """
+ Commit the given files to the repository
+
+ @param files: file or files to commit
+ @type files: C{str} or C{list}
+ @param msg: commit message
+ @type msg: C{str}
+ @param author_info: authorship information
+ @type author_info: L{GitModifier}
+ """
+ if isinstance(files, str):
+ files = [files]
+ self._commit(msg=msg, args=files, author_info=author_info)
+
+ def commit_dir(self, unpack_dir, msg, branch, other_parents=None,
+ author={}, committer={}, create_missing_branch=False):
+ """
+ Replace the current tip of branch I{branch} with the contents from I{unpack_dir}
+
+ @param unpack_dir: content to add
+ @type unpack_dir: C{str}
+ @param msg: commit message to use
+ @type msg: C{str}
+ @param branch: branch to add the contents of unpack_dir to
+ @type branch: C{str}
+ @param other_parents: additional parents of this commit
+ @type other_parents: C{list} of C{str}
+ @param author: author information to use for commit
+ @type author: C{dict} with keys I{name}, I{email}, I{date}
+ @param committer: committer information to use for commit
+ @type committer: C{dict} with keys I{name}, I{email}, I{date}
+ or L{GitModifier}
+ @param create_missing_branch: create I{branch} as detached branch if it
+ doesn't already exist.
+ @type create_missing_branch: C{bool}
+ """
+
+ git_index_file = os.path.join(self.path, self._git_dir, 'gbp_index')
+ try:
+ os.unlink(git_index_file)
+ except OSError:
+ pass
+ self.add_files('.', force=True, index_file=git_index_file,
+ work_tree=unpack_dir)
+ tree = self.write_tree(git_index_file)
+
+ if branch:
+ try:
+ cur = self.rev_parse(branch)
+ except GitRepositoryError:
+ if create_missing_branch:
+ log.debug("Will create missing branch '%s'..." % branch)
+ cur = None
+ else:
+ raise
+ else: # empty repo
+ cur = None
+ branch = 'master'
+
+ # Build list of parents:
+ parents = []
+ if cur:
+ parents.append(cur)
+ if other_parents:
+ for parent in other_parents:
+ sha = self.rev_parse(parent)
+ if sha not in parents:
+ parents.append(sha)
+
+ commit = self.commit_tree(tree=tree, msg=msg, parents=parents,
+ author=author, committer=committer)
+ if not commit:
+ raise GitRepositoryError("Failed to commit tree")
+ self.update_ref("refs/heads/%s" % branch, commit, cur,
+ msg="gbp: %s" % msg.split('\n')[0])
+ return commit
+
+ def commit_tree(self, tree, msg, parents, author={}, committer={}):
+ """
+ Commit a tree with commit msg I{msg} and parents I{parents}
+
+ @param tree: tree to commit
+ @param msg: commit message
+ @param parents: parents of this commit
+ @param author: authorship information
+ @type author: C{dict} with keys 'name' and 'email' or L{GitModifier}
+ @param committer: committer information
+ @type committer: C{dict} with keys 'name' and 'email'
+ """
+ extra_env = {}
+ for key, val in author.items():
+ if val:
+ extra_env['GIT_AUTHOR_%s' % key.upper()] = val
+ for key, val in committer.items():
+ if val:
+ extra_env['GIT_COMMITTER_%s' % key.upper()] = val
+
+ args = [tree]
+ for parent in parents:
+ args += ['-p', parent]
+ sha1, stderr, ret = self._git_inout('commit-tree',
+ args,
+ msg.encode(),
+ extra_env,
+ capture_stderr=True)
+ if not ret:
+ return self.strip_sha1(sha1.decode())
+ else:
+ raise GbpError("Failed to commit tree: %s" % stderr.decode().strip())
+
+#{ Commit Information
+
+ def get_commits(self, since=None, until=None, paths=None, num=0,
+ first_parent=False, options=None):
+ """
+ Get commits from since to until touching paths
+
+ @param since: commit to start from
+ @type since: C{str}
+ @param until: last commit to get
+ @type until: C{str}
+ @param paths: only list commits touching paths
+ @type paths: C{list} of C{str}
+ @param num: maximum number of commits to fetch
+ @type num: C{int}
+ @param options: list of additional options passed to git log
+ @type options: C{list} of C{str}ings
+ @param first_parent: only follow first parent when seeing a
+ merge commit
+ @type first_parent: C{bool}
+ """
+ args = GitArgs('--pretty=format:%H')
+ args.add_true(num, '-%d' % num)
+ args.add_true(first_parent, '--first-parent')
+ if since:
+ args.add("%s..%s" % (since, until or 'HEAD'))
+ elif until:
+ args.add(until)
+ args.add_cond(options, options)
+ args.add("--")
+ if isinstance(paths, str):
+ paths = [paths]
+ args.add_cond(paths, paths)
+
+ commits, ret = self._git_getoutput('log', args.args)
+ if ret:
+ where = " on %s" % paths if paths else ""
+ raise GitRepositoryError("Error getting commits %s..%s%s" %
+ (since, until, where))
+ return [commit.decode().strip() for commit in commits]
+
+ def show(self, id):
+ """
+ Show a git object
+
+ @rtype: C{bytestr}
+ """
+ obj, stderr, ret = self._git_inout('show', ["--pretty=medium", id],
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("can't get %s: %s" % (id, stderr.decode().rstrip()))
+ return obj
+
+ def grep_log(self, regex, since=None, merges=True):
+ """
+ Get commmits matching I{regex}
+
+ @param regex: regular expression
+ @type regex: C{str}
+ @param since: where to start grepping (e.g. a branch)
+ @type since: C{str}
+ """
+ args = GitArgs('--pretty=format:%H')
+ args.add_false(merges, '--no-merges')
+ args.add('--grep=%s' % regex)
+ args.add_true(since, since)
+ args.add('--')
+
+ stdout, stderr, ret = self._git_inout('log', args.args,
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Error grepping log for %s: %s" %
+ (regex, stderr.decode().strip()))
+ if stdout:
+ return [commit.strip() for commit in stdout.decode().split('\n')[::-1]]
+ else:
+ return []
+
+ def get_subject(self, commit):
+ """
+ Gets the subject of a commit.
+
+ @deprecated: Use get_commit_info directly
+
+ @param commit: the commit to get the subject from
+ @return: the commit's subject
+ @rtype: C{str}
+ """
+ return self.get_commit_info(commit)['subject']
+
+ def get_commit_info(self, commitish):
+ """
+ Look up data of a specific commit-ish. Dereferences given commit-ish
+ to the commit it points to.
+
+ @param commitish: the commit-ish to inspect
+ @return: the commit's including id, author, email, subject and body
+ @rtype: dict
+ """
+ commit_sha1 = self.rev_parse("%s^0" % commitish)
+ args = GitArgs('--pretty=format:%an%x00%ae%x00%ad%x00%cn%x00%ce%x00%cd%x00%s%x00%f%x00%b%x00',
+ '-z', '--date=raw', '--no-renames', '--name-status',
+ commit_sha1)
+ out, err, ret = self._git_inout('show', args.args)
+ if ret:
+ raise GitRepositoryError("Unable to retrieve commit info for %s"
+ % commitish)
+
+ fields = out.split(b'\x00')
+
+ author = GitModifier(fields[0].decode().strip(),
+ fields[1].decode().strip(),
+ fields[2].decode().strip())
+ committer = GitModifier(fields[3].decode().strip(),
+ fields[4].decode().strip(),
+ fields[5].decode().strip())
+
+ files = defaultdict(list)
+ file_fields = fields[9:]
+
+ # For some reason git returns one extra empty field for merge commits
+ if file_fields[0] == b'':
+ file_fields.pop(0)
+ while len(file_fields) and file_fields[0] != b'':
+ status = file_fields.pop(0).decode().strip()
+ path = file_fields.pop(0)
+ files[status].append(path)
+
+ return {'id': commitish,
+ 'author': author,
+ 'committer': committer,
+ 'subject': fields[6].decode(),
+ 'patchname': fields[7].decode(),
+ 'body': fields[8].decode(),
+ 'files': files}
+
+#{ Patches
+ def format_patches(self, start, end, output_dir,
+ signature=True,
+ thread=None,
+ symmetric=True):
+ """
+ Output the commits between start and end as patches in output_dir.
+
+ This outputs the revisions I{start...end} by default. When using
+ I{symmetric} to C{false} it uses I{start..end} instead.
+
+ @param start: the commit on the left side of the revision range
+ @param end: the commit on the right hand side of the revisino range
+ @param output_dir: directory to write the patches to
+ @param signature: whether to output a signature
+ @param thread: whether to include In-Reply-To references
+ @param symmetric: whether to use the symmetric difference (see above)
+ """
+ options = GitArgs('-N', '-k',
+ '-o', output_dir)
+ options.add_cond(not signature, '--no-signature')
+ options.add('%s%s%s' % (start, '...' if symmetric else '..', end))
+ options.add_cond(thread, '--thread=%s' % thread, '--no-thread')
+
+ output, ret = self._git_getoutput('format-patch', options.args)
+ return [line.strip() for line in output]
+
+ def apply_patch(self, patch, index=True, context=None, strip=None, fix_ws=False):
+ """Apply a patch using git apply"""
+ args = []
+ if context:
+ args += ['-C', context]
+ if index:
+ args.append("--index")
+ if fix_ws:
+ args.append("--whitespace=fix")
+ if strip is not None:
+ args += ['-p', str(strip)]
+ args.append(patch)
+ self._git_command("apply", args)
+
+ def diff(self, obj1, obj2=None, paths=None, stat=False, summary=False,
+ text=False, ignore_submodules=True, abbrev=None, renames=False):
+ """
+ Diff two git repository objects
+
+ @param obj1: first object
+ @type obj1: C{str}
+ @param obj2: second object
+ @type obj2: C{str}
+ @param paths: List of paths to diff
+ @type paths: C{list}
+ @param stat: Show diffstat
+ @type stat: C{bool} or C{int} or C{str}
+ @param summary: Show diffstat
+ @type summary: C{bool}
+ @param text: Generate textual diffs, treat all files as text
+ @type text: C{bool}
+ @param ignore_submodules: ignore changes to submodules
+ @type ignore_submodules: C{bool}
+ @return: diff
+ @rtype: C{binary}
+ """
+ options = GitArgs('-p', '--no-ext-diff')
+ config_args = GitArgs()
+ if stat is True:
+ options.add('--stat')
+ elif stat:
+ options.add('--stat=%s' % stat)
+ options.add_true(summary, '--summary')
+ options.add_true(text, '--text')
+ options.add_true(ignore_submodules, '--ignore-submodules=all')
+ if isinstance(renames, bool):
+ options.add('-M' if renames else '--no-renames')
+ else:
+ options.add('-M=%s', renames)
+ options.add(obj1)
+ options.add_true(obj2, obj2)
+ if paths:
+ options.add('--', paths)
+ if abbrev is not None:
+ config_args.add('core.abbrev=%d' % abbrev)
+ output, stderr, ret = self._git_inout('diff',
+ options.args,
+ config_args=config_args.args)
+ if ret:
+ raise GitRepositoryError("Git diff failed")
+ return output
+
+ def diff_status(self, obj1, obj2):
+ """
+ Get file-status of two git repository objects
+
+ @param obj1: first object
+ @type obj1: C{str}
+ @param obj2: second object
+ @type obj2: C{str}
+ @return: name-status
+ @rtype: C{defaultdict} of C{str}
+ """
+ options = GitArgs('--name-status', '-z', obj1, obj2)
+ output, stderr, ret = self._git_inout('diff', options.args)
+
+ elements = output.split(b'\x00')
+ result = defaultdict(list)
+
+ while elements[0] != b'':
+ status = elements.pop(0).decode()[0]
+ filepath = elements.pop(0)
+ # Expect to have two filenames for renames and copies
+ if status in ['R', 'C']:
+ filepath = elements.pop(0) + '\x00' + filepath
+ result[status].append(filepath)
+
+ return result
+#}
+
+ def archive(self, format, prefix, output, treeish, cwd=None):
+ """
+ Create an archive from a treeish
+
+ @param format: the type of archive to create, e.g. 'tar.gz'
+ @type format: C{str}
+ @param prefix: prefix to prepend to each filename in the archive
+ @type prefix: C{str}
+ @param output: the name of the archive to create
+ @type output: C{str}
+ @param treeish: the treeish to create the archive from
+ @type treeish: C{str}
+ @param cwd: The directory to run in. Defaults to the current dir
+ @type cwd: C{str}
+ """
+ args = ['--format=%s' % format,
+ '--prefix=%s' % prefix,
+ '--output=%s' % output,
+ treeish]
+ out, err, ret = self._git_inout('archive', args, cwd=cwd, capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Unable to archive %s: %s" % (treeish, err.decode().strip()))
+
+ def collect_garbage(self, auto=False, prune=False, aggressive=False):
+ """
+ Cleanup unnecessary files and optimize the local repository
+
+ param auto: only cleanup if required
+ param auto: C{bool}
+ """
+ args = GitArgs('--quiet')
+ if prune is True:
+ args.add('--prune')
+ else:
+ args.add_true(prune, '--prune=%s' % prune)
+ args.add_true(aggressive, '--aggressive')
+ args.add_true(auto, '--auto')
+ self._git_command("gc", args.args)
+
+
+#{ Submodules
+
+ def has_submodules(self, treeish=None):
+ """
+ Does the repo have any submodules?
+
+ @param treeish: look into treeish
+ @type treeish: C{str}
+ @return: C{True} if the repository has any submodules, C{False}
+ otherwise
+ @rtype: C{bool}
+ """
+ if treeish:
+ try:
+ self.show('%s:.gitmodules' % treeish)
+ except GitRepositoryError:
+ return False
+ return True
+ return os.path.exists(os.path.join(self.path, '.gitmodules'))
+
+ def add_submodule(self, repo_path):
+ """
+ Add a submodule
+
+ @param repo_path: path to submodule
+ @type repo_path: C{str}
+ """
+ self._git_command("submodule", ["add", repo_path])
+
+ def update_submodules(self, init=True, recursive=True, fetch=False):
+ """
+ Update all submodules
+
+ @param init: whether to initialize the submodule if necessary
+ @type init: C{bool}
+ @param recursive: whether to update submodules recursively
+ @type recursive: C{bool}
+ @param fetch: whether to fetch new objects
+ @type fetch: C{bool}
+ """
+
+ if not self.has_submodules():
+ return
+ args = ["update"]
+ if recursive:
+ args.append("--recursive")
+ if init:
+ args.append("--init")
+ if not fetch:
+ args.append("--no-fetch")
+
+ self._git_command("submodule", args)
+
+ def get_submodules(self, treeish, path=None, recursive=True):
+ """
+ List the submodules of treeish
+
+ @return: a list of submodule/commit-id tuples
+ @rtype: list of tuples
+ """
+ # Note that we is lstree instead of submodule commands because
+ # there's no way to list the submodules of another branch with
+ # the latter.
+ submodules = []
+ if path is None:
+ path = self.path
+
+ args = [treeish]
+ if recursive:
+ args += ['-r']
+
+ out, err, ret = self._git_inout('ls-tree',
+ args,
+ cwd=path,
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Failed to list submodules of %s: %s" %
+ (treeish, err.decode().strip()))
+ for line in out.split(b'\n'):
+ if not line:
+ continue
+ mode, objtype, commit, name = line.decode().split(None, 3)
+
+ # A submodules is shown as "commit" object in ls-tree:
+ if objtype == "commit":
+ nextpath = os.path.join(path, name)
+ submodules.append((nextpath.replace(self.path, '').lstrip('/'),
+ commit))
+ if recursive:
+ submodules += self.get_submodules(commit, path=nextpath,
+ recursive=recursive)
+ return submodules
+
+#{ Repository Creation
+
+ @classmethod
+ def create(cls, path, description=None, bare=False):
+ """
+ Create a repository at path
+
+ @param path: where to create the repository
+ @type path: C{str}
+ @param bare: whether to create a bare repository
+ @type bare: C{bool}
+ @return: git repository object
+ @rtype: L{GitRepository}
+ """
+ args = GitArgs()
+ abspath = os.path.abspath(path)
+
+ args.add_true(bare, '--bare')
+ git_dir = '' if bare else '.git'
+
+ try:
+ if not os.path.exists(abspath):
+ os.makedirs(abspath)
+ try:
+ stdout, stderr, ret = cls.__git_inout(command='init',
+ args=args.args,
+ input=None,
+ extra_env=None,
+ cwd=abspath,
+ capture_stderr=True)
+ except Exception as excobj:
+ raise GitRepositoryError("Error running git init: %s" % excobj)
+ if ret:
+ raise GitRepositoryError("Error running git init: %s" % stderr.decode().strip())
+
+ if description:
+ with open(os.path.join(abspath, git_dir, "description"), 'w') as f:
+ description += '\n' if description[-1] != '\n' else ''
+ f.write(description)
+ return cls(abspath)
+ except OSError as err:
+ raise GitRepositoryError("Cannot create Git repository at '%s': %s"
+ % (abspath, err))
+ return None
+
+ @classmethod
+ def clone(cls, path, remote, depth=0, recursive=False, mirror=False,
+ bare=False, auto_name=True, reference=None):
+ """
+ Clone a git repository at I{remote} to I{path}.
+
+ @param path: where to clone the repository to
+ @type path: C{str}
+ @param remote: URL to clone
+ @type remote: C{str}
+ @param depth: create a shallow clone of depth I{depth}
+ @type depth: C{int}
+ @param recursive: whether to clone submodules
+ @type recursive: C{bool}
+ @param mirror: whether to pass --mirror to git-clone
+ @type mirror: C{bool}
+ @param bare: whether to create a bare repository
+ @type bare: C{bool}
+ @param auto_name: If I{True} create a directory below I{path} based on
+ the I{remote}s name. Otherwise create the repo directly at I{path}.
+ @type auto_name: C{bool}
+ @param reference: create a clone using local objects from I{reference} repository
+ @type reference: C{str}
+ @return: git repository object
+ @rtype: L{GitRepository}
+ """
+ abspath = os.path.abspath(path)
+ if auto_name:
+ name = None
+ else:
+ abspath, name = abspath.rsplit('/', 1)
+
+ args = GitArgs('--quiet')
+ args.add_true(depth, '--depth', depth)
+ args.add_true(recursive, '--recursive')
+ args.add_true(mirror, '--mirror')
+ args.add_true(bare, '--bare')
+ args.add_true(reference, '--reference', reference)
+ args.add(remote)
+ args.add_true(name, name)
+ try:
+ if not os.path.exists(abspath):
+ os.makedirs(abspath)
+
+ try:
+ stdout, stderr, ret = cls.__git_inout(command='clone',
+ args=args.args,
+ input=None,
+ extra_env=None,
+ cwd=abspath,
+ capture_stderr=True)
+ except Exception as excobj:
+ raise GitRepositoryError("Error running git clone: %s" % excobj)
+ if ret:
+ raise GitRepositoryError("Error running git clone: %s" % stderr.decode())
+
+ if not name:
+ try:
+ name = remote.rstrip('/').rsplit('/', 1)[1]
+ except IndexError:
+ name = remote.split(':', 1)[1]
+ if (mirror or bare):
+ if not name.endswith('.git'):
+ name = "%s.git" % name
+ elif name.endswith('.git'):
+ name = name[:-4]
+ return cls(os.path.join(abspath, name))
+ except OSError as err:
+ raise GitRepositoryError("Cannot clone Git repository "
+ "'%s' to '%s': %s"
+ % (remote, abspath, err[1]))
+ return None
+#}
diff --git a/gbp/git/vfs.py b/gbp/git/vfs.py
new file mode 100644
index 0000000..ec47201
--- /dev/null
+++ b/gbp/git/vfs.py
@@ -0,0 +1,78 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""Make blobs in a git repository accessible as file like objects"""
+
+import io
+from gbp.git.repository import GitRepositoryError
+
+
+class GitVfs(object):
+
+ class _File(object):
+ """
+ A file like object representing a file in git
+
+ @todo: We don't support any byte ranges yet.
+ """
+ def __init__(self, content, binary=False):
+ self._iter = iter
+ if binary:
+ self._data = io.BytesIO(content)
+ else:
+ try:
+ self._data = io.StringIO(content.decode())
+ except UnicodeDecodeError:
+ self._data = io.StringIO(content.decode("iso-8859-1"))
+
+ def readline(self):
+ return self._data.readline()
+
+ def readlines(self):
+ return self._data.readlines()
+
+ def read(self, size=None):
+ return self._data.read(size)
+
+ def close(self):
+ return self._data.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.close()
+
+ def __init__(self, repo, committish=None):
+ """
+ @param repo: the git repository to act on
+ @param committish: the committish to act on
+ """
+ self._repo = repo
+ self._committish = committish or 'HEAD'
+
+ def open(self, path, flags=None):
+ flags = flags or 'r'
+
+ for flag in flags:
+ if flag not in ['r', 't', 'b']:
+ raise NotImplementedError("Flag '%s' unsupported so far" % flag)
+ try:
+ return GitVfs._File(self._repo.show(
+ "%s:%s" % (self._committish, path)),
+ True if 'b' in flags else False)
+ except GitRepositoryError as e:
+ raise OSError(e)
diff --git a/gbp/log.py b/gbp/log.py
new file mode 100644
index 0000000..ce02e48
--- /dev/null
+++ b/gbp/log.py
@@ -0,0 +1,178 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Simple colored logging classes"""
+
+import os
+import sys
+import logging
+from logging import (DEBUG, INFO, WARNING, ERROR, CRITICAL, getLogger)
+import gbp.tristate
+
+
+COLORS = dict([('none', 0)] + list(zip(['black', 'red', 'green', 'yellow', 'blue',
+ 'magenta', 'cyan', 'white'], range(30, 38))))
+DEFAULT_COLOR_SCHEME = {DEBUG: COLORS['green'],
+ INFO: COLORS['green'],
+ WARNING: COLORS['red'],
+ ERROR: COLORS['red'],
+ CRITICAL: COLORS['red']}
+
+
+class GbpFilter(object):
+ """Filter for enabling selective output"""
+ def __init__(self, levels):
+ self._levels = levels
+
+ def filter(self, record):
+ """Do we show the record"""
+ if record.levelno in self._levels:
+ return True
+ return False
+
+
+class GbpStreamHandler(logging.StreamHandler):
+ """Special stream handler for enabling colored output"""
+
+ COLOR_SEQ = "\033[%dm"
+ OFF_SEQ = "\033[0m"
+
+ def __init__(self, stream=None, color='auto'):
+ super(GbpStreamHandler, self).__init__(stream)
+ self._color = gbp.tristate.Tristate(color)
+ self._color_scheme = DEFAULT_COLOR_SCHEME.copy()
+ msg_fmt = "%(color)s%(name)s:%(levelname)s:%(coloroff)s %(message)s"
+ self.setFormatter(logging.Formatter(fmt=msg_fmt))
+
+ def set_color(self, color):
+ """Set/unset colorized output"""
+ self._color = gbp.tristate.Tristate(color)
+
+ def set_color_scheme(self, color_scheme={}):
+ """Set logging colors"""
+ self._color_scheme = DEFAULT_COLOR_SCHEME.copy()
+ self._color_scheme.update(color_scheme)
+
+ def set_format(self, fmt):
+ """Set logging format"""
+ self.setFormatter(logging.Formatter(fmt=fmt))
+
+ def _use_color(self):
+ """Check if to print in color or not"""
+ if self._color.is_on():
+ return True
+ elif self._color.is_auto() and hasattr(self.stream, 'isatty'):
+ in_emacs = (os.getenv("EMACS") and
+ os.getenv("INSIDE_EMACS", "").endswith(",comint"))
+ return self.stream.isatty() and not in_emacs
+ return False
+
+ def format(self, record):
+ """Colorizing formatter"""
+ record.color = record.coloroff = ""
+ if self._use_color():
+ record.color = self.COLOR_SEQ % self._color_scheme[record.levelno]
+ record.coloroff = self.OFF_SEQ
+ record.levelname = record.levelname.lower()
+ return super(GbpStreamHandler, self).format(record)
+
+
+class GbpLogger(logging.Logger):
+ """Logger class for git-buildpackage"""
+
+ def __init__(self, name, color='auto', *args, **kwargs):
+ super(GbpLogger, self).__init__(name, *args, **kwargs)
+ self._default_handlers = [GbpStreamHandler(sys.stdout, color),
+ GbpStreamHandler(sys.stderr, color)]
+ self._default_handlers[0].addFilter(GbpFilter([DEBUG, INFO]))
+ self._default_handlers[1].addFilter(GbpFilter([WARNING, ERROR,
+ CRITICAL]))
+ for hdlr in self._default_handlers:
+ self.addHandler(hdlr)
+
+ def set_color(self, color):
+ """Set/unset colorized output of the default handlers"""
+ for hdlr in self._default_handlers:
+ hdlr.set_color(color)
+
+ def set_color_scheme(self, color_scheme={}):
+ """Set the color scheme of the default handlers"""
+ for hdlr in self._default_handlers:
+ hdlr.set_color_scheme(color_scheme)
+
+ def set_format(self, fmt):
+ """Set the format of the default handlers"""
+ for hdlr in self._default_handlers:
+ hdlr.set_format(fmt)
+
+
+def err(msg):
+ """Logs a message with level ERROR on the GBP logger"""
+ LOGGER.error(msg)
+
+
+def warn(msg):
+ """Logs a message with level WARNING on the GBP logger"""
+ LOGGER.warning(msg)
+
+
+def info(msg):
+ """Logs a message with level INFO on the GBP logger"""
+ LOGGER.info(msg)
+
+
+def debug(msg):
+ """Logs a message with level DEBUG on the GBP logger"""
+ LOGGER.debug(msg)
+
+
+def _parse_color_scheme(color_scheme=""):
+ """Set logging colors"""
+ scheme = {}
+ colors = color_scheme.split(':')
+ levels = (DEBUG, INFO, WARNING, ERROR)
+
+ if color_scheme and len(colors) != len(levels):
+ raise ValueError("Number color fields in color scheme not %d'"
+ % len(levels))
+
+ for field, color in enumerate(colors):
+ level = levels[field]
+ try:
+ scheme[level] = int(color)
+ except ValueError:
+ try:
+ scheme[level] = COLORS[color.lower()]
+ except KeyError:
+ pass
+ return scheme
+
+
+def setup(color, verbose, color_scheme=""):
+ """Basic logger setup"""
+ LOGGER.set_color(color)
+ LOGGER.set_color_scheme(_parse_color_scheme(color_scheme))
+ if verbose:
+ LOGGER.setLevel(DEBUG)
+ else:
+ LOGGER.setLevel(INFO)
+
+
+# Initialize the module
+logging.setLoggerClass(GbpLogger)
+
+LOGGER = getLogger("gbp")
diff --git a/gbp/notifications.py b/gbp/notifications.py
new file mode 100644
index 0000000..ae0d5de
--- /dev/null
+++ b/gbp/notifications.py
@@ -0,0 +1,73 @@
+# 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, please see
+# <http://www.gnu.org/licenses/>
+
+import warnings
+
+notify_module = None
+
+
+def enable_notifications():
+ global notify_module
+
+ with warnings.catch_warnings():
+ # Avoid GTK+ cannot open display warning:
+ warnings.simplefilter("ignore")
+ try:
+ import notify2
+ notify_module = notify2
+ except (ImportError, RuntimeError):
+ return False
+
+ try:
+ return notify_module.init("git-buildpackage")
+ except Exception:
+ return False
+
+
+def build_msg(cp, success):
+ summary = "Gbp %s" % ["failed", "successful"][success]
+ msg = ("Build of %s %s %s" %
+ (cp['Source'], cp['Version'], ["failed", "succeeded"][success]))
+
+ return summary, msg
+
+
+def send_notification(summary, msg):
+ n = notify_module.Notification(summary, msg)
+ n.set_hint('transient', True)
+ try:
+ if not n.show():
+ return False
+ except Exception:
+ return False
+ return True
+
+
+def notify(summary, message, notify_opt):
+ """
+ Send a notifications
+ @return: False on error
+ """
+
+ if notify_opt.is_off():
+ return True
+
+ enable = enable_notifications()
+ if not enable:
+ return [True, False][notify_opt.is_on()]
+
+ return notify_opt.do(send_notification, summary, message)
diff --git a/gbp/patch_series.py b/gbp/patch_series.py
new file mode 100644
index 0000000..47f5135
--- /dev/null
+++ b/gbp/patch_series.py
@@ -0,0 +1,378 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011,2015,2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""Handle Patches and Patch Series"""
+
+import collections
+import os
+import re
+import subprocess
+import tempfile
+from gbp.errors import GbpError
+
+VALID_DEP3_ENDS = re.compile(r'(?:---|\*\*\*|Index:)[ \t][^ \t]|^diff -|^---')
+
+
+class Patch(object):
+ """
+ A patch in a L{PatchSeries}
+
+ @ivar path: path to the patch
+ @type path: string
+ @ivar topic: the topic of the patch (the directory component)
+ @type topic: string
+ @ivar strip: path components to strip (think patch -p<strip>)
+ @type strip: integer
+ @ivar info: Information retrieved from a RFC822 style patch header
+ @type info: C{dict} with C{str} keys and values
+ @ivar long_desc: the long description of the patch
+ """
+ patch_exts = ['diff', 'patch']
+
+ def __init__(self, path, topic=None, strip=None):
+ self.path = path
+ self.topic = topic
+ self.strip = strip
+ self.info = None
+ self.long_desc = None
+
+ def __repr__(self):
+ repr = "<gbp.patch_series.Patch path='%s' " % self.path
+ if self.topic:
+ repr += "topic='%s' " % self.topic
+ if self.strip is not None:
+ repr += "strip=%d " % self.strip
+ repr += ">"
+ return repr
+
+ def _read_info(self):
+ self._read_git_mailinfo()
+
+ def _read_git_mailinfo(self):
+ """
+ Read patch information into a structured form
+
+ using I{git mailinfo}
+ """
+ self.info = {}
+ body = tempfile.NamedTemporaryFile(prefix='gbp_')
+ pipe = subprocess.Popen("git mailinfo -k '%s' /dev/null 2>/dev/null < '%s'" %
+ (body.name, self.path),
+ shell=True,
+ stdout=subprocess.PIPE).stdout
+ for line in pipe:
+ line = line.decode()
+ if ':' in line:
+ rfc_header, value = line.split(" ", 1)
+ header = rfc_header[:-1].lower()
+ self.info[header] = value.strip()
+ try:
+ self.long_desc = "".join([l.decode("utf-8", "backslashreplace") for l in body])
+ except (IOError, UnicodeDecodeError) as msg:
+ raise GbpError("Failed to read patch header of '%s': %s" %
+ (self.path, msg))
+ finally:
+ body.close()
+ if os.path.exists(body.name):
+ os.unlink(body.name)
+
+ def _get_subject_from_filename(self):
+ """
+ Determine the patch's subject based on the its filename
+
+ >>> p = Patch('debian/patches/foo.patch')
+ >>> p._get_subject_from_filename()
+ 'foo'
+ >>> Patch('foo.patch')._get_subject_from_filename()
+ 'foo'
+ >>> Patch('debian/patches/foo.bar')._get_subject_from_filename()
+ 'foo.bar'
+ >>> p = Patch('debian/patches/foo')
+ >>> p._get_subject_from_filename()
+ 'foo'
+ >>> Patch('0123-foo.patch')._get_subject_from_filename()
+ 'foo'
+ >>> Patch('0123.patch')._get_subject_from_filename()
+ '0123'
+ >>> Patch('0123-foo-0123.patch')._get_subject_from_filename()
+ 'foo-0123'
+
+ @return: the patch's subject
+ @rtype: C{str}
+ """
+ subject = os.path.basename(self.path)
+ # Strip of .diff or .patch from patch name
+ try:
+ base, ext = subject.rsplit('.', 1)
+ if ext in self.patch_exts:
+ subject = base
+ except ValueError:
+ pass # No ext so keep subject as is
+ return subject.lstrip('0123456789-') or subject
+
+ def _get_info_field(self, key, get_val=None):
+ """
+ Return the key I{key} from the info C{dict}
+ or use val if I{key} is not a valid key.
+
+ Fill self.info if not already done.
+
+ @param key: key to fetch
+ @type key: C{str}
+ @param get_val: alternate value if key is not in info dict
+ @type get_val: C{()->str}
+ """
+ if self.info is None:
+ self._read_info()
+
+ if key in self.info:
+ return self.info[key]
+ else:
+ return get_val() if get_val else None
+
+ @property
+ def subject(self):
+ """
+ The patch's subject, either from the patch header or from the filename.
+ """
+ return self._get_info_field('subject', self._get_subject_from_filename)
+
+ @property
+ def author(self):
+ """The patch's author"""
+ return self._get_info_field('author')
+
+ @property
+ def email(self):
+ """The patch author's email address"""
+ return self._get_info_field('email')
+
+ @property
+ def date(self):
+ """The patch's modification time"""
+ return self._get_info_field('date')
+
+
+class Dep3Patch(Patch):
+ def _read_info(self):
+ super(Dep3Patch, self)._read_info()
+ if not self.info:
+ self._check_dep3()
+
+ def _dep3_get_value(self, lines):
+ value = []
+ for line in lines:
+ if line.startswith(' '):
+ line = line[1:]
+ if line == '.\n':
+ line = line[1:]
+ else:
+ line = line.split(':', 1)[1].lstrip()
+ value.append(line)
+ return ''.join(value)
+
+ def _dep3_to_info(self, headers):
+ """
+ Process the ordered dict generated by check_dep3 and add the
+ information to self.info
+ """
+
+ def add_author(lines):
+ value = self._dep3_get_value(lines).strip()
+ m = re.match('(.*)<([^<>]+)>', value)
+ if m:
+ value = m.group(1).strip()
+ self.info['email'] = m.group(2)
+ self.info['author'] = value
+ return 1
+
+ def add_subject(lines, long_desc, changes):
+ value = self._dep3_get_value(lines).lstrip()
+ if '\n' in value:
+ value, description = value.split('\n', 1)
+ # prepend the continuation lines
+ long_desc = description + long_desc
+ self.info['subject'] = value
+ return long_desc, changes + 1
+
+ changes = 0
+ long_desc = self._dep3_get_value(headers.get('long_desc', list()))
+
+ for k, v in headers.items():
+ if k in ('author', 'from'):
+ changes += add_author(v)
+ elif k in ('subject', 'description'):
+ long_desc, changes = add_subject(v, long_desc, changes)
+ elif k == 'long_desc':
+ pass
+ else:
+ long_desc += ''.join(v)
+ changes += 1
+ if changes:
+ self.long_desc = long_desc + self.long_desc
+
+ def _check_dep3(self):
+ """
+ Read DEP3 patch information into a structured form
+ """
+ if not os.path.exists(self.path):
+ return
+
+ # patch_header logic from quilt plus any line starting with ---
+ # which is the dep3 stop processing and the git separation between the
+ # header and diff stat
+ headers = collections.OrderedDict()
+ current = 'long_desc'
+ with open(self.path, errors='replace') as file:
+ for line in file:
+ if VALID_DEP3_ENDS.search(line):
+ break
+
+ if line.startswith(' '):
+ # continuation
+ headers.setdefault(current, list()).append(line)
+ elif ':' in line:
+ current = line.split(':', 1)[0].lower()
+ headers.setdefault(current, list()).append(line)
+ else:
+ # end of paragraph or not a header, read_info already left
+ # everything else in the long_desc, nothing else to do
+ break
+ self._dep3_to_info(headers)
+
+
+class PatchSeries(list):
+ """
+ A series of L{Patch}es as read from a quilt series file).
+ """
+ comment_re = re.compile('\s+#.*$')
+ level_re = re.compile('-p(?P<level>[0-9]+)')
+
+ @classmethod
+ def read_series_file(cls, seriesfile):
+ """Read a series file into L{Patch} objects"""
+ patch_dir = os.path.dirname(seriesfile)
+
+ if not os.path.exists(seriesfile):
+ return []
+
+ try:
+ s = open(seriesfile)
+ except Exception as err:
+ raise GbpError("Cannot open series file: %s" % err)
+
+ queue = cls._read_series(s, patch_dir)
+ s.close()
+ return queue
+
+ @classmethod
+ def _read_series(cls, series, patch_dir):
+ """
+ Read patch series
+
+ >>> PatchSeries._read_series(['a/b',
+ ... 'a -p1 # comment',
+ ... 'a/b -p2'], '.')
+ ... # doctest:+NORMALIZE_WHITESPACE
+ [<gbp.patch_series.Patch path='./a/b' topic='a' >,
+ <gbp.patch_series.Patch path='./a' strip=1 >,
+ <gbp.patch_series.Patch path='./a/b' topic='a' strip=2 >]
+
+ >>> PatchSeries._read_series(['# foo', 'a/b', '', '# bar'], '.')
+ [<gbp.patch_series.Patch path='./a/b' topic='a' >]
+
+ @param series: series of patches in quilt format
+ @type series: iterable of strings
+ @param patch_dir: path prefix to prepend to each patch path
+ @type patch_dir: string
+ """
+ queue = PatchSeries()
+ for line in series:
+ try:
+ if line[0] in ['\n', '#']:
+ continue
+ except IndexError:
+ continue # ignore empty lines
+ queue.append(cls._parse_line(line, patch_dir))
+ return queue
+
+ @staticmethod
+ def _get_topic(line):
+ """
+ Get the topic from the patch's path
+
+ >>> PatchSeries._get_topic("a/b c")
+ 'a'
+ >>> PatchSeries._get_topic("asdf")
+ >>> PatchSeries._get_topic("/asdf")
+ """
+ topic = os.path.dirname(line)
+ if topic in ['', '/']:
+ topic = None
+ return topic
+
+ @classmethod
+ def _strip_comment(cls, line):
+ """
+ Strip a comment from a series file line
+
+ >>> PatchSeries._strip_comment("does/not matter")
+ 'does/not matter'
+ >>> PatchSeries._strip_comment("remove/the # comment # text")
+ 'remove/the'
+ >>> PatchSeries._strip_comment("leave/level/intact -p1 # comment # text")
+ 'leave/level/intact -p1'
+ """
+ return re.sub(cls.comment_re, '', line)
+
+ @classmethod
+ def _split_strip(cls, line):
+ """
+ Separate the -p<num> option from the patch name
+
+ >>> PatchSeries._split_strip("asdf -p1")
+ ('asdf', 1)
+ >>> PatchSeries._split_strip("a/nice/patch")
+ ('a/nice/patch', None)
+ >>> PatchSeries._split_strip("asdf foo")
+ ('asdf foo', None)
+ """
+ patch = line
+ strip = None
+
+ split = line.rsplit(None, 1)
+ if len(split) > 1:
+ m = cls.level_re.match(split[1])
+ if m:
+ patch = split[0]
+ strip = int(m.group('level'))
+
+ return (patch, strip)
+
+ @classmethod
+ def _parse_line(cls, line, patch_dir):
+ """
+ Parse a single line from a series file
+
+ >>> PatchSeries._parse_line("a/b -p1", '/tmp/patches')
+ <gbp.patch_series.Patch path='/tmp/patches/a/b' topic='a' strip=1 >
+ >>> PatchSeries._parse_line("a/b", '.')
+ <gbp.patch_series.Patch path='./a/b' topic='a' >
+ """
+ line = cls._strip_comment(line.rstrip())
+ topic = cls._get_topic(line)
+ (patch, split) = cls._split_strip(line)
+ return Dep3Patch(os.path.join(patch_dir, patch), topic, split)
diff --git a/gbp/paths.py b/gbp/paths.py
new file mode 100644
index 0000000..1cb7bcc
--- /dev/null
+++ b/gbp/paths.py
@@ -0,0 +1,26 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+"Helpers to handle paths"
+
+
+def to_bin(path):
+ """Convert to binary if not already
+
+ We want paths to be binary since we can't assume an encoding but
+ it shall still be convenient to pass in unicode strings
+ """
+ return path.encode() if not isinstance(path, bytes) else path
diff --git a/gbp/pkg/__init__.py b/gbp/pkg/__init__.py
new file mode 100644
index 0000000..fb38e52
--- /dev/null
+++ b/gbp/pkg/__init__.py
@@ -0,0 +1,24 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Common functionality of the Debian/RPM package helpers"""
+
+from gbp.pkg.pkgpolicy import PkgPolicy # noqa: F401
+from gbp.pkg.compressor import Compressor # noqa: F401
+from gbp.pkg.archive import Archive # noqa: F401
+from gbp.pkg.upstreamsource import UpstreamSource # noqa: F401
+from gbp.pkg.pristinetar import PristineTar # noqa: F401
diff --git a/gbp/pkg/archive.py b/gbp/pkg/archive.py
new file mode 100644
index 0000000..cfb96b8
--- /dev/null
+++ b/gbp/pkg/archive.py
@@ -0,0 +1,80 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+from .compressor import Compressor
+
+
+class Archive(object):
+ # Supported archive formats
+ Formats = ['tar', 'zip']
+
+ # Map combined file extensions to archive and compression format
+ Ext_aliases = {'tgz': ('tar', 'gzip'),
+ 'tbz2': ('tar', 'bzip2'),
+ 'tlz': ('tar', 'lzma'),
+ 'txz': ('tar', 'xz')}
+
+ @staticmethod
+ def parse_filename(filename):
+ """
+ Given an filename return the basename (filename without the
+ archive and compression extensions), archive format and
+ compression method used.
+
+ @param filename: the name of the file
+ @type filename: string
+ @return: tuple containing basename, archive format and compression method
+ @rtype: C{tuple} of C{str}
+
+ >>> Archive.parse_filename("abc.tar.gz")
+ ('abc', 'tar', 'gzip')
+ >>> Archive.parse_filename("abc.tar.bz2")
+ ('abc', 'tar', 'bzip2')
+ >>> Archive.parse_filename("abc.def.tbz2")
+ ('abc.def', 'tar', 'bzip2')
+ >>> Archive.parse_filename("abc.def.tar.xz")
+ ('abc.def', 'tar', 'xz')
+ >>> Archive.parse_filename("abc.zip")
+ ('abc', 'zip', None)
+ >>> Archive.parse_filename("abc.lzma")
+ ('abc', None, 'lzma')
+ >>> Archive.parse_filename("abc.tar.foo")
+ ('abc.tar.foo', None, None)
+ >>> Archive.parse_filename("abc")
+ ('abc', None, None)
+ """
+ (base_name, archive_fmt, compression) = (filename, None, None)
+
+ # Split filename into pieces
+ split = filename.split(".")
+ if len(split) > 1:
+ if split[-1] in Archive.Ext_aliases:
+ base_name = ".".join(split[:-1])
+ (archive_fmt, compression) = Archive.Ext_aliases[split[-1]]
+ elif split[-1] in Archive.Formats:
+ base_name = ".".join(split[:-1])
+ (archive_fmt, compression) = (split[-1], None)
+ else:
+ for (c, ext) in Compressor.Exts.items():
+ if ext == split[-1]:
+ base_name = ".".join(split[:-1])
+ compression = c
+ if len(split) > 2 and split[-2] in Archive.Formats:
+ base_name = ".".join(split[:-2])
+ archive_fmt = split[-2]
+
+ return (base_name, archive_fmt, compression)
diff --git a/gbp/pkg/compressor.py b/gbp/pkg/compressor.py
new file mode 100644
index 0000000..68670f5
--- /dev/null
+++ b/gbp/pkg/compressor.py
@@ -0,0 +1,75 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+
+class Compressor(object):
+ # Map frequently used names of compression types to the internal ones:
+ Aliases = {'bz2': 'bzip2',
+ 'gz': 'gzip', }
+
+ Opts = {'gzip': '-n',
+ 'bzip2': '',
+ 'lzma': '',
+ 'xz': ''}
+
+ Exts = {'gzip': 'gz',
+ 'bzip2': 'bz2',
+ 'lzma': 'lzma',
+ 'xz': 'xz'}
+
+ def __init__(self, type_, level=None):
+ self._type = type_
+ self._level = int(level) if level not in [None, ''] else None
+
+ def is_known(self):
+ return self.type in self.Opts.keys()
+
+ @property
+ def type(self):
+ return self._type
+
+ @property
+ def level(self):
+ return self._level
+
+ @property
+ def _level_opt(self):
+ return '-%d' % self.level if self.level is not None else ''
+
+ @property
+ def _more_opts(self):
+ return self.Opts.get(self._type, '')
+
+ def cmdline(self, stdout=True):
+ """
+ >>> Compressor('gzip', level=9).cmdline()
+ 'gzip -9 -n -c'
+ >>> Compressor('gzip').cmdline(True)
+ 'gzip -n -c'
+ """
+ return "%s %s %s %s" % (self.type, self._level_opt, self._more_opts,
+ "-c" if stdout else '')
+
+ def __repr__(self):
+ """
+ >>> Compressor('gzip').__repr__()
+ "<compressor type='gzip' >"
+ >>> Compressor('gzip', 9).__repr__()
+ "<compressor type='gzip' level=9>"
+ """
+ level_str = "level=%s" % self.level if self.level is not None else ''
+ return "<compressor type='%s' %s>" % (self.type, level_str)
diff --git a/gbp/pkg/git.py b/gbp/pkg/git.py
new file mode 100644
index 0000000..7dc32e6
--- /dev/null
+++ b/gbp/pkg/git.py
@@ -0,0 +1,122 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""A Git Repository that keeps a Distro Package"""
+
+import os
+import pipes
+import shutil
+import tempfile
+
+from gbp.command_wrappers import (CatenateTarArchive, CatenateZipArchive)
+from gbp.git import GitRepository, GitRepositoryError
+from gbp.deb.pristinetar import DebianPristineTar
+
+
+import gbp.log
+
+
+class PkgGitRepository(GitRepository):
+ """A git repository that holds the source of a Distro package"""
+
+ def __init__(self, *args, **kwargs):
+ super(PkgGitRepository, self).__init__(*args, **kwargs)
+ self.pristine_tar = DebianPristineTar(self)
+
+ @staticmethod
+ def sanitize_prefix(prefix):
+ """
+ Make sure git-archive prefix ends with a slash
+
+ >>> PkgGitRepository.sanitize_prefix('')
+ '/'
+ >>> PkgGitRepository.sanitize_prefix('foo/')
+ 'foo/'
+ >>> PkgGitRepository.sanitize_prefix('/foo/bar')
+ 'foo/bar/'
+ """
+ if prefix:
+ return prefix.strip('/') + '/'
+ return '/'
+
+ def archive_comp(self, treeish, output, prefix, comp, format='tar', submodules=False):
+ """Create a compressed source tree archive with the given options"""
+ if comp and not comp.is_known():
+ raise GitRepositoryError("Unsupported compression type '%s'" % comp.type)
+
+ if submodules:
+ return self._archive_comp_submodules(treeish, output, prefix, comp, format)
+ else:
+ return self._archive_comp_single(treeish, output, prefix, comp, format)
+
+ def _archive_comp_submodules(self, treeish, output, prefix, comp, format='tar'):
+ """
+ Create a compressed source tree archive with submodules.
+
+ Concatenates the archives generated by git-archive into one and compresses
+ the end result.
+
+ Exception handling is left to the caller.
+ """
+ prefix = self.sanitize_prefix(prefix)
+ tempdir = tempfile.mkdtemp()
+ main_archive = os.path.join(tempdir, "main.%s" % format)
+ submodule_archive = os.path.join(tempdir, "submodule.%s" % format)
+ try:
+ # generate main (tmp) archive
+ self.archive(format=format, prefix=prefix,
+ output=main_archive, treeish=treeish)
+
+ # generate each submodule's archive and append it to the main archive
+ for (subdir, commit) in self.get_submodules(treeish):
+ tarpath = [subdir, subdir[2:]][subdir.startswith("./")]
+
+ gbp.log.debug("Processing submodule %s (%s)" % (subdir, commit[0:8]))
+ self.archive(format=format, prefix='%s%s/' % (prefix, tarpath),
+ output=submodule_archive, treeish=commit, cwd=subdir)
+ if format == 'tar':
+ CatenateTarArchive(main_archive)(submodule_archive)
+ elif format == 'zip':
+ CatenateZipArchive(main_archive)(submodule_archive)
+
+ # compress the output
+ if comp and comp.type:
+ # Redirect through stdout directly to the correct output file in
+ # order to avoid determining the output filename of the compressor
+ ret = os.system("%s %s > %s" % (comp.cmdline(), main_archive, output))
+ if ret:
+ raise GitRepositoryError("Error creating %s: %d" % (output, ret))
+ else:
+ shutil.move(main_archive, output)
+ finally:
+ shutil.rmtree(tempdir)
+
+ def _archive_comp_single(self, treeish, output, prefix, comp, format='tar'):
+ """
+ Create a compressed source tree archive without submodules
+
+ We have this as a special case since it avoids a temporary file
+ """
+ prefix = self.sanitize_prefix(prefix)
+ pipe = pipes.Template()
+ pipe.prepend("git archive --format=%s --prefix=%s %s" % (format, prefix, treeish), '.-')
+ if comp and comp.type:
+ pipe.append(comp.cmdline(), '--')
+ ret = pipe.copy('', output)
+ if ret:
+ raise GitRepositoryError("Error creating %s: %d" % (output, ret))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/pkg/pkgpolicy.py b/gbp/pkg/pkgpolicy.py
new file mode 100644
index 0000000..e6b3683
--- /dev/null
+++ b/gbp/pkg/pkgpolicy.py
@@ -0,0 +1,167 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+
+import os
+import re
+
+from gbp.pkg.archive import Archive
+
+
+class PkgPolicy(object):
+ """
+ Common helpers for packaging policy.
+ """
+ packagename_re = None
+ packagename_msg = None
+ upstreamversion_re = None
+ upstreamversion_msg = None
+
+ @classmethod
+ def is_valid_packagename(cls, name):
+ """
+ Is this a valid package name?
+
+ >>> PkgPolicy.is_valid_packagename('doesnotmatter')
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: Class needs to provide packagename_re
+ """
+ if cls.packagename_re is None:
+ raise NotImplementedError("Class needs to provide packagename_re")
+ return True if cls.packagename_re.match(name) else False
+
+ @classmethod
+ def is_valid_upstreamversion(cls, version):
+ """
+ Is this a valid upstream version number?
+
+ >>> PkgPolicy.is_valid_upstreamversion('doesnotmatter')
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: Class needs to provide upstreamversion_re
+ """
+ if cls.upstreamversion_re is None:
+ raise NotImplementedError("Class needs to provide upstreamversion_re")
+ return True if cls.upstreamversion_re.match(version) else False
+
+ @staticmethod
+ def guess_upstream_src_version(filename, extra_regex=r''):
+ """
+ Guess the package name and version from the filename of an upstream
+ archive.
+
+ @param filename: filename (archive or directory) from which to guess
+ @type filename: C{string}
+ @param extra_regex: additional regex to apply, needs a 'package' and a
+ 'version' group
+ @return: (package name, version) or ('', '')
+ @rtype: tuple
+
+ >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.gz')
+ ('foo-bar', '0.2')
+ >>> PkgPolicy.guess_upstream_src_version('foo-Bar_0.2.orig.tar.gz')
+ ('', '')
+ >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2.tar.gz')
+ ('git-bar', '0.2')
+ >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2-rc1.tar.gz')
+ ('git-bar', '0.2-rc1')
+ >>> PkgPolicy.guess_upstream_src_version('git-bar-0.2:~-rc1.tar.gz')
+ ('git-bar', '0.2:~-rc1')
+ >>> PkgPolicy.guess_upstream_src_version('git-Bar-0A2d:rc1.tar.bz2')
+ ('git-Bar', '0A2d:rc1')
+ >>> PkgPolicy.guess_upstream_src_version('git-1.tar.bz2')
+ ('git', '1')
+ >>> PkgPolicy.guess_upstream_src_version('kvm_87+dfsg.orig.tar.gz')
+ ('kvm', '87+dfsg')
+ >>> PkgPolicy.guess_upstream_src_version('foo-Bar-a.b.tar.gz')
+ ('', '')
+ >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.xz')
+ ('foo-bar', '0.2')
+ >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.orig.tar.lzma')
+ ('foo-bar', '0.2')
+ >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.zip')
+ ('foo-bar', '0.2')
+ >>> PkgPolicy.guess_upstream_src_version('foo-bar-0.2.tlz')
+ ('foo-bar', '0.2')
+ >>> PkgPolicy.guess_upstream_src_version('foo-bar_0.2.tar.gz')
+ ('foo-bar', '0.2')
+ """
+ version_chars = r'[a-zA-Z\d\.\~\-\:\+]'
+ basename = Archive.parse_filename(os.path.basename(filename))[0]
+
+ version_filters = map(
+ lambda x: x % version_chars,
+ ( # Debian upstream tarball: package_'<version>.orig.tar.gz'
+ r'^(?P<package>[a-z\d\.\+\-]+)_(?P<version>%s+)\.orig',
+ # Debian native: 'package_<version>.tar.gz'
+ r'^(?P<package>[a-z\d\.\+\-]+)_(?P<version>%s+)',
+ # Upstream 'package-<version>.tar.gz'
+ # or directory 'package-<version>':
+ r'^(?P<package>[a-zA-Z\d\.\+\-]+)(-)(?P<version>[0-9]%s*)'))
+ if extra_regex:
+ version_filters = extra_regex + version_filters
+
+ for filter in version_filters:
+ m = re.match(filter, basename)
+ if m:
+ return (m.group('package'), m.group('version'))
+ return ('', '')
+
+ @staticmethod
+ def has_origs(orig_files, dir):
+ "Check orig tarball and additional tarballs exists in dir"
+ for o in orig_files:
+ if not os.path.exists(os.path.join(dir, o)):
+ return False
+ return True
+
+ @classmethod
+ def has_orig(cls, orig_file, dir):
+ return cls.has_origs([orig_file], dir)
+
+ @staticmethod
+ def symlink_origs(orig_files, orig_dir, output_dir, force=False):
+ """
+ symlink orig tarball from orig_dir to output_dir
+ @return: [] if all links were created, list of
+ failed links otherwise
+ """
+ orig_dir = os.path.abspath(orig_dir)
+ output_dir = os.path.abspath(output_dir)
+ err = []
+
+ if orig_dir == output_dir:
+ return []
+
+ for f in orig_files:
+ src = os.path.join(orig_dir, f)
+ dst = os.path.join(output_dir, f)
+ if not os.access(src, os.F_OK):
+ err.append(f)
+ continue
+ try:
+ if os.path.exists(dst) and force:
+ os.unlink(dst)
+ os.symlink(src, dst)
+ except OSError:
+ err.append(f)
+ return err
+
+ @classmethod
+ def symlink_orig(cls, orig_file, orig_dir, output_dir, force=False):
+ return cls.symlink_origs([orig_file], orig_dir, output_dir, force=force)
diff --git a/gbp/pkg/pristinetar.py b/gbp/pkg/pristinetar.py
new file mode 100644
index 0000000..be30132
--- /dev/null
+++ b/gbp/pkg/pristinetar.py
@@ -0,0 +1,109 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 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, please see
+# <http://www.gnu.org/licenses/>
+"""Handle checkin and checkout of archives from the pristine-tar branch"""
+
+import re
+import os
+import gbp.log
+from gbp.command_wrappers import Command
+
+
+class PristineTar(Command):
+ """The pristine-tar branch in a git repository"""
+ branch = 'pristine-tar'
+
+ def __init__(self, repo):
+ self.repo = repo
+ super(PristineTar, self).__init__('pristine-tar',
+ cwd=repo.path,
+ capture_stderr=True)
+
+ def _has_feature(self, feature):
+ """
+ Check if pristine_tar has a certain feature enabled.
+
+ @param feature: feature / command option to check
+ @type feature: C{str}
+ @return: True if feature is supported
+ @rtype: C{bool}
+ """
+ self.call(['--help'], quiet=True) # There's no --help so we always exit 1
+ r = re.compile('.* pristine-tar .* %s' % feature)
+ for line in self.stderr.splitlines():
+ if r.match(line):
+ return True
+ return False
+
+ def has_feature_verify(self):
+ return self._has_feature("verify")
+
+ def has_commit(self, archive_regexp):
+ """
+ Do we have a pristine-tar commit for a package matching I{archive_regexp}.
+
+ @param archive_regexp: archive name to look for (regexp wildcards allowed)
+ @type archive_regexp: C{str}
+ """
+ return True if self.get_commit(archive_regexp) else False
+
+ def get_commit(self, archive_regexp):
+ """
+ Get the pristine-tar commit of a package matching I{archive_regexp}.
+
+ @param archive_regexp: archive name to look for (regexp wildcards allowed)
+ @type archive_regexp: C{str}
+ """
+ if not self.repo.has_pristine_tar_branch():
+ return None
+
+ regex = ('pristine-tar .* %s' % archive_regexp)
+ commits = self.repo.grep_log(regex, self.branch, merges=False)
+ if commits:
+ commit = commits[-1]
+ gbp.log.debug("Found pristine-tar commit at '%s'" % commit)
+ return commit
+ return None
+
+ def checkout(self, archive, quiet=False):
+ """
+ Checkout an orig archive from pristine-tar branch
+
+ @param archive: the name of the orig archive
+ @type archive: C{str}
+ """
+ self.run_error = 'Pristine-tar couldn\'t checkout "%s": {stderr_or_reason}' % os.path.basename(archive)
+ self.__call__(['checkout', archive], quiet=quiet)
+
+ def commit(self, archive, upstream, quiet=False):
+ """
+ Commit an archive I{archive} to the pristine tar branch using upstream
+ branch ${upstream}.
+
+ @param archive: the archive to commit
+ @type archive: C{str}
+ @param upstream: the upstream branch to diff against
+ @type upstream: C{str}
+ """
+ self.run_error = ("Couldn't commit to '%s' with upstream '%s': {stderr_or_reason}" %
+ (self.branch, upstream))
+ self.__call__(['commit', archive, upstream], quiet=quiet)
+
+ def verify(self, archive, quiet=False):
+ """Verify an archive's I{archive} checksum using to the pristine tar branch"""
+
+ self.run_error = 'Pristine-tar couldn\'t verify "%s": {stderr_or_reason}' % os.path.basename(archive)
+ self.__call__(['verify', archive], quiet=quiet)
diff --git a/gbp/pkg/upstreamsource.py b/gbp/pkg/upstreamsource.py
new file mode 100644
index 0000000..ab9e61d
--- /dev/null
+++ b/gbp/pkg/upstreamsource.py
@@ -0,0 +1,193 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import glob
+import os
+
+import gbp.command_wrappers as gbpc
+
+from gbp.pkg.compressor import Compressor
+from gbp.pkg.pkgpolicy import PkgPolicy
+
+from gbp.errors import GbpError
+
+
+class UpstreamSource(object):
+ """
+ Upstream source. Can be either an unpacked dir, a tarball or another type
+ of archive
+
+ @cvar _orig: are the upstream sources already suitable as an upstream
+ tarball
+ @type _orig: boolean
+ @cvar _path: path to the upstream sources
+ @type _path: string
+ @cvar _unpacked: path to the unpacked source tree
+ @type _unpacked: string
+ """
+ def __init__(self, name, unpacked=None, pkg_policy=PkgPolicy):
+ self._orig = False
+ self._pkg_policy = pkg_policy
+ self._path = name
+ self.unpacked = unpacked
+
+ self._check_orig()
+ if self.is_dir():
+ self.unpacked = self.path
+
+ def _check_orig(self):
+ """
+ Check if upstream source format can be used as orig tarball.
+ This doesn't imply that the tarball is correctly named.
+
+ @return: C{True} if upstream source format is suitable
+ as upstream tarball, C{False} otherwise.
+ @rtype: C{bool}
+ """
+ if self.is_dir():
+ self._orig = False
+ return
+
+ parts = self._path.split('.')
+ try:
+ if parts[-1] == 'tgz':
+ self._orig = True
+ elif parts[-2] == 'tar':
+ if (parts[-1] in Compressor.Opts or
+ parts[-1] in Compressor.Aliases):
+ self._orig = True
+ except IndexError:
+ self._orig = False
+
+ def is_orig(self):
+ """
+ @return: C{True} if sources are suitable as orig tarball,
+ C{False} otherwise
+ @rtype: C{bool}
+ """
+ return self._orig
+
+ def is_dir(self):
+ """
+ @return: C{True} if if upstream sources are an unpacked directory,
+ C{False} otherwise
+ @rtype: C{bool}
+ """
+ return True if os.path.isdir(self._path) else False
+
+ @property
+ def path(self):
+ return self._path.rstrip('/')
+
+ def unpack(self, dir, filters=None):
+ """
+ Unpack packed upstream sources into a given directory
+ (filtering out files specified by filters) and determine the
+ toplevel of the source tree.
+ """
+ if self.is_dir():
+ raise GbpError("Cannot unpack directory %s" % self.path)
+
+ if not filters:
+ filters = []
+
+ if not isinstance(filters, list):
+ raise GbpError("Filters must be a list")
+
+ self._unpack_archive(dir, filters)
+ self.unpacked = self._unpacked_toplevel(dir)
+
+ def _unpack_archive(self, dir, filters):
+ """
+ Unpack packed upstream sources into a given directory
+ allowing to filter out files in case of tar archives.
+ """
+ ext = os.path.splitext(self.path)[1]
+ if ext in [".zip", ".xpi"]:
+ if filters:
+ raise GbpError("Can only filter tar archives: %s", (ext, self.path))
+ self._unpack_zip(dir)
+ else:
+ self._unpack_tar(dir, filters)
+
+ def _unpack_zip(self, dir):
+ try:
+ gbpc.UnpackZipArchive(self.path, dir)()
+ except gbpc.CommandExecFailed:
+ raise GbpError("Unpacking of %s failed" % self.path)
+
+ def _unpacked_toplevel(self, dir):
+ """unpacked archives can contain a leading directory or not"""
+ unpacked = glob.glob('%s/*' % dir)
+ unpacked.extend(glob.glob("%s/.*" % dir)) # include hidden files and folders
+ # Check that dir contains nothing but a single folder:
+ if len(unpacked) == 1 and os.path.isdir(unpacked[0]):
+ return unpacked[0]
+ else:
+ return dir
+
+ def _unpack_tar(self, dir, filters):
+ """
+ Unpack a tarball to I{dir} applying a list of I{filters}. Leave the
+ cleanup to the caller in case of an error.
+ """
+ try:
+ unpackArchive = gbpc.UnpackTarArchive(self.path, dir, filters)
+ unpackArchive()
+ except gbpc.CommandExecFailed:
+ # unpackArchive already printed an error message
+ raise GbpError
+
+ def pack(self, newarchive, filters=None):
+ """
+ Recreate a new archive from the current one
+
+ @param newarchive: the name of the new archive
+ @type newarchive: string
+ @param filters: tar filters to apply
+ @type filters: array of strings
+ @return: the new upstream source
+ @rtype: UpstreamSource
+ """
+ if not self.unpacked:
+ raise GbpError("Need an unpacked source tree to pack")
+
+ if not filters:
+ filters = []
+
+ if not isinstance(filters, list):
+ raise GbpError("Filters must be a list")
+
+ try:
+ unpacked = self.unpacked.rstrip('/')
+ repackArchive = gbpc.PackTarArchive(newarchive,
+ os.path.dirname(unpacked),
+ os.path.basename(unpacked),
+ filters)
+ repackArchive()
+ except gbpc.CommandExecFailed:
+ # repackArchive already printed an error
+ raise GbpError
+ return type(self)(newarchive)
+
+ @staticmethod
+ def known_compressions():
+ return Compressor.Exts.values()
+
+ def guess_version(self, extra_regex=r''):
+ return self._pkg_policy.guess_upstream_src_version(self.path,
+ extra_regex)
diff --git a/gbp/rpm/__init__.py b/gbp/rpm/__init__.py
new file mode 100644
index 0000000..b37dfda
--- /dev/null
+++ b/gbp/rpm/__init__.py
@@ -0,0 +1,1009 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""provides some rpm source package related helpers"""
+
+import os
+import re
+import tempfile
+from optparse import OptionParser
+from collections import defaultdict
+
+import gbp.command_wrappers as gbpc
+from gbp.errors import GbpError
+from gbp.git import GitRepositoryError
+from gbp.patch_series import (PatchSeries, Patch)
+import gbp.log
+from gbp.pkg import (UpstreamSource, Archive)
+from gbp.rpm.policy import RpmPkgPolicy
+from gbp.rpm.linkedlist import LinkedList
+from gbp.rpm.lib_rpm import librpm, get_librpm_log
+
+
+def _decode(s):
+ if s is not None:
+ return s.decode()
+
+
+class NoSpecError(Exception):
+ """Spec file parsing error"""
+ pass
+
+
+class MacroExpandError(Exception):
+ """Macro expansion in spec file failed"""
+ pass
+
+
+class RpmUpstreamSource(UpstreamSource):
+ """Upstream source class for RPM packages"""
+ def __init__(self, name, unpacked=None, **kwargs):
+ super(RpmUpstreamSource, self).__init__(name,
+ unpacked,
+ RpmPkgPolicy,
+ **kwargs)
+
+
+class SrcRpmFile(object):
+ """Keeps all needed data read from a source rpm"""
+ def __init__(self, srpmfile):
+ # Do not required signed packages to be able to import
+ ts_vsflags = 0
+ for flag in ['RPMVSF_NOMD5HEADER', 'RPMVSF_NORSAHEADER',
+ 'RPMVSF_NOSHA1HEADER', 'RPMVSF_NODSAHEADER',
+ 'RPMVSF_NOMD5', 'RPMVSF_NORSA', 'RPMVSF_NOSHA1',
+ 'RPMVSF_NODSA']:
+ try:
+ # Ignore flags not present in different librpm versions
+ ts_vsflags |= getattr(librpm, flag)
+ except AttributeError:
+ pass
+ with open(srpmfile) as srpmfp:
+ self.rpmhdr = librpm.ts(vsflags=ts_vsflags).hdrFromFdno(srpmfp.fileno())
+ self.srpmfile = os.path.abspath(srpmfile)
+
+ @property
+ def version(self):
+ """Get the (downstream) version of the RPM package"""
+ version = dict(upstreamversion=self.rpmhdr[librpm.RPMTAG_VERSION].decode(),
+ release=self.rpmhdr[librpm.RPMTAG_RELEASE].decode())
+ if self.rpmhdr[librpm.RPMTAG_EPOCH] is not None:
+ version['epoch'] = str(self.rpmhdr[librpm.RPMTAG_EPOCH])
+ return version
+
+ @property
+ def name(self):
+ """Get the name of the RPM package"""
+ return self.rpmhdr[librpm.RPMTAG_NAME].decode()
+
+ @property
+ def upstreamversion(self):
+ """Get the upstream version of the RPM package"""
+ return self.rpmhdr[librpm.RPMTAG_VERSION].decode()
+
+ @property
+ def packager(self):
+ """Get the packager of the RPM package"""
+ return _decode(self.rpmhdr[librpm.RPMTAG_PACKAGER])
+
+ def unpack(self, dest_dir):
+ """
+ Unpack the source rpm to tmpdir.
+ Leave the cleanup to the caller in case of an error.
+ """
+ c = gbpc.RunAtCommand('rpm2cpio',
+ [self.srpmfile, '|', 'cpio', '-id'],
+ shell=True, capture_stderr=True)
+ c.run_error = "'%s' failed: {stderr_or_reason}" % (" ".join([c.cmd] + c.args))
+ c(dir=dest_dir)
+
+
+class SpecFile(object):
+ """Class for parsing/modifying spec files"""
+ tag_re = re.compile(r'^(?P<name>[a-z]+)(?P<num>[0-9]+)?\s*:\s*'
+ '(?P<value>\S(.*\S)?)\s*$', flags=re.I)
+ directive_re = re.compile(r'^%(?P<name>[a-z]+)(?P<num>[0-9]+)?'
+ '(\s+(?P<args>.*))?$', flags=re.I)
+ gbptag_re = re.compile(r'^\s*#\s*gbp-(?P<name>[a-z-]+)'
+ '(\s*:\s*(?P<args>\S.*))?$', flags=re.I)
+ # Here "sections" stand for all scripts, scriptlets and other directives,
+ # but not macros
+ section_identifiers = ('package', 'description', 'prep', 'build', 'install',
+ 'clean', 'check', 'pre', 'preun', 'post', 'postun', 'verifyscript',
+ 'files', 'changelog', 'triggerin', 'triggerpostin', 'triggerun',
+ 'triggerpostun')
+
+ def __init__(self, filename=None, filedata=None):
+
+ self._content = LinkedList()
+
+ # Check args: only filename or filedata can be given, not both
+ if filename is None and filedata is None:
+ raise NoSpecError("No filename or raw data given for parsing!")
+ elif filename and filedata:
+ raise NoSpecError("Both filename and raw data given, don't know "
+ "which one to parse!")
+ elif filename:
+ # Load spec file into our special data structure
+ self.specfile = os.path.basename(filename)
+ self.specdir = os.path.dirname(os.path.abspath(filename))
+ try:
+ with open(filename) as spec_file:
+ for line in spec_file.readlines():
+ self._content.append(line)
+ except IOError as err:
+ raise NoSpecError("Unable to read spec file: %s" % err)
+ else:
+ self.specfile = None
+ self.specdir = None
+ for line in filedata.splitlines():
+ self._content.append(line + '\n')
+
+ # Use rpm-python to parse the spec file content
+ self._filtertags = ("excludearch", "excludeos", "exclusivearch",
+ "exclusiveos", "buildarch")
+ self._listtags = self._filtertags + ('source', 'patch',
+ 'requires', 'conflicts', 'recommends',
+ 'suggests', 'supplements', 'enhances',
+ 'provides', 'obsoletes', 'buildrequires',
+ 'buildconflicts', 'buildrecommends',
+ 'buildsuggests', 'buildsupplements',
+ 'buildenhances', 'collections',
+ 'nosource', 'nopatch')
+ self._specinfo = self._parse_filtered_spec(self._filtertags)
+
+ # Other initializations
+ source_header = self._specinfo.packages[0].header
+ self.name = source_header[librpm.RPMTAG_NAME].decode()
+ self.upstreamversion = source_header[librpm.RPMTAG_VERSION].decode()
+ self.release = source_header[librpm.RPMTAG_RELEASE].decode()
+ # rpm-python returns epoch as 'long', convert that to string
+ self.epoch = str(source_header[librpm.RPMTAG_EPOCH]) \
+ if source_header[librpm.RPMTAG_EPOCH] is not None else None
+ self.packager = _decode(source_header[librpm.RPMTAG_PACKAGER])
+ self._tags = {}
+ self._special_directives = defaultdict(list)
+ self._gbp_tags = defaultdict(list)
+
+ # Parse extra info from spec file
+ self._parse_content()
+
+ # Find 'Packager' tag. Needed to circumvent a bug in python-rpm where
+ # spec.sourceHeader[librpm.RPMTAG_PACKAGER] is not reset when a new spec
+ # file is parsed
+ if 'packager' not in self._tags:
+ self.packager = None
+
+ self.orig_src = self._guess_orig_file()
+
+ def _parse_filtered_spec(self, skip_tags):
+ """Parse a filtered spec file in rpm-python"""
+ skip_tags = [tag.lower() for tag in skip_tags]
+ with tempfile.NamedTemporaryFile(prefix='gbp', mode='w+') as filtered:
+ filtered.writelines(str(line) for line in self._content
+ if str(line).split(":")[0].strip().lower() not in skip_tags)
+ filtered.flush()
+ try:
+ # Parse two times to circumvent a rpm-python problem where
+ # macros are not expanded if used before their definition
+ librpm.spec(filtered.name)
+ return librpm.spec(filtered.name)
+ except ValueError as err:
+ rpmlog = get_librpm_log()
+ gbp.log.debug("librpm log:\n %s" %
+ "\n ".join(rpmlog))
+ raise GbpError("RPM error while parsing %s: %s (%s)" %
+ (self.specfile, err, rpmlog[-1]))
+
+ @property
+ def version(self):
+ """Get the (downstream) version"""
+ version = dict(upstreamversion=self.upstreamversion,
+ release=self.release)
+ if self.epoch is not None:
+ version['epoch'] = self.epoch
+ return version
+
+ @property
+ def specpath(self):
+ """Get the dir/filename"""
+ return os.path.join(self.specdir, self.specfile)
+
+ @property
+ def ignorepatches(self):
+ """Get numbers of ignored patches as a sorted list"""
+ if 'ignore-patches' in self._gbp_tags:
+ data = self._gbp_tags['ignore-patches'][-1]['args'].split()
+ return sorted([int(num) for num in data])
+ return []
+
+ def _patches(self):
+ """Get all patch tags as a dict"""
+ if 'patch' not in self._tags:
+ return {}
+ return {patch['num']: patch for patch in self._tags['patch']['lines']}
+
+ def _sources(self):
+ """Get all source tags as a dict"""
+ if 'source' not in self._tags:
+ return {}
+ return {src['num']: src for src in self._tags['source']['lines']}
+
+ def sources(self):
+ """Get all source tags as a dict"""
+ return {src['num']: src['linevalue']
+ for src in self._sources().values()}
+
+ def _macro_replace(self, matchobj):
+ macro_dict = {'name': self.name,
+ 'version': self.upstreamversion,
+ 'release': self.release}
+
+ if matchobj.group(2) in macro_dict:
+ return macro_dict[matchobj.group(2)]
+ raise MacroExpandError("Unknown macro '%s'" % matchobj.group(0))
+
+ def macro_expand(self, text):
+ """
+ Expand the rpm macros (that gbp knows of) in the given text.
+
+ @param text: text to check for macros
+ @type text: C{str}
+ @return: text with macros expanded
+ @rtype: C{str}
+ """
+ # regexp to match '%{macro}' and '%macro'
+ macro_re = re.compile(r'%({)?(?P<macro_name>[a-z_][a-z0-9_]*)(?(1)})', flags=re.I)
+ return macro_re.sub(self._macro_replace, text)
+
+ def write_spec_file(self):
+ """
+ Write, possibly updated, spec to disk
+ """
+ with open(os.path.join(self.specdir, self.specfile), 'w') as spec_file:
+ for line in self._content:
+ spec_file.write(str(line))
+
+ def _parse_tag(self, lineobj):
+ """Parse tag line"""
+
+ line = str(lineobj)
+
+ matchobj = self.tag_re.match(line)
+ if not matchobj:
+ return False
+
+ tagname = matchobj.group('name').lower()
+ tagnum = int(matchobj.group('num')) if matchobj.group('num') else None
+ # 'Source:' tags
+ if tagname == 'source':
+ tagnum = 0 if tagnum is None else tagnum
+ # 'Patch:' tags
+ elif tagname == 'patch':
+ tagnum = -1 if tagnum is None else tagnum
+
+ # Record all tag locations
+ try:
+ header = self._specinfo.packages[0].header
+ tagvalue = header[getattr(librpm, 'RPMTAG_%s' % tagname.upper())]
+ except AttributeError:
+ tagvalue = None
+ # We don't support "multivalue" tags like "Provides:" or "SourceX:"
+ # Rpm python doesn't support many of these, thus the explicit list
+ if isinstance(tagvalue, int):
+ tagvalue = str(tagvalue)
+ elif type(tagvalue) is list or tagname in self._listtags:
+ tagvalue = None
+ elif not tagvalue:
+ # Rpm python doesn't give the following, for reason or another
+ if tagname not in ('buildroot', 'autoprov', 'autoreq',
+ 'autoreqprov') + self._filtertags:
+ gbp.log.warn("BUG: '%s:' tag not found by rpm" % tagname)
+ tagvalue = matchobj.group('value')
+ linerecord = {'line': lineobj,
+ 'num': tagnum,
+ 'linevalue': matchobj.group('value')}
+ if tagname in self._tags:
+ self._tags[tagname]['value'] = tagvalue
+ self._tags[tagname]['lines'].append(linerecord)
+ else:
+ if tagvalue and not isinstance(tagvalue, str):
+ tagvalue = tagvalue.decode()
+ self._tags[tagname] = {'value': tagvalue, 'lines': [linerecord]}
+
+ return tagname
+
+ @staticmethod
+ def _patch_macro_opts(args):
+ """Parse arguments of the '%patch' macro"""
+
+ patchparser = OptionParser(
+ prog="%s internal patch macro opts parser" % __name__,
+ usage="%prog for " + args)
+ patchparser.add_option("-p", dest="strip")
+ patchparser.add_option("-s", dest="silence")
+ patchparser.add_option("-P", dest="patchnum")
+ patchparser.add_option("-b", dest="backup")
+ patchparser.add_option("-E", dest="removeempty")
+ patchparser.add_option("-F", dest="fuzz")
+ arglist = args.split()
+ return patchparser.parse_args(arglist)[0]
+
+ @staticmethod
+ def _setup_macro_opts(args):
+ """Parse arguments of the '%setup' macro"""
+
+ setupparser = OptionParser(
+ prog="%s internal setup macro opts parser" % __name__,
+ usage="%prog for " + args)
+ setupparser.add_option("-n", dest="name")
+ setupparser.add_option("-c", dest="create_dir", action="store_true")
+ setupparser.add_option("-D", dest="no_delete_dir", action="store_true")
+ setupparser.add_option("-T", dest="no_unpack_default",
+ action="store_true")
+ setupparser.add_option("-b", dest="unpack_before")
+ setupparser.add_option("-a", dest="unpack_after")
+ setupparser.add_option("-q", dest="quiet", action="store_true")
+ arglist = args.split()
+ return setupparser.parse_args(arglist)[0]
+
+ def _parse_directive(self, lineobj):
+ """Parse special directive/scriptlet/macro lines"""
+
+ line = str(lineobj)
+ matchobj = self.directive_re.match(line)
+ if not matchobj:
+ return None
+
+ directivename = matchobj.group('name')
+ # '%patch' macros
+ directiveid = None
+ if directivename == 'patch':
+ opts = self._patch_macro_opts(matchobj.group('args'))
+ if matchobj.group('num'):
+ directiveid = int(matchobj.group('num'))
+ elif opts.patchnum:
+ directiveid = int(opts.patchnum)
+ else:
+ directiveid = -1
+
+ # Record special directive/scriptlet/macro locations
+ if directivename in self.section_identifiers + ('setup', 'patch',
+ 'autosetup'):
+ linerecord = {'line': lineobj,
+ 'id': directiveid,
+ 'args': matchobj.group('args')}
+ self._special_directives[directivename].append(linerecord)
+ return directivename
+
+ def _parse_gbp_tag(self, linenum, lineobj):
+ """Parse special git-buildpackage tags"""
+
+ line = str(lineobj)
+ matchobj = self.gbptag_re.match(line)
+ if matchobj:
+ gbptagname = matchobj.group('name').lower()
+ if gbptagname not in ('ignore-patches', 'patch-macros'):
+ gbp.log.info("Found unrecognized Gbp tag on line %s: '%s'" %
+ (linenum, line))
+ if matchobj.group('args'):
+ args = matchobj.group('args').strip()
+ else:
+ args = None
+ record = {'line': lineobj, 'args': args}
+ self._gbp_tags[gbptagname].append(record)
+ return gbptagname
+
+ return None
+
+ def _parse_content(self):
+ """
+ Go through spec file content line-by-line and (re-)parse info from it
+ """
+ in_preamble = True
+ for linenum, lineobj in enumerate(self._content):
+ matched = False
+ if in_preamble:
+ if self._parse_tag(lineobj):
+ continue
+ matched = self._parse_directive(lineobj)
+ if matched:
+ if matched in self.section_identifiers:
+ in_preamble = False
+ continue
+ self._parse_gbp_tag(linenum, lineobj)
+
+ # Update sources info (basically possible macros expanded by rpm)
+ # And, double-check that we parsed spec content correctly
+ patches = self._patches()
+ sources = self._sources()
+ for name, num, typ in self._specinfo.sources:
+ # workaround rpm parsing bug
+ if typ == 1 or typ == 9:
+ if num in sources:
+ sources[num]['linevalue'] = name
+ else:
+ gbp.log.err("BUG: failed to parse all 'Source' tags!")
+ elif typ == 2 or typ == 10:
+ # Patch tag without any number defined is treated by RPM as
+ # having number (2^31-1), we use number -1
+ if num >= pow(2, 30):
+ num = -1
+ if num in patches:
+ patches[num]['linevalue'] = name
+ else:
+ gbp.log.err("BUG: failed to parse all 'Patch' tags!")
+
+ def _delete_tag(self, tag, num):
+ """Delete a tag"""
+ key = tag.lower()
+ tagname = '%s%s' % (tag, num) if num is not None else tag
+ if key not in self._tags:
+ gbp.log.warn("Trying to delete non-existent tag '%s:'" % tag)
+ return None
+
+ sparedlines = []
+ prev = None
+ for line in self._tags[key]['lines']:
+ if line['num'] == num:
+ gbp.log.debug("Removing '%s:' tag from spec" % tagname)
+ prev = self._content.delete(line['line'])
+ else:
+ sparedlines.append(line)
+ self._tags[key]['lines'] = sparedlines
+ if not self._tags[key]['lines']:
+ self._tags.pop(key)
+ return prev
+
+ def _set_tag(self, tag, num, value, insertafter):
+ """Set a tag value"""
+ key = tag.lower()
+ tagname = '%s%s' % (tag, num) if num is not None else tag
+ value = value.strip()
+ if not value:
+ raise GbpError("Cannot set empty value to '%s:' tag" % tag)
+
+ # Check type of tag, we don't support values for 'multivalue' tags
+ try:
+ header = self._specinfo.packages[0].header
+ tagvalue = header[getattr(librpm, 'RPMTAG_%s' % tagname.upper())]
+ except AttributeError:
+ tagvalue = None
+ tagvalue = None if type(tagvalue) is list else value
+
+ # Try to guess the correct indentation from the previous or next tag
+ indent_re = re.compile(r'^([a-z]+([0-9]+)?\s*:\s*)', flags=re.I)
+ match = indent_re.match(str(insertafter))
+ if not match:
+ match = indent_re.match(str(insertafter.next))
+ indent = 12 if not match else len(match.group(1))
+ text = '%-*s%s\n' % (indent, '%s:' % tagname, value)
+ if key in self._tags:
+ self._tags[key]['value'] = tagvalue
+ for line in reversed(self._tags[key]['lines']):
+ if line['num'] == num:
+ gbp.log.debug("Updating '%s:' tag in spec" % tagname)
+ line['line'].set_data(text)
+ line['linevalue'] = value
+ return line['line']
+
+ gbp.log.debug("Adding '%s:' tag after '%s...' line in spec" %
+ (tagname, str(insertafter)[0:20]))
+ line = self._content.insert_after(insertafter, text)
+ linerec = {'line': line, 'num': num, 'linevalue': value}
+ if key in self._tags:
+ self._tags[key]['lines'].append(linerec)
+ else:
+ self._tags[key] = {'value': tagvalue, 'lines': [linerec]}
+ return line
+
+ def set_tag(self, tag, num, value, insertafter=None):
+ """Update a tag in spec file content"""
+ key = tag.lower()
+ tagname = '%s%s' % (tag, num) if num is not None else tag
+ if key in ('patch', 'vcs'):
+ if key in self._tags:
+ insertafter = key
+ elif insertafter not in self._tags:
+ insertafter = 'name'
+ after_line = self._tags[insertafter]['lines'][-1]['line']
+ if value:
+ self._set_tag(tag, num, value, after_line)
+ elif key in self._tags:
+ self._delete_tag(tag, num)
+ else:
+ raise GbpError("Setting '%s:' tag not supported" % tagname)
+
+ def _delete_special_macro(self, name, identifier):
+ """Delete a special macro line in spec file content"""
+ if name != 'patch':
+ raise GbpError("Deleting '%s:' macro not supported" % name)
+
+ key = name.lower()
+ fullname = '%%%s%s' % (name, identifier)
+ sparedlines = []
+ prev = None
+ for line in self._special_directives[key]:
+ if line['id'] == identifier:
+ gbp.log.debug("Removing '%s' macro from spec" % fullname)
+ prev = self._content.delete(line['line'])
+ else:
+ sparedlines.append(line)
+ self._special_directives[key] = sparedlines
+ if not prev:
+ gbp.log.warn("Tried to delete non-existent macro '%s'" % fullname)
+ return prev
+
+ def _set_special_macro(self, name, identifier, args, insertafter):
+ """Update a special macro line in spec file content"""
+ key = name.lower()
+ fullname = '%%%s%s' % (name, identifier)
+ if key != 'patch':
+ raise GbpError("Setting '%s' macro not supported" % name)
+
+ updated = 0
+ text = "%%%s%d %s\n" % (name, identifier, args)
+ for line in self._special_directives[key]:
+ if line['id'] == identifier:
+ gbp.log.debug("Updating '%s' macro in spec" % fullname)
+ line['args'] = args
+ line['line'].set_data(text)
+ ret = line['line']
+ updated += 1
+ if not updated:
+ gbp.log.debug("Adding '%s' macro after '%s...' line in spec" %
+ (fullname, str(insertafter)[0:20]))
+ ret = self._content.insert_after(insertafter, text)
+ linerec = {'line': ret, 'id': identifier, 'args': args}
+ self._special_directives[key].append(linerec)
+ return ret
+
+ def _set_section(self, name, text):
+ """Update/create a complete section in spec file."""
+ if name not in self.section_identifiers:
+ raise GbpError("Not a valid section directive: '%s'" % name)
+ # Delete section, if it exists
+ if name in self._special_directives:
+ if len(self._special_directives[name]) > 1:
+ raise GbpError("Multiple %%%s sections found, don't know "
+ "which to update" % name)
+ line = self._special_directives[name][0]['line']
+ gbp.log.debug("Removing content of %s section" % name)
+ while line.next:
+ match = self.directive_re.match(str(line.next))
+ if match and match.group('name') in self.section_identifiers:
+ break
+ self._content.delete(line.next)
+ else:
+ gbp.log.debug("Adding %s section to the end of spec file" % name)
+ line = self._content.append('%%%s\n' % name)
+ linerec = {'line': line, 'id': None, 'args': None}
+ self._special_directives[name] = [linerec]
+ # Add new lines
+ gbp.log.debug("Updating content of %s section" % name)
+ for linetext in text.splitlines():
+ line = self._content.insert_after(line, linetext + '\n')
+
+ def set_changelog(self, text):
+ """Update or create the %changelog section"""
+ self._set_section('changelog', text)
+
+ def get_changelog(self):
+ """Get the %changelog section"""
+ text = ''
+ if 'changelog' in self._special_directives:
+ line = self._special_directives['changelog'][0]['line']
+ while line.next:
+ line = line.next
+ match = self.directive_re.match(str(line))
+ if match and match.group('name') in self.section_identifiers:
+ break
+ text += str(line)
+ return text
+
+ def update_patches(self, patches, commands):
+ """Update spec with new patch tags and patch macros"""
+ # Remove non-ignored patches
+ tag_prev = None
+ macro_prev = None
+ ignored = self.ignorepatches
+ # Remove 'Patch:Í„' tags
+ for tag in self._patches().values():
+ if not tag['num'] in ignored:
+ tag_prev = self._delete_tag('patch', tag['num'])
+ # Remove a preceding comment if it seems to originate from GBP
+ if re.match("^\s*#.*patch.*auto-generated",
+ str(tag_prev), flags=re.I):
+ tag_prev = self._content.delete(tag_prev)
+
+ # Remove '%patch:' macros
+ for macro in self._special_directives['patch']:
+ if not macro['id'] in ignored:
+ macro_prev = self._delete_special_macro('patch', macro['id'])
+ # Remove surrounding if-else
+ macro_next = macro_prev.next
+ if (str(macro_prev).startswith('%if') and
+ str(macro_next).startswith('%endif')):
+ self._content.delete(macro_next)
+ macro_prev = self._content.delete(macro_prev)
+
+ # Remove a preceding comment line if it ends with '.patch' or
+ # '.diff' plus an optional compression suffix
+ if re.match("^\s*#.+(patch|diff)(\.(gz|bz2|xz|lzma))?\s*$",
+ str(macro_prev), flags=re.I):
+ macro_prev = self._content.delete(macro_prev)
+
+ if len(patches) == 0:
+ return
+
+ # Determine where to add Patch tag lines
+ if tag_prev:
+ gbp.log.debug("Adding 'Patch' tags in place of the removed tags")
+ tag_line = tag_prev
+ elif 'patch' in self._tags:
+ gbp.log.debug("Adding new 'Patch' tags after the last 'Patch' tag")
+ tag_line = self._tags['patch']['lines'][-1]['line']
+ elif 'source' in self._tags:
+ gbp.log.debug("Didn't find any old 'Patch' tags, adding new "
+ "patches after the last 'Source' tag.")
+ tag_line = self._tags['source']['lines'][-1]['line']
+ else:
+ gbp.log.debug("Didn't find any old 'Patch' or 'Source' tags, "
+ "adding new patches after the last 'Name' tag.")
+ tag_line = self._tags['name']['lines'][-1]['line']
+
+ # Determine where to add %patch macro lines
+ if self._special_directives['autosetup']:
+ gbp.log.debug("Found '%autosetup, skip adding %patch macros")
+ macro_line = None
+ elif 'patch-macros' in self._gbp_tags:
+ gbp.log.debug("Adding '%patch' macros after the start marker")
+ macro_line = self._gbp_tags['patch-macros'][-1]['line']
+ elif macro_prev:
+ gbp.log.debug("Adding '%patch' macros in place of the removed "
+ "macros")
+ macro_line = macro_prev
+ elif self._special_directives['patch']:
+ gbp.log.debug("Adding new '%patch' macros after the last existing"
+ "'%patch' macro")
+ macro_line = self._special_directives['patch'][-1]['line']
+ elif self._special_directives['setup']:
+ gbp.log.debug("Didn't find any old '%patch' macros, adding new "
+ "patches after the last '%setup' macro")
+ macro_line = self._special_directives['setup'][-1]['line']
+ elif self._special_directives['prep']:
+ gbp.log.warn("Didn't find any old '%patch' or '%setup' macros, "
+ "adding new patches directly after '%prep' directive")
+ macro_line = self._special_directives['prep'][-1]['line']
+ else:
+ raise GbpError("Couldn't determine where to add '%patch' macros")
+
+ startnum = sorted(ignored)[-1] + 1 if ignored else 0
+ gbp.log.debug("Starting autoupdate patch numbering from %s" % startnum)
+ # Add a comment indicating gbp generated patch tags
+ comment_text = "# Patches auto-generated by git-buildpackage:\n"
+ tag_line = self._content.insert_after(tag_line, comment_text)
+ for ind, patch in enumerate(patches):
+ cmds = commands[patch] if patch in commands else {}
+ patchnum = startnum + ind
+ tag_line = self._set_tag("Patch", patchnum, patch, tag_line)
+
+ # Add '%patch' macro and a preceding comment line
+ if macro_line is not None:
+ comment_text = "# %s\n" % patch
+ macro_line = self._content.insert_after(macro_line, comment_text)
+ macro_line = self._set_special_macro('patch', patchnum, '-p1',
+ macro_line)
+ for cmd, args in cmds.items():
+ if cmd in ('if', 'ifarch'):
+ self._content.insert_before(macro_line, '%%%s %s\n' %
+ (cmd, args))
+ macro_line = self._content.insert_after(macro_line,
+ '%endif\n')
+ # We only support one command per patch, for now
+ break
+
+ def patchseries(self, unapplied=False, ignored=False):
+ """Return non-ignored patches of the RPM as a gbp patchseries"""
+ series = PatchSeries()
+
+ ignored = set() if ignored else set(self.ignorepatches)
+ tags = dict([(k, v) for k, v in self._patches().items() if k not in ignored])
+
+ if self._special_directives['autosetup']:
+ # Return all patchses if %autosetup is used
+ for num in sorted(tags):
+ filename = os.path.basename(tags[num]['linevalue'])
+ series.append(Patch(os.path.join(self.specdir, filename)))
+ else:
+ applied = []
+ for macro in self._special_directives['patch']:
+ if macro['id'] in tags:
+ applied.append((macro['id'], macro['args']))
+
+ # Put all patches that are applied first in the series
+ for num, args in applied:
+ opts = self._patch_macro_opts(args)
+ strip = int(opts.strip) if opts.strip else 0
+ filename = os.path.basename(tags[num]['linevalue'])
+ series.append(Patch(os.path.join(self.specdir, filename),
+ strip=strip))
+ # Finally, append all unapplied patches to the series, if requested
+ if unapplied:
+ applied_nums = set([num for num, _args in applied])
+ unapplied = set(tags.keys()).difference(applied_nums)
+ for num in sorted(unapplied):
+ filename = os.path.basename(tags[num]['linevalue'])
+ series.append(Patch(os.path.join(self.specdir, filename),
+ strip=0))
+ return series
+
+ def _guess_orig_prefix(self, orig):
+ """Guess prefix for the orig file"""
+ # Make initial guess about the prefix in the archive
+ filename = orig['filename']
+ name, version = RpmPkgPolicy.guess_upstream_src_version(filename)
+ if name and version:
+ prefix = "%s-%s/" % (name, version)
+ else:
+ prefix = orig['filename_base'] + "/"
+
+ # Refine our guess about the prefix
+ for macro in self._special_directives['setup']:
+ args = macro['args']
+ opts = self._setup_macro_opts(args)
+ srcnum = None
+ if opts.no_unpack_default:
+ if opts.unpack_before:
+ srcnum = int(opts.unpack_before)
+ elif opts.unpack_after:
+ srcnum = int(opts.unpack_after)
+ else:
+ srcnum = 0
+ if srcnum == orig['num']:
+ if opts.create_dir:
+ prefix = ''
+ elif opts.name:
+ try:
+ prefix = self.macro_expand(opts.name) + '/'
+ except MacroExpandError as err:
+ gbp.log.warn("Couldn't determine prefix from %%setup "
+ "macro (%s). Using filename base as a "
+ "fallback" % err)
+ prefix = orig['filename_base'] + '/'
+ else:
+ # RPM default
+ prefix = "%s-%s/" % (self.name, self.upstreamversion)
+ break
+ return prefix
+
+ def _guess_orig_file(self):
+ """
+ Try to guess the name of the primary upstream/source archive.
+ Returns a dict with all the relevant information.
+ """
+ orig = None
+ sources = self.sources()
+ for num, filename in sorted(sources.items()):
+ src = {'num': num, 'filename': os.path.basename(filename),
+ 'uri': filename}
+ src['filename_base'], src['archive_fmt'], src['compression'] = \
+ Archive.parse_filename(os.path.basename(filename))
+ if (src['filename_base'].startswith(self.name) and
+ src['archive_fmt']):
+ # Take the first archive that starts with pkg name
+ orig = src
+ break
+ # otherwise we take the first archive
+ elif not orig and src['archive_fmt']:
+ orig = src
+ # else don't accept
+ if orig:
+ orig['prefix'] = self._guess_orig_prefix(orig)
+
+ return orig
+
+
+def parse_srpm(srpmfile):
+ """parse srpm by creating a SrcRpmFile object"""
+ try:
+ srcrpm = SrcRpmFile(srpmfile)
+ except IOError as err:
+ raise GbpError("Error reading src.rpm file: %s" % err)
+ except librpm.error as err:
+ raise GbpError("RPM error while reading src.rpm: %s" % err)
+
+ return srcrpm
+
+
+def guess_spec_fn(file_list, preferred_name=None):
+ """Guess spec file from a list of filenames"""
+ specs = []
+ for filepath in file_list:
+ filename = os.path.basename(filepath)
+ # Stop at the first file matching the preferred name
+ if filename == preferred_name:
+ gbp.log.debug("Found a preferred spec file %s" % filepath)
+ specs = [filepath]
+ break
+ if filename.endswith(".spec"):
+ gbp.log.debug("Found spec file %s" % filepath)
+ specs.append(filepath)
+ if len(specs) == 0:
+ raise NoSpecError("No spec file found.")
+ elif len(specs) > 1:
+ raise NoSpecError("Multiple spec files found (%s), don't know which "
+ "to use." % ', '.join(specs))
+ return specs[0]
+
+
+def guess_spec(topdir, recursive=True, preferred_name=None):
+ """Guess a spec file"""
+ file_list = []
+ if not topdir:
+ topdir = '.'
+ for root, dirs, files in os.walk(topdir):
+ file_list.extend([os.path.join(root, fname) for fname in files])
+ if not recursive:
+ del dirs[:]
+ # Skip .git dir in any case
+ if '.git' in dirs:
+ dirs.remove('.git')
+ return SpecFile(os.path.abspath(guess_spec_fn(file_list, preferred_name)))
+
+
+def guess_spec_repo(repo, treeish, topdir='', recursive=True, preferred_name=None):
+ """
+ Try to find/parse the spec file from a given git treeish.
+ """
+ topdir = topdir.rstrip('/') + ('/') if topdir else ''
+ try:
+ file_list = [nam.decode() for (mod, typ, sha, nam) in
+ repo.list_tree(treeish, recursive, topdir) if typ == 'blob']
+ except GitRepositoryError as err:
+ raise NoSpecError("Cannot find spec file from treeish %s, Git error: %s"
+ % (treeish, err))
+ spec_path = guess_spec_fn(file_list, preferred_name)
+ return spec_from_repo(repo, treeish, spec_path)
+
+
+def spec_from_repo(repo, treeish, spec_path):
+ """Get and parse a spec file from a give Git treeish"""
+ try:
+ spec = SpecFile(filedata=repo.show('%s:%s' % (treeish, spec_path)).decode())
+ spec.specdir = os.path.dirname(spec_path)
+ spec.specfile = os.path.basename(spec_path)
+ return spec
+ except GitRepositoryError as err:
+ raise NoSpecError("Git error: %s" % err)
+
+
+def string_to_int(val_str):
+ """
+ Convert string of possible unit identifier to int.
+
+ @param val_str: value to be converted
+ @type val_str: C{str}
+ @return: value as integer
+ @rtype: C{int}
+
+ >>> string_to_int("1234")
+ 1234
+ >>> string_to_int("123k")
+ 125952
+ >>> string_to_int("1234K")
+ 1263616
+ >>> string_to_int("1M")
+ 1048576
+ """
+ units = {'k': 1024,
+ 'm': 1024**2,
+ 'g': 1024**3,
+ 't': 1024**4}
+
+ if val_str[-1].lower() in units:
+ return int(val_str[:-1]) * units[val_str[-1].lower()]
+ else:
+ return int(val_str)
+
+
+def split_version_str(version):
+ """
+ Parse full version string and split it into individual "version
+ components", i.e. upstreamversion, epoch and release
+
+ @param version: full version of a package
+ @type version: C{str}
+ @return: individual version components
+ @rtype: C{dict}
+
+ >>> sorted(split_version_str("1").items())
+ [('epoch', None), ('release', None), ('upstreamversion', '1')]
+ >>> sorted(split_version_str("1.2.3-5.3").items())
+ [('epoch', None), ('release', '5.3'), ('upstreamversion', '1.2.3')]
+ >>> sorted(split_version_str("3:1.2.3").items())
+ [('epoch', '3'), ('release', None), ('upstreamversion', '1.2.3')]
+ >>> sorted(split_version_str("3:1-0").items())
+ [('epoch', '3'), ('release', '0'), ('upstreamversion', '1')]
+ """
+ ret = {'epoch': None, 'upstreamversion': None, 'release': None}
+
+ e_vr = version.split(":", 1)
+ if len(e_vr) == 1:
+ v_r = e_vr[0].split("-", 1)
+ else:
+ ret['epoch'] = e_vr[0]
+ v_r = e_vr[1].split("-", 1)
+ ret['upstreamversion'] = v_r[0]
+ if len(v_r) > 1:
+ ret['release'] = v_r[1]
+
+ return ret
+
+
+def compose_version_str(evr):
+ """
+ Compose a full version string from individual "version components",
+ i.e. epoch, version and release
+
+ @param evr: dict of version components
+ @type evr: C{dict} of C{str}
+ @return: full version
+ @rtype: C{str}
+
+ >>> compose_version_str({'epoch': '', 'upstreamversion': '1.0'})
+ '1.0'
+ >>> compose_version_str({'epoch': '2', 'upstreamversion': '1.0', 'release': None})
+ '2:1.0'
+ >>> compose_version_str({'epoch': None, 'upstreamversion': '1', 'release': '0'})
+ '1-0'
+ >>> compose_version_str({'epoch': '2', 'upstreamversion': '1.0', 'release': '2.3'})
+ '2:1.0-2.3'
+ >>> compose_version_str({'epoch': '2', 'upstreamversion': '', 'release': '2.3'})
+ """
+ if 'upstreamversion' in evr and evr['upstreamversion']:
+ version = ""
+ if 'epoch' in evr and evr['epoch']:
+ version += "%s:" % evr['epoch']
+ version += evr['upstreamversion']
+ if 'release' in evr and evr['release']:
+ version += "-%s" % evr['release']
+ if version:
+ return version
+ return None
+
+
+def filter_version(evr, *keys):
+ """
+ Remove entry from the version dict
+
+ @param evr: dict of version components
+ @type evr: C{dict} of C{str}
+ @param keys: keys to remove
+ @type keys: C{str}s
+ @return: new version dict
+ @rtype: C{dict} of C{str}
+
+ >>> sorted(list(filter_version({'epoch': 'foo', 'upstreamversion': 'bar', 'vendor': 'baz'}, 'vendor').keys()))
+ ['epoch', 'upstreamversion']
+ >>> list(filter_version({'epoch': 'foo', 'upstreamversion': 'bar', 'revision': 'baz'}, 'epoch', 'revision').keys())
+ ['upstreamversion']
+ """
+ return {k: evr[k] for k in evr if k not in keys}
+
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/rpm/changelog.py b/gbp/rpm/changelog.py
new file mode 100644
index 0000000..7f59816
--- /dev/null
+++ b/gbp/rpm/changelog.py
@@ -0,0 +1,261 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2014-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""An RPM Changelog"""
+
+import locale
+import datetime
+import re
+
+from functools import wraps
+
+import gbp.log
+
+
+def c_locale(category):
+ def _decorator(f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ saved = locale.setlocale(category, None)
+ locale.setlocale(category, 'C')
+ ret = f(*args, **kwargs)
+ locale.setlocale(category, saved)
+ return ret
+ return wrapper
+ return _decorator
+
+
+class ChangelogError(Exception):
+ """Problem parsing changelog"""
+ pass
+
+
+class _ChangelogHeader(object):
+ """The header part of one changelog section"""
+
+ def __init__(self, pkgpolicy, time=None, **kwargs):
+ self._pkgpolicy = pkgpolicy
+ self._data = {'time': time}
+ self._data.update(kwargs)
+
+ def __contains__(self, key):
+ return key in self._data
+
+ def __getitem__(self, key):
+ if key in self._data:
+ return self._data[key]
+ return None
+
+ @c_locale(locale.LC_TIME)
+ def __str__(self):
+ keys = dict(self._data)
+ keys['time'] = self._data['time'].strftime(
+ self._pkgpolicy.Changelog.header_time_format)
+ try:
+ return self._pkgpolicy.Changelog.header_format % keys + '\n'
+ except KeyError as err:
+ raise ChangelogError("Unable to format changelog header, missing "
+ "property %s" % err)
+
+
+class _ChangelogEntry(object):
+ """An entry (one 'change') in an RPM changelog"""
+
+ def __init__(self, pkgpolicy, author, text):
+ """
+ @param pkgpolicy: RPM packaging policy
+ @type pkgpolicy: L{RpmPkgPolicy}
+ @param author: author of the change
+ @type author: C{str}
+ @param text: message of the changelog entry
+ @type text: C{str} or C{list} of C{str}
+ """
+ self._pkgpolicy = pkgpolicy
+ self.author = author
+ if isinstance(text, str):
+ self._text = text.splitlines()
+ else:
+ self._text = text
+ # Strip trailing empty lines
+ while text and not text[-1].strip():
+ text.pop()
+
+ def __str__(self):
+ # Currently no (re-)formatting, just raw text
+ string = ""
+ for line in self._text:
+ string += line + '\n'
+ return string
+
+
+class _ChangelogSection(object):
+ """One section (set of changes) in an RPM changelog"""
+
+ def __init__(self, pkgpolicy, *args, **kwargs):
+ self._pkgpolicy = pkgpolicy
+ self.header = _ChangelogHeader(pkgpolicy, *args, **kwargs)
+ self.entries = []
+ self._trailer = '\n'
+
+ def __str__(self):
+ text = str(self.header)
+ for entry in self.entries:
+ text += str(entry)
+ # Add "section separator"
+ text += self._trailer
+ return text
+
+ def set_header(self, *args, **kwargs):
+ """Change the section header"""
+ self.header = _ChangelogHeader(self._pkgpolicy, *args, **kwargs)
+
+ def append_entry(self, entry):
+ """Add a new entry to the end of the list of entries"""
+ self.entries.append(entry)
+ return entry
+
+
+class Changelog(object):
+ """An RPM changelog"""
+
+ def __init__(self, pkgpolicy):
+ self._pkgpolicy = pkgpolicy
+ self.sections = []
+
+ def __str__(self):
+ string = ""
+ for section in self.sections:
+ string += str(section)
+ return string
+
+ def create_entry(self, *args, **kwargs):
+ """Create and return new entry object"""
+ return _ChangelogEntry(self._pkgpolicy, *args, **kwargs)
+
+ def add_section(self, *args, **kwargs):
+ """Add new empty section"""
+ section = _ChangelogSection(self._pkgpolicy, *args, **kwargs)
+ self.sections.insert(0, section)
+ return section
+
+
+class ChangelogParser(object):
+ """Parser for RPM changelogs"""
+
+ def __init__(self, pkgpolicy):
+ self._pkgpolicy = pkgpolicy
+ self.section_match_re = pkgpolicy.Changelog.section_match_re
+ self.section_split_re = pkgpolicy.Changelog.section_split_re
+ self.header_split_re = pkgpolicy.Changelog.header_split_re
+ self.header_name_split_re = pkgpolicy.Changelog.header_name_split_re
+ self.body_name_re = pkgpolicy.Changelog.body_name_re
+
+ def raw_parse_string(self, string):
+ """Parse changelog - only splits out raw changelog sections."""
+ changelog = Changelog(self._pkgpolicy)
+ ch_section = ""
+ for line in string.splitlines():
+ if re.match(self.section_match_re, line, re.M | re.S):
+ if ch_section:
+ changelog.sections.append(ch_section)
+ ch_section = line + '\n'
+ elif ch_section:
+ ch_section += line + '\n'
+ else:
+ raise ChangelogError("First line in changelog is invalid")
+ if ch_section:
+ changelog.sections.append(ch_section)
+ return changelog
+
+ def raw_parse_file(self, changelog):
+ """Parse changelog file - only splits out raw changelog sections."""
+ try:
+ with open(changelog) as ch_file:
+ return self.raw_parse_string(ch_file.read())
+ except IOError as err:
+ raise ChangelogError("Unable to read changelog file: %s" % err)
+
+ @c_locale(locale.LC_TIME)
+ def _parse_section_header(self, text):
+ """Parse one changelog section header"""
+ # Try to split out time stamp and "changelog name"
+ match = re.match(self.header_split_re, text, re.M)
+ if not match:
+ raise ChangelogError("Unable to parse changelog header: %s" % text)
+ try:
+ time = datetime.datetime.strptime(match.group('ch_time'),
+ "%a %b %d %Y")
+ except ValueError:
+ raise ChangelogError("Unable to parse changelog header: invalid "
+ "timestamp '%s'" % match.group('ch_time'))
+ # Parse "name" part which consists of name and/or email and an optional
+ # revision
+ name_text = match.group('ch_name')
+ match = re.match(self.header_name_split_re, name_text)
+ if not match:
+ raise ChangelogError("Unable to parse changelog header: invalid "
+ "name / revision '%s'" % name_text)
+ kwargs = match.groupdict()
+ return _ChangelogSection(self._pkgpolicy, time=time, **kwargs)
+
+ def _create_entry(self, author, text):
+ """Create a new changelog entry"""
+ return _ChangelogEntry(self._pkgpolicy, author=author, text=text)
+
+ def _parse_section_entries(self, text, default_author):
+ """Parse entries from a string and add them to a section"""
+ entries = []
+ entry_text = []
+ author = default_author
+ for line in text.splitlines():
+ match = re.match(self.body_name_re, line)
+ if match:
+ if entry_text:
+ entries.append(self._create_entry(author, entry_text))
+ author = match.group('name')
+ else:
+ if line.startswith("-"):
+ if entry_text:
+ entries.append(self._create_entry(author, entry_text))
+ entry_text = [line]
+ else:
+ if not entry_text:
+ gbp.log.info("First changelog entry (%s) is garbled, "
+ "entries should start with a dash ('-')" %
+ line)
+ entry_text.append(line)
+ if entry_text:
+ entries.append(self._create_entry(author, entry_text))
+
+ return entries
+
+ def parse_section(self, text):
+ """Parse one section"""
+ # Check that the first line(s) look like a changelog header
+ match = re.match(self.section_split_re, text, re.M | re.S)
+ if not match:
+ raise ChangelogError("Doesn't look like changelog header: %s..." %
+ text.splitlines()[0])
+ # Parse header
+ section = self._parse_section_header(match.group('ch_header'))
+ header = section.header
+ # Parse entries
+ default_author = header['name'] if 'name' in header else header['email']
+ for entry in self._parse_section_entries(match.group('ch_body'),
+ default_author):
+ section.append_entry(entry)
+
+ return section
diff --git a/gbp/rpm/git.py b/gbp/rpm/git.py
new file mode 100644
index 0000000..9fe38ba
--- /dev/null
+++ b/gbp/rpm/git.py
@@ -0,0 +1,108 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+
+from gbp.format import format_str
+from gbp.errors import GbpError
+from gbp.pkg.git import PkgGitRepository, GitRepositoryError # noqa: F401
+from gbp.pkg.pristinetar import PristineTar
+from gbp.rpm import compose_version_str
+
+
+class RpmGitRepository(PkgGitRepository):
+ """A git repository that holds the source of an RPM package"""
+
+ def __init__(self, path):
+ super(RpmGitRepository, self).__init__(path)
+ self.pristine_tar = PristineTar(self)
+
+ def find_version(self, format, str_fields):
+ """
+ Check if a certain version is stored in this repo and return the SHA1
+ of the related commit. That is, an annotated tag is dereferenced to the
+ commit object it points to.
+
+ @param format: tag pattern
+ @type format: C{str}
+ @param str_fields: arguments for format string ('upstreamversion', 'release', 'vendor'...)
+ @type str_fields: C{dict} of C{str}
+ @return: sha1 of the commit the tag references to
+ """
+ try:
+ tag = self.version_to_tag(format, str_fields)
+ except GbpError:
+ return None
+ if self.has_tag(tag): # new tags are injective
+ # dereference to a commit object
+ return self.rev_parse("%s^0" % tag)
+ return None
+
+ @staticmethod
+ def version_to_tag(format, str_fields):
+ """
+ Generate a tag from a given format and a version
+
+ @param format: tag pattern
+ @type format: C{str}
+ @param str_fields: arguments for format string ('upstreamversion', 'release', 'vendor'...)
+ @type str_fields: C{dict} of C{str}
+ @return: version tag
+
+ >>> RpmGitRepository.version_to_tag("packaging/%(version)s", dict(epoch='0', upstreamversion='0~0'))
+ 'packaging/0%0_0'
+ >>> RpmGitRepository.version_to_tag("%(vendor)s/v%(version)s", dict(upstreamversion='1.0', release='2', vendor="myvendor"))
+ 'myvendor/v1.0-2'
+ """
+ version_tag = format_str(format,
+ dict(str_fields,
+ version=compose_version_str(str_fields)))
+ return RpmGitRepository._sanitize_tag(version_tag)
+
+ @staticmethod
+ def _sanitize_tag(tag):
+ """sanitize a version so git accepts it as a tag
+
+ >>> RpmGitRepository._sanitize_tag("0.0.0")
+ '0.0.0'
+ >>> RpmGitRepository._sanitize_tag("0.0~0")
+ '0.0_0'
+ >>> RpmGitRepository._sanitize_tag("0:0.0")
+ '0%0.0'
+ >>> RpmGitRepository._sanitize_tag("0%0~0")
+ '0%0_0'
+ """
+ return tag.replace('~', '_').replace(':', '%')
+
+ @property
+ def pristine_tar_branch(self):
+ """
+ The name of the pristine-tar branch, whether it already exists or
+ not.
+ """
+ return PristineTar.branch
+
+ def has_pristine_tar_branch(self):
+ """
+ Whether the repo has a I{pristine-tar} branch.
+
+ @return: C{True} if the repo has pristine-tar commits already, C{False}
+ otherwise
+ @rtype: C{Bool}
+ """
+ return True if self.has_branch(self.pristine_tar_branch) else False
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/rpm/lib_rpm.py b/gbp/rpm/lib_rpm.py
new file mode 100644
index 0000000..5fc0354
--- /dev/null
+++ b/gbp/rpm/lib_rpm.py
@@ -0,0 +1,46 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Wrapper module for librpm"""
+
+import tempfile
+
+import gbp.log
+from gbp.rpm.policy import RpmPkgPolicy
+
+try:
+ # Try to load special RPM lib to be used for GBP (only)
+ librpm = __import__(RpmPkgPolicy.python_rpmlib_module_name)
+except ImportError:
+ gbp.log.warn("Failed to import '%s' as rpm python module, using host's "
+ "default rpm library instead" %
+ RpmPkgPolicy.python_rpmlib_module_name)
+ import rpm as librpm
+
+# Module initialization
+_rpmlog = tempfile.NamedTemporaryFile(prefix='gbp_rpmlog')
+_rpmlogfd = _rpmlog.file
+librpm.setVerbosity(librpm.RPMLOG_INFO)
+librpm.setLogFile(_rpmlogfd)
+
+
+def get_librpm_log(truncate=True):
+ """Get rpmlib log output"""
+ _rpmlogfd.seek(0)
+ log = [line.strip() for line in _rpmlogfd.readlines()]
+ if truncate:
+ _rpmlogfd.truncate(0)
+ return log
diff --git a/gbp/rpm/linkedlist.py b/gbp/rpm/linkedlist.py
new file mode 100644
index 0000000..ca7e34c
--- /dev/null
+++ b/gbp/rpm/linkedlist.py
@@ -0,0 +1,216 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Simple implementation of a doubly linked list"""
+
+import collections
+
+import gbp.log
+
+
+class LinkedListNode(object):
+ """Node of the linked list"""
+
+ def __init__(self, data="", prev_node=None, next_node=None):
+ self.prev = prev_node
+ self.next = next_node
+ self._data = data
+
+ def __str__(self):
+ return str(self.data)
+
+ @property
+ def data(self):
+ """Get data stored into node"""
+ if self._data is None:
+ gbp.log.debug("BUG: referencing a deleted node!")
+ return("")
+ return self._data
+
+ def set_data(self, data):
+ """
+ Set data stored into node
+
+ >>> node = LinkedListNode('foo')
+ >>> node.data
+ 'foo'
+ >>> node.set_data('bar')
+ >>> node.data
+ 'bar'
+ >>> node.set_data(None)
+ >>> node.data
+ ''
+ """
+ if data is None:
+ gbp.log.debug("BUG: trying to store 'None', not allowed")
+ data = ""
+ self._data = data
+
+ def delete(self):
+ """Delete node"""
+ if self.prev:
+ self.prev.next = self.next
+ if self.next:
+ self.next.prev = self.prev
+ self._data = None
+
+
+class LinkedListIterator(collections.Iterator):
+ """Iterator for the linked list"""
+
+ def __init__(self, obj):
+ self._next = obj.first
+
+ def __next__(self):
+ ret = self._next
+ if ret:
+ self._next = ret.next
+ else:
+ raise StopIteration
+ return ret
+
+ def next(self):
+ return self.__next__()
+
+
+class LinkedList(collections.Iterable):
+ """Doubly linked list"""
+
+ def __init__(self):
+ self._first = None
+ self._last = None
+
+ def __iter__(self):
+ return LinkedListIterator(self)
+
+ def __len__(self):
+ for num, data in enumerate(self):
+ pass
+ return num + 1
+
+ @property
+ def first(self):
+ """Get the first node of the list"""
+ return self._first
+
+ def prepend(self, data):
+ """
+ Insert to the beginning of list
+
+ >>> list = LinkedList()
+ >>> [str(data) for data in list]
+ []
+ >>> node = list.prepend("foo")
+ >>> len(list)
+ 1
+ >>> node = list.prepend("bar")
+ >>> [str(data) for data in list]
+ ['bar', 'foo']
+ """
+ if self._first is None:
+ new = self._first = self._last = LinkedListNode(data)
+ else:
+ new = self.insert_before(self._first, data)
+ return new
+
+ def append(self, data):
+ """
+ Insert to the end of list
+
+ >>> list = LinkedList()
+ >>> node = list.append('foo')
+ >>> len(list)
+ 1
+ >>> node = list.append('bar')
+ >>> [str(data) for data in list]
+ ['foo', 'bar']
+ """
+ if self._last is None:
+ return self.prepend(data)
+ else:
+ return self.insert_after(self._last, data)
+
+ def insert_before(self, node, data=""):
+ """
+ Insert before a node
+
+ >>> list = LinkedList()
+ >>> node1 = list.append('foo')
+ >>> node2 = list.insert_before(node1, 'bar')
+ >>> node3 = list.insert_before(node1, 'baz')
+ >>> [str(data) for data in list]
+ ['bar', 'baz', 'foo']
+ """
+ new = LinkedListNode(data, prev_node=node.prev, next_node=node)
+ if node.prev:
+ node.prev.next = new
+ else:
+ self._first = new
+ node.prev = new
+ return new
+
+ def insert_after(self, node, data=""):
+ """
+ Insert after a node
+
+ >>> list = LinkedList()
+ >>> node1 = list.prepend('foo')
+ >>> node2 = list.insert_after(node1, 'bar')
+ >>> node3 = list.insert_after(node1, 'baz')
+ >>> [str(data) for data in list]
+ ['foo', 'baz', 'bar']
+ """
+ new = LinkedListNode(data, prev_node=node, next_node=node.next)
+ if node.next:
+ node.next.prev = new
+ else:
+ self._last = new
+ node.next = new
+ return new
+
+ def delete(self, node):
+ """
+ Delete node
+
+ >>> list = LinkedList()
+ >>> node1 = list.prepend('foo')
+ >>> node2 = list.insert_after(node1, 'bar')
+ >>> node3 = list.insert_before(node2, 'baz')
+ >>> [str(data) for data in list]
+ ['foo', 'baz', 'bar']
+ >>> str(list.delete(node3))
+ 'foo'
+ >>> [str(data) for data in list]
+ ['foo', 'bar']
+ >>> print("%s" % node3)
+ <BLANKLINE>
+ >>> str(list.delete(node1))
+ 'bar'
+ >>> [str(data) for data in list]
+ ['bar']
+ >>> list.delete(node2)
+ >>> [str(data) for data in list]
+ []
+ """
+ ret = node.prev
+ if node is self._first:
+ ret = self._first = self._first.next
+ if node is self._last:
+ self._last = self._last.prev
+ node.delete()
+ return ret
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/rpm/policy.py b/gbp/rpm/policy.py
new file mode 100644
index 0000000..a027ed9
--- /dev/null
+++ b/gbp/rpm/policy.py
@@ -0,0 +1,200 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Default packaging policy for RPM"""
+
+import re
+
+from gbp.pkg import PkgPolicy, Archive
+from gbp.scripts.common.pq import parse_gbp_commands
+
+
+class RpmPkgPolicy(PkgPolicy):
+ """Packaging policy for RPM"""
+
+ # Special rpmlib python module for GBP (only)
+ python_rpmlib_module_name = "rpm"
+
+ alnum = 'a-zA-Z0-9'
+ # Valid characters for RPM pkg name
+ name_whitelist_chars = '._+%{}\-'
+ # Valid characters for RPM pkg version
+ version_whitelist_chars = '._+%{}~'
+
+ # Regexp for checking the validity of package name
+ packagename_re = re.compile("^[%s][%s%s]+$" %
+ (alnum, alnum, name_whitelist_chars))
+ packagename_msg = ("Package names must be at least two characters long, "
+ "start with an alphanumeric and can only contain "
+ "alphanumerics or characters in %s" %
+ list(name_whitelist_chars))
+
+ # Regexp for checking the validity of package (upstream) version
+ upstreamversion_re = re.compile("^[0-9][%s%s]*$" %
+ (alnum, version_whitelist_chars))
+ upstreamversion_msg = ("Upstream version numbers must start with a digit "
+ "and can only containg alphanumerics or characters "
+ "in %s" % list(version_whitelist_chars))
+
+ @classmethod
+ def is_valid_orig_archive(cls, filename):
+ """
+ Is this a valid orig source archive
+
+ @param filename: upstream source archive filename
+ @type filename: C{str}
+ @return: true if valid upstream source archive filename
+ @rtype: C{bool}
+
+ >>> RpmPkgPolicy.is_valid_orig_archive("foo/bar_baz.tar.gz")
+ True
+ >>> RpmPkgPolicy.is_valid_orig_archive("foo.bar.tar")
+ True
+ >>> RpmPkgPolicy.is_valid_orig_archive("foo.bar")
+ False
+ >>> RpmPkgPolicy.is_valid_orig_archive("foo.gz")
+ False
+ """
+ _base, arch_fmt, _compression = Archive.parse_filename(filename)
+ if arch_fmt:
+ return True
+ return False
+
+ class Changelog(object):
+ """Container for changelog related policy settings"""
+
+ # Regexps for splitting/parsing the changelog section (of
+ # Tizen / Fedora style changelogs)
+ section_match_re = r'^\*'
+ section_split_re = r'^\*\s*(?P<ch_header>\S.*?)$\n(?P<ch_body>.*)'
+ header_split_re = r'(?P<ch_time>\S.*\s[0-9]{4})\s+(?P<ch_name>\S.*$)'
+ header_name_split_re = r'(?P<name>[^<]*)\s+<(?P<email>[^>]+)>((\s*-)?\s+(?P<revision>\S+))?$'
+ body_name_re = r'\[(?P<name>.*)\]'
+
+ # Changelog header format (when writing out changelog)
+ header_format = "* %(time)s %(name)s <%(email)s> %(revision)s"
+ header_time_format = "%a %b %d %Y"
+ header_rev_format = "%(version)s"
+
+ class ChangelogEntryFormatter(object):
+ """Helper class for generating changelog entries from git commits"""
+
+ # Maximum length for a changelog entry line
+ max_entry_line_length = 76
+ # Bug tracking system related meta tags recognized from git commit msg
+ bts_meta_tags = ("Close", "Closes", "Fixes", "Fix")
+ # Regexp for matching bug tracking system ids (e.g. "bgo#123")
+ bug_id_re = r'[A-Za-z0-9#_\-]+'
+
+ @classmethod
+ def _parse_bts_tags(cls, lines, meta_tags):
+ """
+ Parse and filter out bug tracking system related meta tags from
+ commit message.
+
+ @param lines: commit message
+ @type lines: C{list} of C{str}
+ @param meta_tags: meta tags to look for
+ @type meta_tags: C{tuple} of C{str}
+ @return: bts-ids per meta tag and the non-mathced lines
+ @rtype: (C{dict}, C{list} of C{str})
+ """
+ tags = {}
+ other_lines = []
+ bts_re = re.compile(r'^(?P<tag>%s):\s*(?P<ids>.*)' %
+ ('|'.join(meta_tags)), re.I)
+ bug_id_re = re.compile(cls.bug_id_re)
+ for line in lines:
+ match = bts_re.match(line)
+ if match:
+ tag = match.group('tag')
+ ids_str = match.group('ids')
+ bug_ids = [bug_id.strip() for bug_id in
+ bug_id_re.findall(ids_str)]
+ if tag in tags:
+ tags[tag] += bug_ids
+ else:
+ tags[tag] = bug_ids
+ else:
+ other_lines.append(line)
+ return (tags, other_lines)
+
+ @classmethod
+ def _extra_filter(cls, lines, ignore_re):
+ """
+ Filter out specific lines from the commit message.
+
+ @param lines: commit message
+ @type lines: C{list} of C{str}
+ @param ignore_re: regexp for matching ignored lines
+ @type ignore_re: C{str}
+ @return: filtered commit message
+ @rtype: C{list} of C{str}
+ """
+ if ignore_re:
+ match = re.compile(ignore_re)
+ return [line for line in lines if not match.match(line)]
+ else:
+ return lines
+
+ @classmethod
+ def compose(cls, commit_info, **kwargs):
+ """
+ Generate a changelog entry from a git commit.
+
+ @param commit_info: info about the commit
+ @type commit_info: C{commit_info} object from
+ L{gbp.git.repository.GitRepository.get_commit_info()}.
+ @param kwargs: additional arguments to the compose() method,
+ currently we recognize 'full', 'id_len' and 'ignore_re'
+ @type kwargs: C{dict}
+ @return: formatted changelog entry
+ @rtype: C{list} of C{str}
+ """
+ # Parse and filter out gbp command meta-tags
+ cmds, body = parse_gbp_commands(commit_info, 'gbp-rpm-ch',
+ ('ignore', 'short', 'full'), ())
+ body = body.splitlines()
+ if 'ignore' in cmds:
+ return None
+
+ # Parse and filter out bts-related meta-tags
+ bts_tags, body = cls._parse_bts_tags(body, cls.bts_meta_tags)
+
+ # Additional filtering
+ body = cls._extra_filter(body, kwargs['ignore_re'])
+
+ # Generate changelog entry
+ subject = commit_info['subject']
+ commitid = commit_info['id']
+ if kwargs['id_len']:
+ text = ["- [%s] %s" % (commitid[0:kwargs['id_len']], subject)]
+ else:
+ text = ["- %s" % subject]
+
+ # Add all non-filtered-out lines from commit message, unless 'short'
+ if (kwargs['full'] or 'full' in cmds) and 'short' not in cmds:
+ # Add all non-blank body lines.
+ text.extend([" " + line for line in body if line.strip()])
+
+ # Add bts tags and ids in the end
+ for tag, ids in bts_tags.items():
+ bts_msg = " (%s: %s)" % (tag, ', '.join(ids))
+ if len(text[-1]) + len(bts_msg) >= cls.max_entry_line_length:
+ text.append(" ")
+ text[-1] += bts_msg
+
+ return text
diff --git a/gbp/scripts/__init__.py b/gbp/scripts/__init__.py
new file mode 100644
index 0000000..10122c2
--- /dev/null
+++ b/gbp/scripts/__init__.py
@@ -0,0 +1,17 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""The gbp commands"""
diff --git a/gbp/scripts/buildpackage.py b/gbp/scripts/buildpackage.py
new file mode 100755
index 0000000..b055f8d
--- /dev/null
+++ b/gbp/scripts/buildpackage.py
@@ -0,0 +1,598 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006-2017 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Build a Debian package out of a Git repository"""
+
+import errno
+import os
+import pipes
+import shutil
+import shlex
+import sys
+import time
+import gbp.deb as du
+from gbp.command_wrappers import (Command,
+ RunAtCommand, CommandExecFailed,
+ RemoveTree)
+from gbp.config import (GbpOptionParserDebian, GbpOptionGroup)
+from gbp.deb.git import (GitRepositoryError, DebianGitRepository)
+from gbp.deb.source import DebianSource, DebianSourceError, FileVfs
+from gbp.deb.format import DebianSourceFormat
+from gbp.git.vfs import GitVfs
+from gbp.deb.upstreamsource import DebianUpstreamSource, unpack_component_tarball
+from gbp.errors import GbpError
+import gbp.log
+import gbp.notifications
+from gbp.scripts.common.buildpackage import (index_name, wc_name,
+ dump_tree,
+ write_wc, drop_index)
+from gbp.scripts.common import ExitCodes
+from gbp.scripts.common.hook import Hook
+
+from gbp.scripts.export_orig import prepare_upstream_tarballs, guess_comp_type
+from gbp.scripts.tag import perform_tagging
+
+
+# Functions to handle export-dir
+def maybe_write_tree(repo, options):
+ """
+ Write a tree of the index or working copy if necessary
+
+ @param repo: the git repository we're acting on
+ @type repo: L{GitRepository}
+ @return: the sha1 of the tree
+ @rtype: C{str}
+ """
+ if options.export_dir:
+ 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("%s is not a valid treeish" % tree)
+ else:
+ tree = None
+ return tree
+
+
+def export_source(repo, tree, source, options, dest_dir, tarball_dir):
+ """
+ Export a version of the source tree when building in a separate directory
+
+ @param repo: the git repository to export from
+ @type repo: L{gbp.git.GitRepository}
+ @param source: the source package
+ @param options: options to apply
+ @param dest_dir: where to export the source to
+ @param tarball_dir: where to fetch the tarball from in overlay mode
+ @returns: the temporary directory
+ """
+ # Extract orig tarball if git-overlay option is selected:
+ if options.overlay:
+ if source.is_native():
+ raise GbpError("Cannot overlay Debian native package")
+ overlay_extract_origs(source, tarball_dir, dest_dir, options)
+
+ gbp.log.info("Exporting '%s' to '%s'" % (options.export, dest_dir))
+ if not dump_tree(repo, dest_dir, tree, options.with_submodules):
+ raise GbpError
+
+
+def move_old_export(target):
+ """move a build tree away if it exists"""
+ try:
+ os.makedirs(target)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ os.rename(target, "%s.obsolete.%s" % (target, time.time()))
+
+
+def overlay_extract_origs(source, tarball_dir, dest_dir, options):
+ """Overlay extract orig tarballs to export dir before exporting debian dir from git"""
+
+ comp_type = guess_comp_type(options.comp_type,
+ source,
+ repo=None,
+ tarball_dir=tarball_dir)
+ tarball = os.path.join(tarball_dir, source.upstream_tarball_name(comp_type))
+ gbp.log.info("Extracting %s to '%s'" % (os.path.basename(tarball), dest_dir))
+
+ move_old_export(dest_dir)
+ upstream = DebianUpstreamSource(tarball)
+ upstream.unpack(dest_dir)
+
+ # Check if tarball extracts into a single folder:
+ if upstream.unpacked != dest_dir:
+ # If it extracts a single folder, move its contents to dest_dir:
+ gbp.log.debug("Moving %s to %s" % (upstream.unpacked, dest_dir))
+ tmpdir = dest_dir + '.new'
+ os.rename(upstream.unpacked, tmpdir)
+ os.rmdir(dest_dir)
+ os.rename(tmpdir, dest_dir)
+
+ # Remove debian/ from unpacked upstream tarball in case of non 1.0 format
+ underlay_debian_dir = os.path.join(dest_dir, 'debian')
+ format_file = os.path.join('debian', 'source', 'format')
+ if os.path.exists(underlay_debian_dir) and os.path.exists(format_file):
+ format = DebianSourceFormat.parse_file(format_file)
+ if format.version in ['2.0', '3.0']:
+ gbp.log.info("Removing debian/ from unpacked upstream "
+ "source at %s" % underlay_debian_dir)
+ shutil.rmtree(underlay_debian_dir)
+
+ # Unpack additional tarballs
+ for c in options.components:
+ tarball = os.path.join(tarball_dir, source.upstream_tarball_name(
+ comp_type, component=c))
+ gbp.log.info("Unpacking '%s' to '%s'" % (os.path.basename(tarball), dest_dir))
+ unpack_component_tarball(dest_dir, c, tarball, [])
+
+
+def source_vfs(repo, options, tree):
+ """Init source package info either from git or from working copy"""
+ vfs = GitVfs(repo, tree) if tree else FileVfs('.')
+ try:
+ source = DebianSource(vfs)
+ source.is_native() # check early if this works
+ except Exception as e:
+ raise GbpError("Can't determine package type: %s" % e)
+ return source
+
+
+def prepare_output_dir(dir):
+ """Prepare the directory where the build result will be put"""
+ output_dir = os.path.abspath(dir or '..')
+
+ try:
+ os.makedirs(output_dir)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise GbpError("Cannot create output dir %s" % output_dir)
+ return output_dir
+
+
+def clean_working_tree(options, repo):
+ """
+ Clean the working tree.
+
+ :param options: Program run-time options, as an `optparse.OptionContainer`.
+ :param repo: The Git repository, as a `DebianGitRepository`.
+ :raise GbpError: When the working tree has uncommitted changes.
+ :return: None.
+ """
+ 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.")
+
+
+def check_tag(options, repo, source):
+ """Perform specified consistency checks on git history"""
+ tag = repo.version_to_tag(options.debian_tag, source.version)
+ if (options.tag or options.tag_only) and not options.retag:
+ if repo.has_tag(tag):
+ raise GbpError("Tag '%s' already exists" % tag)
+
+
+def get_pbuilder_dist(options, repo, native=False):
+ """
+ Determin the dist to build for with pbuilder/cowbuilder
+ """
+ dist = None
+ if options.pbuilder_dist == 'DEP14':
+ vendor = du.get_vendor().lower()
+ branch = repo.branch
+ if not branch:
+ raise GbpError("Failed to setup DIST for %s. "
+ "Can't determine current branch." % options.builder)
+ parts = branch.rsplit('/')
+ if len(parts) == 2: # e.g. debian/stretch
+ suite = parts[1]
+ if vendor == parts[0]:
+ dist = '' if suite in ['sid', 'master'] else suite
+ else:
+ dist = '%s_%s' % (parts[0], suite)
+ # Branches in Debian often omit the debian/ prefix
+ elif len(parts) == 1 and vendor.lower() == "debian":
+ if branch in ['master', 'sid']:
+ dist = ''
+ elif branch in du.Releases:
+ dist = branch
+
+ if dist is None:
+ raise GbpError("DEP14 DIST: Current branch '%s' does not match vendor/suite" % branch)
+ else:
+ dist = options.pbuilder_dist
+ return dist
+
+
+def setup_pbuilder(options, repo, native):
+ """
+ Setup environment variables for git-pbuilder
+
+ We return two dictionaries (pbd_env, hook_env) that can be passed
+ as environment when running commands
+
+ *pbd_env* is used for the actual build command while *hook_env* is
+ passed to all hooks. They both contain the same information but
+ *pbd_env* contains the depreated variable names not starting with
+ *GBP_*.
+ """
+ pbd_env = {}
+
+ if options.use_pbuilder or options.use_qemubuilder:
+ options.builder = 'git-pbuilder'
+ pr_builder = os.getenv("BUILDER") or '(cowbuilder)'
+ options.cleaner = '/bin/true'
+
+ dist = get_pbuilder_dist(options, repo, native)
+ pbd_env['GBP_PBUILDER_DIST'] = pbd_env['DIST'] = dist
+ pr_dist = dist or 'sid'
+ if options.pbuilder_arch:
+ arch = options.pbuilder_arch
+ pbd_env['GBP_PBUILDER_ARCH'] = pbd_env['ARCH'] = arch
+ pr_arch = ":%s" % arch
+ else:
+ pr_arch = ""
+ if options.use_qemubuilder:
+ pbd_env['GBP_PBUILDER_BUILDER'] = pbd_env['BUILDER'] = "qemubuilder"
+ pr_builder = pbd_env["GBP_PBUILDER_BUILDER"]
+ if not options.pbuilder_autoconf:
+ pbd_env['GBP_PBUILDER_AUTOCONF'] = pbd_env['GIT_PBUILDER_AUTOCONF'] = "no"
+ if options.pbuilder_options:
+ pbd_env['GBP_PBUILDER_OPTIONS'] = pbd_env['GIT_PBUILDER_OPTIONS'] = options.pbuilder_options
+ gbp.log.info("Building with %s for %s%s" % (pr_builder, pr_dist, pr_arch))
+
+ hook_env = dict([(k, pbd_env[k]) for k in pbd_env if k.startswith("GBP_")])
+ return pbd_env, hook_env
+
+
+def mangle_export_wc_opts(options):
+ """
+ Make building with --export=WC simpler
+ """
+ if options.export == wc_name:
+ options.ignore_branch = True
+ options.ignore_new = True
+
+
+def disable_hooks(options):
+ """Disable all hooks (except for builder)"""
+ for hook in ['cleaner', 'postexport', 'prebuild', 'postbuild', 'posttag']:
+ if getattr(options, hook):
+ gbp.log.info("Disabling '%s' hook" % hook)
+ setattr(options, hook, '')
+
+
+def changes_file_suffix(builder, dpkg_args):
+ """
+ >>> changes_file_suffix('debuild', ['-A'])
+ 'all'
+ >>> changes_file_suffix('debuild', ['-S'])
+ 'source'
+ >>> changes_file_suffix('debuild -A', ['-uc', '-us'])
+ 'all'
+ >>> changes_file_suffix('debuild -S', ['-uc', '-us'])
+ 'source'
+ >>> changes_file_suffix('debuild', []) == du.get_arch()
+ True
+ """
+ args = shlex.split(builder) + dpkg_args
+ if '-S' in args:
+ return 'source'
+ elif '-A' in args:
+ return 'all'
+ else:
+ return os.getenv('ARCH', None) or du.get_arch()
+
+
+def changes_file_name(source, build_dir, builder, dpkg_args):
+ return os.path.abspath("%s/../%s_%s_%s.changes" %
+ (build_dir,
+ source.changelog.name,
+ source.changelog.noepoch,
+ changes_file_suffix(builder, dpkg_args)))
+
+
+def check_branch(repo, options):
+ """
+ Check if we're on the right branch and bail out otherwise
+
+ returns: the current branch or C{None} if in detached head mode
+ """
+ branch = None
+ try:
+ branch = repo.get_branch()
+ except GitRepositoryError:
+ # Not being on any branch is o.k. with --git-ignore-branch
+ if not options.ignore_branch:
+ raise
+
+ ignore = options.ignore_new or options.ignore_branch
+ if branch != options.debian_branch and not ignore:
+ 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.")
+ return branch
+
+
+def build_parser(name, prefix=None):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix=prefix)
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ tag_group = GbpOptionGroup(parser,
+ "tag options",
+ "options related to git tag creation")
+ orig_group = GbpOptionGroup(parser,
+ "orig tarball options",
+ "options related to the creation of the orig tarball")
+ 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")
+ export_group = GbpOptionGroup(parser,
+ "export build-tree options",
+ "alternative build tree related options")
+ for group in [tag_group, orig_group, branch_group, cmd_group, export_group]:
+ parser.add_option_group(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="color-scheme",
+ dest="color_scheme")
+ 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="debian-tag-msg", dest="debian_tag_msg")
+ 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_boolean_config_file_option(option_name="pristine-tar-commit",
+ dest="pristine_tar_commit")
+ orig_group.add_config_file_option(option_name="force-create", dest="force_create",
+ help="force creation of orig tarball", action="store_true")
+ orig_group.add_config_file_option(option_name="no-create-orig", dest="no_create_orig",
+ help="don't create orig tarball", 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'")
+ orig_group.add_config_file_option("component", action="append", metavar='COMPONENT',
+ dest="components")
+ 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="hook to run before a build, "
+ "default is '%(prebuild)s'")
+ cmd_group.add_config_file_option(option_name="postexport", dest="postexport",
+ help="hook to run after exporting the source tree, "
+ "default is '%(postexport)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")
+ cmd_group.add_boolean_config_file_option(option_name="pbuilder-autoconf",
+ dest="pbuilder_autoconf")
+ cmd_group.add_config_file_option(option_name="pbuilder-options", dest="pbuilder_options")
+ cmd_group.add_boolean_config_file_option(option_name="hooks", dest="hooks")
+ 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_boolean_config_file_option(option_name="purge", dest="purge")
+ export_group.add_boolean_config_file_option(option_name="overlay", dest="overlay")
+ return parser
+
+
+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)
+
+ parser = build_parser(argv[0], prefix=prefix)
+ if not parser:
+ return None, None, None
+ options, args = parser.parse_args(args)
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+ if not options.hooks:
+ disable_hooks(options)
+ 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
+
+ if options.components and options.pristine_tar_commit:
+ gbp.log.warn("Components specified, pristine-tar-commit not yet supported - disabling it.")
+ options.pristine_tar_commit = False
+
+ mangle_export_wc_opts(options)
+ return options, args, dpkg_args
+
+
+def main(argv):
+ retval = 0
+ prefix = "git-"
+ source = None
+ hook_env = {}
+
+ options, gbp_args, dpkg_args = parse_args(argv, prefix)
+
+ if not options:
+ return ExitCodes.parse_error
+
+ try:
+ repo = DebianGitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+
+ try:
+ clean_working_tree(options, repo)
+ check_branch(repo, options)
+ tree = maybe_write_tree(repo, options)
+ source = source_vfs(repo, options, tree)
+
+ check_tag(options, repo, source)
+
+ if not options.tag_only:
+ output_dir = prepare_output_dir(options.export_dir)
+ tarball_dir = options.tarball_dir or output_dir
+ tmp_dir = os.path.join(output_dir, "%s-tmp" % source.sourcepkg)
+ build_env, hook_env = setup_pbuilder(options, repo, source.is_native())
+ major = (source.debian_version if source.is_native()
+ else source.upstream_version)
+ export_dir = os.path.join(output_dir, "%s-%s" % (source.sourcepkg, major))
+ build_dir = export_dir if options.export_dir else repo.path
+ changes_file = changes_file_name(source, build_dir, options.builder, dpkg_args)
+
+ # Get/build the upstream tarball if necessary. We delay this in
+ # case of a postexport hook so the hook gets a chance to modify the
+ # sources and create different tarballs (#640382)
+ # We don't delay it in general since we want to fail early if the
+ # tarball is missing.
+ if not source.is_native():
+ if options.postexport:
+ gbp.log.info("Postexport hook set, delaying tarball creation")
+ else:
+ prepare_upstream_tarballs(repo, source, options, tarball_dir,
+ output_dir)
+
+ # Export to another build dir if requested:
+ if options.export_dir:
+ export_source(repo, tree, source, options, tmp_dir, tarball_dir)
+
+ # Run postexport hook
+ if options.postexport:
+ Hook('Postexport', options.postexport,
+ extra_env=Hook.md(hook_env,
+ {'GBP_GIT_DIR': repo.git_dir,
+ 'GBP_TMP_DIR': tmp_dir})
+ )(dir=tmp_dir)
+
+ gbp.log.info("Moving '%s' to '%s'" % (tmp_dir, export_dir))
+ move_old_export(export_dir)
+ os.rename(tmp_dir, export_dir)
+
+ # Delayed tarball creation in case a postexport hook is used:
+ if not source.is_native() and options.postexport:
+ prepare_upstream_tarballs(repo, source, options, tarball_dir,
+ output_dir)
+ if options.prebuild:
+ Hook('Prebuild', options.prebuild,
+ extra_env=Hook.md(hook_env,
+ {'GBP_GIT_DIR': repo.git_dir,
+ 'GBP_BUILD_DIR': build_dir})
+ )(dir=build_dir)
+
+ # Finally build the package:
+ gbp.log.info("Performing the build")
+ RunAtCommand(options.builder,
+ [pipes.quote(arg) for arg in dpkg_args],
+ shell=True,
+ extra_env=Hook.md(build_env,
+ {'GBP_BUILD_DIR': build_dir})
+ )(dir=build_dir)
+ if options.postbuild:
+ gbp.log.debug("Looking for changes file %s" % changes_file)
+ Hook('Postbuild', options.postbuild,
+ extra_env=Hook.md(hook_env,
+ {'GBP_CHANGES_FILE': changes_file,
+ 'GBP_BUILD_DIR': build_dir})
+ )()
+ if options.tag or options.tag_only:
+ perform_tagging(repo, source, options, hook_env)
+
+ except KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except CommandExecFailed:
+ retval = 1
+ except (GbpError, GitRepositoryError) as err:
+ if str(err):
+ gbp.log.err(err)
+ retval = 1
+ except DebianSourceError as err:
+ gbp.log.err(err)
+ source = None
+ retval = 1
+ finally:
+ drop_index(repo)
+
+ if not options.tag_only:
+ if options.export_dir and options.purge and not retval:
+ RemoveTree(export_dir)()
+
+ if source:
+ summary, msg = gbp.notifications.build_msg(source.changelog,
+ not retval)
+ if not gbp.notifications.notify(summary, msg, 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/buildpackage_rpm.py b/gbp/scripts/buildpackage_rpm.py
new file mode 100644
index 0000000..16b5eb5
--- /dev/null
+++ b/gbp/scripts/buildpackage_rpm.py
@@ -0,0 +1,657 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006-2011,2015-2017 Guido Günther <agx@sigxcpu.org>
+# (C) 2012-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Build an RPM package out of a Git repository"""
+
+import os
+import pipes
+import shutil
+import sys
+
+import gbp.log
+import gbp.notifications
+import gbp.rpm as rpm
+from gbp.command_wrappers import Command, RunAtCommand, CommandExecFailed
+from gbp.config import GbpOptionParserRpm, GbpOptionGroup
+from gbp.errors import GbpError
+from gbp.format import format_str
+from gbp.pkg import Compressor
+from gbp.rpm.git import GitRepositoryError, RpmGitRepository
+from gbp.rpm.policy import RpmPkgPolicy
+from gbp.tmpfile import init_tmpdir, del_tmpdir, tempfile
+from gbp.scripts.common import ExitCodes
+from gbp.scripts.common.buildpackage import (index_name, wc_name,
+ dump_tree, write_wc, drop_index)
+from gbp.scripts.pq_rpm import parse_spec
+
+
+class GbpAutoGenerateError(GbpError):
+ pass
+
+
+def makedir(path):
+ """Create directory"""
+ try:
+ if not os.path.exists(path):
+ os.makedirs(path)
+ except OSError as err:
+ raise GbpError("Cannot create dir %s: %s" % (path, err))
+ return path
+
+
+def git_archive(repo, spec, output_dir, treeish, prefix, comp, with_submodules):
+ "Create a compressed orig tarball in output_dir using git_archive"
+ output = os.path.join(output_dir, spec.orig_src['filename'])
+
+ # Remove extra slashes from prefix, will be added by git_archive_x funcs
+ prefix = prefix.strip('/')
+ try:
+ submodules = False
+ if repo.has_submodules(treeish) and with_submodules:
+ submodules = True
+ repo.update_submodules()
+ repo.archive_comp(treeish, output, prefix, comp,
+ format=spec.orig_src['archive_fmt'],
+ submodules=submodules)
+ except (GitRepositoryError, CommandExecFailed) as e:
+ gbp.log.err("Error generating submodules' archives: %s" % e)
+ return False
+ return True
+
+
+def prepare_upstream_tarball(repo, spec, options, output_dir):
+ """Make sure we have an upstream tarball"""
+ # look in tarball_dir first, if found force a symlink to it
+ orig_file = spec.orig_src['filename']
+ if options.tarball_dir:
+ gbp.log.debug("Looking for orig tarball '%s' at '%s'" %
+ (orig_file, options.tarball_dir))
+ if not RpmPkgPolicy.symlink_orig(orig_file, options.tarball_dir,
+ output_dir, force=True):
+ gbp.log.info("Orig tarball '%s' not found at '%s'" %
+ (orig_file, options.tarball_dir))
+ else:
+ gbp.log.info("Orig tarball '%s' found at '%s'" %
+ (orig_file, options.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
+ RpmPkgPolicy.has_orig(orig_file, output_dir)):
+ if not pristine_tar_build_orig(repo, orig_file, output_dir, options):
+ upstream_tree = git_archive_build_orig(repo, spec, output_dir,
+ options)
+ if options.pristine_tar_commit:
+ if repo.pristine_tar.has_commit(orig_file):
+ gbp.log.debug("%s already on pristine tar branch" %
+ orig_file)
+ else:
+ archive = os.path.join(output_dir, orig_file)
+ gbp.log.debug("Adding %s to pristine-tar branch" %
+ archive)
+ repo.pristine_tar.commit(archive, upstream_tree)
+
+
+def pristine_tar_build_orig(repo, orig_file, output_dir, options):
+ """Build orig using pristine-tar"""
+ if options.pristine_tar:
+ if not repo.has_branch(repo.pristine_tar_branch):
+ gbp.log.warn('Pristine-tar branch "%s" not found' %
+ repo.pristine_tar.branch)
+ try:
+ repo.pristine_tar.checkout(os.path.join(output_dir, orig_file))
+ return True
+ except CommandExecFailed:
+ if options.pristine_tar_commit:
+ gbp.log.debug("pristine-tar checkout failed, "
+ "will commit tarball due to "
+ "'--pristine-tar-commit'")
+ elif not options.force_create:
+ raise
+ return False
+
+
+def get_upstream_tree(repo, version, options):
+ """Determine the upstream tree from the given options"""
+ if options.upstream_tree.upper() == 'TAG':
+ tag_str_fields = {'upstreamversion': version,
+ 'version': version}
+ upstream_tree = repo.version_to_tag(options.upstream_tag,
+ tag_str_fields)
+ elif options.upstream_tree.upper() == 'BRANCH':
+ if not repo.has_branch(options.upstream_branch):
+ raise GbpError("%s is not a valid branch" % options.upstream_branch)
+ upstream_tree = options.upstream_branch
+ else:
+ upstream_tree = get_tree(repo, options.upstream_tree)
+ if not repo.has_treeish(upstream_tree):
+ raise GbpError('Invalid upstream treeish %s' % upstream_tree)
+ return upstream_tree
+
+
+def get_tree(repo, tree_name):
+ """
+ Get/create a tree-ish to be used for exporting and diffing. Accepts
+ special keywords for git index and working copies.
+ """
+ try:
+ if tree_name == index_name:
+ # Write a tree of the index
+ tree = repo.write_tree()
+ elif tree_name == wc_name:
+ # Write a tree of the working copy
+ tree = write_wc(repo)
+ else:
+ tree = tree_name
+ except GitRepositoryError as err:
+ raise GbpError(err)
+ if not repo.has_treeish(tree):
+ raise GbpError('Invalid treeish object %s' % tree)
+
+ return tree
+
+
+def get_current_branch(repo):
+ """Get the currently checked-out branch"""
+ try:
+ branch = repo.get_branch()
+ except GitRepositoryError:
+ branch = None
+ return branch
+
+
+def get_vcs_info(repo, treeish):
+ """Get the info for spec vcs tag"""
+ info = {}
+ try:
+ info['tagname'] = repo.describe(treeish, longfmt=True, always=True,
+ abbrev=40)
+ info['commit'] = repo.rev_parse('%s^0' % treeish)
+ info['commitish'] = repo.rev_parse('%s' % treeish)
+ except GitRepositoryError:
+ # If tree is not commit-ish, expect it to be from current HEAD
+ info['tagname'] = repo.describe('HEAD', longfmt=True, always=True,
+ abbrev=40) + '-dirty'
+ info['commit'] = repo.rev_parse('HEAD') + '-dirty'
+ info['commitish'] = info['commit']
+ return info
+
+
+def git_archive_build_orig(repo, spec, output_dir, options):
+ """
+ Build orig tarball using git-archive
+
+ @param repo: our git repository
+ @type repo: L{RpmGitRepository}
+ @param spec: spec file of the package
+ @type spec: L{SpecFile}
+ @param output_dir: where to put the tarball
+ @type output_dir: C{Str}
+ @param options: the parsed options
+ @type options: C{dict} of options
+ @return: the tree we built the tarball from
+ @rtype: C{str}
+ """
+ comp = None
+ try:
+ orig_prefix = spec.orig_src['prefix']
+ upstream_tree = get_upstream_tree(repo, spec.upstreamversion, options)
+ gbp.log.info("%s does not exist, creating from '%s'" %
+ (spec.orig_src['filename'], upstream_tree))
+ if spec.orig_src['compression']:
+ comp = Compressor(spec.orig_src['compression'],
+ options.comp_level)
+ gbp.log.debug("Building upstream tarball with compression %s" % comp)
+ if not git_archive(repo, spec, output_dir, upstream_tree,
+ orig_prefix, comp, options.with_submodules):
+ raise GbpError("Cannot create upstream tarball at '%s'" %
+ output_dir)
+ except (GitRepositoryError, GbpError) as err:
+ raise GbpAutoGenerateError(str(err))
+ return upstream_tree
+
+
+def is_native(repo, options):
+ """Determine whether a package is native or non-native"""
+ if options.native.is_auto():
+ if repo.has_branch(options.upstream_branch):
+ return False
+ # Check remotes, too
+ for remote_branch in repo.get_remote_branches():
+ remote, branch = remote_branch.split('/', 1)
+ if branch == options.upstream_branch:
+ gbp.log.debug("Found upstream branch '%s' from remote '%s'" %
+ (remote, branch))
+ return False
+ return True
+
+ return options.native.is_on()
+
+
+def setup_builder(options, builder_args):
+ """Setup args and options for builder script"""
+ if options.builder == 'rpmbuild':
+ if len(builder_args) == 0:
+ builder_args.append('-ba')
+ builder_args.extend([
+ '--define', "_topdir %s" % os.path.abspath(options.export_dir),
+ '--define', "_specdir %%_topdir/%s" % options.export_specdir,
+ '--define', "_sourcedir %%_topdir/%s" % options.export_sourcedir])
+
+
+def packaging_tag_data(repo, commit, name, version, options):
+ """Compose packaging tag name and msg"""
+ version_dict = dict(version, version=rpm.compose_version_str(version))
+
+ # Compose tag name and message
+ tag_name_fields = dict(version_dict, vendor=options.vendor.lower())
+ tag_name = repo.version_to_tag(options.packaging_tag, tag_name_fields)
+
+ tag_msg = format_str(options.packaging_tag_msg,
+ dict(version_dict, pkg=name,
+ vendor=options.vendor))
+ return (tag_name, tag_msg)
+
+
+def setup_mock(options):
+ """setup everything to use gbp-builder-mock"""
+ if options.use_mock:
+ options.builder = '/usr/share/git-buildpackage/gbp-builder-mock'
+ options.cleaner = '/bin/true'
+ os.environ['GBP_BUILDER_MOCK_DIST'] = options.mock_dist
+ if options.mock_arch:
+ os.environ['GBP_BUILDER_MOCK_ARCH'] = options.mock_arch
+ if options.mock_root:
+ os.environ['GBP_BUILDER_MOCK_ROOT'] = options.mock_root
+ os.environ['GBP_BUILDER_MOCK_EXPORT_DIR'] = options.export_dir
+ if options.mock_options:
+ os.environ['GBP_BUILDER_MOCK_OPTIONS'] = options.mock_options
+
+
+def create_packaging_tag(repo, commit, name, version, options):
+ """Create a packaging/release Git tag"""
+ tag_name, tag_msg = packaging_tag_data(repo, commit, name, version, options)
+
+ if options.retag and repo.has_tag(tag_name):
+ repo.delete_tag(tag_name)
+ repo.create_tag(name=tag_name, msg=tag_msg, sign=options.sign_tags,
+ keyid=options.keyid, commit=commit)
+ return tag_name
+
+
+def disable_hooks(options):
+ """Disable all hooks (except for builder)"""
+ for hook in ['cleaner', 'postexport', 'prebuild', 'postbuild', 'posttag']:
+ if getattr(options, hook):
+ gbp.log.info("Disabling '%s' hook" % hook)
+ setattr(options, hook, '')
+
+
+def build_parser(name, prefix=None, git_treeish=None):
+ """Construct config/option parser"""
+ try:
+ parser = GbpOptionParserRpm(command=os.path.basename(name),
+ prefix=prefix)
+ except GbpError as err:
+ gbp.log.err(err)
+ return 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="tmp-dir", dest="tmp_dir")
+ 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_config_file_option(option_name="notify", dest="notify",
+ type='tristate')
+ parser.add_config_file_option(option_name="vendor", action="store",
+ dest="vendor")
+ parser.add_config_file_option(option_name="native", dest="native",
+ 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="packaging-tag",
+ dest="packaging_tag")
+ tag_group.add_config_file_option(option_name="packaging-tag-msg",
+ dest="packaging_tag_msg")
+ 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_boolean_config_file_option(option_name="pristine-tar-commit",
+ dest="pristine_tar_commit")
+ orig_group.add_config_file_option(option_name="force-create",
+ dest="force_create", action="store_true",
+ help="force creation of upstream source tarball")
+ orig_group.add_config_file_option(option_name="no-create-orig",
+ dest="no_create_orig", action="store_true",
+ help="don't create upstream source tarball")
+ 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-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="packaging-branch",
+ dest="packaging_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 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="postexport",
+ dest="postexport",
+ help="command to run after exporting the source tree, "
+ "default is '%(postexport)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="mock", dest="use_mock")
+ cmd_group.add_config_file_option(option_name="dist", dest="mock_dist")
+ cmd_group.add_config_file_option(option_name="arch", dest="mock_arch")
+ cmd_group.add_config_file_option(option_name="mock-root", dest="mock_root")
+ cmd_group.add_config_file_option(option_name="mock-options", dest="mock_options")
+ cmd_group.add_boolean_config_file_option(option_name="hooks", dest="hooks")
+ export_group.add_option("--git-no-build", action="store_true",
+ dest="no_build",
+ help="Don't run builder or the associated hooks")
+ export_group.add_config_file_option(option_name="export-dir",
+ dest="export_dir", type="path",
+ help="Build topdir, also export the sources under "
+ "EXPORT_DIR, default is '%(export-dir)s'")
+ export_group.add_config_file_option(option_name="export-specdir",
+ dest="export_specdir", type="path")
+ export_group.add_config_file_option(option_name="export-sourcedir",
+ dest="export_sourcedir", type="path")
+ export_group.add_config_file_option("export", dest="export",
+ metavar="TREEISH",
+ help="export treeish object TREEISH, default is "
+ "'%(export)s'")
+ export_group.add_config_file_option(option_name="packaging-dir",
+ dest="packaging_dir")
+ export_group.add_config_file_option(option_name="spec-file",
+ dest="spec_file")
+ export_group.add_config_file_option("spec-vcs-tag", dest="spec_vcs_tag")
+ return parser
+
+
+def parse_args(argv, prefix, git_treeish=None):
+ """Parse config and command line arguments"""
+ args = [arg for arg in argv[1:] if arg.find('--%s' % prefix) == 0]
+ builder_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 builder_args:
+ args.append(arg)
+
+ parser = build_parser(argv[0], prefix=prefix, git_treeish=git_treeish)
+ if not parser:
+ return None, None, None
+ options, args = parser.parse_args(args)
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+ if not options.hooks:
+ disable_hooks(options)
+ 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
+
+ return options, args, builder_args
+
+
+def main(argv):
+ """Entry point for gbp-buildpackage-rpm"""
+ retval = 0
+ prefix = "git-"
+ spec = None
+
+ options, gbp_args, builder_args = parse_args(argv, prefix)
+
+ if not options:
+ return ExitCodes.parse_error
+
+ try:
+ repo = RpmGitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+
+ # Determine tree-ish to be exported
+ try:
+ tree = get_tree(repo, options.export)
+ except GbpError as err:
+ gbp.log.err('Failed to determine export treeish: %s' % err)
+ return 1
+ # Re-parse config options with using the per-tree config file(s) from the
+ # exported tree-ish
+ options, gbp_args, builder_args = parse_args(argv, prefix, tree)
+
+ branch = get_current_branch(repo)
+
+ try:
+ init_tmpdir(options.tmp_dir, prefix='buildpackage-rpm_')
+
+ tree = get_tree(repo, options.export)
+ spec = parse_spec(options, repo, treeish=tree)
+
+ 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.packaging_branch:
+ gbp.log.err("You are not on branch '%s' but on '%s'" %
+ (options.packaging_branch, branch))
+ raise GbpError("Use --git-ignore-branch to ignore or "
+ "--git-packaging-branch to set the branch name.")
+
+ # Dump from git to a temporary directory:
+ packaging_tree = '%s:%s' % (tree, options.packaging_dir)
+ dump_dir = tempfile.mkdtemp(prefix='packaging_')
+ gbp.log.debug("Dumping packaging files to '%s'" % dump_dir)
+ if not dump_tree(repo, dump_dir, packaging_tree, False, False):
+ raise GbpError
+ # Re-parse spec from dump dir to get version etc.
+ spec = rpm.SpecFile(os.path.join(dump_dir, spec.specfile))
+
+ if not options.tag_only:
+ # Setup builder opts
+ setup_builder(options, builder_args)
+ if options.use_mock:
+ setup_mock(options)
+
+ # Prepare final export dirs
+ export_dir = makedir(options.export_dir)
+ source_dir = makedir(os.path.join(export_dir,
+ options.export_sourcedir))
+ spec_dir = makedir(os.path.join(export_dir, options.export_specdir))
+
+ # Move packaging files to final export dir
+ gbp.log.debug("Exporting packaging files from '%s' to '%s'" %
+ (dump_dir, export_dir))
+ for fname in os.listdir(dump_dir):
+ src = os.path.join(dump_dir, fname)
+ if fname == spec.specfile:
+ dst = os.path.join(spec_dir, fname)
+ else:
+ dst = os.path.join(source_dir, fname)
+ try:
+ shutil.copy2(src, dst)
+ except IOError as err:
+ raise GbpError("Error exporting packaging files: %s" % err)
+ spec.specdir = os.path.abspath(spec_dir)
+
+ # Get/build the orig tarball
+ if is_native(repo, options):
+ if spec.orig_src and not options.no_create_orig:
+ # Just build source archive from the exported tree
+ gbp.log.info("Creating (native) source archive %s from '%s'"
+ % (spec.orig_src['filename'], tree))
+ comp = None
+ if spec.orig_src['compression']:
+ comp = Compressor(spec.orig_src['compression'],
+ options.comp_level)
+ gbp.log.debug("Building source archive with "
+ "compression '%s" % comp)
+ orig_prefix = spec.orig_src['prefix']
+ if not git_archive(repo, spec, source_dir, tree,
+ orig_prefix, comp,
+ options.with_submodules):
+ raise GbpError("Cannot create source tarball at '%s'" %
+ source_dir)
+ # Non-native packages: create orig tarball from upstream
+ elif spec.orig_src:
+ prepare_upstream_tarball(repo, spec, options, source_dir)
+
+ # Run postexport hook
+ if options.postexport:
+ RunAtCommand(options.postexport, shell=True,
+ extra_env={'GBP_GIT_DIR': repo.git_dir,
+ 'GBP_TMP_DIR': export_dir}
+ )(dir=export_dir)
+ # Do actual build
+ if not options.no_build and not options.tag_only:
+ if options.prebuild:
+ RunAtCommand(options.prebuild, shell=True,
+ extra_env={'GBP_GIT_DIR': repo.git_dir,
+ 'GBP_BUILD_DIR': export_dir}
+ )(dir=export_dir)
+
+ # Finally build the package:
+ if options.builder.startswith("rpmbuild"):
+ builder_args.append(os.path.join(spec.specdir,
+ spec.specfile))
+ else:
+ builder_args.append(spec.specfile)
+ RunAtCommand(options.builder,
+ [pipes.quote(arg) for arg in builder_args],
+ shell=True,
+ extra_env={'GBP_BUILD_DIR': export_dir}
+ )(dir=export_dir)
+ if options.postbuild:
+ changes = os.path.abspath("%s/%s.changes" % (source_dir,
+ spec.name))
+ gbp.log.debug("Looking for changes file %s" % changes)
+ Command(options.postbuild, shell=True,
+ extra_env={'GBP_CHANGES_FILE': changes,
+ 'GBP_BUILD_DIR': export_dir})()
+
+ # Tag (note: tags the exported version)
+ if options.tag or options.tag_only:
+ gbp.log.info("Tagging %s" % rpm.compose_version_str(spec.version))
+ tag = create_packaging_tag(repo, tree, spec.name, spec.version,
+ options)
+ vcs_info = get_vcs_info(repo, tag)
+ 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})()
+ else:
+ vcs_info = get_vcs_info(repo, tree)
+
+ # Put 'VCS:' tag to .spec
+ spec.set_tag('VCS', None, format_str(options.spec_vcs_tag, vcs_info))
+ spec.write_spec_file()
+ except KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except CommandExecFailed:
+ retval = 1
+ except GitRepositoryError as err:
+ gbp.log.err("Git command failed: %s" % err)
+ retval = 1
+ except GbpAutoGenerateError as err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ retval = 2
+ except GbpError as err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ retval = 1
+ finally:
+ drop_index(repo)
+ del_tmpdir()
+
+ if not options.tag_only:
+ if spec:
+ summary = "Gbp-rpm %s" % ["failed", "successful"][not retval]
+ message = ("Build of %s %s %s" % (spec.name,
+ rpm.compose_version_str(spec.version),
+ ["failed", "succeeded"][not retval]))
+ if not gbp.notifications.notify(summary, message, options.notify):
+ gbp.log.err("Failed to send notification")
+ retval = 1
+
+ return retval
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/gbp/scripts/clone.py b/gbp/scripts/clone.py
new file mode 100755
index 0000000..edc991e
--- /dev/null
+++ b/gbp/scripts/clone.py
@@ -0,0 +1,229 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2009, 2010, 2015, 2017 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, please see
+# <http://www.gnu.org/licenses/>
+#
+# inspired by dom-git-checkout
+#
+"""Clone a Git repository and set it up for gbp"""
+
+import re
+import sys
+import os
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.deb.git import DebianGitRepository
+from gbp.git import (GitRepository, GitRepositoryError)
+from gbp.errors import GbpError
+from gbp.scripts.common import ExitCodes
+from gbp.scripts.common import repo_setup
+from gbp.scripts.common.hook import Hook
+from gbp.command_wrappers import Command, CommandExecFailed
+from gbp.deb import DpkgCompareVersions
+import gbp.log
+
+from functools import cmp_to_key
+
+
+def apt_showsrc(pkg):
+ try:
+ aptsrc = Command("apt-cache", ["showsrc", pkg], capture_stdout=True)
+ aptsrc(quiet=True)
+ return aptsrc.stdout
+ except CommandExecFailed:
+ return ''
+
+
+def vcs_git_url(pkg):
+ repos = {}
+
+ out = apt_showsrc(pkg)
+ vcs_re = re.compile(r'(x-)?vcs-git:\s*(?P<repo>[^ ]+)$', re.I)
+ version_re = re.compile(r'Version:\s*(?P<version>.*)$', re.I)
+ end_re = re.compile(r'\s*$')
+
+ version = repo = None
+ for line in out.split('\n'):
+ m = vcs_re.match(line)
+ if m:
+ repo = m.group('repo')
+ continue
+ m = version_re.match(line)
+ if m:
+ version = m.group('version')
+ continue
+ m = end_re.match(line)
+ if m:
+ if version and repo:
+ repos[version] = repo
+ version = repo = None
+
+ if not repos:
+ gbp.log.err("Can't find any vcs-git URL for '%s'" % pkg)
+ return None
+
+ s = sorted(repos, key=cmp_to_key(DpkgCompareVersions()))
+ return repos[s[-1]]
+
+
+def repo_to_url(repo):
+ """
+ >>> repo_to_url("https://foo.example.com")
+ 'https://foo.example.com'
+ >>> repo_to_url("github:agx/git-buildpackage")
+ 'https://github.com/agx/git-buildpackage.git'
+ """
+ parts = repo.split(":", 1)
+ if len(parts) != 2:
+ return repo
+ else:
+ proto, path = parts
+
+ if proto == 'github':
+ return 'https://github.com/%s.git' % path
+ elif proto in ['vcsgit', 'vcs-git']:
+ return vcs_git_url(path)
+ else:
+ return repo
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParser(command=os.path.basename(name), prefix='',
+ usage='%prog [options] repository - clone a remote repository')
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ branch_group = GbpOptionGroup(parser, "branch options", "branch tracking and layout options")
+ cmd_group = GbpOptionGroup(parser, "external command options", "how and when to invoke hooks")
+ parser.add_option_group(branch_group)
+ parser.add_option_group(cmd_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")
+ branch_group.add_option("--depth", action="store", dest="depth", default=0,
+ help="git history depth (for creating shallow clones)")
+ branch_group.add_option("--reference", action="store", dest="reference", default=None,
+ help="git reference repository (use local copies where possible)")
+ cmd_group.add_config_file_option(option_name="postclone", dest="postclone",
+ help="hook to run after cloning the source tree, "
+ "default is '%(postclone)s'")
+ cmd_group.add_boolean_config_file_option(option_name="hooks", dest="hooks")
+
+ 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ parser.add_config_file_option(option_name="repo-user", dest="repo_user",
+ choices=['DEBIAN', 'GIT'])
+ parser.add_config_file_option(option_name="repo-email", dest="repo_email",
+ choices=['DEBIAN', 'GIT'])
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+
+ (options, args) = parser.parse_args(argv)
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+ return (options, args)
+
+
+def main(argv):
+ retval = 0
+
+ (options, args) = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ if len(args) < 2:
+ gbp.log.err("Need a repository to clone.")
+ return 1
+ else:
+ source = repo_to_url(args[1])
+ if not source:
+ return 1
+
+ clone_to, auto_name = (os.path.curdir, True) if len(args) < 3 else (args[2], False)
+ try:
+ GitRepository(clone_to)
+ gbp.log.err("Can't run inside a git repository.")
+ return 1
+ except GitRepositoryError:
+ pass
+
+ try:
+ gbp.log.info("Cloning from '%s'%s" % (source, " into '%s'" % clone_to if not auto_name else ''))
+ repo = DebianGitRepository.clone(clone_to, source, options.depth,
+ auto_name=auto_name, reference=options.reference)
+ os.chdir(repo.path)
+
+ # Reparse the config files of the cloned repository so we pick up the
+ # branch information from there but don't overwrite hooks:
+ postclone = options.postclone
+ (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 += [repo.pristine_tar_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)
+
+ repo_setup.set_user_name_and_email(options.repo_user, options.repo_email, repo)
+
+ if postclone:
+ Hook('Postclone', options.postclone,
+ extra_env={'GBP_GIT_DIR': repo.git_dir},
+ )()
+
+ except KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except GitRepositoryError as err:
+ gbp.log.err("Git command failed: %s" % err)
+ retval = 1
+ except GbpError as err:
+ if str(err):
+ 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/common/__init__.py b/gbp/scripts/common/__init__.py
new file mode 100644
index 0000000..e49930e
--- /dev/null
+++ b/gbp/scripts/common/__init__.py
@@ -0,0 +1,69 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""Parts shared between the deb and rpm commands"""
+
+import re
+import os
+from gbp.errors import GbpError
+from gbp.deb import DebianPkgPolicy
+from gbp.pkg import Archive
+
+
+class ExitCodes(object):
+ ok = 0,
+ failed = 1 # All other errors
+ no_value = 2 # Value does not exist (gbp config only)
+ parse_error = 3 # Failed to parse configuration file
+ uscan_up_to_date = 4 # Uscan up to date (import-orig only)
+
+
+def maybe_debug_raise():
+ if 'raise' in os.getenv("GBP_DEBUG", '').split(','):
+ raise
+
+
+def is_download(args):
+ """
+ >>> is_download(["http://foo.example.com"])
+ True
+ >>> is_download([])
+ False
+ >>> is_download(["foo-1.1.orig.tar.gz"])
+ False
+ """
+ if args and re.match("https?://", args[0]):
+ return True
+ return False
+
+
+def get_component_tarballs(name, version, tarball, components):
+ """
+ Figure out the paths to the component tarballs based on the main
+ tarball.
+ """
+ tarballs = []
+ (_, _, comp_type) = Archive.parse_filename(tarball)
+ for component in components:
+ cname = DebianPkgPolicy.build_tarball_name(name,
+ version,
+ comp_type,
+ os.path.dirname(tarball),
+ component)
+ tarballs.append((component, cname))
+ if not os.path.exists(cname):
+ raise GbpError("Can not find component tarball %s" % cname)
+ return tarballs
diff --git a/gbp/scripts/common/buildpackage.py b/gbp/scripts/common/buildpackage.py
new file mode 100644
index 0000000..7071cdc
--- /dev/null
+++ b/gbp/scripts/common/buildpackage.py
@@ -0,0 +1,102 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006-2011, 2016 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Common functionality for Debian and RPM buildpackage scripts"""
+
+import os
+import os.path
+import pipes
+from gbp.git import GitRepositoryError
+from gbp.pkg.git import PkgGitRepository
+from gbp.errors import GbpError
+import gbp.log
+
+# 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"
+
+
+# Functions to handle export-dir
+def dump_tree(repo, export_dir, treeish, with_submodules, recursive=True):
+ "dump a tree to output_dir"
+ output_dir = os.path.dirname(export_dir)
+ prefix = PkgGitRepository.sanitize_prefix(os.path.basename(export_dir))
+ if recursive:
+ paths = []
+ else:
+ paths = ["'%s'" % nam.decode() for _mod, typ, _sha, nam in
+ repo.list_tree(treeish) if typ == 'blob']
+
+ pipe = pipes.Template()
+ pipe.prepend('git archive --format=tar --prefix=%s %s -- %s' %
+ (prefix, treeish, ' '.join(paths)), '.-')
+ 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 recursive and 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 as err:
+ gbp.log.err("Error dumping tree to %s: %s" % (output_dir, err[0]))
+ return False
+ except (GitRepositoryError, GbpError) as 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 wc_index(repo):
+ """Get path of the temporary index file used for exporting working copy"""
+ return os.path.join(repo.git_dir, "gbp_index")
+
+
+def write_wc(repo, force=True):
+ """write out the current working copy as a treeish object"""
+ index_file = wc_index(repo)
+ repo.add_files(repo.path, force=force, index_file=index_file)
+ tree = repo.write_tree(index_file=index_file)
+ return tree
+
+
+def drop_index(repo):
+ """drop our custom index"""
+ index_file = wc_index(repo)
+ if os.path.exists(index_file):
+ os.unlink(index_file)
diff --git a/gbp/scripts/common/hook.py b/gbp/scripts/common/hook.py
new file mode 100644
index 0000000..bcaf3cd
--- /dev/null
+++ b/gbp/scripts/common/hook.py
@@ -0,0 +1,39 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2016 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, please see
+# <http://www.gnu.org/licenses/>
+"""Common code for runniing hooks"""
+
+from gbp.command_wrappers import RunAtCommand
+import gbp.log
+
+
+class Hook(RunAtCommand):
+ "A hook run by one of the scripts"
+ def __init__(self, name, cmd, extra_env):
+ RunAtCommand.__init__(self, cmd, shell=True, extra_env=extra_env)
+ self.name = name
+ self.run_error = '%s-hook %s' % (name, self.run_error)
+
+ def __call__(self, *args, **kwargs):
+ gbp.log.info("Running %s hook" % self.name)
+ return RunAtCommand.__call__(self, *args, **kwargs)
+
+ @staticmethod
+ def md(a, b):
+ "Merge two dictionaires a and b into a new one"
+ c = a.copy()
+ c.update(b)
+ return c
diff --git a/gbp/scripts/common/import_orig.py b/gbp/scripts/common/import_orig.py
new file mode 100644
index 0000000..d43a143
--- /dev/null
+++ b/gbp/scripts/common/import_orig.py
@@ -0,0 +1,179 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006, 2007, 2009, 2011 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Common functionality for import-orig scripts"""
+import contextlib
+import os
+import tempfile
+import gbp.command_wrappers as gbpc
+import gbp.log
+
+from gbp.errors import GbpError
+from gbp.deb.upstreamsource import DebianUpstreamSource
+
+# Try to import readline, since that will cause raw_input to get fancy
+# line editing and history capabilities. However, if readline is not
+# available, input() will still work.
+try:
+ import readline # noqa: F401
+except ImportError:
+ pass
+
+
+def orig_needs_repack(upstream_source, 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. we don't have a suitable upstream tarball (e.g. zip archive or unpacked dir)
+ and want to use filters
+ 3. 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 upstream_source.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 ask_package_name(default, name_validator_func, err_msg):
+ """
+ Ask the user for the source package name.
+ @param default: The default package name to suggest to the user.
+ """
+ while True:
+ sourcepackage = 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 name_validator_func(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, err_msg))
+
+
+def ask_package_version(default, ver_validator_func, err_msg):
+ """
+ Ask the user for the upstream package version.
+ @param default: The default package version to suggest to the user.
+ """
+ while True:
+ version = input("What is the upstream version? [%s] " % default)
+ if not version: # No input, use the default.
+ version = default
+ # Valid version, return it.
+ if ver_validator_func(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, err_msg))
+
+
+def repacked_tarball_name(upstream, name, version):
+ if upstream.is_orig():
+ # Repacked orig tarball needs a different name since there's already
+ # one with that name
+ name = os.path.join(
+ os.path.dirname(upstream.path),
+ os.path.basename(upstream.path).replace(".tar", ".gbp.tar"))
+ else:
+ # non tarballs (zips, unpacked dirs) get the canonical name
+ name = os.path.join(
+ os.path.dirname(upstream.path),
+ "%s_%s.orig.tar.gz" % (name, version))
+ return name
+
+
+def repack_upstream(upstream, name, version, tmpdir, filters):
+ """Repack the upstream source tree"""
+ name = repacked_tarball_name(upstream, name, version)
+ repacked = upstream.pack(name, filters)
+ if upstream.is_orig(): # Orig already was a tarball so it was filtered on unpack
+ repacked.unpacked = upstream.unpacked
+ else: # otherwise unpack the generated tarball again to get a filtered tree
+ if tmpdir:
+ cleanup_tmp_tree(tmpdir)
+ tmpdir = tempfile.mkdtemp(dir='../')
+ repacked.unpack(tmpdir, filters)
+ return (repacked, tmpdir)
+
+
+def download_orig(url):
+ """
+ Download orig tarball from given URL
+ @param url: the download URL
+ @type url: C{str}
+ @returns: The upstream source tarball
+ @rtype: DebianUpstreamSource
+ @raises GbpError: on all errors
+ """
+ CHUNK_SIZE = 4096
+
+ try:
+ import requests
+ except ImportError:
+ requests = None
+
+ if requests is None:
+ raise GbpError("python-requests not installed")
+
+ tarball = os.path.basename(url)
+ target = os.path.join('..', tarball)
+
+ if os.path.exists(target):
+ raise GbpError("Failed to download %s: %s already exists" % (url, target))
+
+ try:
+ with contextlib.closing(requests.get(url, verify=True, stream=True)) as r:
+ r.raise_for_status()
+ with open(target, 'wb', CHUNK_SIZE) as target_fd:
+ for d in r.iter_content(CHUNK_SIZE):
+ target_fd.write(d)
+ except Exception as e:
+ if os.path.exists(target):
+ os.unlink(target)
+ raise GbpError("Failed to download %s: %s" % (url, e))
+
+ return DebianUpstreamSource(target)
diff --git a/gbp/scripts/common/pq.py b/gbp/scripts/common/pq.py
new file mode 100644
index 0000000..b6033a2
--- /dev/null
+++ b/gbp/scripts/common/pq.py
@@ -0,0 +1,353 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011,2015,2017 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Common functionality for Debian and RPM patchqueue management"""
+
+import re
+import os
+import datetime
+import time
+from email.message import Message
+from email.header import Header
+from email.charset import Charset, QP
+from email.policy import Compat32
+
+from gbp.git import GitRepositoryError
+from gbp.git.modifier import GitModifier, GitTz
+from gbp.errors import GbpError
+import gbp.log
+
+PQ_BRANCH_PREFIX = "patch-queue/"
+
+
+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")
+ 'patch-queue/master'
+ >>> pq_branch_name("foo")
+ 'patch-queue/foo'
+ """
+ if not is_pq_branch(branch):
+ return PQ_BRANCH_PREFIX + branch
+ else:
+ return branch
+
+
+def pq_branch_base(branch):
+ """
+ get the branch corresponding to the given patch queue branch
+
+ >>> pq_branch_base("patch-queue/master")
+ 'master'
+ >>> pq_branch_base("foo")
+ 'foo'
+ """
+ if is_pq_branch(branch):
+ return branch[len(PQ_BRANCH_PREFIX):]
+ else:
+ return branch
+
+
+def parse_gbp_commands(info, cmd_tag, noarg_cmds, arg_cmds, filter_cmds=None):
+ """
+ Parses gbp commands from commit message. Args with and wthout
+ arguments are supported as is filtering out of commands from the
+ commit body.
+
+ @param info: the commit into to parse for commands
+ @param cmd_tag: the command tag
+ @param noarg_cmds: commands without an argument
+ @type noarg_cmds: C{list} of C{str}
+ @param arg_cmds: command with an argumnt
+ @type arg_cmds: C{list} of C{str}
+ @param filter_cmds: commands to filter out of the passed in info
+ @type filter_cmds: C{list} of C{str}
+ @returns: the parsed commands and the filtered commit body.
+ """
+ body = []
+ cmd_re = re.compile(r'^%s:\s*(?P<cmd>[a-z-]+)(\s+(?P<args>\S.*))?' %
+ cmd_tag, flags=re.I)
+ commands = {}
+ for line in info['body'].splitlines():
+ match = re.match(cmd_re, line)
+ if match:
+ cmd = match.group('cmd').lower()
+ if arg_cmds and cmd in arg_cmds:
+ if match.group('args'):
+ commands[cmd] = match.group('args')
+ else:
+ gbp.log.warn("Ignoring gbp-command '%s' in commit %s: "
+ "missing cmd arguments" % (line, info['id']))
+ elif noarg_cmds and cmd in noarg_cmds:
+ commands[cmd] = match.group('args')
+ else:
+ gbp.log.warn("Ignoring unknown gbp-command '%s' in commit %s"
+ % (line, info['id']))
+ if filter_cmds is None or cmd not in filter_cmds:
+ body.append(line)
+ else:
+ body.append(line)
+ msg = '\n'.join(body)
+ return (commands, msg)
+
+
+def patch_path_filter(file_status, exclude_regex=None):
+ """
+ Create patch include paths, i.e. a "negation" of the exclude paths.
+ """
+ if exclude_regex:
+ include_paths = []
+ for file_list in file_status.values():
+ for fname in file_list:
+ if not re.match(exclude_regex, fname):
+ include_paths.append(fname)
+ else:
+ include_paths = ['.']
+
+ return include_paths
+
+
+def write_patch_file(filename, commit_info, diff):
+ """Write patch file"""
+ if not diff:
+ gbp.log.debug("I won't generate empty diff %s" % filename)
+ return None
+ try:
+ with open(filename, 'wb') as patch:
+ msg = Message()
+ charset = Charset('utf-8')
+ charset.body_encoding = None
+ charset.header_encoding = QP
+
+ # Write headers
+ name = commit_info['author']['name']
+ email = commit_info['author']['email']
+ # Git compat: put name in quotes if special characters found
+ if re.search("[,.@()\[\]\\\:;]", name):
+ name = '"%s"' % name
+ from_header = Header(header_name='from')
+ try:
+ from_header.append(name, 'us-ascii')
+ except UnicodeDecodeError:
+ from_header.append(name, charset)
+ from_header.append('<%s>' % email)
+ msg['From'] = from_header
+ date = commit_info['author'].datetime
+ datestr = date.strftime('%a, %-d %b %Y %H:%M:%S %z')
+ msg['Date'] = Header(datestr, 'us-ascii', 'date')
+ subject_header = Header(header_name='subject')
+ try:
+ subject_header.append(commit_info['subject'], 'us-ascii')
+ except UnicodeDecodeError:
+ subject_header.append(commit_info['subject'], charset)
+ msg['Subject'] = subject_header
+ # Write message body
+ if commit_info['body']:
+ # Strip extra linefeeds
+ body = commit_info['body'].rstrip() + '\n'
+ try:
+ msg.set_payload(body.encode('us-ascii'))
+ except (UnicodeEncodeError):
+ msg.set_payload(body, charset)
+ policy = Compat32(max_line_length=77)
+ patch.write(msg.as_bytes(unixfrom=False, policy=policy))
+
+ # Write diff
+ patch.write(b'---\n')
+ patch.write(diff)
+ except IOError as err:
+ raise GbpError('Unable to create patch file: %s' % err)
+ return filename
+
+
+DEFAULT_PATCH_NUM_PREFIX_FORMAT = "%04d-"
+
+
+def format_patch(outdir, repo, commit_info, series, abbrev, numbered=True,
+ path_exclude_regex=None, topic='', name=None, renumber=False,
+ patch_num_prefix_format=DEFAULT_PATCH_NUM_PREFIX_FORMAT):
+ """Create patch of a single commit"""
+
+ # Determine filename and path
+ outdir = os.path.join(outdir, topic)
+ if not os.path.exists(outdir):
+ os.makedirs(outdir)
+
+ try:
+ num_prefix = str(patch_num_prefix_format) % (len(series) + 1) \
+ if numbered else ''
+ except Exception:
+ gbp.log.warn("Bad format format string '%s', "
+ "falling back to default '%s'" %
+ (str(patch_num_prefix_format),
+ DEFAULT_PATCH_NUM_PREFIX_FORMAT))
+ num_prefix = DEFAULT_PATCH_NUM_PREFIX_FORMAT % (len(series) + 1)
+
+ if name is not None:
+ if renumber:
+ # Remove any existing numeric prefix if the patch
+ # should be renumbered
+ name = re.sub('^\d+[-_]*', '', name)
+ else:
+ # Otherwise, clear proposed prefix
+ num_prefix = ''
+ (base, suffix) = os.path.splitext(name)
+ else:
+ suffix = '.patch'
+ base_maxlen = 63 - len(num_prefix) - len(suffix)
+ base = commit_info['patchname'][:base_maxlen]
+
+ filename = num_prefix + base + suffix
+ filepath = os.path.join(outdir, filename)
+ # Make sure that we don't overwrite existing patches in the series
+ if filepath in series:
+ presuffix = '-%d' % len([p for p in series
+ if p.startswith(os.path.splitext(filepath)[0])])
+ filename = num_prefix + base + presuffix + suffix
+ filepath = os.path.join(outdir, filename)
+
+ # Determine files to include
+ paths = patch_path_filter(commit_info['files'], path_exclude_regex)
+
+ # Finally, create the patch
+ patch = None
+ if paths:
+ diff = repo.diff('%s^!' % commit_info['id'], paths=paths, stat=80,
+ summary=True, text=True, abbrev=abbrev, renames=False)
+ patch = write_patch_file(filepath, commit_info, diff)
+ if patch:
+ series.append(patch)
+ return patch
+
+
+def format_diff(outdir, filename, repo, start, end, abbrev, path_exclude_regex=None):
+ """Create a patch of diff between two repository objects"""
+
+ info = {'author': repo.get_author_info()}
+ now = datetime.datetime.now().replace(tzinfo=GitTz(-time.timezone))
+ info['author'].set_date(now)
+ info['subject'] = "Raw diff %s..%s" % (start, end)
+ info['body'] = ("Raw diff between %s '%s' and\n%s '%s'\n" %
+ (repo.get_obj_type(start), start,
+ repo.get_obj_type(end), end))
+ if not filename:
+ filename = '%s-to-%s.diff' % (start, end)
+ filename = os.path.join(outdir, filename)
+
+ file_status = repo.diff_status(start, end)
+ paths = patch_path_filter(file_status, path_exclude_regex)
+ if paths:
+ diff = repo.diff(start, end, paths=paths, stat=80, summary=True,
+ text=True, abbrev=abbrev, renames=False)
+ return write_patch_file(filename, info, diff)
+ return None
+
+
+def get_maintainer_from_control(repo):
+ """Get the maintainer from the control file"""
+ control = os.path.join(repo.path, 'debian', 'control')
+
+ maint_re = re.compile('Maintainer: +(?P<name>.*[^ ]) *<(?P<email>.*)>')
+ with open(control, encoding='utf-8') as f:
+ for line in f:
+ m = maint_re.match(line)
+ if m:
+ return GitModifier(m.group('name'), m.group('email'))
+ return GitModifier()
+
+
+def switch_to_pq_branch(repo, branch):
+ """
+ Switch to patch-queue branch if not already on it.
+ doesn't exist yet
+ """
+ if is_pq_branch(branch):
+ return
+
+ pq_branch = pq_branch_name(branch)
+ if not repo.has_branch(pq_branch):
+ raise GbpError("Branch '%s' does not exist, try "
+ "'import' instead" % pq_branch)
+
+ gbp.log.info("Switching to '%s'" % pq_branch)
+ repo.set_branch(pq_branch)
+
+
+def apply_single_patch(repo, branch, patch, fallback_author, topic=None):
+ switch_to_pq_branch(repo, branch)
+ apply_and_commit_patch(repo, patch, fallback_author, topic)
+ gbp.log.info("Applied %s" % os.path.basename(patch.path))
+
+
+def apply_and_commit_patch(repo, patch, fallback_author, topic=None, name=None):
+ """apply a single patch 'patch', add topic 'topic' and commit it"""
+ author = {'name': patch.author,
+ 'email': patch.email,
+ 'date': patch.date}
+
+ patch_fn = os.path.basename(patch.path)
+ if not (author['name'] and author['email']):
+ if fallback_author and fallback_author['name']:
+ author = {}
+ for key in 'name', 'email', 'date':
+ author[key] = fallback_author.get(key)
+ gbp.log.warn("Patch '%s' has no authorship information, using "
+ "'%s <%s>'" % (patch_fn, author['name'],
+ author['email']))
+ else:
+ gbp.log.warn("Patch '%s' has no authorship information" % patch_fn)
+
+ try:
+ repo.apply_patch(patch.path, strip=patch.strip)
+ except GitRepositoryError:
+ gbp.log.warn("Patch %s failed to apply, retrying with whitespace fixup" % patch_fn)
+ repo.apply_patch(patch.path, strip=patch.strip, fix_ws=True)
+ tree = repo.write_tree()
+ msg = "%s\n\n%s" % (patch.subject, patch.long_desc)
+ if topic:
+ msg += "\nGbp-Pq: Topic %s" % topic
+ if name:
+ msg += "\nGbp-Pq: Name %s" % name
+ if author['name']:
+ author['name'] = author['name'].encode('utf-8')
+ commit = repo.commit_tree(tree, msg, [repo.head], author=author)
+ repo.update_ref('HEAD', commit, msg="gbp-pq import %s" % patch.path)
+
+
+def drop_pq(repo, branch):
+ repo.checkout(pq_branch_base(branch))
+ 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.")
diff --git a/gbp/scripts/common/repo_setup.py b/gbp/scripts/common/repo_setup.py
new file mode 100644
index 0000000..d76b585
--- /dev/null
+++ b/gbp/scripts/common/repo_setup.py
@@ -0,0 +1,30 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006-2011, 2016 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Common repository setup functionality."""
+
+import os
+
+
+def set_user_name_and_email(repo_user, repo_email, repo):
+ if repo_user == 'DEBIAN':
+ if os.getenv('DEBFULLNAME'):
+ repo.set_user_name(os.getenv('DEBFULLNAME'))
+
+ if repo_email == 'DEBIAN':
+ if os.getenv('DEBEMAIL'):
+ repo.set_user_email(os.getenv('DEBEMAIL'))
diff --git a/gbp/scripts/config.py b/gbp/scripts/config.py
new file mode 100755
index 0000000..e988748
--- /dev/null
+++ b/gbp/scripts/config.py
@@ -0,0 +1,142 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2014 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Query and display config file values"""
+
+import sys
+import os
+from gbp.config import GbpOptionParser
+from gbp.errors import GbpError
+from gbp.scripts.supercommand import import_command
+from gbp.scripts.common import ExitCodes
+import gbp.log
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParser(command=os.path.basename(name), prefix='',
+ usage='%prog [options] command[.optionname] - display configuration settings')
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+ return parser.parse_args(argv)
+
+
+def build_cmd_parser(section):
+ """
+ Populate the parser to get a list of valid options
+ """
+ try:
+ # Populate the parser to get a list of
+ # valid options
+ module = import_command(section)
+ parser = module.build_parser(section)
+ except (AttributeError, ImportError):
+ # Use the default parser for section that don't
+ # map to a command
+ parser = GbpOptionParser(section)
+ parser.parse_config_files()
+ return parser
+
+
+def print_single_option(parser, option, printer):
+ value = parser.get_config_file_value(option)
+ if value is not None:
+ printer("%s" % value)
+ else:
+ return 2
+ return 0
+
+
+def print_all_options(parser, printer):
+ if not parser.valid_options:
+ return 2
+ for opt in parser.valid_options:
+ value = parser.get_config_file_value(opt)
+ printer("%s.%s=%s" % (parser.command, opt, value))
+ return 0
+
+
+def print_cmd_values(query, printer):
+ """
+ Print configuration values of a command
+
+ @param query: the section to print the values for or section.option to
+ print
+ @param printer: the printer to output the values
+ """
+ if not query:
+ return 2
+
+ try:
+ section, option = query.split('.')
+ except ValueError:
+ section = query
+ option = None
+
+ parser = build_cmd_parser(section)
+
+ if option: # Single option query
+ return print_single_option(parser, option, printer)
+ else: # all options
+ return print_all_options(parser, printer)
+
+
+def value_printer(output):
+ print(output)
+
+
+def main(argv):
+ retval = 1
+
+ (options, args) = parse_args(argv)
+
+ if options is None:
+ return ExitCodes.parse_error
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if not args:
+ gbp.log.err("No command given")
+ return 2
+ elif len(args) != 2:
+ gbp.log.err("Can only take a command or command.optionname, check --help")
+ return 2
+ else:
+ query = args[1]
+
+ retval = print_cmd_values(query, value_printer)
+ 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..6c1ff7a
--- /dev/null
+++ b/gbp/scripts/create_remote_repo.py
@@ -0,0 +1,406 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010,2012,2015,2016 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, please see
+# <http://www.gnu.org/licenses/>
+#
+# Based on the aa-create-git-repo and dom-new-git-repo shell scripts
+"""Create a remote Git repository based on the current one"""
+
+import sys
+import os
+import urllib.parse
+import subprocess
+import tty
+import termios
+import re
+import configparser
+
+from gbp.deb.changelog import ChangeLog, NoChangeLogError
+from gbp.command_wrappers import (CommandExecFailed, GitCommand)
+from gbp.config import (GbpOptionParserDebian, GbpOptionGroup)
+from gbp.errors import GbpError
+from gbp.git import GitRepositoryError
+from gbp.deb.git import DebianGitRepository
+from gbp.scripts.common import ExitCodes
+
+import gbp.log
+
+
+def print_config(remote, branches):
+ """
+ Print out the git config to push to the newly created repo.
+
+ >>> print_config({'name': 'name', 'url': 'url'}, ['foo', 'bar'])
+ [remote "name"]
+ url = url
+ fetch = +refs/heads/*:refs/remotes/name/*
+ push = foo
+ push = bar
+ [branch "foo"]
+ remote = name
+ merge = refs/heads/foo
+ [branch "bar"]
+ remote = name
+ merge = refs/heads/bar
+ """
+
+ 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_url(remote_url, name, pkg, template_dir=None, bare=True):
+ """
+ Sanity check our remote URL
+
+ """
+ frags = urllib.parse.urlparse(remote_url)
+ if frags.scheme in ['ssh', 'git+ssh', '']:
+ scheme = frags.scheme
+ else:
+ raise GbpError("URL must use ssh protocol.")
+
+ if '%(pkg)s' not in remote_url and not remote_url.endswith(".git"):
+ raise GbpError("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("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("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,
+ 'scheme': scheme,
+ 'template-dir': template_dir,
+ 'bare': bare}
+ return remote
+
+
+def build_remote_script(remote, branch):
+ """
+ Create the script that will be run on the remote side
+ """
+ args = remote
+ args['branch'] = branch
+ args['git-init-args'] = '--shared'
+ if args['bare']:
+ args['git-init-args'] += ' --bare'
+ args['checkout_cmd'] = ''
+ args['git_dir'] = '.'
+ else:
+ args['checkout_cmd'] = 'git checkout -f'
+ args['git_dir'] = '.git'
+ if args['template-dir']:
+ args['git-init-args'] += (' --template=%s'
+ % args['template-dir'])
+ remote_script_pattern = \
+ ['',
+ '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 %(git-init-args)s',
+ 'echo "%(pkg)s packaging" > %(git_dir)s/description',
+ 'echo "ref: refs/heads/%(branch)s" > %(git_dir)s/HEAD',
+ '']
+ remote_script = '\n'.join(remote_script_pattern) % args
+ return remote_script
+
+
+def build_cmd(remote):
+ """
+ Build the command we pass the script to
+
+ >>> build_cmd({'scheme': ''})
+ ['sh']
+ >>> build_cmd({'scheme': 'ssh', 'host': 'host', 'port': 80})
+ ['ssh', '-p', 80, 'host', 'sh']
+ """
+ cmd = []
+ if remote["scheme"]:
+ cmd.append('ssh')
+ if remote["port"]:
+ cmd.extend(['-p', remote['port']])
+ cmd.append(remote["host"])
+ cmd.append('sh')
+ return cmd
+
+
+def read_yn():
+ fd = sys.stdin.fileno()
+ try:
+ old_settings = termios.tcgetattr(fd)
+ except termios.error:
+ old_settings = None
+
+ try:
+ if old_settings:
+ tty.setraw(sys.stdin.fileno())
+ ch = sys.stdin.read(1)
+ finally:
+ if old_settings:
+ 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-to'])
+ for branch in branches:
+ gitTrackRemote(['%s/%s' % (remote['name'], branch), branch])
+
+
+def push_branches(remote, branches):
+ gitPush = GitCommand('push')
+ gitPush([remote['url']] + branches)
+ gitPush([remote['url'], '--tags'])
+
+
+def usage_msg():
+ return """%prog [options] - create a remote git repository
+Actions:
+ create create the repository. This is the default when no action is
+ given.
+ list list available configuration templates for remote repositories"""
+
+
+def build_parser(name, sections=[]):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
+ usage=usage_msg(),
+ sections=sections)
+ except (GbpError, configparser.NoSectionError) as err:
+ gbp.log.err(err)
+ return None
+
+ 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')
+ branch_group.add_boolean_config_file_option(option_name="bare",
+ dest='bare')
+ 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ parser.add_option("--remote-name",
+ dest="name",
+ default="origin",
+ help="The name of the remote, default is 'origin'")
+ parser.add_config_file_option(option_name="template-dir",
+ dest="template_dir")
+ parser.add_config_file_option(option_name="remote-config",
+ dest="remote_config")
+ return parser
+
+
+def parse_args(argv):
+ """
+ Parse the command line arguments and config files.
+
+ @param argv: the command line arguments
+ @type argv: C{list} of C{str}
+ """
+ sections = []
+ # We handle the template section as an additional config file
+ # section to parse, this makes e.g. --help work as expected:
+ for arg in argv:
+ if arg.startswith('--remote-config='):
+ sections = ['remote-config %s' % arg.split('=', 1)[1]]
+ break
+
+ parser = build_parser(argv[0], sections)
+ if not parser:
+ return None, None, None
+
+ return list(parser.parse_args(argv)) + [parser.config_file_sections]
+
+
+def do_create(options):
+ retval = 0
+ changelog = 'debian/changelog'
+ cmd = []
+
+ try:
+ repo = DebianGitRepository(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_pristine_tar_branch() and options.pristine_tar:
+ branches += [repo.pristine_tar_branch]
+
+ try:
+ cp = ChangeLog(filename=changelog)
+ pkg = cp['Source']
+ except 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_url(options.remote_url,
+ options.name,
+ pkg,
+ options.template_dir,
+ options.bare)
+ 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.")
+
+ remote_default = branches[0] if branches else options.debian_branch
+ remote_script = build_remote_script(remote, remote_default)
+ if options.verbose:
+ print(remote_script)
+
+ cmd = build_cmd(remote)
+ if options.verbose:
+ print(cmd)
+
+ proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
+ proc.communicate(remote_script.encode())
+ if proc.returncode:
+ raise GbpError("Error creating remote repository")
+
+ if branches:
+ 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 KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except CommandExecFailed:
+ retval = 1
+ except (GbpError, GitRepositoryError) as err:
+ if str(err):
+ gbp.log.err(err)
+ retval = 1
+ return retval
+
+
+def get_config_names(sections):
+ config_names = []
+ for section in sections:
+ if section.startswith("remote-config "):
+ config_names.append(section.split(' ', 1)[1])
+ return config_names
+
+
+def do_list(sections):
+ names = get_config_names(sections)
+ if names:
+ gbp.log.info("Available remote config templates:")
+ for n in names:
+ print(" %s" % n)
+ else:
+ gbp.log.info("No remote config templates found.")
+ return 0
+
+
+def main(argv):
+ retval = 1
+
+ options, args, sections = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if len(args) == 1:
+ args.append('create') # the default
+ elif len(args) > 2:
+ gbp.log.err("Only one action allowed")
+ return 1
+
+ action = args[1]
+ if action == 'create':
+ retval = do_create(options)
+ elif action == 'list':
+ retval = do_list(sections)
+ else:
+ gbp.log.err("Unknown action '%s'" % action)
+ 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..6297d2d
--- /dev/null
+++ b/gbp/scripts/dch.py
@@ -0,0 +1,613 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2007,2008,2009,2010,2013,2015,2017 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Generate Debian changelog entries from Git commit messages"""
+
+import os.path
+import re
+import sys
+import shutil
+import gbp.command_wrappers as gbpc
+import gbp.dch as dch
+import gbp.log
+from gbp.config import GbpOptionParserDebian, GbpOptionGroup
+from gbp.errors import GbpError
+from gbp.deb import compare_versions
+from gbp.deb.source import DebianSource, DebianSourceError
+from gbp.deb.git import GitRepositoryError, DebianGitRepository
+from gbp.deb.changelog import ChangeLog, NoChangeLogError
+from gbp.scripts.common import ExitCodes, maybe_debug_raise
+from gbp.scripts.common.hook import Hook
+
+user_customizations = {}
+snapshot_re = re.compile("\s*\*\* SNAPSHOT build @(?P<commit>[a-z0-9]+)\s+\*\*")
+
+
+def guess_version_from_upstream(repo, upstream_tag_format, upstream_branch, cp=None):
+ """
+ Guess the version based on the latest version on the upstream branch.
+ If the version in dch is already higher this function returns None.
+ """
+ epoch = cp.epoch if cp else None
+ cmp_version = cp.version if cp else '0~'
+ try:
+ version = repo.debian_version_from_upstream(upstream_tag_format,
+ upstream_branch,
+ epoch=epoch,
+ debian_release=False)
+ gbp.log.debug("Found upstream version %s." % version)
+ if compare_versions(version, cmp_version) > 0:
+ return "%s-1" % version
+ except GitRepositoryError as e:
+ gbp.log.debug("No upstream tag found: %s" % e)
+ return None
+
+
+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_section(repo, use_git_author, options, dch_options):
+ """
+ Fixup the changelog header and trailer's committer and email address
+
+ It might otherwise point to the last git committer instead of the person
+ creating the changelog
+
+ This also applies --distribution and --urgency options passed to gbp dch
+ """
+ author, email = get_author_email(repo, use_git_author)
+ used_options = ['distribution', 'urgency']
+ opts = []
+ mainttrailer_opts = ['--nomainttrailer', '--mainttrailer', '-t']
+
+ # This must not be done for snapshots or snapshots changelog entries
+ # will not be concatenated
+ if not options.snapshot:
+ for opt in used_options:
+ val = getattr(options, opt)
+ if val:
+ gbp.log.debug("Set header option '%s' to '%s'" % (opt, val))
+ opts.append("--%s=%s" % (opt, val))
+ else:
+ gbp.log.debug("Snapshot enabled: do not fixup options in header")
+
+ for opt in mainttrailer_opts:
+ if opt in dch_options:
+ break
+ else:
+ opts.append(mainttrailer_opts[0])
+ ChangeLog.spawn_dch(msg='', author=author, email=email, dch_options=dch_options + opts)
+
+
+def snapshot_version(version):
+ """
+ Get the current release and snapshot version.
+
+ Format is <debian-version>~<release>.gbp<short-commit-id>
+
+ >>> snapshot_version('1.0-1')
+ ('1.0-1', 0)
+ >>> snapshot_version('1.0-1~1.test0')
+ ('1.0-1~1.test0', 0)
+ >>> snapshot_version('1.0-1~2.gbp1234')
+ ('1.0-1', 2)
+ """
+ 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: C{str}
+ """
+ try:
+ tmpfile = '%s.%s' % (changelog, snapshot)
+ cw = open(tmpfile, 'w', encoding='utf-8')
+ cr = open(changelog, 'r', encoding='utf-8')
+
+ print("%(Source)s (%(MangledVersion)s) "
+ "%(Distribution)s; urgency=%(urgency)s\n" % cp, file=cw)
+
+ cr.readline() # skip version and empty line
+ cr.readline()
+ line = cr.readline()
+ if snapshot_re.match(line):
+ cr.readline() # consume the empty line after the snapshot header
+ line = ''
+
+ if snapshot:
+ print(" ** SNAPSHOT build @%s **\n" % snapshot, file=cw)
+
+ if line:
+ print(line.rstrip(), file=cw)
+ shutil.copyfileobj(cr, cw)
+ cw.close()
+ cr.close()
+ os.unlink(changelog)
+ os.rename(tmpfile, changelog)
+ except OSError as e:
+ raise GbpError("Error mangling changelog %s" % e)
+
+
+def do_release(changelog, repo, cp, use_git_author, dch_options):
+ """Remove the snapshot header and set the distribution"""
+ author, email = get_author_email(repo, use_git_author)
+ (release, snapshot) = snapshot_version(cp['Version'])
+ if snapshot:
+ cp['MangledVersion'] = release
+ mangle_changelog(changelog, cp)
+ 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 = 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, cp['MangledVersion']
+
+
+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'].name
+ email = commit_info['author'].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_documented_commit(cp, repo, tagformat):
+ """
+ Guess the last commit documented in the changelog from the snapshot banner,
+ the last tagged version or the last point the changelog was touched.
+
+ @param cp: the changelog
+ @param repo: the git repository
+ @param tagformat: the format for Debian tags
+ @returns: the commit that was last documented in the changelog
+ @rtype: C{str}
+ @raises GbpError: In case we fail to find a commit to start at
+ """
+ # Check for snapshot banner
+ sr = re.search(snapshot_re, cp['Changes'])
+ if sr:
+ return sr.group('commit')
+
+ # Check if the latest version in the changelog is already tagged. If
+ # so this is the last documented commit.
+ commit = repo.find_version(tagformat, cp.version)
+ if commit:
+ gbp.log.info("Found tag for topmost changelog version '%s'" % commit)
+ return commit
+
+ # Check when the changelog was last touched
+ last = repo.get_commits(paths="debian/changelog", num=1)
+ if last:
+ gbp.log.info("Changelog last touched at '%s'" % last[0])
+ return last[0]
+
+ # Changelog not touched yet
+ return None
+
+
+def has_snapshot_banner(cp):
+ """Whether the changelog has a snapshot banner"""
+ sr = re.search(snapshot_re, cp['Changes'])
+ return True if sr else False
+
+
+def get_customizations(customization_file):
+ if customization_file:
+ try:
+ with open(customization_file) as f:
+ exec(f.read(), user_customizations, user_customizations)
+ except Exception as err:
+ raise GbpError("Failed to load customization file: %s" % err)
+
+
+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 not options.since and not options.auto:
+ options.auto = True
+
+ dch_options = []
+ if options.multimaint_merge:
+ dch_options.append("--multimaint-merge")
+ else:
+ dch_options.append("--nomultimaint-merge")
+
+ if options.multimaint:
+ dch_options.append("--multimaint")
+ else:
+ dch_options.append("--nomultimaint")
+
+ if options.force_distribution:
+ dch_options.append("--force-distribution")
+
+ return dch_options + options.dch_opts
+
+
+def process_editor_option(options):
+ """Determine text 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 == 'never' or options.spawn_editor not in states:
+ return None
+ else:
+ return "sensible-editor"
+
+
+def changelog_commit_msg(options, version):
+ return options.commit_msg % dict(version=version)
+
+
+def create_changelog(repo, source, options):
+ try:
+ name = source.control.name
+ except DebianSourceError:
+ raise GbpError("Did not find debian/changelog or debian/source. Is this a Debian package?")
+ version = guess_version_from_upstream(repo, options.upstream_tag,
+ options.upstream_branch, None)
+ return ChangeLog.create(name, version)
+
+
+def maybe_create_changelog(repo, source, options):
+ """
+ Get the changelog or create a new one if it does not exist yet
+ """
+ try:
+ return source.changelog
+ except DebianSourceError:
+ return create_changelog(repo, source, options)
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name),
+ usage='%prog [options] paths')
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ 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="upstream-branch", dest="upstream_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')
+ parser.add_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ 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("-D", "--distribution", dest="distribution", help="Set distribution")
+ version_group.add_option("--force-distribution", action="store_true", dest="force_distribution", default=False,
+ help="Force the provided distribution to be used, "
+ "even if it doesn't match the list of known distributions")
+ version_group.add_option("-N", "--new-version", dest="new_version",
+ help="use this as base for the new version number")
+ version_group.add_config_file_option("urgency", dest="urgency")
+ version_group.add_option("--bpo", dest="bpo", action="store_true", default=False,
+ help="Increment the Debian release number for an upload to 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_option("--team", dest="team", action="store_true", default=False,
+ help="Increment the Debian release number for a Debian Team upload, "
+ "and add a Team upload changelog comment.")
+ version_group.add_option("--security", dest="security", action="store_true", default=False,
+ help="Increment the Debian release number for a security upload and "
+ "add a security upload changelog comment.")
+ version_group.add_boolean_config_file_option(option_name="git-author", dest="use_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")
+ commit_group.add_config_file_option(option_name="meta-closes-bugnum", dest="meta_closes_bugnum")
+ 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")
+ parser.add_config_file_option(option_name="commit-msg",
+ dest="commit_msg")
+ parser.add_option("-c", "--commit", action="store_true", dest="commit", default=False,
+ help="commit changelog file after generating")
+ parser.add_config_file_option(option_name="dch-opt",
+ dest="dch_opts", action="append",
+ help="option to pass to dch verbatim, "
+ "can be given multiple times",
+ metavar="DCH_OPT")
+
+ 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)
+ custom_group.add_config_file_option(option_name="postedit", dest="postedit",
+ help="Hook to run after changes to the changelog file"
+ "have been finalized default is '%(postedit)s'")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return [None] * 4
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+ dch_options = process_options(options, parser)
+ editor_cmd = process_editor_option(options)
+ return options, args, dch_options, editor_cmd
+
+
+def main(argv):
+ ret = 0
+ changelog = 'debian/changelog'
+ until = 'HEAD'
+ found_snapshot_banner = False
+ version_change = {}
+ branch = None
+
+ options, args, dch_options, editor_cmd = parse_args(argv)
+
+ if not options:
+ return ExitCodes.parse_error
+
+ try:
+ old_cwd = os.path.abspath(os.path.curdir)
+ try:
+ repo = DebianGitRepository('.', toplevel=False)
+ os.chdir(repo.path)
+ except GitRepositoryError:
+ raise GbpError("%s is not a git repository" % (os.path.abspath('.')))
+
+ get_customizations(options.customization_file)
+ try:
+ branch = repo.get_branch()
+ except GitRepositoryError:
+ # Not being on any branch is o.k. with --ignore-branch
+ if not options.ignore_branch:
+ raise
+
+ 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.")
+
+ source = DebianSource('.')
+ cp = maybe_create_changelog(repo, source, options)
+
+ if options.since:
+ since = options.since
+ else:
+ since = guess_documented_commit(cp, repo, options.debian_tag)
+ if since:
+ msg = "Continuing from commit '%s'" % since
+ else:
+ msg = "Starting from first commit"
+ gbp.log.info(msg)
+ found_snapshot_banner = has_snapshot_banner(cp)
+
+ if args:
+ gbp.log.info("Only looking for changes on '%s'" % " ".join(args))
+ commits = repo.get_commits(since=since, until=until, paths=args,
+ options=options.git_log.split(" "))
+ commits.reverse()
+
+ add_section = False
+ # add a new changelog section if:
+ if (options.new_version or options.bpo or options.nmu or options.qa or
+ options.team or options.security):
+ if options.bpo:
+ version_change['increment'] = '--bpo'
+ elif options.nmu:
+ version_change['increment'] = '--nmu'
+ elif options.qa:
+ version_change['increment'] = '--qa'
+ elif options.team:
+ version_change['increment'] = '--team'
+ elif options.security:
+ version_change['increment'] = '--security'
+ 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_banner:
+ if commits:
+ # the last version was a release and we have pending commits
+ add_section = True
+ if options.snapshot:
+ # the user want to switch to snapshot mode
+ add_section = True
+
+ if add_section and not version_change and not source.is_native():
+ # Get version from upstream if none provided
+ v = guess_version_from_upstream(repo, options.upstream_tag,
+ options.upstream_branch, cp)
+ if v:
+ version_change['version'] = v
+
+ 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)
+ cp.add_section(distribution="UNRELEASED", msg=commit_msg,
+ version=version_change,
+ author=commit_author,
+ email=commit_email,
+ dch_options=dch_options)
+ # Adding a section only needs to happen once.
+ add_section = False
+ else:
+ cp.add_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.
+ cp.add_section(distribution="UNRELEASED", msg=["UNRELEASED"],
+ version=version_change,
+ dch_options=dch_options)
+
+ fixup_section(repo, use_git_author=options.use_git_author, options=options,
+ dch_options=dch_options)
+
+ if options.release:
+ do_release(changelog, repo, cp, use_git_author=options.use_git_author,
+ dch_options=dch_options)
+ elif options.snapshot:
+ (snap, commit, version) = do_snapshot(changelog, repo, options.snapshot_number)
+ gbp.log.info("Changelog %s (snapshot #%d) prepared up to %s" % (version, snap, commit[:7]))
+
+ if editor_cmd:
+ gbpc.Command(editor_cmd, ["debian/changelog"])()
+
+ if options.postedit:
+ cp = ChangeLog(filename=changelog)
+ Hook('Postimport', options.postedit,
+ extra_env={'GBP_DEBIAN_VERSION': cp.version})()
+
+ if options.commit:
+ # Get the version from the changelog file (since dch might
+ # have incremented it, there's no way we can already know
+ # the version).
+ version = ChangeLog(filename=changelog).version
+ # Commit the changes to the changelog file
+ msg = changelog_commit_msg(options, version)
+ repo.commit_files([changelog], msg)
+ gbp.log.info("Changelog committed for version %s" % version)
+ except KeyboardInterrupt:
+ ret = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except (gbpc.CommandExecFailed,
+ GbpError,
+ GitRepositoryError,
+ DebianSourceError,
+ NoChangeLogError) as err:
+ if str(err):
+ gbp.log.err(err)
+ ret = 1
+ maybe_debug_raise()
+ finally:
+ os.chdir(old_cwd)
+ 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/export_orig.py b/gbp/scripts/export_orig.py
new file mode 100755
index 0000000..ea6c887
--- /dev/null
+++ b/gbp/scripts/export_orig.py
@@ -0,0 +1,366 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Create orig tarballs from git"""
+
+import os
+import sys
+import gbp.deb as du
+from gbp.command_wrappers import CommandExecFailed
+from gbp.config import (GbpOptionParserDebian, GbpOptionGroup)
+from gbp.deb.git import (GitRepositoryError, DebianGitRepository)
+from gbp.deb.source import DebianSource, DebianSourceError
+from gbp.errors import GbpError
+import gbp.log
+import gbp.notifications
+from gbp.scripts.common import ExitCodes
+from gbp.pkg import Compressor, Archive
+
+
+def prepare_upstream_tarballs(repo, source, options, tarball_dir, output_dir):
+ """
+ Make sure we have the needed upstream tarballs. The default order is:
+ - look in tarball_dir and if found symlink to it
+ - create tarball using pristine-tar
+ - create tarball using git-archive
+
+ Afterwards
+ - create pristine-tar commmits if pristine-tar-commit is in use
+ - verify tarball checksums if pristine-tar is in use
+ """
+ if hasattr(options, 'no_create_orig') and options.no_create_orig:
+ return
+
+ if not source.is_native() and not source.upstream_version:
+ raise GbpError("Non-native package '%s' "
+ "has invalid version '%s'" % (source.name, source.version))
+
+ options.comp_type = guess_comp_type(options.comp_type,
+ source,
+ repo,
+ options.tarball_dir)
+ orig_files = source.upstream_tarball_names(options.comp_type, options.components)
+
+ # look in tarball_dir first, if found force a symlink to it
+ if options.tarball_dir:
+ gbp.log.debug("Looking for orig tarballs '%s' at '%s'" % (", ".join(orig_files), tarball_dir))
+ missing = du.DebianPkgPolicy.symlink_origs(orig_files, tarball_dir, output_dir, force=True)
+ if missing:
+ msg = "Tarballs '%s' not found at '%s'" % (", ".join(missing), tarball_dir)
+ else:
+ msg = "All Orig tarballs '%s' found at '%s'" % (", ".join(orig_files), tarball_dir)
+ gbp.log.info(msg)
+
+ # Create tarball if missing or forced
+ if not du.DebianPkgPolicy.has_origs(orig_files, output_dir) or options.force_create:
+ if not pristine_tar_build_origs(repo, source, output_dir, options):
+ git_archive_build_origs(repo, source, output_dir, options)
+ maybe_pristine_tar_commit(repo, source, options, output_dir, orig_files)
+ pristine_tar_verify_origs(repo, source, options, output_dir, orig_files)
+
+
+def pristine_tar_prepare_orig_tree(repo, source, options):
+ """
+ Make sure the upstream tree exists
+
+ In case of component tarballs we need to recreate a tree for the
+ main tarball without the component subdirs.
+ """
+ if options.components:
+ try:
+ upstream_tag = repo.version_to_tag(options.upstream_tag,
+ source.upstream_version)
+ tree_name = "%s^{tree}" % upstream_tag
+ repo.tree_drop_dirs(tree_name, options.components)
+ except GitRepositoryError:
+ raise GbpError("Couldn't find upstream tree '%s' to create "
+ "orig tarball via pristine-tar" % tree_name)
+
+
+def pristine_tar_build_origs(repo, source, output_dir, options):
+ """
+ Build orig tarball using pristine-tar
+
+ @returns: C{True} if tarball was build, C{False} otherwise
+ """
+ if not options.pristine_tar:
+ return False
+
+ if not repo.has_branch(repo.pristine_tar_branch):
+ gbp.log.warn('Pristine-tar branch "%s" not found' %
+ repo.pristine_tar.branch)
+
+ comp = Compressor(options.comp_type)
+ pristine_tar_prepare_orig_tree(repo, source, options)
+ try:
+ gbp.log.info("Creating %s" %
+ os.path.abspath(os.path.join(output_dir,
+ source.upstream_tarball_name(comp.type))))
+ repo.create_upstream_tarball_via_pristine_tar(source,
+ output_dir,
+ comp)
+ for component in options.components:
+ gbp.log.info("Creating %s" %
+ os.path.abspath(os.path.join(output_dir,
+ source.upstream_tarball_name(comp.type, component))))
+ repo.create_upstream_tarball_via_pristine_tar(source,
+ output_dir,
+ comp,
+ component=component)
+ return True
+ except GitRepositoryError:
+ if hasattr(options, 'pristine_tar_commit') and options.pristine_tar_commit:
+ gbp.log.debug("pristine-tar checkout failed, will commit tarball "
+ "due to '--pristine-tar-commit'")
+ else:
+ raise
+ return False
+
+
+def pristine_tar_verify_origs(repo, source, options, output_dir, orig_files):
+ """
+ Verify orig tarballs using prstine tar
+
+ @returns: C{True} if tarball was build, C{False} otherwise
+ """
+ if not options.pristine_tar:
+ return True
+
+ if not repo.pristine_tar.has_feature_verify():
+ gbp.log.warn("pristine-tar does not support verify. "
+ "Skipping verification.")
+ return True
+
+ pristine_tar_prepare_orig_tree(repo, source, options)
+ for f in orig_files:
+ repo.pristine_tar.verify(os.path.join(output_dir, f))
+ return True
+
+
+def maybe_pristine_tar_commit(repo, source, options, output_dir, orig_files):
+ if not (hasattr(options, 'pristine_tar_commit') and options.pristine_tar_commit):
+ return
+
+ if repo.pristine_tar.has_commit(source.name,
+ source.upstream_version,
+ options.comp_type):
+ gbp.log.debug("%s already on pristine tar branch" % orig_files[0])
+ else:
+ upstream_tree = git_archive_get_upstream_tree(repo, source, options)
+ archive = os.path.join(output_dir, orig_files[0])
+ gbp.log.debug("Adding %s to pristine-tar branch" % archive)
+ repo.pristine_tar.commit(archive, upstream_tree)
+
+
+def git_archive_get_upstream_tree(repo, source, options):
+ """
+ Determine the upstream tree from the given options
+
+ for a git archive export
+ """
+ if options.upstream_tree.upper() == 'TAG':
+ if source.upstream_version is None:
+ raise GitRepositoryError("Can't determine upstream version from changelog")
+ upstream_tree = repo.version_to_tag(options.upstream_tag,
+ source.upstream_version)
+ elif options.upstream_tree.upper() == 'BRANCH':
+ if not repo.has_branch(options.upstream_branch):
+ raise GbpError("%s is not a valid branch" % options.upstream_branch)
+ upstream_tree = options.upstream_branch
+ elif options.upstream_tree.upper() == 'SLOPPY':
+ tree_name = "%s^{tree}" % options.debian_branch
+ upstream_tree = repo.tree_drop_dirs(tree_name, ["debian"])
+ else:
+ upstream_tree = options.upstream_tree
+ if not repo.has_treeish(upstream_tree):
+ raise GbpError("%s is not a valid treeish" % upstream_tree)
+ return upstream_tree
+
+
+def git_archive_build_origs(repo, source, output_dir, options):
+ """
+ Build orig tarball(s) using git-archive
+
+ @param source: the source of the package we're acting on
+ @type source: L{DebianSource}
+ @param output_dir: where to put the tarball
+ @type output_dir: C{Str}
+ @param options: the parsed options
+ @type options: C{dict} of options
+ """
+ comp = Compressor(options.comp_type, options.comp_level)
+ upstream_tree = git_archive_get_upstream_tree(repo, source, options)
+ gbp.log.info("Creating %s from '%s'" % (source.upstream_tarball_name(comp.type),
+ upstream_tree))
+ gbp.log.debug("Building upstream tarball with compression %s" % comp)
+ tree = repo.tree_drop_dirs(upstream_tree, options.components) if options.components else upstream_tree
+ repo.create_upstream_tarball_via_git_archive(source, output_dir, tree, comp, options.with_submodules)
+ for component in options.components:
+ subtree = repo.tree_get_dir(upstream_tree, component)
+ if not subtree:
+ raise GbpError("No tree for '%s' found in '%s' to create additional tarball from"
+ % (component, upstream_tree))
+ gbp.log.info("Creating additional tarball '%s' from '%s'"
+ % (source.upstream_tarball_name(options.comp_type, component=component),
+ subtree))
+ repo.create_upstream_tarball_via_git_archive(source, output_dir, subtree, comp,
+ options.with_submodules, component=component)
+
+
+def guess_comp_type(comp_type, source, repo, tarball_dir):
+ """Guess compression type to use for the to be built upstream tarball
+
+ We prefer pristine-tar over everything else since this is what's carried around with
+ the repo and might be more reliable than what a user has in tarball_dir.
+ """
+ if comp_type != 'auto':
+ comp_type = Compressor.Aliases.get(comp_type, comp_type)
+ if comp_type not in Compressor.Opts:
+ gbp.log.warn("Unknown compression type - guessing.")
+ comp_type = 'auto'
+
+ if comp_type == 'auto':
+ if repo and repo.has_pristine_tar_branch():
+ regex = 'pristine-tar .* %s_%s\.orig.tar\.' % (source.name, source.upstream_version)
+ commits = repo.grep_log(regex, repo.pristine_tar_branch, merges=False)
+ if commits:
+ commit = commits[-1]
+ gbp.log.debug("Found pristine-tar commit at '%s'" % commit)
+ else:
+ commit = repo.pristine_tar_branch
+ tarball = repo.get_commit_info(commit)['subject']
+ (base_name, archive_fmt, comp_type) = Archive.parse_filename(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))
+ else:
+ if not tarball_dir:
+ tarball_dir = '..'
+ detected = None
+ for comp in Compressor.Opts.keys():
+ if du.DebianPkgPolicy.has_orig(source.upstream_tarball_name(comp), tarball_dir):
+ if detected is not None:
+ raise GbpError("Multiple orig tarballs found.")
+ detected = comp
+ comp_type = 'gzip' if detected is None else detected
+ return comp_type
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='')
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ tag_group = GbpOptionGroup(parser,
+ "tag options",
+ "options related to git tag creation")
+ orig_group = GbpOptionGroup(parser,
+ "orig tarball options",
+ "options related to the creation of the orig tarball")
+ branch_group = GbpOptionGroup(parser,
+ "branch options",
+ "branch layout options")
+ for group in [tag_group, orig_group, branch_group]:
+ parser.add_option_group(group)
+
+ parser.add_option("--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="color-scheme",
+ dest="color_scheme")
+ 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 tarball", 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'")
+ orig_group.add_config_file_option("component", action="append", metavar='COMPONENT',
+ dest="components")
+ branch_group.add_config_file_option(option_name="upstream-branch", dest="upstream_branch")
+ branch_group.add_boolean_config_file_option(option_name="submodules", dest="with_submodules")
+ return parser
+
+
+def parse_args(argv, prefix):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+ options, args = parser.parse_args(argv[1:])
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+ return options, args
+
+
+def main(argv):
+ retval = 0
+ source = None
+
+ options, args = parse_args(argv, '')
+
+ if args or not options:
+ return ExitCodes.parse_error
+
+ 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:
+ try:
+ source = DebianSource(repo.path)
+ source.is_native()
+ except Exception as e:
+ raise GbpError("Can't determine package type: %s" % e)
+
+ output_dir = options.tarball_dir or os.path.join(repo.path, '..')
+
+ if source.is_native():
+ gbp.log.info("Nothing to be done for native package")
+ return 0
+
+ prepare_upstream_tarballs(repo, source, options, output_dir,
+ output_dir)
+ except KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except CommandExecFailed:
+ retval = 1
+ except (GbpError, GitRepositoryError) as err:
+ if str(err):
+ gbp.log.err(err)
+ retval = 1
+ except DebianSourceError as err:
+ gbp.log.err(err)
+ source = None
+ 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/import_dsc.py b/gbp/scripts/import_dsc.py
new file mode 100644
index 0000000..8cd22c0
--- /dev/null
+++ b/gbp/scripts/import_dsc.py
@@ -0,0 +1,557 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006, 2007, 2011, 2012, 2015-2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""Import a Debian source package into a Git repository"""
+
+import sys
+import re
+import os
+import shutil
+import tempfile
+import glob
+import pipes
+import time
+import gbp.command_wrappers as gbpc
+from gbp.deb.dscfile import DscFile
+from gbp.deb.upstreamsource import DebianUpstreamSource, unpack_component_tarball
+from gbp.deb.git import (DebianGitRepository, GitRepositoryError)
+from gbp.deb.changelog import ChangeLog
+from gbp.git import rfc822_date_to_git
+from gbp.git.modifier import GitModifier
+from gbp.git.vfs import GitVfs
+from gbp.config import (GbpOptionParserDebian, GbpOptionGroup,
+ no_upstream_branch_msg)
+from gbp.errors import GbpError
+from gbp.scripts.common import ExitCodes
+from gbp.scripts.common import repo_setup
+import gbp.log
+
+
+class SkipImport(Exception):
+ pass
+
+
+def download_source(pkg, dirs, unauth):
+ opts = ['--download-only']
+ if unauth:
+ opts.append('--allow-unauthenticated')
+
+ if re.match(r'[a-z]{1,5}://', pkg):
+ cmd = 'dget'
+ opts += ['-q', pkg]
+ else:
+ cmd = 'apt-get'
+ opts += ['-qq', 'source', pkg]
+
+ dirs['download'] = os.path.abspath(tempfile.mkdtemp())
+ gbp.log.info("Downloading '%s' using '%s'..." % (pkg, cmd))
+
+ gbpc.RunAtCommand(cmd, opts, shell=False)(dir=dirs['download'])
+ try:
+ dsc = glob.glob(os.path.join(dirs['download'], '*.dsc'))[0]
+ except IndexError:
+ raise GbpError("Did not find a dsc file at %s/" % dirs['download'])
+ return dsc
+
+
+def apply_patch(diff):
+ "Apply patch to a source tree"
+ patch_opts = ['-N', '-p1', '-F0', '-u', '-t',
+ '-Vnever', '-g0', '-z.gbp.orig',
+ '--quiet']
+
+ pipe = pipes.Template()
+ pipe.prepend('gunzip -c %s' % diff, '.-')
+ pipe.append('patch %s' % ' '.join(patch_opts), '-.')
+
+ try:
+ ret = pipe.copy('', '')
+ if ret:
+ raise GbpError("Error import %s: %d" % (diff, ret))
+ except OSError as err:
+ raise GbpError("Error importing %s: %s" % (diff, err[0]))
+
+
+def apply_deb_tgz(deb_tgz, filters):
+ """Apply .debian.tar.gz (V3 source format)"""
+ # Remove any existing data in debian/ as dpkg-source -x does
+ if os.path.isdir('debian'):
+ shutil.rmtree('debian')
+ gbpc.UnpackTarArchive(deb_tgz, ".", filters)()
+
+
+def get_changes(dir, repo, debian_branch):
+ if repo.empty:
+ version = "0~"
+ else:
+ vfs = GitVfs(repo, debian_branch)
+ try:
+ with vfs.open('debian/changelog') as f:
+ version = ChangeLog(contents=f.read()).version
+ except IOError:
+ version = "0~" # Use full history if debian branch has no changelog
+ cl = ChangeLog(filename=os.path.join(dir, 'debian/changelog'))
+ return cl.get_changes(version)
+
+
+def get_author_from_changelog(dir):
+ """
+ Get author from debian/changelog
+ """
+ dch = ChangeLog(filename=os.path.join(dir, 'debian/changelog'))
+ date = rfc822_date_to_git(dch.date, fuzzy=True)
+ if not (dch.author or dch.email):
+ gbp.log.warn("Failed to parse maintainer")
+
+ return GitModifier(dch.author, dch.email, date)
+
+
+def get_committer_from_author(author, options):
+ """
+ Based on the options fill in the committer
+ """
+ committer = GitModifier()
+ if options.author_committer:
+ committer.name = author.name
+ committer.email = author.email
+ if options.author_committer_date:
+ committer.date = author.date
+ return committer
+
+
+def check_parents(repo, branch, tag):
+ """
+ Check if the upstream tag is already merged, if not, return
+ the additional parent to merge
+ """
+ parents = None
+ rev = None
+
+ try:
+ rev = repo.rev_parse("%s^{commit}" % tag)
+ except GitRepositoryError:
+ pass
+
+ if rev and not repo.branch_contains(branch, rev):
+ gbp.log.debug("Tag '%s' not yet merged into '%s'"
+ % (tag, branch))
+ parents = [rev]
+
+ return parents
+
+
+def apply_debian_patch(repo, source, dsc, upstream_commit, options):
+ """apply the debian patch and tag appropriately"""
+
+ try:
+ os.chdir(source.unpacked)
+
+ if dsc.diff:
+ apply_patch(dsc.diff)
+ elif dsc.deb_tgz:
+ apply_deb_tgz(dsc.deb_tgz, options.filters)
+ else:
+ raise GbpError("Neither a Debian diff nor tarball found")
+
+ if os.path.exists('debian/rules'):
+ os.chmod('debian/rules', 0o755)
+ os.chdir(repo.path)
+
+ parents = check_parents(repo, options.debian_branch, upstream_commit)
+ author = get_author_from_changelog(source.unpacked)
+ committer = get_committer_from_author(author, options)
+
+ changes = get_changes(source.unpacked,
+ repo,
+ options.debian_branch)
+ commit_msg = "Import Debian changes %s\n%s" % (dsc.version, changes)
+ commit = repo.commit_dir(source.unpacked,
+ commit_msg,
+ branch=options.debian_branch,
+ other_parents=parents,
+ author=author,
+ committer=committer)
+ if not options.skip_debian_tag:
+ repo.create_tag(repo.version_to_tag(options.debian_tag, dsc.version),
+ msg="Debian release %s" % dsc.version,
+ commit=commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+ except (gbpc.CommandExecFailed, GitRepositoryError) as err:
+ msg = str(err) or 'Unknown error, please report a bug'
+ raise GbpError("Failed to import Debian package: %s" % msg)
+ finally:
+ os.chdir(repo.path)
+
+
+def create_missing_branch(repo, branch, options):
+ if not repo.has_branch(branch):
+ if options.create_missing_branches:
+ gbp.log.info("Creating missing branch '%s'" % branch)
+ repo.create_branch(branch)
+ else:
+ raise GbpError(no_upstream_branch_msg % branch +
+ "\nAlso check the --create-missing-branches option.")
+
+
+def import_native(repo, source, dsc, options):
+ tag = repo.version_to_tag(options.debian_tag, dsc.upstream_version)
+ msg = "Debian version %s" % dsc.upstream_version
+
+ gbp.log.info("Tag %s not found, importing Debian tarball" % tag)
+ if repo.empty:
+ branch = None
+ else:
+ branch = options.debian_branch
+ create_missing_branch(repo, branch, options)
+
+ author = get_author_from_changelog(source.unpacked)
+ committer = get_committer_from_author(author, options)
+ commit_msg = "Import %s\n%s" % (msg, get_changes(source.unpacked,
+ repo,
+ options.debian_branch))
+ commit = repo.commit_dir(source.unpacked,
+ commit_msg,
+ branch,
+ author=author,
+ committer=committer)
+ if not options.skip_debian_tag:
+ repo.create_tag(name=tag,
+ msg=msg,
+ commit=commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+ if repo.empty and not repo.has_branch(options.debian_branch):
+ repo.create_branch(options.debian_branch, commit)
+ return commit
+
+
+def import_upstream(repo, source, dsc, options):
+ tag = repo.version_to_tag(options.upstream_tag, dsc.upstream_version)
+ msg = "Upstream version %s" % dsc.upstream_version
+
+ if repo.empty:
+ branch = None
+ else:
+ branch = options.upstream_branch
+ create_missing_branch(repo, branch, options)
+
+ author = committer = {}
+ commit_msg = "Import %s" % msg
+
+ commit = repo.commit_dir(source.unpacked,
+ commit_msg,
+ branch,
+ author=author,
+ committer=committer)
+
+ # if the repo was just created make sure debian branch is in .git/HEAD
+ # and upstream points to the first commit
+ if repo.empty:
+ if repo.branch != options.debian_branch:
+ repo.rename_branch(repo.branch, options.debian_branch)
+ repo.create_branch(options.upstream_branch, options.debian_branch)
+
+ repo.create_tag(name=tag,
+ msg=msg,
+ commit=commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+ return commit
+
+
+def print_dsc(dsc):
+ if dsc.native:
+ gbp.log.debug("Debian Native Package")
+ 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.additional_tarballs:
+ gbp.log.debug("Additional tarballs: %s" % ", ".join(dsc.additional_tarballs.values()))
+ 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 = repo.version_to_tag(format, version)
+ timestamped = "%s~%s" % (version, int(time.time()))
+ new = repo.version_to_tag(format, timestamped)
+ repo.move_tag(old, new)
+
+
+def disable_pristine_tar(options, reason):
+ """Disable pristine tar if enabled"""
+ if options.pristine_tar:
+ gbp.log.info("%s: setting '--no-pristine-tar' option" % reason)
+ options.pristine_tar = False
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
+ usage='%prog [options] /path/to/package.dsc [target]')
+ except GbpError as err:
+ gbp.log.err(err)
+ return 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ 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")
+ tag_group.add_option("--skip-debian-tag", dest="skip_debian_tag",
+ action="store_true", default=False,
+ help="Don't add a tag after importing the Debian patch")
+
+ 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 import of 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")
+ import_group.add_boolean_config_file_option(option_name="allow-unauthenticated",
+ dest="allow_unauthenticated")
+
+ parser.add_config_file_option(option_name="repo-user", dest="repo_user",
+ choices=['DEBIAN', 'GIT'])
+ parser.add_config_file_option(option_name="repo-email", dest="repo_email",
+ choices=['DEBIAN', 'GIT'])
+ parser.add_option("--download", dest='download', action="store_true",
+ default=False, help="Ignored. Accepted for compatibility; see EXAMPLES in gbp-import-dsc(1).")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if options.download:
+ gbp.log.warn("Passing --download explicitly is deprecated.")
+
+ return options, args
+
+
+def is_download(pkg):
+ """
+ >>> is_download("http://foo.example.com/apackage.dsc")
+ (True, 'http://foo.example.com/apackage.dsc')
+ >>> is_download("apt:///apackage/sid")
+ (True, 'apackage/sid')
+ >>> is_download("apt://apackage/sid")
+ (True, 'apackage/sid')
+ >>> is_download("apt:apackage/sid")
+ (True, 'apackage/sid')
+ >>> is_download("apt_1.0_amd64.dsc")
+ (False, 'apt_1.0_amd64.dsc')
+ >>> is_download("file:///foo/apackage.dsc")
+ (False, '/foo/apackage.dsc')
+ """
+ if pkg.startswith('file://'):
+ return (False, pkg[len('file://'):])
+ elif pkg.startswith('apt:'):
+ return (True, re.match(r'apt:([/]{2,3})?(?P<pkg>.*)', pkg).group('pkg'))
+ elif re.match("[a-z]{1,5}://", pkg):
+ return (True, pkg)
+ return (False, pkg)
+
+
+def parse_all(argv):
+ options, args = parse_args(argv)
+ if not options:
+ return None, None, None
+
+ if len(args) == 1:
+ pkg = args[0]
+ target = None
+ elif len(args) == 2:
+ pkg = args[0]
+ target = args[1]
+ else:
+ gbp.log.err("Need to give exactly one package to import. Try --help.")
+ return None, None, None
+
+ download, pkg = is_download(pkg)
+ # honor options.download until removed
+ options.download = download or options.download
+ return options, pkg, target
+
+
+def main(argv):
+ dirs = dict(top=os.path.abspath(os.curdir))
+ needs_repo = False
+ ret = 1
+ skipped = False
+
+ options, pkg, target = parse_all(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ try:
+ try:
+ repo = DebianGitRepository('.')
+ # remember if the was repo initially empty
+ repo.empty = repo.is_empty()
+
+ (clean, out) = repo.is_clean()
+ if not clean and not repo.empty:
+ raise GbpError("Repository has uncommitted changes, commit these first: %s" % out)
+ except GitRepositoryError:
+ # no repo found, create one
+ needs_repo = True
+
+ if options.download:
+ dscfile = download_source(pkg,
+ dirs=dirs,
+ unauth=options.allow_unauthenticated)
+ else:
+ dscfile = pkg
+
+ dsc = DscFile.parse(dscfile)
+ if dsc.pkgformat not in ['1.0', '3.0']:
+ raise GbpError("Importing %s source format not yet supported." % dsc.pkgformat)
+ if options.verbose:
+ print_dsc(dsc)
+
+ if needs_repo:
+ target = target or dsc.pkg
+ if os.path.exists(target):
+ raise GbpError("Directory '%s' already exists. If you want to import into it, "
+ "please change into this directory otherwise move it away first."
+ % target)
+ gbp.log.info("No git repository found, creating one.")
+ repo = DebianGitRepository.create(target)
+ repo.empty = True
+ os.chdir(repo.path)
+ repo_setup.set_user_name_and_email(options.repo_user, options.repo_email, repo)
+
+ if repo.bare:
+ disable_pristine_tar(options, "Bare repository")
+
+ # unpack
+ dirs['tmp'] = os.path.abspath(tempfile.mkdtemp(dir='..'))
+ source = DebianUpstreamSource(dsc.tgz)
+ source.unpack(dirs['tmp'], options.filters)
+ for (component, tarball) in dsc.additional_tarballs.items():
+ gbp.log.info("Found component tarball '%s'" % os.path.basename(tarball))
+ unpack_component_tarball(source.unpacked, component, tarball, options.filters)
+
+ if repo.find_version(options.debian_tag, dsc.version):
+ gbp.log.warn("Version %s already imported." % dsc.version)
+ if options.allow_same_version:
+ gbp.log.info("Moving tag of version '%s' since import forced" % dsc.version)
+ move_tag_stamp(repo, options.debian_tag, dsc.version)
+ else:
+ raise SkipImport
+
+ # import
+ if dsc.native:
+ import_native(repo, source, dsc, options)
+ else:
+ imported = False
+ commit = repo.find_version(options.upstream_tag, dsc.upstream_version)
+ if not repo.find_version(options.upstream_tag, dsc.upstream_version):
+ commit = import_upstream(repo, source, dsc, options)
+ imported = True
+
+ if not repo.has_branch(options.debian_branch):
+ if options.create_missing_branches:
+ repo.create_branch(options.debian_branch, commit)
+ else:
+ raise GbpError("Branch %s does not exist, use --create-missing-branches" %
+ options.debian_branch)
+
+ if dsc.diff or dsc.deb_tgz:
+ apply_debian_patch(repo, source, dsc, commit, options)
+ else:
+ gbp.log.warn("Didn't find a diff to apply.")
+
+ if imported and options.pristine_tar:
+ repo.create_pristine_tar_commits(commit,
+ dsc.tgz,
+ dsc.additional_tarballs.items())
+
+ if repo.get_branch() == options.debian_branch or repo.empty:
+ # Update HEAD if we modified the checked out branch
+ repo.force_head(options.debian_branch, hard=True)
+ ret = 0
+ except KeyboardInterrupt:
+ gbp.log.err("Interrupted. Aborting.")
+ except gbpc.CommandExecFailed:
+ pass # command itself printed an error
+ except GitRepositoryError as msg:
+ gbp.log.err("Git command failed: %s" % msg)
+ except GbpError as err:
+ if str(err):
+ gbp.log.err(err)
+ except SkipImport:
+ skipped = True
+ ret = 0
+ finally:
+ os.chdir(dirs['top'])
+ for d in ['tmp', 'download']:
+ if d in dirs:
+ gbpc.RemoveTree(dirs[d])()
+
+ if not ret and not skipped:
+ gbp.log.info("Version '%s' imported under '%s'" % (dsc.version, repo.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/import_dscs.py b/gbp/scripts/import_dscs.py
new file mode 100644
index 0000000..997e3cc
--- /dev/null
+++ b/gbp/scripts/import_dscs.py
@@ -0,0 +1,207 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2008, 2009, 2010, 2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""Import multiple dsc files into Git in one go"""
+
+import glob
+import os
+import sys
+import tempfile
+import gbp.command_wrappers as gbpc
+from gbp.deb import DpkgCompareVersions
+from gbp.deb.dscfile import DscFile
+from gbp.errors import GbpError
+from gbp.git import GitRepository, GitRepositoryError
+from gbp.scripts import import_dsc
+from gbp.config import GbpOptionParser
+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)
+
+
+def cmp_to_key(mycmp):
+ 'Convert a cmp= function into a key= function'
+ class K(object):
+ def __init__(self, obj, *args):
+ self.obj = obj
+
+ def __lt__(self, other):
+ return mycmp(self.obj, other.obj) < 0
+
+ def __gt__(self, other):
+ return mycmp(self.obj, other.obj) > 0
+
+ def __eq__(self, other):
+ return mycmp(self.obj, other.obj) == 0
+
+ def __le__(self, other):
+ return mycmp(self.obj, other.obj) <= 0
+
+ def __ge__(self, other):
+ return mycmp(self.obj, other.obj) >= 0
+
+ def __ne__(self, other):
+ return mycmp(self.obj, other.obj) != 0
+ return K
+
+
+class GitImportDsc(object):
+ def __init__(self, args):
+ self.args = args
+
+ def importdsc(self, dsc):
+ return import_dsc.main(['import-dsc'] + self.args + [dsc.dscfile])
+
+
+def fetch_snapshots(pkg, downloaddir):
+ "Fetch snapshots using debsnap from 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(quiet=True)
+ except gbpc.CommandExecFailed as e:
+ if debsnap.retcode == 2:
+ gbp.log.err(e)
+ 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 set_gbp_conf_files():
+ """
+ Filter out all gbp.conf files that are local to the git repository and set
+ GBP_CONF_FILES accordingly so gbp import-dsc will only use these.
+ """
+ global_config = GbpOptionParser.get_config_files(no_local=True)
+ gbp_conf_files = ':'.join(global_config)
+ os.environ['GBP_CONF_FILES'] = gbp_conf_files
+ gbp.log.debug("Setting GBP_CONF_FILES to '%s'" % gbp_conf_files)
+
+
+def print_help():
+ print("""Usage: gbp import-dscs [options] [gbp-import-dsc options] /path/to/dsc1 [/path/to/dsc2] ...
+ gbp import-dscs --debsnap [options] [gbp-import-dsc options] package
+
+Options:
+
+ --debsnap: use debsnap command to download packages
+ --ignore-repo-config ignore gbp.conf in git repo
+""")
+
+
+def main(argv):
+ dirs = dict(top=os.path.abspath(os.curdir))
+ dscs = []
+ ret = 0
+ verbose = False
+ dsc_key = cmp_to_key(DscCompareVersions())
+ use_debsnap = False
+
+ try:
+ import_args = argv[1:]
+
+ if '--verbose' in import_args:
+ verbose = True
+ gbp.log.setup(False, verbose)
+
+ if '--ignore-repo-config' in import_args:
+ set_gbp_conf_files()
+ import_args.remove('--ignore-repo-config')
+ # Not using Configparser since we want to pass all unknown options
+ # unaltered to gbp 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(DscFile.parse(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 = [DscFile.parse(f) for f in fetch_snapshots(pkg, dirs['tmp'])]
+
+ dscs.sort(key=dsc_key)
+ 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)
+
+ if importer.importdsc(dscs[0]):
+ raise GbpError("Failed to import '%s'" % dscs[0].dscfile)
+ os.chdir(dirs['pkg'])
+
+ for dsc in dscs[1:]:
+ if importer.importdsc(dsc):
+ raise GbpError("Failed to import '%s'" % dscs[0].dscfile)
+ except KeyboardInterrupt:
+ ret = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except (GbpError, gbpc.CommandExecFailed, GitRepositoryError) as err:
+ if str(err):
+ gbp.log.err(err)
+ ret = 1
+ finally:
+ if 'tmp' in dirs:
+ 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..298e7e7
--- /dev/null
+++ b/gbp/scripts/import_orig.py
@@ -0,0 +1,538 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006, 2007, 2009, 2011, 2015, 2016 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Import a new upstream version into a Git repository"""
+
+import os
+import shutil
+import sys
+import tempfile
+import time
+import gbp.command_wrappers as gbpc
+from gbp.deb import (DebianPkgPolicy, parse_changelog_repo)
+from gbp.deb.format import DebianSourceFormat
+from gbp.deb.upstreamsource import DebianUpstreamSource, unpack_component_tarball
+from gbp.deb.uscan import (Uscan, UscanError)
+from gbp.deb.changelog import ChangeLog, NoChangeLogError
+from gbp.deb.git import GitRepositoryError
+from gbp.config import GbpOptionParserDebian, GbpOptionGroup, no_upstream_branch_msg
+from gbp.errors import GbpError
+from gbp.format import format_str
+from gbp.git.vfs import GitVfs
+import gbp.log
+from gbp.scripts.common import ExitCodes, is_download, get_component_tarballs
+from gbp.scripts.common.import_orig import (orig_needs_repack, cleanup_tmp_tree,
+ ask_package_name, ask_package_version,
+ repack_upstream, is_link_target, download_orig)
+from gbp.scripts.common.hook import Hook
+from gbp.deb.rollbackgit import RollbackDebianGitRepository
+
+
+def prepare_pristine_tar(archive, pkg, version):
+ """
+ Prepare the upstream source for pristine tar import.
+
+ This checks if the upstream source is actually a tarball
+ and creates a symlink from I{archive}
+ to I{<pkg>_<version>.orig.tar.<ext>} so pristine-tar will
+ see the correct basename.
+
+ @param archive: the upstream source's name
+ @type archive: C{str}
+ @param pkg: the source package's name
+ @type pkg: C{str}
+ @param version: the upstream version number
+ @type version: C{str}
+ @rtype: C{str}
+ """
+ linked = False
+ if os.path.isdir(archive):
+ return None, None
+
+ ext = os.path.splitext(archive)[1]
+ if ext in ['.tgz', '.tbz2', '.tlz', '.txz']:
+ ext = ".%s" % ext[2:]
+
+ 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):
+ if os.path.exists(link):
+ backup = "%s.%d" % (link, time.time())
+ gbp.log.info("%s already exists, moving to %s" % (link, backup))
+ shutil.move(link, backup)
+ os.symlink(os.path.abspath(archive), link)
+ linked = True
+ except OSError as err:
+ raise GbpError("Cannot symlink '%s' to '%s': %s" % (archive, link, err[1]))
+ return (link, linked)
+ else:
+ return (archive, linked)
+
+
+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()
+
+ # Try to find the source package name
+ try:
+ cp = 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,
+ DebianPkgPolicy.is_valid_packagename,
+ DebianPkgPolicy.packagename_msg)
+ 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,
+ DebianPkgPolicy.is_valid_upstreamversion,
+ DebianPkgPolicy.upstreamversion_msg)
+ else:
+ if guessed_version:
+ version = guessed_version
+ else:
+ raise GbpError("Couldn't determine upstream version. Use '-u<version>' or --interactive.")
+
+ return (sourcepackage, version)
+
+
+def find_upstream(use_uscan, args):
+ """Find the main 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
+
+ >>> find_upstream(False, ['too', 'many'])
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: More than one archive specified. Try --help.
+ >>> find_upstream(False, [])
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: No archive to import specified. Try --help.
+ >>> find_upstream(True, ['tarball'])
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: you can't pass both --uscan and a filename.
+ >>> find_upstream(False, ['tarball']).path
+ 'tarball'
+ """
+ if use_uscan:
+ if args:
+ raise GbpError("you can't pass both --uscan and a filename.")
+
+ uscan = Uscan()
+ gbp.log.info("Launching uscan...")
+ try:
+ if not uscan.scan():
+ gbp.log.info("package is up to date, nothing to do.")
+ return None
+ except UscanError as e:
+ raise GbpError("%s" % e)
+
+ if uscan.tarball:
+ gbp.log.info("Using uscan downloaded tarball %s" % uscan.tarball)
+ args.append(uscan.tarball)
+ else:
+ raise GbpError("uscan didn't download anything, and no source was found in ../")
+ 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:
+ return DebianUpstreamSource(args[0])
+
+
+def debian_branch_merge(repo, tag, version, options):
+ try:
+ func = globals()["debian_branch_merge_by_%s" % options.merge_mode]
+ except KeyError:
+ raise GbpError("%s is not a valid merge mode" % options.merge_mode)
+ func(repo, tag, version, options)
+
+
+def postimport_hook(repo, tag, version, options):
+ 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 = ChangeLog(filename='debian/changelog')
+ if cp.has_epoch():
+ epoch = '%s:' % cp.epoch
+ debian_version = "%s%s-1" % (epoch, version)
+ info = {'version': debian_version}
+ env = {'GBP_BRANCH': options.debian_branch,
+ 'GBP_TAG': tag,
+ 'GBP_UPSTREAM_VERSION': version,
+ 'GBP_DEBIAN_VERSION': debian_version,
+ }
+ Hook('Postimport',
+ format_str(options.postimport, info),
+ extra_env=env)()
+
+
+def is_30_quilt(repo, options):
+ format_file = DebianSourceFormat.format_file
+ try:
+ content = GitVfs(repo, options.debian_branch).open(format_file).read()
+ except IOError:
+ return False
+ return str(DebianSourceFormat(content)) == "3.0 (quilt)"
+
+
+def debian_branch_merge_by_auto(repo, tag, version, options):
+ if is_30_quilt(repo, options):
+ gbp.log.debug("3.0 (quilt) package, replacing debian/ dir")
+ return debian_branch_merge_by_replace(repo, tag, version, options)
+ else:
+ gbp.log.debug("not 3.0 (quilt) package, using git merge")
+ return debian_branch_merge_by_merge(repo, tag, version, options)
+
+
+def debian_branch_merge_by_replace(repo, tag, version, options):
+ gbp.log.info("Replacing upstream source on '%s'" % options.debian_branch)
+
+ tree = [x for x in repo.list_tree("%s^{tree}" % tag)
+ if x[-1] != b'debian']
+ msg = "Update upstream source from tag '%s'" % (tag)
+
+ # Get the current debian/ tree on the debian branch
+ try:
+ deb_sha = [x for x in repo.list_tree("%s^{tree}" % options.debian_branch)
+ if x[-1] == b'debian' and x[1] == 'tree'][0][2]
+ gbp.log.debug("Using %s as debian/ tree" % deb_sha)
+ tree.append(['040000', 'tree', deb_sha, 'debian'])
+ msg += "\n\nUpdate to upstream version '%s'\nwith Debian dir %s" % (version, deb_sha)
+ except IndexError:
+ pass # no debian/ dir is fine
+
+ sha = repo.make_tree(tree)
+ commit = repo.commit_tree(sha, msg, ["%s^{commit}" % options.debian_branch,
+ "%s^{commit}" % tag])
+ repo.update_ref("refs/heads/%s" % options.debian_branch, commit,
+ msg="gbp: Updating %s after import of %s" % (options.debian_branch,
+ tag))
+ repo.force_head(commit, hard=True)
+
+
+def debian_branch_merge_by_merge(repo, tag, version, options):
+ gbp.log.info("Merging to '%s'" % options.debian_branch)
+ branch = repo.get_branch()
+ repo.set_branch(options.debian_branch)
+ try:
+ repo.merge(tag)
+ except GitRepositoryError:
+ raise GbpError("Automatic merge failed.")
+ repo.set_branch(branch)
+
+
+def unpack_tarballs(sourcepackage, source, version, component_tarballs, options):
+ tmpdir = tempfile.mkdtemp(dir='../')
+ if not source.is_dir(): # Unpack main tarball
+ source.unpack(tmpdir, options.filters)
+ gbp.log.debug("Unpacked '%s' to '%s'" % (source.path, source.unpacked))
+
+ if orig_needs_repack(source, options):
+ gbp.log.debug("Filter pristine-tar: repacking '%s' from '%s'" % (source.path, source.unpacked))
+ (source, tmpdir) = repack_upstream(source, sourcepackage, version, tmpdir, options.filters)
+
+ if not source.is_dir(): # Unpack component tarballs
+ for (component, tarball) in component_tarballs:
+ unpack_component_tarball(source.unpacked, component, tarball, options.filters)
+ return (source, 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 rollback(repo, options):
+ if repo and repo.has_rollbacks() and options.rollback:
+ gbp.log.err("Error detected, Will roll back changes.")
+ try:
+ repo.rollback()
+ # Make sure the very last line as an error message
+ gbp.log.err("Rolled back changes after import error.")
+ except Exception as e:
+ gbp.log.err("%s" % e)
+ gbp.log.err("Clean up manually and please report a bug: %s" %
+ repo.rollback_errors)
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
+ usage='%prog [options] /path/to/upstream-version.tar.gz | --uscan')
+ except GbpError as err:
+ gbp.log.err(err)
+ return 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_config_file_option(option_name="upstream-vcs-tag", dest="vcs_tag",
+ help="Upstream VCS tag add to the merge commit")
+ branch_group.add_boolean_config_file_option(option_name="merge", dest="merge")
+ branch_group.add_config_file_option(option_name="merge-mode", dest="merge_mode")
+
+ 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")
+ import_group.add_boolean_config_file_option(option_name="symlink-orig",
+ dest="symlink_orig")
+ import_group.add_config_file_option("component", action="append", metavar='COMPONENT',
+ dest="components")
+ cmd_group.add_config_file_option(option_name="postimport", dest="postimport")
+
+ parser.add_boolean_config_file_option(option_name="interactive",
+ dest='interactive')
+ parser.add_boolean_config_file_option(option_name="rollback",
+ dest="rollback")
+ 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ parser.add_option("--uscan", dest='uscan', action="store_true",
+ default=False, help="use uscan(1) to download the new tarball.")
+
+ # Accepted for compatibility
+ parser.add_option("--download", dest='download', action="store_true",
+ default=False, help="Ignored. Accepted for compatibility; see EXAMPLES in gbp-import-orig(1).")
+ return parser
+
+
+def parse_args(argv):
+ """Parse the command line arguments
+ @return: options and arguments
+ """
+
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if options.download:
+ gbp.log.warn("Passing --download explicitly is deprecated.")
+
+ options.download = is_download(args)
+ return options, args
+
+
+def main(argv):
+ ret = 0
+ tmpdir = None
+ pristine_orig = None
+ linked = False
+ repo = None
+
+ (options, args) = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ try:
+ try:
+ repo = RollbackDebianGitRepository('.')
+ except GitRepositoryError:
+ raise GbpError("%s is not a git repository" % (os.path.abspath('.')))
+
+ is_empty = repo.is_empty()
+
+ if not repo.has_branch(options.upstream_branch) and not is_empty:
+ raise GbpError(no_upstream_branch_msg % options.upstream_branch)
+
+ (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)
+
+ # Download the main tarball
+ if options.download:
+ upstream = download_orig(args[0])
+ else:
+ upstream = find_upstream(options.uscan, args)
+ if not upstream:
+ return ExitCodes.uscan_up_to_date
+
+ # The main tarball
+ (sourcepackage, version) = detect_name_and_version(repo, upstream, options)
+ # Additional tarballs we expect to exist
+ component_tarballs = get_component_tarballs(sourcepackage,
+ version,
+ upstream.path,
+ options.components)
+
+ tag = repo.version_to_tag(options.upstream_tag, version)
+ if repo.has_tag(tag):
+ raise GbpError("Upstream tag '%s' already exists" % tag)
+
+ if repo.bare:
+ set_bare_repo_options(options)
+
+ upstream, tmpdir = unpack_tarballs(sourcepackage, upstream, version, component_tarballs, options)
+
+ (pristine_orig, linked) = prepare_pristine_tar(upstream.path,
+ sourcepackage,
+ version)
+
+ # Don't mess up our repo with git metadata from an upstream tarball
+ try:
+ if os.path.isdir(os.path.join(upstream.unpacked, '.git/')):
+ raise GbpError("The orig tarball contains .git metadata - giving up.")
+ except OSError:
+ pass
+
+ try:
+ import_branch = options.upstream_branch
+ filter_msg = ["", " (filtering out %s)"
+ % options.filters][len(options.filters) > 0]
+ gbp.log.info("Importing '%s' to branch '%s'%s..." % (upstream.path,
+ import_branch,
+ filter_msg))
+ gbp.log.info("Source package is %s" % sourcepackage)
+ gbp.log.info("Upstream version is %s" % version)
+
+ msg = upstream_import_commit_msg(options, version)
+
+ commit = repo.commit_dir(upstream.unpacked,
+ msg=msg,
+ branch=import_branch,
+ other_parents=repo.vcs_tag_parent(options.vcs_tag, version),
+ create_missing_branch=is_empty,
+ )
+
+ if options.pristine_tar:
+ if pristine_orig:
+ repo.rrr_branch('pristine-tar')
+ repo.create_pristine_tar_commits(import_branch,
+ pristine_orig,
+ component_tarballs)
+ else:
+ gbp.log.warn("'%s' not an archive, skipping pristine-tar" % upstream.path)
+
+ 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(branch=options.debian_branch, rev=commit)
+ repo.force_head(options.debian_branch, hard=True)
+ # In an empty repo avoid master branch defaulted to by
+ # git and check out debian branch instead.
+ if not repo.bare:
+ cur = repo.branch
+ if cur != options.debian_branch:
+ repo.set_branch(options.debian_branch)
+ repo.delete_branch(cur)
+ elif options.merge:
+ repo.rrr_branch(options.debian_branch)
+ debian_branch_merge(repo, tag, version, options)
+
+ # Update working copy and index if we've possibly updated the
+ # checked out branch
+ current_branch = repo.get_branch()
+ if current_branch in [options.upstream_branch,
+ repo.pristine_tar_branch]:
+ repo.force_head(current_branch, hard=True)
+
+ postimport_hook(repo, tag, version, options)
+ except (gbpc.CommandExecFailed, GitRepositoryError) as err:
+ msg = str(err) or 'Unknown error, please report a bug'
+ raise GbpError("Import of %s failed: %s" % (upstream.path, msg))
+ except KeyboardInterrupt:
+ raise GbpError("Import of %s failed: aborted by user" % (upstream.path))
+ except GbpError as err:
+ if str(err):
+ gbp.log.err(err)
+ ret = 1
+ rollback(repo, options)
+
+ if pristine_orig and linked and not options.symlink_orig:
+ os.unlink(pristine_orig)
+
+ if tmpdir:
+ cleanup_tmp_tree(tmpdir)
+
+ if not ret:
+ gbp.log.info("Successfully imported version %s of %s" % (version, upstream.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/import_srpm.py b/gbp/scripts/import_srpm.py
new file mode 100755
index 0000000..4abfd9c
--- /dev/null
+++ b/gbp/scripts/import_srpm.py
@@ -0,0 +1,511 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2006,2007,2011,2016 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Import an RPM source package into a Git repository"""
+
+import sys
+import re
+import os
+import glob
+import time
+import shutil
+import errno
+from urllib.request import urlopen
+import urllib
+
+import gbp.command_wrappers as gbpc
+from gbp.tmpfile import init_tmpdir, del_tmpdir, tempfile
+from gbp.rpm import (parse_srpm, guess_spec, SpecFile, NoSpecError,
+ RpmUpstreamSource, compose_version_str, filter_version)
+from gbp.rpm.git import (RpmGitRepository, GitRepositoryError)
+from gbp.git.modifier import GitModifier
+from gbp.config import (GbpOptionParserRpm, GbpOptionGroup,
+ no_upstream_branch_msg)
+from gbp.errors import GbpError
+from gbp.scripts.common import ExitCodes, is_download
+from gbp.scripts.common import repo_setup
+import gbp.log
+from gbp.pkg import Archive
+
+no_packaging_branch_msg = """
+Repository does not have branch '%s' for packaging/distribution sources.
+You need to reate it or use --packaging-branch to specify it.
+"""
+
+
+class SkipImport(Exception):
+ """Nothing imported"""
+ pass
+
+
+def download_file(target_dir, url):
+ """Download a remote file"""
+ gbp.log.info("Downloading '%s'..." % url)
+ try:
+ urlobj = urlopen(url)
+ local_fn = os.path.join(target_dir, os.path.basename(url))
+ with open(local_fn, "wb") as local_file:
+ local_file.write(urlobj.read())
+ except urllib.error.HTTPError as err:
+ raise GbpError("Download failed: %s" % err)
+ except urllib.error.URLError as err:
+ raise GbpError("Download failed: %s" % err.reason)
+ return local_fn
+
+
+def download_source(pkg):
+ """Download package from a remote location"""
+ if re.match(r'[a-z]{1,5}://', pkg):
+ mode = 'python urllib'
+ else:
+ mode = 'yumdownloader'
+
+ tmpdir = tempfile.mkdtemp(prefix='download_')
+ gbp.log.info("Trying to download '%s' using '%s'..." % (pkg, mode))
+ if mode == 'yumdownloader':
+ gbpc.RunAtCommand('yumdownloader',
+ ['--source', '--destdir=', '.', pkg],
+ shell=False)(dir=tmpdir)
+ else:
+ download_file(tmpdir, pkg)
+ srpm = glob.glob(os.path.join(tmpdir, '*.src.rpm'))[0]
+ return srpm
+
+
+def committer_from_author(author, options):
+ """Get committer info based on options"""
+ committer = GitModifier()
+ if options.author_is_committer:
+ committer.name = author.name
+ committer.email = author.email
+ return committer
+
+
+def move_tag_stamp(repo, tag_format, tag_str_fields):
+ "Move tag out of the way appending the current timestamp"
+ old = repo.version_to_tag(tag_format, tag_str_fields)
+ new = repo.version_to_tag('%s~%d' % (tag_format, int(time.time())),
+ tag_str_fields)
+ 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 = False
+
+
+def force_to_branch_head(repo, branch):
+ """Checkout branch and reset --hard"""
+ if repo.get_branch() == branch:
+ # Update HEAD if we modified the checked out branch
+ repo.force_head(branch, hard=True)
+ # Checkout packaging branch
+ repo.set_branch(branch)
+
+
+def build_parser(name):
+ """Construct command line parser"""
+ try:
+ parser = GbpOptionParserRpm(command=os.path.basename(name),
+ prefix='',
+ usage='%prog [options] /path/to/package'
+ '.src.rpm [target]')
+ except GbpError as err:
+ gbp.log.err(err)
+ return 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ parser.add_config_file_option(option_name="tmp-dir", dest="tmp_dir")
+ parser.add_config_file_option(option_name="vendor", action="store",
+ dest="vendor")
+ parser.add_option("--download", action="store_true", dest="download",
+ default=False, help="download source package")
+ branch_group.add_config_file_option(option_name="packaging-branch",
+ dest="packaging_branch")
+ branch_group.add_config_file_option(option_name="upstream-branch",
+ dest="upstream_branch")
+ branch_group.add_option("--upstream-vcs-tag", dest="vcs_tag",
+ help="Upstream VCS tag on top of which to import "
+ "the orig sources")
+ branch_group.add_boolean_config_file_option(
+ option_name="create-missing-branches",
+ dest="create_missing_branches")
+ branch_group.add_option("--orphan-packaging", action="store_true",
+ dest="orphan_packaging", default=False,
+ help="The packaging branch doesn't base on upstream")
+ branch_group.add_option("--native", action="store_true",
+ dest="native", default=False,
+ help="This is a dist native package, no separate "
+ "upstream branch")
+
+ 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="packaging-tag",
+ dest="packaging_tag")
+ tag_group.add_config_file_option(option_name="upstream-tag",
+ dest="upstream_tag")
+ tag_group.add_option("--skip-packaging-tag", dest="skip_packaging_tag",
+ action="store_true",
+ help="Don't add a tag after importing packaging files")
+
+ 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 import of already imported version")
+ import_group.add_boolean_config_file_option(
+ option_name="author-is-committer",
+ dest="author_is_committer")
+ import_group.add_config_file_option(option_name="packaging-dir",
+ dest="packaging_dir")
+
+ parser.add_config_file_option(option_name="repo-user", dest="repo_user",
+ choices=['DEBIAN', 'GIT'])
+ parser.add_config_file_option(option_name="repo-email", dest="repo_email",
+ choices=['DEBIAN', 'GIT'])
+ return parser
+
+
+def parse_args(argv):
+ """Parse commandline arguments"""
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if options.download:
+ gbp.log.warn("Passing --download explicitly is deprecated.")
+
+ options.download = is_download(args)
+ return options, args
+
+
+def main(argv):
+ """Main function of the git-import-srpm script"""
+ dirs = dict(top=os.path.abspath(os.curdir))
+
+ ret = 0
+ skipped = False
+
+ options, args = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ if len(args) == 1:
+ srpm = args[0]
+ target = None
+ elif len(args) == 2:
+ srpm = args[0]
+ target = args[1]
+ else:
+ gbp.log.err("Need to give exactly one package to import. Try --help.")
+ return 1
+ try:
+ dirs['tmp_base'] = init_tmpdir(options.tmp_dir, 'import-srpm_')
+ except GbpError as err:
+ gbp.log.err(err)
+ return 1
+ try:
+ if options.download:
+ srpm = download_source(srpm)
+
+ # Real srpm, we need to unpack, first
+ true_srcrpm = False
+ if not os.path.isdir(srpm) and not srpm.endswith(".spec"):
+ src = parse_srpm(srpm)
+ true_srcrpm = True
+ dirs['pkgextract'] = tempfile.mkdtemp(prefix='pkgextract_')
+ gbp.log.info("Extracting src rpm to '%s'" % dirs['pkgextract'])
+ src.unpack(dirs['pkgextract'])
+ preferred_spec = src.name + '.spec'
+ srpm = dirs['pkgextract']
+ elif os.path.isdir(srpm):
+ preferred_spec = os.path.basename(srpm.rstrip('/')) + '.spec'
+ else:
+ preferred_spec = None
+
+ # Find and parse spec file
+ if os.path.isdir(srpm):
+ gbp.log.debug("Trying to import an unpacked srpm from '%s'" % srpm)
+ dirs['src'] = os.path.abspath(srpm)
+ spec = guess_spec(srpm, True, preferred_spec)
+ else:
+ gbp.log.debug("Trying to import an srpm from '%s' with spec "
+ "file '%s'" % (os.path.dirname(srpm), srpm))
+ dirs['src'] = os.path.abspath(os.path.dirname(srpm))
+ spec = SpecFile(srpm)
+
+ # Check the repository state
+ try:
+ repo = RpmGitRepository('.')
+ 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:
+ gbp.log.info("No git repository found, creating one.")
+ is_empty = True
+ target = target or spec.name
+ repo = RpmGitRepository.create(target)
+ os.chdir(repo.path)
+ repo_setup.set_user_name_and_email(options.repo_user, options.repo_email, repo)
+
+ if repo.bare:
+ set_bare_repo_options(options)
+
+ # Create more tempdirs
+ dirs['origsrc'] = tempfile.mkdtemp(prefix='origsrc_')
+ dirs['packaging_base'] = tempfile.mkdtemp(prefix='packaging_')
+ dirs['packaging'] = os.path.join(dirs['packaging_base'],
+ options.packaging_dir)
+ try:
+ os.mkdir(dirs['packaging'])
+ except OSError as err:
+ if err.errno != errno.EEXIST:
+ raise
+
+ if true_srcrpm:
+ # For true src.rpm we just take everything
+ files = os.listdir(dirs['src'])
+ else:
+ # Need to copy files to the packaging directory given by caller
+ files = [os.path.basename(patch.path)
+ for patch in spec.patchseries(unapplied=True, ignored=True)]
+ for filename in spec.sources().values():
+ files.append(os.path.basename(filename))
+ files.append(os.path.join(spec.specdir, spec.specfile))
+ # Don't copy orig source archive, though
+ if spec.orig_src and spec.orig_src['filename'] in files:
+ files.remove(spec.orig_src['filename'])
+
+ for fname in files:
+ fpath = os.path.join(dirs['src'], fname)
+ if os.path.exists(fpath):
+ shutil.copy2(fpath, dirs['packaging'])
+ else:
+ gbp.log.err("File '%s' listed in spec not found" % fname)
+ raise GbpError
+
+ # Unpack orig source archive
+ if spec.orig_src:
+ orig_tarball = os.path.join(dirs['src'], spec.orig_src['filename'])
+ sources = RpmUpstreamSource(orig_tarball)
+ sources.unpack(dirs['origsrc'], options.filters)
+ else:
+ sources = None
+
+ tag_str_fields = dict(spec.version, vendor=options.vendor.lower())
+ if options.native:
+ upstream_tag_format = options.packaging_tag
+ upstream_str_fields = tag_str_fields
+ else:
+ upstream_tag_format = options.upstream_tag
+ upstream_str_fields = filter_version(tag_str_fields, 'release', 'epoch')
+ upstream_tag = repo.version_to_tag(upstream_tag_format, upstream_str_fields)
+
+ full_version = compose_version_str(spec.version)
+ if repo.find_version(options.packaging_tag, tag_str_fields):
+ gbp.log.warn("Version %s already imported." % full_version)
+
+ if options.allow_same_version:
+ gbp.log.info("Moving tag of version '%s' since import forced" %
+ full_version)
+ move_tag_stamp(repo, options.packaging_tag, tag_str_fields)
+ else:
+ raise SkipImport
+
+ if is_empty:
+ options.create_missing_branches = True
+
+ # Determine author and committer info, currently same info is used
+ # for both sources and packaging files
+ author = None
+ if spec.packager:
+ match = re.match(r'(?P<name>.*[^ ])\s*<(?P<email>\S*)>',
+ spec.packager.strip())
+ if match:
+ author = GitModifier(match.group('name'), match.group('email'))
+ if not author:
+ author = GitModifier()
+ gbp.log.debug("Couldn't determine packager info")
+ committer = committer_from_author(author, options)
+
+ # Import sources
+ if sources:
+ upstream_commit = repo.find_version(upstream_tag_format, upstream_str_fields)
+ if not upstream_commit:
+ gbp.log.info("Tag %s not found, importing sources" % upstream_tag)
+
+ branch = [options.upstream_branch,
+ options.packaging_branch][options.native]
+ if not repo.has_branch(branch):
+ if options.create_missing_branches:
+ gbp.log.info("Will create missing branch '%s'" %
+ branch)
+ else:
+ gbp.log.err(no_upstream_branch_msg % branch + "\n"
+ "Also check the --create-missing-branches option.")
+ raise GbpError
+ upstream_vendor = "Native" if options.native else "Upstream"
+ upstream_version = full_version if options.native else spec.upstreamversion
+ msg = "%s version %s" % (upstream_vendor, upstream_version)
+ if options.vcs_tag:
+ vcs_tag = repo.version_to_tag(options.vcs_tag, upstream_str_fields)
+ parents = [repo.rev_parse("%s^{}" % vcs_tag)]
+ else:
+ parents = None
+ upstream_commit = repo.commit_dir(sources.unpacked,
+ "Import %s" % msg,
+ branch,
+ other_parents=parents,
+ author=author,
+ committer=committer,
+ create_missing_branch=options.create_missing_branches)
+ if not (options.native and options.skip_packaging_tag):
+ repo.create_tag(name=upstream_tag,
+ msg=msg,
+ commit=upstream_commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+
+ if not options.native:
+ if options.pristine_tar:
+ archive_fmt = Archive.parse_filename(orig_tarball)[1]
+ if archive_fmt == 'tar':
+ repo.pristine_tar.commit(orig_tarball,
+ 'refs/heads/%s' %
+ options.upstream_branch)
+ else:
+ gbp.log.warn('Ignoring pristine-tar, %s archives '
+ 'not supported' % archive_fmt)
+ else:
+ gbp.log.info("No orig source archive imported")
+
+ # Import packaging files. For native packages we assume that also
+ # packaging files are found in the source tarball
+ if not options.native or not sources:
+ gbp.log.info("Importing packaging files")
+ branch = options.packaging_branch
+ if not repo.has_branch(branch):
+ if options.create_missing_branches:
+ gbp.log.info("Will create missing branch '%s'" % branch)
+ else:
+ gbp.log.err(no_packaging_branch_msg % branch + "\n"
+ "Also check the --create-missing-branches "
+ "option.")
+ raise GbpError
+
+ msg = "%s release %s" % (options.vendor, full_version)
+
+ if options.orphan_packaging or not sources:
+ commit = repo.commit_dir(dirs['packaging_base'],
+ "Import %s" % msg,
+ branch,
+ author=author,
+ committer=committer,
+ create_missing_branch=options.create_missing_branches)
+ else:
+ # Copy packaging files to the unpacked sources dir
+ try:
+ pkgsubdir = os.path.join(sources.unpacked,
+ options.packaging_dir)
+ os.mkdir(pkgsubdir)
+ except OSError as err:
+ if err.errno != errno.EEXIST:
+ raise
+ for fname in os.listdir(dirs['packaging']):
+ shutil.copy2(os.path.join(dirs['packaging'], fname),
+ pkgsubdir)
+ commit = repo.commit_dir(sources.unpacked,
+ "Import %s" % msg,
+ branch,
+ other_parents=[upstream_commit],
+ author=author,
+ committer=committer,
+ create_missing_branch=options.create_missing_branches)
+ # Import patches on top of the source tree
+ # (only for non-native packages with non-orphan packaging)
+ force_to_branch_head(repo, options.packaging_branch)
+
+ # Create packaging tag
+ if not options.skip_packaging_tag:
+ tag = repo.version_to_tag(options.packaging_tag, tag_str_fields)
+ repo.create_tag(name=tag,
+ msg=msg,
+ commit=commit,
+ sign=options.sign_tags,
+ keyid=options.keyid)
+
+ force_to_branch_head(repo, options.packaging_branch)
+
+ except KeyboardInterrupt:
+ ret = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except gbpc.CommandExecFailed:
+ ret = 1
+ except GitRepositoryError as err:
+ gbp.log.err("Git command failed: %s" % err)
+ ret = 1
+ except GbpError as err:
+ if str(err):
+ gbp.log.err(err)
+ ret = 1
+ except NoSpecError as err:
+ gbp.log.err("Failed determine spec file: %s" % err)
+ ret = 1
+ except SkipImport:
+ skipped = True
+ finally:
+ os.chdir(dirs['top'])
+ del_tmpdir()
+
+ if not ret and not skipped:
+ gbp.log.info("Version '%s' imported under '%s'" % (full_version, repo.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 100755
index 0000000..7bf7736
--- /dev/null
+++ b/gbp/scripts/pq.py
@@ -0,0 +1,513 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011,2014,2017 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Manage Debian patches on a patch queue branch"""
+
+import errno
+import os
+import shutil
+import sys
+import tempfile
+import re
+from gbp.config import GbpOptionParserDebian
+from gbp.deb.source import DebianSource
+from gbp.deb.git import DebianGitRepository
+from gbp.git import GitRepositoryError
+from gbp.command_wrappers import (GitCommand, CommandExecFailed)
+from gbp.errors import GbpError
+import gbp.log
+from gbp.patch_series import (PatchSeries, Patch)
+from gbp.scripts.common.pq import (is_pq_branch, pq_branch_name, pq_branch_base,
+ parse_gbp_commands, format_patch,
+ apply_single_patch,
+ apply_and_commit_patch,
+ drop_pq, get_maintainer_from_control,
+ switch_to_pq_branch)
+from gbp.scripts.common import ExitCodes
+from gbp.dch import extract_bts_cmds
+
+PATCH_DIR = "debian/patches/"
+SERIES_FILE = os.path.join(PATCH_DIR, "series")
+
+
+def parse_old_style_topic(commit_info):
+ """Parse 'gbp-pq-topic:' line(s) from commit info"""
+
+ commit = commit_info['id']
+ topic_regex = 'gbp-pq-topic:\s*(?P<topic>\S.*)'
+ mangled_body = ''
+ topic = ''
+ # Parse and filter commit message body
+ for line in commit_info['body'].splitlines():
+ match = re.match(topic_regex, line, flags=re.I)
+ if match:
+ topic = match.group('topic')
+ gbp.log.debug("Topic %s found for %s" % (topic, commit))
+ gbp.log.warn("Deprecated 'gbp-pq-topic: <topic>' in %s, please "
+ "use 'Gbp[-Pq]: Topic <topic>' instead" % commit)
+ continue
+ mangled_body += line + '\n'
+ commit_info['body'] = mangled_body
+ return topic
+
+
+def generate_patches(repo, start, end, outdir, options):
+ """
+ Generate patch files from git
+ """
+ gbp.log.info("Generating patches from git (%s..%s)" % (start, end))
+ patches = []
+ for treeish in [start, end]:
+ if not repo.has_treeish(treeish):
+ raise GbpError('%s not a valid tree-ish' % treeish)
+
+ # Generate patches
+ rev_list = reversed(repo.get_commits(start, end))
+ for commit in rev_list:
+ info = repo.get_commit_info(commit)
+ # Parse 'gbp-pq-topic:'
+ topic = parse_old_style_topic(info)
+ cmds = {'topic': topic} if topic else {}
+ # Parse 'Gbp: ' style commands
+ (cmds_gbp, info['body']) = parse_gbp_commands(info, 'gbp',
+ ('ignore'),
+ ('topic', 'name'),
+ ('topic', 'name'))
+ cmds.update(cmds)
+ # Parse 'Gbp-Pq: ' style commands
+ (cmds_gbp_pq, info['body']) = parse_gbp_commands(info,
+ 'gbp-pq',
+ ('ignore'),
+ ('topic', 'name'),
+ ('topic', 'name'))
+ cmds.update(cmds_gbp_pq)
+ if 'ignore' not in cmds:
+ if 'topic' in cmds:
+ topic = cmds['topic']
+ name = cmds.get('name', None)
+ format_patch(outdir, repo, info, patches, options.abbrev,
+ numbered=options.patch_numbers,
+ topic=topic, name=name,
+ renumber=options.renumber,
+ patch_num_prefix_format=options.patch_num_format)
+ else:
+ gbp.log.info('Ignoring commit %s' % info['id'])
+
+ return patches
+
+
+def compare_series(old, new):
+ """
+ Compare new pathes to lists of patches already exported
+
+ >>> compare_series(['# comment', 'a', 'b'], ['b', 'c'])
+ (['c'], ['a'])
+ >>> compare_series([], [])
+ ([], [])
+ """
+ added = set(new).difference(old)
+ removed = [l for l in set(old).difference(new) if not l.startswith('#')]
+ return (list(added), removed)
+
+
+def format_series_diff(added, removed, options):
+ """
+ Format the patch differences into a suitable commit message
+
+ >>> format_series_diff(['a'], ['b'], None)
+ 'Rediff patches\\n\\nAdd a: <REASON>\\nDrop b: <REASON>\\n'
+ """
+ if len(added) == 1 and not removed:
+ # Single patch added, create a more thorough commit message
+ patch = Patch(os.path.join('debian', 'patches', added[0]))
+ msg = patch.subject
+ bugs, dummy = extract_bts_cmds(patch.long_desc.split('\n'), options)
+ if bugs:
+ msg += '\n'
+ for k, v in bugs.items():
+ msg += '\n%s: %s' % (k, ', '.join(v))
+ else:
+ msg = "Rediff patches\n\n"
+ for p in added:
+ msg += 'Add %s: <REASON>\n' % p
+ for p in removed:
+ msg += 'Drop %s: <REASON>\n' % p
+ return msg
+
+
+def commit_patches(repo, branch, patches, options, patch_dir):
+ """
+ Commit chanages exported from patch queue
+ """
+ clean, dummy = repo.is_clean()
+ if clean:
+ return ([], [])
+
+ vfs = gbp.git.vfs.GitVfs(repo, branch)
+ try:
+ with vfs.open('debian/patches/series') as oldseries:
+ oldpatches = [p.strip() for p in oldseries.readlines()]
+ except IOError:
+ # No series file yet
+ oldpatches = []
+ newpatches = [p[len(patch_dir):] for p in patches]
+
+ # FIXME: handle case were only the contents of the patches changed
+ added, removed = compare_series(oldpatches, newpatches)
+ msg = format_series_diff(added, removed, options)
+
+ if not repo.is_clean(paths='debian/patches')[0]:
+ repo.add_files(PATCH_DIR, force=True)
+ repo.commit_staged(msg=msg)
+ return added, removed
+
+
+def find_upstream_commit(repo, branch, upstream_tag):
+ """
+ Find commit corresponding to upstream version based on changelog
+ """
+ vfs = gbp.git.vfs.GitVfs(repo, pq_branch_base(branch))
+ cl = DebianSource(vfs).changelog
+ upstream_commit = repo.find_version(upstream_tag, cl.upstream_version)
+ if not upstream_commit:
+ raise GbpError("Couldn't find upstream version %s" %
+ cl.upstream_version)
+ return upstream_commit
+
+
+def pq_on_upstream_tag(pq_from):
+ """Return True if the patch queue is based on the uptream tag,
+ False if its based on the debian packaging branch"""
+ return True if pq_from.upper() == 'TAG' else False
+
+
+def export_patches(repo, branch, options):
+ """Export patches from the pq branch into a patch series"""
+ patch_dir = os.path.join(repo.path, PATCH_DIR)
+ series_file = os.path.join(repo.path, SERIES_FILE)
+ 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 as e:
+ if e.errno != errno.ENOENT:
+ raise GbpError("Failed to remove patch dir: %s" % e.strerror)
+ else:
+ gbp.log.debug("%s does not exist." % patch_dir)
+
+ if pq_on_upstream_tag(options.pq_from):
+ base = find_upstream_commit(repo, branch, options.upstream_tag)
+ else:
+ base = branch
+
+ patches = generate_patches(repo, base, pq_branch, patch_dir, options)
+
+ if patches:
+ with open(series_file, 'w') as seriesfd:
+ for patch in patches:
+ seriesfd.write(os.path.relpath(patch, patch_dir) + '\n')
+ else:
+ gbp.log.info("No patches on '%s' - nothing to export." % pq_branch)
+
+ if options.commit:
+ added, removed = commit_patches(repo, branch, patches, options, patch_dir)
+ if added:
+ what = 'patches' if len(added) > 1 else 'patch'
+ gbp.log.info("Added %s %s to patch series" % (what, ', '.join(added)))
+ if removed:
+ what = 'patches' if len(removed) > 1 else 'patch'
+ gbp.log.info("Removed %s %s from patch series" % (what, ', '.join(removed)))
+ else:
+ gbp.log.info("Updated existing patches.")
+
+ if options.drop:
+ drop_pq(repo, branch)
+
+
+def safe_patches(series, repo):
+ """
+ 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=repo.git_dir, 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, force, pq_from,
+ upstream_tag):
+ """
+ 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 patch 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.
+ @param force: import the patch series even if the branch already exists
+ @param pq_from: what to use as the starting point for the pq branch.
+ DEBIAN indicates the current branch, TAG indicates that
+ the corresponding upstream tag should be used.
+ @param upstream_tag: upstream tag template to use
+ """
+ tmpdir = None
+ series = os.path.join(repo.path, series)
+
+ if is_pq_branch(branch):
+ if force:
+ branch = pq_branch_base(branch)
+ pq_branch = pq_branch_name(branch)
+ repo.checkout(branch)
+ else:
+ raise GbpError("Already on a patch-queue branch '%s' - doing nothing." % branch)
+ else:
+ pq_branch = pq_branch_name(branch)
+
+ if repo.has_branch(pq_branch):
+ if force:
+ drop_pq(repo, branch)
+ else:
+ raise GbpError("Patch queue branch '%s'. already exists. Try 'rebase' or 'switch' instead."
+ % pq_branch)
+
+ maintainer = get_maintainer_from_control(repo)
+ if pq_on_upstream_tag(pq_from):
+ commits = [find_upstream_commit(repo, branch, upstream_tag)]
+ else: # pq_from == 'DEBIAN'
+ commits = repo.get_commits(num=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 we are using the upstream_tag, we always need a copy of the patches
+ if len(commits) > 1 or pq_on_upstream_tag(pq_from):
+ if os.path.exists(series):
+ tmpdir, series = safe_patches(series, repo)
+
+ queue = PatchSeries.read_series_file(series)
+
+ i = len(commits)
+ for commit in commits:
+ if len(commits) > 1:
+ gbp.log.info("%d %s left" % (i, 'tries' if i > 1 else 'try'))
+ try:
+ gbp.log.info("Trying to apply patches at '%s'" % commit)
+ repo.create_branch(pq_branch, commit)
+ except GitRepositoryError:
+ 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:
+ name = os.path.basename(patch.path)
+ apply_and_commit_patch(repo, patch, maintainer, patch.topic, name)
+ except (GbpError, GitRepositoryError) as e:
+ gbp.log.err("Failed to apply '%s': %s" % (patch.path, e))
+ repo.force_head('HEAD', hard=True)
+ repo.set_branch(branch)
+ repo.delete_branch(pq_branch)
+ break
+ else:
+ # All patches applied successfully
+ break
+ i -= 1
+ else:
+ raise GbpError("Couldn't apply patches")
+
+ if tmpdir:
+ gbp.log.debug("Remove temporary patch safe '%s'" % tmpdir)
+ shutil.rmtree(tmpdir)
+
+ return len(queue)
+
+
+def rebase_pq(repo, branch, options):
+ maybe_import_pq(repo, branch, options)
+ # Make sure we're on the pq branch
+ switch_to_pq_branch(repo, branch)
+ if pq_on_upstream_tag(options.pq_from):
+ base = find_upstream_commit(repo, branch, options.upstream_tag)
+ else:
+ base = pq_branch_base(repo.branch)
+
+ GitCommand("rebase", cwd=repo.path)([base])
+
+
+def import_pq(repo, branch, options):
+ """Import quilt patches onto pq branch"""
+ series = SERIES_FILE
+ tries = options.time_machine if (options.time_machine > 0) else 1
+ num = import_quilt_patches(repo, branch, series, tries,
+ options.force, options.pq_from,
+ options.upstream_tag)
+ gbp.log.info("%d patches listed in '%s' imported on '%s'" %
+ (num, series, repo.get_branch()))
+
+
+def maybe_import_pq(repo, branch, options):
+ """Import quilt patches onto pq branch if pq branch does not exist yet"""
+ if not repo.has_branch(pq_branch_name(branch)):
+ gbp.log.info("No pq branch found, importing patches")
+ import_pq(repo, branch, options)
+ return True
+ return False
+
+
+def switch_pq(repo, branch, options):
+ """Switch to patch-queue branch if on base branch and vice versa"""
+ if is_pq_branch(branch):
+ base = pq_branch_base(branch)
+ gbp.log.info("Switching to %s" % base)
+ repo.checkout(base)
+ else:
+ maybe_import_pq(repo, branch, options)
+ switch_to_pq_branch(repo, branch)
+
+
+def usage_msg():
+ return """%prog [options] action - maintain patches on a patch queue branch
+Actions:
+ export export the patch queue associated to the current branch
+ into a quilt patch series in debian/patches/ and update the
+ series file.
+ import create a patch queue branch from quilt patches in debian/patches.
+ rebase switch to patch queue branch associated to the current
+ branch and rebase against current branch.
+ drop drop (delete) the patch queue associated to the current branch.
+ apply apply a patch
+ switch switch to patch-queue branch and vice versa"""
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name),
+ usage=usage_msg())
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ parser.add_boolean_config_file_option(option_name="patch-numbers", dest="patch_numbers")
+ parser.add_config_file_option(option_name="patch-num-format", dest="patch_num_format")
+ parser.add_boolean_config_file_option(option_name="renumber", dest="renumber")
+ 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_boolean_config_file_option("drop", dest='drop')
+ parser.add_boolean_config_file_option(option_name="commit", dest="commit")
+ parser.add_config_file_option(option_name="abbrev", dest="abbrev", type="int")
+ parser.add_option("--force", dest="force", action="store_true", default=False,
+ help="in case of import even import if the branch already exists")
+ 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_config_file_option(option_name="meta-closes", dest="meta_closes")
+ parser.add_config_file_option(option_name="meta-closes-bugnum", dest="meta_closes_bugnum")
+ parser.add_config_file_option(option_name="pq-from", dest="pq_from", choices=['DEBIAN', 'TAG'])
+ parser.add_config_file_option(option_name="upstream-tag", dest="upstream_tag")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+ return parser.parse_args(argv)
+
+
+def main(argv):
+ retval = 0
+
+ (options, args) = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if len(args) < 2:
+ gbp.log.err("No action given.")
+ return 1
+ else:
+ action = args[1]
+
+ if args[1] in ["export", "import", "rebase", "drop", "switch"]:
+ pass
+ elif args[1] in ["apply"]:
+ if len(args) != 3:
+ gbp.log.err("No patch name given.")
+ return 1
+ else:
+ patchfile = args[2]
+ else:
+ gbp.log.err("Unknown action '%s'." % args[1])
+ return 1
+
+ try:
+ repo = DebianGitRepository(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":
+ import_pq(repo, current, options)
+ elif action == "drop":
+ drop_pq(repo, current)
+ elif action == "rebase":
+ rebase_pq(repo, current, options)
+ elif action == "apply":
+ patch = Patch(patchfile)
+ maintainer = get_maintainer_from_control(repo)
+ apply_single_patch(repo, current, patch, maintainer, options.topic)
+ elif action == "switch":
+ switch_pq(repo, current, options)
+ except KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except CommandExecFailed:
+ retval = 1
+ except (GbpError, GitRepositoryError) as err:
+ if str(err):
+ gbp.log.err(err)
+ retval = 1
+
+ return retval
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/gbp/scripts/pq_rpm.py b/gbp/scripts/pq_rpm.py
new file mode 100755
index 0000000..fcefbb6
--- /dev/null
+++ b/gbp/scripts/pq_rpm.py
@@ -0,0 +1,490 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011,2016 Guido Günther <agx@sigxcpu.org>
+# (C) 2012-2014 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Manage RPM patches in a patch queue"""
+
+import bz2
+import errno
+import gzip
+import os
+import re
+import sys
+
+import gbp.log
+from gbp.tmpfile import init_tmpdir, del_tmpdir, tempfile
+from gbp.config import GbpOptionParserRpm
+from gbp.rpm.git import GitRepositoryError, RpmGitRepository
+from gbp.git.modifier import GitModifier
+from gbp.command_wrappers import GitCommand, CommandExecFailed
+from gbp.errors import GbpError
+from gbp.patch_series import PatchSeries, Patch
+from gbp.pkg import Archive
+from gbp.rpm import (SpecFile, NoSpecError, guess_spec, guess_spec_repo,
+ spec_from_repo)
+from gbp.scripts.common import ExitCodes
+from gbp.scripts.common.pq import (is_pq_branch, pq_branch_name, pq_branch_base,
+ parse_gbp_commands, format_patch, format_diff,
+ switch_to_pq_branch, apply_single_patch,
+ apply_and_commit_patch,
+ drop_pq)
+
+from gbp.scripts.common.buildpackage import dump_tree
+
+
+def is_ancestor(repo, parent, child):
+ """Check if commit is ancestor of another"""
+ parent_sha1 = repo.rev_parse("%s^0" % parent)
+ child_sha1 = repo.rev_parse("%s^0" % child)
+ try:
+ merge_base = repo.get_merge_base(parent_sha1, child_sha1)
+ except GitRepositoryError:
+ merge_base = None
+ return merge_base == parent_sha1
+
+
+def generate_patches(repo, start, end, outdir, options):
+ """
+ Generate patch files from git
+ """
+ gbp.log.info("Generating patches from git (%s..%s)" % (start, end))
+ patches = []
+ commands = {}
+ for treeish in [start, end]:
+ if not repo.has_treeish(treeish):
+ raise GbpError('Invalid treeish object %s' % treeish)
+
+ start_sha1 = repo.rev_parse("%s^0" % start)
+ try:
+ end_commit = end
+ except GitRepositoryError:
+ # In case of plain tree-ish objects, assume current branch head is the
+ # last commit
+ end_commit = "HEAD"
+ end_commit_sha1 = repo.rev_parse("%s^0" % end_commit)
+
+ start_sha1 = repo.rev_parse("%s^0" % start)
+
+ if not is_ancestor(repo, start_sha1, end_commit_sha1):
+ raise GbpError("Start commit '%s' not an ancestor of end commit "
+ "'%s'" % (start, end_commit))
+ # Check for merge commits, squash if merges found
+ merges = repo.get_commits(start, end_commit, options=['--merges'])
+ if merges:
+ # Shorten SHA1s
+ start_sha1 = repo.rev_parse(start, short=options.abbrev)
+ merge_sha1 = repo.rev_parse(merges[0], short=options.abbrev)
+ patch_fn = format_diff(outdir, None, repo, start_sha1, merge_sha1,
+ abbrev=options.abbrev)
+ if patch_fn:
+ gbp.log.info("Merge commits found! Diff between %s..%s written "
+ "into one monolithic diff" % (start_sha1, merge_sha1))
+ patches.append(patch_fn)
+ start = merge_sha1
+
+ # Generate patches
+ for commit in reversed(repo.get_commits(start, end_commit)):
+ info = repo.get_commit_info(commit)
+ (cmds, info['body']) = parse_gbp_commands(info,
+ 'gbp-rpm',
+ ('ignore'),
+ ('if', 'ifarch'))
+ if 'ignore' not in cmds:
+ patch_fn = format_patch(outdir, repo, info, patches,
+ numbered=options.patch_numbers,
+ abbrev=options.abbrev)
+ if patch_fn:
+ commands[os.path.basename(patch_fn)] = cmds
+ else:
+ gbp.log.info('Ignoring commit %s' % info['id'])
+
+ # Generate diff to the tree-ish object
+ if end_commit != end:
+ gbp.log.info("Generating diff file %s..%s" % (end_commit, end))
+ patch_fn = format_diff(outdir, None, repo, end_commit, end,
+ options.patch_export_ignore_path,
+ abbrev=options.abbrev)
+ if patch_fn:
+ patches.append(patch_fn)
+
+ return [os.path.relpath(p) for p in patches], commands
+
+
+def rm_patch_files(spec):
+ """
+ Delete the patch files listed in the spec file. Doesn't delete patches
+ marked as not maintained by gbp.
+ """
+ # Remove all old patches from the spec dir
+ for patch in spec.patchseries(unapplied=True):
+ gbp.log.debug("Removing '%s'" % patch.path)
+ try:
+ os.unlink(patch.path)
+ except OSError as err:
+ if err.errno != errno.ENOENT:
+ raise GbpError("Failed to remove patch: %s" % err)
+ else:
+ gbp.log.debug("Patch %s does not exist." % patch.path)
+
+
+def update_patch_series(repo, spec, start, end, options):
+ """
+ Export patches to packaging directory and update spec file accordingly.
+ """
+ # Unlink old patch files and generate new patches
+ rm_patch_files(spec)
+
+ patches, commands = generate_patches(repo, start, end,
+ spec.specdir, options)
+ spec.update_patches(patches, commands)
+ spec.write_spec_file()
+ return patches
+
+
+def parse_spec(options, repo, treeish=None):
+ """
+ Find and parse spec file.
+
+ If treeish is given, try to find the spec file from that. Otherwise, search
+ for the spec file in the working copy.
+ """
+ try:
+ if options.spec_file:
+ if not treeish:
+ spec = SpecFile(options.spec_file)
+ else:
+ spec = spec_from_repo(repo, treeish, options.spec_file)
+ else:
+ preferred_name = os.path.basename(repo.path) + '.spec'
+ if not treeish:
+ spec = guess_spec(options.packaging_dir, True, preferred_name)
+ else:
+ spec = guess_spec_repo(repo, treeish, options.packaging_dir,
+ True, preferred_name)
+ except NoSpecError as err:
+ raise GbpError("Can't parse spec: %s" % err)
+ relpath = spec.specpath if treeish else os.path.relpath(spec.specpath,
+ repo.path)
+ options.packaging_dir = os.path.dirname(relpath)
+ gbp.log.debug("Using '%s' from '%s'" % (relpath, treeish or 'working copy'))
+ return spec
+
+
+def find_upstream_commit(repo, spec, upstream_tag):
+ """Find commit corresponding upstream version"""
+ tag_str_fields = {'upstreamversion': spec.upstreamversion,
+ 'version': spec.upstreamversion}
+ upstream_commit = repo.find_version(upstream_tag, tag_str_fields)
+ if not upstream_commit:
+ raise GbpError("Couldn't find upstream version %s" %
+ spec.upstreamversion)
+ return upstream_commit
+
+
+def export_patches(repo, options):
+ """Export patches from the pq branch into a packaging branch"""
+ current = repo.get_branch()
+ if is_pq_branch(current):
+ base = pq_branch_base(current)
+ gbp.log.info("On branch '%s', switching to '%s'" % (current, base))
+ repo.set_branch(base)
+ pq_branch = current
+ else:
+ base = current
+ pq_branch = pq_branch_name(current)
+ spec = parse_spec(options, repo)
+ upstream_commit = find_upstream_commit(repo, spec, options.upstream_tag)
+ export_treeish = pq_branch
+
+ update_patch_series(repo, spec, upstream_commit, export_treeish, options)
+
+ GitCommand('status')(['--', spec.specdir])
+
+ if options.drop:
+ drop_pq(repo, base)
+
+
+def safe_patches(queue):
+ """
+ Safe the current patches in a temporary directory
+
+ @param queue: an existing patch queue
+ @return: safed queue (with patches in tmpdir)
+ @rtype: tuple
+ """
+
+ tmpdir = tempfile.mkdtemp(prefix='patchimport_')
+ safequeue = PatchSeries()
+
+ if len(queue) > 0:
+ gbp.log.debug("Saving patches '%s' in '%s'" %
+ (os.path.dirname(queue[0].path), tmpdir))
+ for patch in queue:
+ base, _archive_fmt, comp = Archive.parse_filename(patch.path)
+ uncompressors = {'gzip': gzip.open, 'bzip2': bz2.BZ2File}
+ if comp in uncompressors:
+ gbp.log.debug("Uncompressing '%s'" % os.path.basename(patch.path))
+ src = uncompressors[comp](patch.path, 'r')
+ dst_name = os.path.join(tmpdir, os.path.basename(base))
+ elif comp:
+ raise GbpError("Unsupported patch compression '%s', giving up"
+ % comp)
+ else:
+ src = open(patch.path, 'rb')
+ dst_name = os.path.join(tmpdir, os.path.basename(patch.path))
+
+ dst = open(dst_name, 'wb')
+ dst.write(src.read())
+ src.close()
+ dst.close()
+
+ safequeue.append(patch)
+ safequeue[-1].path = dst_name
+
+ return safequeue
+
+
+def get_packager(spec):
+ """Get packager information from spec"""
+ if spec.packager:
+ match = re.match(r'(?P<name>.*[^ ])\s*<(?P<email>\S*)>',
+ spec.packager.strip())
+ if match:
+ return GitModifier(match.group('name'), match.group('email'))
+ return GitModifier()
+
+
+def import_spec_patches(repo, options):
+ """
+ apply a series of patches in a spec/packaging dir to branch
+ the patch-queue branch for 'branch'
+
+ @param repo: git repository to work on
+ @param options: command options
+ """
+ current = repo.get_branch()
+ # Get spec and related information
+ if is_pq_branch(current):
+ base = pq_branch_base(current)
+ if options.force:
+ spec = parse_spec(options, repo, base)
+ spec_treeish = base
+ else:
+ raise GbpError("Already on a patch-queue branch '%s' - doing "
+ "nothing." % current)
+ else:
+ spec = parse_spec(options, repo)
+ spec_treeish = None
+ base = current
+ upstream_commit = find_upstream_commit(repo, spec, options.upstream_tag)
+ packager = get_packager(spec)
+ pq_branch = pq_branch_name(base)
+
+ # Create pq-branch
+ if repo.has_branch(pq_branch) and not options.force:
+ raise GbpError("Patch-queue branch '%s' already exists. "
+ "Try 'switch' instead." % pq_branch)
+ try:
+ if repo.get_branch() == pq_branch:
+ repo.force_head(upstream_commit, hard=True)
+ else:
+ repo.create_branch(pq_branch, upstream_commit, force=True)
+ except GitRepositoryError as err:
+ raise GbpError("Cannot create patch-queue branch '%s': %s" %
+ (pq_branch, err))
+
+ # Put patches in a safe place
+ if spec_treeish:
+ packaging_tmp = tempfile.mkdtemp(prefix='dump_')
+ packaging_tree = '%s:%s' % (spec_treeish, options.packaging_dir)
+ dump_tree(repo, packaging_tmp, packaging_tree, with_submodules=False,
+ recursive=False)
+ spec.specdir = packaging_tmp
+ in_queue = spec.patchseries()
+ queue = safe_patches(in_queue)
+ # Do import
+ try:
+ gbp.log.info("Switching to branch '%s'" % pq_branch)
+ repo.set_branch(pq_branch)
+
+ if not queue:
+ return
+ gbp.log.info("Trying to apply patches from branch '%s' onto '%s'" %
+ (base, upstream_commit))
+ for patch in queue:
+ gbp.log.debug("Applying %s" % patch.path)
+ apply_and_commit_patch(repo, patch, packager)
+ except (GbpError, GitRepositoryError) as err:
+ repo.set_branch(base)
+ repo.delete_branch(pq_branch)
+ raise GbpError('Import failed: %s' % err)
+
+ gbp.log.info("%d patches listed in '%s' imported on '%s'" % (len(queue), spec.specfile,
+ pq_branch))
+
+
+def rebase_pq(repo, options):
+ """Rebase pq branch on the correct upstream version (from spec file)."""
+ current = repo.get_branch()
+ if is_pq_branch(current):
+ base = pq_branch_base(current)
+ spec = parse_spec(options, repo, base)
+ else:
+ base = current
+ spec = parse_spec(options, repo)
+ upstream_commit = find_upstream_commit(repo, spec, options.upstream_tag)
+
+ switch_to_pq_branch(repo, base)
+ GitCommand("rebase")([upstream_commit])
+
+
+def switch_pq(repo, current):
+ """Switch to patch-queue branch if on base branch and vice versa"""
+ if is_pq_branch(current):
+ base = pq_branch_base(current)
+ gbp.log.info("Switching to %s" % base)
+ repo.checkout(base)
+ else:
+ switch_to_pq_branch(repo, current)
+
+
+def usage_msg():
+ return """%prog [options] action - maintain patches on a patch queue branch
+Ations:
+export Export the patch queue / devel branch associated to the
+ current branch into a patch series in and update the spec file
+import Create a patch queue / devel branch from spec file
+ and patches in current dir.
+rebase Switch to patch queue / devel branch associated to the current
+ branch and rebase against upstream.
+drop Drop (delete) the patch queue /devel branch associated to
+ the current branch.
+apply Apply a patch
+switch Switch to patch-queue branch and vice versa."""
+
+
+def build_parser(name):
+ """Construct command line parser"""
+ try:
+ parser = GbpOptionParserRpm(command=os.path.basename(name),
+ prefix='', usage=usage_msg())
+
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ 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("--force", dest="force", action="store_true",
+ default=False,
+ help="In case of import even import if the branch already exists")
+ parser.add_boolean_config_file_option("drop", dest='drop')
+ 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_config_file_option(option_name="tmp-dir", dest="tmp_dir")
+ parser.add_config_file_option(option_name="abbrev", dest="abbrev", type="int")
+ parser.add_config_file_option(option_name="upstream-tag",
+ dest="upstream_tag")
+ parser.add_config_file_option(option_name="spec-file", dest="spec_file")
+ parser.add_config_file_option(option_name="packaging-dir",
+ dest="packaging_dir")
+ return parser
+
+
+def parse_args(argv):
+ """Parse command line arguments"""
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+ return parser.parse_args(argv)
+
+
+def main(argv):
+ """Main function for the gbp pq-rpm command"""
+ retval = 0
+
+ (options, args) = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if len(args) < 2:
+ gbp.log.err("No action given.")
+ return 1
+ else:
+ action = args[1]
+
+ if args[1] in ["export", "import", "rebase", "drop", "switch", "convert"]:
+ pass
+ elif args[1] in ["apply"]:
+ if len(args) != 3:
+ gbp.log.err("No patch name given.")
+ return 1
+ else:
+ patchfile = args[2]
+ else:
+ gbp.log.err("Unknown action '%s'." % args[1])
+ return 1
+
+ try:
+ repo = RpmGitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+
+ try:
+ # Create base temporary directory for this run
+ init_tmpdir(options.tmp_dir, prefix='pq-rpm_')
+ current = repo.get_branch()
+ if action == "export":
+ export_patches(repo, options)
+ elif action == "import":
+ import_spec_patches(repo, options)
+ elif action == "drop":
+ drop_pq(repo, current)
+ elif action == "rebase":
+ rebase_pq(repo, options)
+ elif action == "apply":
+ patch = Patch(patchfile)
+ apply_single_patch(repo, current, patch, fallback_author=None)
+ elif action == "switch":
+ switch_pq(repo, current)
+ except KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except CommandExecFailed:
+ retval = 1
+ except GitRepositoryError as err:
+ gbp.log.err("Git command failed: %s" % err)
+ retval = 1
+ except GbpError as err:
+ if str(err):
+ gbp.log.err(err)
+ retval = 1
+ finally:
+ del_tmpdir()
+
+ return retval
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/gbp/scripts/pristine_tar.py b/gbp/scripts/pristine_tar.py
new file mode 100644
index 0000000..792bcab
--- /dev/null
+++ b/gbp/scripts/pristine_tar.py
@@ -0,0 +1,122 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+#
+"""Perform pristine-tar import into a Git repository"""
+
+import os
+import sys
+import gbp.log
+from gbp.command_wrappers import CommandExecFailed
+from gbp.config import GbpOptionParserDebian
+from gbp.deb.git import (GitRepositoryError, DebianGitRepository)
+from gbp.deb.source import DebianSource
+from gbp.errors import GbpError
+from gbp.scripts.common import ExitCodes, get_component_tarballs
+
+
+def usage_msg():
+ return """%prog [action] [options] /path/to/upstream-version.tar.gz
+
+Actions:
+ commit recreate the pristine-tar commits on the pristine-tar branch
+"""
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParserDebian(command=os.path.basename(name), prefix='',
+ usage=usage_msg())
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ parser.add_config_file_option(option_name="upstream-tag",
+ dest="upstream_tag")
+ parser.add_config_file_option("component", action="append", metavar='COMPONENT',
+ dest="components")
+ 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ return parser
+
+
+def parse_args(argv):
+ """Parse the command line arguments
+ @return: options and arguments
+ """
+
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+
+ (options, args) = parser.parse_args(argv[1:])
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+ return options, args
+
+
+def main(argv):
+ ret = 1
+ repo = None
+
+ (options, args) = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ if len(args) != 2 or args[0] not in ['commit']:
+ gbp.log.err("No action given")
+ return 1
+ else:
+ tarball = args[1]
+
+ try:
+ try:
+ repo = DebianGitRepository('.')
+ except GitRepositoryError:
+ raise GbpError("%s is not a git repository" % (os.path.abspath('.')))
+
+ source = DebianSource('.')
+ component_tarballs = get_component_tarballs(source.sourcepkg,
+ source.upstream_version,
+ tarball,
+ options.components)
+ upstream_tag = repo.version_to_tag(options.upstream_tag,
+ source.upstream_version)
+ repo.create_pristine_tar_commits(upstream_tag,
+ tarball,
+ component_tarballs)
+ ret = 0
+ except (GitRepositoryError, GbpError, CommandExecFailed) as err:
+ if str(err):
+ gbp.log.err(err)
+ except KeyboardInterrupt:
+ gbp.log.err("Interrupted. Aborting.")
+
+ if not ret:
+ comp_msg = (' with additional tarballs for %s'
+ % ", ".join([os.path.basename(t[1]) for t in component_tarballs])) if component_tarballs else ''
+ gbp.log.info("Successfully committed pristine-tar data for version %s of %s%s" % (source.upstream_version,
+ tarball,
+ comp_msg))
+ 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/pull.py b/gbp/scripts/pull.py
new file mode 100755
index 0000000..9027d90
--- /dev/null
+++ b/gbp/scripts/pull.py
@@ -0,0 +1,240 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2009,2013,2017,2018 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, please see
+# <http://www.gnu.org/licenses/>
+#
+# heavily inspired by dom-safe-pull which is © 2009 Stéphane Glondu <steph@glondu.net>
+#
+"""Pull remote changes and fast forward debian, upstream and pristine-tar branch"""
+
+import sys
+import os
+import os.path
+from gbp.command_wrappers import (Command, CommandExecFailed)
+from gbp.config import (GbpOptionParser, GbpOptionGroup)
+from gbp.errors import GbpError
+from gbp.git import GitRepositoryError
+from gbp.deb.git import DebianGitRepository
+from gbp.scripts.common import ExitCodes
+import gbp.log
+
+
+def fast_forward_branch(rem_repo, 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
+
+ if rem_repo:
+ remote = 'refs/remotes/%s/%s' % (rem_repo, branch)
+ else:
+ 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 or "
+ "update manually" % branch)
+
+ if update:
+ gbp.log.info("Updating '%s': %s..%s" % (branch,
+ repo.rev_parse(branch, short=12),
+ repo.rev_parse(remote, short=12)))
+ if repo.branch == branch:
+ repo.merge(remote)
+ else:
+ sha1 = repo.rev_parse(remote)
+ repo.update_ref("refs/heads/%s" % branch, sha1,
+ msg="gbp: forward %s to %s" % (branch, remote))
+ return update
+
+
+def build_parser(name):
+ try:
+ parser = GbpOptionParser(command=os.path.basename(name), prefix='',
+ usage='%prog [options] [repo] - safely update a repository from remote')
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ branch_group = GbpOptionGroup(parser, "branch options", "branch update and layout options")
+ parser.add_option_group(branch_group)
+ branch_group.add_boolean_config_file_option(option_name="ignore-branch", dest="ignore_branch")
+ branch_group.add_option("--force", action="store_true", dest="force", default=False,
+ help="force a branch update even if it can't be fast forwarded")
+ branch_group.add_option("--all", action="store_true", default=False,
+ help="update all remote-tracking branches that "
+ "have identical name in the remote")
+ 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")
+ branch_group.add_boolean_config_file_option(option_name="track-missing", dest="track_missing")
+ branch_group.add_option("--depth", action="store", dest="depth", default=0,
+ help="git history depth (for deepening shallow clones)")
+ 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_config_file_option(option_name="color-scheme",
+ dest="color_scheme")
+ return parser
+
+
+def parse_args(argv):
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+ options, args = parser.parse_args(argv)
+ if len(args) > 2:
+ parser.print_help(file=sys.stderr)
+ return None, None
+ return options, args
+
+
+def get_remote(repo, current):
+ current_remote = repo.get_merge_branch(current)
+ if current_remote:
+ fetch_remote = current_remote.split('/')[0]
+ else:
+ fetch_remote = 'origin'
+ return fetch_remote
+
+
+def track_missing(repo, remote, branch, options):
+ upstream = "remotes/{}/{}".format(remote, branch)
+ if not repo.has_branch(branch):
+ try:
+ repo.fetch(remote, depth=options.depth, refspec=branch)
+ except GitRepositoryError:
+ pass # it's o.k. if the remote branch is missing
+ else:
+ repo.create_branch(branch, upstream)
+
+
+def main(argv):
+ retval = 0
+ current = None
+ rem_repo = None
+
+ (options, args) = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ if len(args) == 2:
+ rem_repo = args[1]
+ gbp.log.info("Fetching from '%s'" % rem_repo)
+ else:
+ gbp.log.info("Fetching from default remote for each branch")
+
+ try:
+ repo = DebianGitRepository(os.path.curdir)
+ except GitRepositoryError:
+ gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
+ return 1
+
+ try:
+ branches = set()
+ try:
+ current = repo.get_branch()
+ except GitRepositoryError:
+ # Not being on any branch is o.k. with --ignore-branch
+ if options.ignore_branch:
+ current = repo.head
+ gbp.log.info("Found detached head at '%s'" % current)
+ else:
+ raise
+
+ (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(rem_repo, depth=options.depth)
+ repo.fetch(rem_repo, depth=options.depth, tags=True)
+
+ fetch_remote = get_remote(repo, current)
+ for branch in [options.debian_branch, options.upstream_branch]:
+ if not branch:
+ continue
+ if options.track_missing:
+ track_missing(repo, fetch_remote, branch, options)
+
+ if repo.has_branch(branch):
+ branches.add(branch)
+
+ if options.pristine_tar:
+ branch = repo.pristine_tar_branch
+ if options.track_missing:
+ track_missing(repo, fetch_remote, branch, options)
+
+ if repo.has_pristine_tar_branch():
+ branches.add(repo.pristine_tar_branch)
+
+ if options.all:
+ for branch in repo.get_local_branches():
+ merge_branch = repo.get_merge_branch(branch)
+ if merge_branch:
+ rem, rem_br = merge_branch.split('/', 1)
+ if rem == fetch_remote and branch == rem_br:
+ branches.add(branch)
+
+ for branch in branches:
+ if not fast_forward_branch(rem_repo, 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 KeyboardInterrupt:
+ retval = 1
+ gbp.log.err("Interrupted. Aborting.")
+ except CommandExecFailed:
+ retval = 1
+ except (GbpError, GitRepositoryError) as err:
+ if str(err):
+ 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/push.py b/gbp/scripts/push.py
new file mode 100755
index 0000000..209fd41
--- /dev/null
+++ b/gbp/scripts/push.py
@@ -0,0 +1,189 @@
+#!/usr/bin/python3
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""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 DebianSourceError
+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"
+ success = True
+ for dest in dests:
+ for tag in to_push['tags']:
+ gbp.log.info("%s %s to %s" % (verb, tag, dest))
+ try:
+ repo.push_tag(dest, tag, dry_run=dry_run)
+ except GitRepositoryError as e:
+ gbp.log.err(e)
+ success = False
+ for k, v in to_push['refs'].items():
+ gbp.log.info("%s %s to %s:%s" % (verb, v, dest, k))
+ try:
+ repo.push(dest, v, k, dry_run=dry_run)
+ except GitRepositoryError as e:
+ gbp.log.err(e)
+ success = False
+ return success
+
+
+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.branch
+ if not options.ignore_branch:
+ if branch != options.debian_branch:
+ gbp.log.err("You are not on branch '%s' but %s" %
+ (options.debian_branch,
+ "on '%s'" % branch if branch else 'in detached HEAD state'))
+ raise GbpError("Use --ignore-branch to ignore or --debian-branch to set the branch name.")
+
+ if not dest:
+ dest = get_remote(repo, branch)
+
+ if options.debian_tag != '':
+ 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():
+ if options.upstream_tag != '':
+ utag = repo.version_to_tag(options.upstream_tag,
+ source.upstream_version)
+ if repo.has_tag(utag):
+ to_push['tags'].append(utag)
+
+ if options.upstream_branch != '':
+ 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)
+
+ if do_push(repo, [dest], to_push, dry_run=options.dryrun):
+ retval = 0
+ else:
+ gbp.log.err("Failed to push some refs.")
+ retval = 1
+ except (GbpError, GitRepositoryError, DebianSourceError) as err:
+ if str(err):
+ gbp.log.err(err)
+ except KeyboardInterrupt:
+ gbp.log.err("Interrupted. Aborting.")
+
+ 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/rpm_ch.py b/gbp/scripts/rpm_ch.py
new file mode 100644
index 0000000..9212f71
--- /dev/null
+++ b/gbp/scripts/rpm_ch.py
@@ -0,0 +1,458 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2007, 2008, 2009, 2010, 2013 Guido Günther <agx@sigxcpu.org>
+# (C) 2014-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Generate RPM changelog entries from git commit messages"""
+
+from datetime import datetime
+import os.path
+import pwd
+import re
+import sys
+import socket
+
+import gbp.command_wrappers as gbpc
+import gbp.log
+from gbp.config import GbpOptionParserRpm, GbpOptionGroup
+from gbp.errors import GbpError
+from gbp.rpm import (guess_spec, NoSpecError, SpecFile, split_version_str,
+ compose_version_str)
+from gbp.rpm.changelog import Changelog, ChangelogParser, ChangelogError
+from gbp.rpm.git import GitRepositoryError, RpmGitRepository
+from gbp.rpm.policy import RpmPkgPolicy
+from gbp.scripts.common import ExitCodes
+from gbp.tmpfile import init_tmpdir, del_tmpdir
+
+
+ChangelogEntryFormatter = RpmPkgPolicy.ChangelogEntryFormatter
+
+
+class ChangelogFile(object):
+ """Container for changelog file, whether it be a standalone changelog
+ or a spec file"""
+
+ def __init__(self, file_path):
+ parser = ChangelogParser(RpmPkgPolicy)
+
+ if os.path.splitext(file_path)[1] == '.spec':
+ gbp.log.debug("Using spec file '%s' as changelog" % file_path)
+ self._file = SpecFile(file_path)
+ self.changelog = parser.raw_parse_string(self._file.get_changelog())
+ else:
+ self._file = os.path.abspath(file_path)
+ if not os.path.exists(file_path):
+ gbp.log.info("Changelog '%s' not found, creating new "
+ "changelog file" % file_path)
+ self.changelog = Changelog(RpmPkgPolicy)
+ else:
+ gbp.log.debug("Using changelog file '%s'" % file_path)
+ self.changelog = parser.raw_parse_file(self._file)
+
+ # Parse topmost section and try to determine the start commit
+ if self.changelog.sections:
+ self.changelog.sections[0] = parser.parse_section(
+ self.changelog.sections[0])
+
+ def write(self):
+ """Write changelog file to disk"""
+ if isinstance(self._file, SpecFile):
+ self._file.set_changelog(str(self.changelog))
+ self._file.write_spec_file()
+ else:
+ with open(self._file, 'w') as fobj:
+ fobj.write(str(self.changelog))
+
+ @property
+ def path(self):
+ """File path"""
+ if isinstance(self._file, SpecFile):
+ return self._file.specpath
+ else:
+ return self._file
+
+
+def load_customizations(customization_file):
+ """Load user defined customizations file"""
+ # Load customization file
+ if not customization_file:
+ return
+ customizations = {}
+ try:
+ with open(customization_file) as f:
+ exec(f.read(), customizations, customizations)
+ except Exception as err:
+ raise GbpError("Failed to load customization file: %s" % err)
+
+ # Set customization classes / functions
+ global ChangelogEntryFormatter
+ if 'ChangelogEntryFormatter' in customizations:
+ ChangelogEntryFormatter = customizations.get('ChangelogEntryFormatter')
+
+
+def determine_editor(options):
+ """Determine text editor"""
+
+ # Check if we need to spawn an editor
+ states = ['always']
+ if options.release:
+ states.append('release')
+ if options.spawn_editor not in states:
+ return None
+
+ # Determine the correct editor
+ if options.editor_cmd:
+ return options.editor_cmd
+ elif 'EDITOR' in os.environ:
+ return os.environ['EDITOR']
+ else:
+ return 'vi'
+
+
+def check_branch(repo, options):
+ """Check the current git branch"""
+ try:
+ branch = repo.get_branch()
+ except GitRepositoryError:
+ branch = None
+ if options.packaging_branch != branch and not options.ignore_branch:
+ gbp.log.err("You are not on branch '%s' but on '%s'" %
+ (options.packaging_branch, branch))
+ raise GbpError("Use --ignore-branch to ignore or "
+ "--packaging-branch to set the branch name.")
+
+
+def parse_spec_file(repo, options):
+ """Find and parse spec file"""
+ if options.spec_file:
+ spec_path = os.path.join(repo.path, options.spec_file)
+ spec = SpecFile(spec_path)
+ else:
+ spec = guess_spec(os.path.join(repo.path, options.packaging_dir),
+ True, os.path.basename(repo.path) + '.spec')
+ options.packaging_dir = spec.specdir
+ return spec
+
+
+def parse_changelog_file(repo, spec, options):
+ """Find and parse changelog file"""
+ changes_file_name = os.path.splitext(spec.specfile)[0] + '.changes'
+ changes_file_path = os.path.join(options.packaging_dir, changes_file_name)
+
+ # Determine changelog file path
+ if options.changelog_file == "SPEC":
+ changelog_path = spec.specpath
+ elif options.changelog_file == "CHANGES":
+ changelog_path = changes_file_path
+ elif options.changelog_file == 'auto':
+ if os.path.exists(changes_file_path):
+ changelog_path = changes_file_path
+ else:
+ changelog_path = spec.specpath
+ else:
+ changelog_path = os.path.join(repo.path, options.changelog_file)
+
+ return ChangelogFile(changelog_path)
+
+
+def guess_commit(section, repo, options):
+ """Guess the last commit documented in a changelog header"""
+
+ if not section:
+ return None
+ header = section.header
+
+ # Try to parse the fields from the header revision
+ rev_re = '^%s$' % re.sub(r'%\((\S+?)\)s', r'(?P<\1>\S+)',
+ options.changelog_revision)
+ match = re.match(rev_re, header['revision'], re.I)
+ fields = match.groupdict() if match else {}
+
+ # First, try to find tag-name, if present
+ if 'tagname' in fields:
+ gbp.log.debug("Trying to find tagname %s" % fields['tagname'])
+ try:
+ return repo.rev_parse("%s^0" % fields['tagname'])
+ except GitRepositoryError:
+ gbp.log.warn("Changelog points to tagname '%s' which is not found "
+ "in the git repository" % fields['tagname'])
+
+ # Next, try to find packaging tag matching the version
+ tag_str_fields = {'vendor': options.vendor}
+ if 'version' in fields:
+ gbp.log.debug("Trying to find packaging tag for version '%s'" %
+ fields['version'])
+ full_version = fields['version']
+ tag_str_fields.update(split_version_str(full_version))
+ elif 'upstreamversion' in fields:
+ gbp.log.debug("Trying to find packaging tag for version '%s'" %
+ fields['upstreamversion'])
+ tag_str_fields['upstreamversion'] = fields['upstreamversion']
+ if 'release' in fields:
+ tag_str_fields['release'] = fields['release']
+ commit = repo.find_version(options.packaging_tag,
+ tag_str_fields)
+ if commit:
+ return commit
+ else:
+ gbp.log.info("Couldn't find packaging tag for version %s" %
+ header['revision'])
+
+ # As a last resort we look at the timestamp
+ timestamp = header['time'].isoformat()
+ last = repo.get_commits(num=1, options="--until='%s'" % timestamp)
+ if last:
+ gbp.log.info("Using commit (%s) before the last changelog timestamp "
+ "(%s)" % (last, timestamp))
+ return last[0]
+ return None
+
+
+def get_start_commit(changelog, repo, options):
+ """Get the start commit from which to generate new entries"""
+ if options.since:
+ since = options.since
+ else:
+ if changelog.sections:
+ since = guess_commit(changelog.sections[0], repo, options)
+ else:
+ since = None
+ if not since:
+ raise GbpError("Couldn't determine starting point from "
+ "changelog, please use the '--since' option")
+ gbp.log.info("Continuing from commit '%s'" % since)
+ return since
+
+
+def get_author(repo, use_git_config):
+ """Get author and email from git configuration"""
+ author = email = None
+
+ if use_git_config:
+ modifier = repo.get_author_info()
+ author = modifier.name
+ email = modifier.email
+
+ passwd_data = pwd.getpwuid(os.getuid())
+ if not author:
+ # On some distros (Ubuntu, at least) the gecos field has it's own
+ # internal structure of comma-separated fields
+ author = passwd_data.pw_gecos.split(',')[0].strip()
+ if not author:
+ author = passwd_data.pw_name
+ if not email:
+ if 'EMAIL' in os.environ:
+ email = os.environ['EMAIL']
+ else:
+ email = "%s@%s" % (passwd_data.pw_name, socket.getfqdn())
+
+ return author, email
+
+
+def entries_from_commits(changelog, repo, commits, options):
+ """Generate a list of formatted changelog entries from a list of commits"""
+ entries = []
+ for commit in commits:
+ info = repo.get_commit_info(commit)
+ entry_text = ChangelogEntryFormatter.compose(info, full=options.full,
+ ignore_re=options.ignore_regex,
+ id_len=options.idlen)
+ if entry_text:
+ entries.append(changelog.create_entry(author=info['author'].name,
+ text=entry_text))
+ return entries
+
+
+def update_changelog(changelog, entries, repo, spec, options):
+ """Update the changelog with a range of commits"""
+ # Get info for section header
+ now = datetime.now()
+ name, email = get_author(repo, options.git_author)
+ rev_str_fields = dict(spec.version,
+ version=compose_version_str(spec.version),
+ vendor=options.vendor,
+ tagname=repo.describe('HEAD', longfmt=True,
+ always=True))
+ try:
+ revision = options.changelog_revision % rev_str_fields
+ except KeyError as err:
+ raise GbpError("Unable to construct revision field: unknown key "
+ "%s, only %s are accepted" % (err, rev_str_fields.keys()))
+
+ # Add a new changelog section if new release or an empty changelog
+ if options.release or not changelog.sections:
+ top_section = changelog.add_section(time=now, name=name,
+ email=email, revision=revision)
+ else:
+ # Re-use already parsed top section
+ top_section = changelog.sections[0]
+ top_section.set_header(time=now, name=name,
+ email=email, revision=revision)
+
+ # Add new entries to the topmost section
+ for entry in entries:
+ top_section.append_entry(entry)
+
+
+def build_parser(name):
+ """Construct command line parser"""
+ try:
+ parser = GbpOptionParserRpm(command=os.path.basename(name),
+ prefix='', usage='%prog [options] paths')
+ except GbpError as err:
+ gbp.log.err(err)
+ return None
+
+ range_grp = GbpOptionGroup(parser, "commit range options",
+ "which commits to add to the changelog")
+ format_grp = GbpOptionGroup(parser, "changelog entry formatting",
+ "how to format the changelog entries")
+ naming_grp = GbpOptionGroup(parser, "naming",
+ "branch names, tag formats, directory and file naming")
+ parser.add_option_group(range_grp)
+ parser.add_option_group(format_grp)
+ parser.add_option_group(naming_grp)
+
+ # Non-grouped options
+ parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
+ help="verbose command execution")
+ 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_config_file_option(option_name="tmp-dir", dest="tmp_dir")
+ parser.add_config_file_option(option_name="vendor", action="store",
+ dest="vendor")
+ 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_boolean_config_file_option(option_name="ignore-branch",
+ dest="ignore_branch")
+ parser.add_config_file_option(option_name="customizations",
+ dest="customization_file",
+ help="Load Python code from CUSTOMIZATION_FILE. At the "
+ "moment, the only useful thing the code can do is define a "
+ "custom ChangelogEntryFormatter class.")
+
+ # Naming group options
+ naming_grp.add_config_file_option(option_name="packaging-branch",
+ dest="packaging_branch")
+ naming_grp.add_config_file_option(option_name="packaging-tag",
+ dest="packaging_tag")
+ naming_grp.add_config_file_option(option_name="packaging-dir",
+ dest="packaging_dir")
+ naming_grp.add_config_file_option(option_name="changelog-file",
+ dest="changelog_file")
+ naming_grp.add_config_file_option(option_name="spec-file", dest="spec_file")
+ # Range group options
+ range_grp.add_option("-s", "--since", dest="since",
+ help="commit to start from (e.g. HEAD^^^, release/0.1.2)")
+ # Formatting group options
+ format_grp.add_option("--no-release", action="store_false", default=True,
+ dest="release",
+ help="no release, just update the last changelog section")
+ format_grp.add_boolean_config_file_option(option_name="git-author",
+ dest="git_author")
+ format_grp.add_boolean_config_file_option(option_name="full", dest="full")
+ format_grp.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")
+ format_grp.add_config_file_option(option_name="ignore-regex",
+ dest="ignore_regex",
+ help="Ignore lines in commit message matching regex, "
+ "default is '%(ignore-regex)s'")
+ format_grp.add_config_file_option(option_name="changelog-revision",
+ dest="changelog_revision")
+ format_grp.add_config_file_option(option_name="spawn-editor",
+ dest="spawn_editor")
+ format_grp.add_config_file_option(option_name="editor-cmd",
+ dest="editor_cmd")
+ return parser
+
+
+def parse_args(argv):
+ """Parse command line and config file options"""
+ parser = build_parser(argv[0])
+ if not parser:
+ return None, None
+
+ options, args = parser.parse_args(argv[1:])
+
+ if not options.changelog_revision:
+ options.changelog_revision = RpmPkgPolicy.Changelog.header_rev_format
+
+ gbp.log.setup(options.color, options.verbose, options.color_scheme)
+
+ return options, args
+
+
+def main(argv):
+ """Script main function"""
+ options, args = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ try:
+ init_tmpdir(options.tmp_dir, prefix='rpm-ch_')
+
+ load_customizations(options.customization_file)
+ editor_cmd = determine_editor(options)
+
+ repo = RpmGitRepository('.')
+ check_branch(repo, options)
+
+ # Find and parse spec file
+ spec = parse_spec_file(repo, options)
+
+ # Find and parse changelog file
+ ch_file = parse_changelog_file(repo, spec, options)
+ since = get_start_commit(ch_file.changelog, repo, options)
+
+ # Get range of commits from where to generate changes
+ if args:
+ gbp.log.info("Only looking for changes in '%s'" % ", ".join(args))
+ commits = repo.get_commits(since=since, until='HEAD', paths=args,
+ options=options.git_log.split(" "))
+ commits.reverse()
+ if not commits:
+ gbp.log.info("No changes detected from %s to %s." % (since, 'HEAD'))
+
+ # Do the actual update
+ entries = entries_from_commits(ch_file.changelog, repo, commits,
+ options)
+ update_changelog(ch_file.changelog, entries, repo, spec, options)
+
+ # Write to file
+ ch_file.write()
+
+ if editor_cmd:
+ gbpc.Command(editor_cmd, [ch_file.path])()
+
+ except (GbpError, GitRepositoryError, ChangelogError, NoSpecError) as err:
+ if len(err.__str__()):
+ gbp.log.err(err)
+ return 1
+ except KeyboardInterrupt:
+ gbp.log.err("Interrupted. Aborting.")
+ return 1
+ finally:
+ del_tmpdir()
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/gbp/scripts/supercommand.py b/gbp/scripts/supercommand.py
new file mode 100644
index 0000000..3e5a659
--- /dev/null
+++ b/gbp/scripts/supercommand.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python3
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""Supercommand for all gbp commands"""
+
+import glob
+import os
+import re
+import sys
+
+# Command is this module and common/ is shared code
+# so we don't allow these to be imported:
+invalid_modules = ['common', 'supercommand']
+
+
+def sanitize(cmd):
+ """
+ '-' is not allowed in module names
+ so turn it into an underscore.
+ """
+ return cmd.replace('-', '_')
+
+
+def usage():
+ print("""
+Usage:
+ gbp <command> [<args>]
+
+The most commonly used commands are:
+
+ buildpackage - build a Debian package
+ import-orig - import a new upstream tarball
+ import-dsc - import a single Debian source package
+ import-dscs - import multiple Debian source packages
+
+Use '--list-cmds' to list all available commands.
+""")
+
+
+def version(prog):
+ try:
+ from gbp.version import gbp_version
+ except ImportError:
+ gbp_version = '[Unknown version]'
+ print("%s %s" % (os.path.basename(prog), gbp_version))
+
+
+def import_command(cmd):
+ """
+ Import the module that implements the given command
+ """
+ modulename = sanitize(cmd)
+ if (not re.match(r'[a-z][a-z0-9_]', modulename) or
+ modulename in invalid_modules):
+ raise ImportError('Illegal module name %s' % modulename)
+
+ return __import__('gbp.scripts.%s' % modulename, fromlist='main', level=0)
+
+
+def pymod_to_cmd(mod):
+ """
+ >>> pymod_to_cmd('/x/y/z/a_cmd.py')
+ 'a-cmd'
+ """
+ return os.path.basename(mod.rsplit('.', 1)[0]).replace('_', '-')
+
+
+def get_available_commands(path):
+ cmds = []
+ for f in glob.glob(os.path.join(path, '*.py')):
+ if os.path.basename(f) in ['__init__.py', 'supercommand.py']:
+ continue
+ cmds.append((pymod_to_cmd(f), f))
+ return cmds
+
+
+def list_available_commands():
+ mod = __import__('gbp.scripts', fromlist='main', level=0)
+ path = os.path.dirname(mod.__file__)
+ maxlen = 0
+
+ print("Available commands in %s\n" % path)
+ cmds = sorted(get_available_commands(path))
+ for cmd in cmds:
+ if len(cmd[0]) > maxlen:
+ maxlen = len(cmd[0])
+ for cmd in cmds:
+ mod = import_command(cmd[0])
+ doc = mod.__doc__
+ print(" %s - %s" % (cmd[0].rjust(maxlen), doc))
+ print('')
+
+
+def supercommand(argv=None):
+ argv = argv or sys.argv
+
+ if len(argv) < 2:
+ usage()
+ return 1
+
+ prg, cmd = argv[0:2]
+ args = argv[1:]
+
+ if cmd in ['--help', '-h']:
+ usage()
+ return 0
+ elif cmd == 'help' and len(args) > 1:
+ # Make the first argument after help the new commadn and
+ # request it's help output
+ cmd = args[1]
+ args = [cmd, '--help']
+ elif cmd == 'help':
+ usage()
+ return 0
+ elif cmd in ['--version', 'version']:
+ version(argv[0])
+ return 0
+ elif cmd in ['--list-cmds', 'list-cmds']:
+ list_available_commands()
+ return 0
+
+ try:
+ module = import_command(cmd)
+ except ImportError as e:
+ print("'%s' is not a valid command." % cmd, file=sys.stderr)
+ usage()
+ if '--verbose' in args:
+ print(e, file=sys.stderr)
+ return 2
+
+ return module.main(args)
+
+
+if __name__ == '__main__':
+ sys.exit(supercommand())
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/scripts/tag.py b/gbp/scripts/tag.py
new file mode 100755
index 0000000..1e91fcd
--- /dev/null
+++ b/gbp/scripts/tag.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python3
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""Create a Debian tag"""
+
+import os
+import sys
+
+import gbp.log
+from gbp.format import format_str
+from gbp.config import GbpOptionParserDebian
+from gbp.deb.git import DebianGitRepository, GitRepositoryError
+from gbp.deb.source import DebianSourceError
+from gbp.deb.source import DebianSource
+from gbp.errors import GbpError
+
+from gbp.scripts.common import ExitCodes
+from gbp.scripts.common.hook import Hook
+
+from gbp.scripts.common.pq import is_pq_branch, pq_branch_base
+
+
+def create_debian_tag(repo, source, commit, options):
+ """
+ Create the debian tag
+
+ returns: the created tag
+ """
+ tag = repo.version_to_tag(options.debian_tag, source.version)
+ gbp.log.info("Tagging Debian package %s as %s in git" % (source.version, tag))
+ if options.retag and repo.has_tag(tag):
+ repo.delete_tag(tag)
+ tag_msg = format_str(options.debian_tag_msg,
+ dict(pkg=source.sourcepkg,
+ version=source.version))
+ repo.create_tag(name=tag,
+ msg=tag_msg,
+ sign=options.sign_tags,
+ commit=commit,
+ keyid=options.keyid)
+ return tag
+
+
+def perform_tagging(repo, source, options, hook_env=None):
+ """
+ Perform the tagging
+
+ Select brach to tag, create tag and run hooks
+ """
+ branch = repo.branch
+ if branch and is_pq_branch(branch):
+ commit = repo.get_merge_base(branch, pq_branch_base(branch))
+ else:
+ commit = repo.head
+
+ tag = create_debian_tag(repo, source, commit, options)
+ if options.posttag:
+ sha = repo.rev_parse("%s^{}" % tag)
+ Hook('Posttag', options.posttag,
+ extra_env=Hook.md(hook_env or {},
+ {'GBP_TAG': tag,
+ 'GBP_BRANCH': branch or '(no branch)',
+ 'GBP_SHA1': sha})
+ )()
+
+
+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("--retag", action="store_true", dest="retag", default=False,
+ help="don't fail if the tag already exists")
+ 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_config_file_option(option_name="debian-tag-msg", dest="debian_tag_msg")
+ 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="posttag", dest="posttag",
+ help="hook run after a successful tag operation, "
+ "default is '%(posttag)s'")
+ parser.add_boolean_config_file_option(option_name="ignore-branch", dest="ignore_branch")
+ parser.add_boolean_config_file_option(option_name="ignore-new", dest="ignore_new")
+ 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 main(argv):
+ retval = 1
+
+ (options, args) = parse_args(argv)
+ if not options:
+ return ExitCodes.parse_error
+
+ 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)
+ if not (options.ignore_branch or options.ignore_new):
+ if repo.branch != options.debian_branch:
+ gbp.log.err("You are not on branch '%s' but on '%s'"
+ % (options.debian_branch,
+ repo.branch or 'no branch'))
+ raise GbpError("Use --ignore-branch to ignore or "
+ "--debian-branch to set the branch name.")
+
+ 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 --ignore-new to ignore.")
+
+ perform_tagging(repo, source, options)
+ retval = 0
+ except (GbpError, GitRepositoryError, DebianSourceError) as err:
+ if str(err):
+ gbp.log.err(err)
+ except KeyboardInterrupt:
+ gbp.log.err("Interrupted. Aborting.")
+
+ 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/tmpfile.py b/gbp/tmpfile.py
new file mode 100644
index 0000000..755967b
--- /dev/null
+++ b/gbp/tmpfile.py
@@ -0,0 +1,55 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012, 2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Temporary directory handling"""
+
+import os
+import shutil
+import tempfile
+
+from gbp.errors import GbpError
+
+
+_old_tempdirs = []
+
+
+def init_tmpdir(path, prefix):
+ """Initialize a temporary directory structure"""
+ try:
+ if not os.path.exists(path):
+ os.makedirs(path)
+ except OSError as err:
+ raise GbpError("Unable to create tmpdir %s (%s)" % (path, err))
+
+ tmpdir = tempfile.mkdtemp(dir=path, prefix=prefix)
+
+ # Set newly created dir as the default value for all further tempfile
+ # calls
+ _old_tempdirs.append(tempfile.tempdir)
+ tempfile.tempdir = tmpdir
+ return tmpdir
+
+
+def del_tmpdir():
+ """Remove tempdir and restore tempfile module"""
+ if _old_tempdirs:
+ if os.path.exists(tempfile.tempdir) and \
+ not os.getenv('GBP_TMPFILE_NOCLEAN'):
+ shutil.rmtree(tempfile.tempdir)
+ tempfile.tempdir = _old_tempdirs.pop()
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/gbp/tristate.py b/gbp/tristate.py
new file mode 100644
index 0000000..41f0dea
--- /dev/null
+++ b/gbp/tristate.py
@@ -0,0 +1,107 @@
+# 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, please see
+# <http://www.gnu.org/licenses/>
+
+"""
+A switch with three states: on|off|auto
+"""
+
+
+class Tristate(object):
+ """Tri-state value: on, off or auto """
+ ON = True # state is on == do it
+ OFF = False # state is off == don't do it
+ AUTO = -1 # autodetect == do if possible
+
+ # We accept true as alias for on and false as alias for off
+ _VALID_NAMES = ['on', 'off', 'true', 'false', 'auto']
+
+ def __init__(self, val):
+ if type(val) in [type(t) for t in (True, 1)]:
+ if val > 0:
+ self._state = self.ON
+ elif val < 0:
+ self._state = self.AUTO
+ else:
+ self._state = self.OFF
+ elif type(val) is str:
+ if val.lower() in ['on', 'true']:
+ self._state = self.ON
+ elif val.lower() in ['auto']:
+ self._state = self.AUTO
+ else:
+ self._state = self.OFF
+ elif type(val) is Tristate:
+ self._state = val.state
+ else:
+ raise TypeError
+
+ def __repr__(self):
+ """
+ >>> Tristate('on').__repr__()
+ 'on'
+ >>> Tristate(True).__repr__()
+ 'on'
+ >>> Tristate(False).__repr__()
+ 'off'
+ >>> Tristate('auto').__repr__()
+ 'auto'
+ """
+ if self._state == self.ON:
+ return 'on'
+ elif self._state == self.AUTO:
+ return 'auto'
+ else:
+ return 'off'
+
+ def __nonzero__(self):
+ """
+ >>> Tristate('on').__nonzero__()
+ True
+ >>> Tristate('auto').__nonzero__()
+ True
+ >>> Tristate('off').__nonzero__()
+ False
+ """
+ return self._state is not self.OFF
+
+ @property
+ def state(self):
+ """Get current state"""
+ return self._state
+
+ def is_auto(self):
+ return [False, True][self._state == self.AUTO]
+
+ def is_on(self):
+ return [False, True][self._state == self.ON]
+
+ def is_off(self):
+ return [False, True][self._state == self.OFF]
+
+ def do(self, function, *args, **kwargs):
+ """
+ Run function if tristate is on or auto, only report a failure if
+ tristate is on since failing is o.k. for autodetect.
+ """
+ if self.is_off():
+ return True
+
+ success = function(*args, **kwargs)
+ if not success:
+ return [True, False][self.is_on()]
+
+ return True
diff --git a/nosetests.xml b/nosetests.xml
new file mode 100644
index 0000000..5fc0456
--- /dev/null
+++ b/nosetests.xml
@@ -0,0 +1,2312 @@
+<?xml version="1.0" encoding="UTF-8"?><testsuite name="nosetests" tests="572" errors="0" failures="1" skip="8"><testcase classname="gbp.command_wrappers.Command" name="__call__" time="0.010"></testcase><testcase classname="gbp.command_wrappers.Command" name="_f" time="0.002"></testcase><testcase classname="gbp.command_wrappers.Command" name="call" time="0.022"></testcase><testcase classname="gbp.config.GbpOptionParser" name="_listify" time="0.003"></testcase><testcase classname="gbp.config.GbpOptionParser" name="_name_to_filename" time="0.001"></testcase><testcase classname="gbp.config.GbpOptionParser" name="get_config_files" time="0.002"></testcase><testcase classname="gbp.deb.format" name="DebianSourceFormat" time="0.001"></testcase><testcase classname="gbp.deb.format.DebianSourceFormat" name="parse_file" time="0.001"></testcase><testcase classname="gbp.deb.git.DebianGitRepository" name="_build_legacy_tag" time="0.001"></testcase><testcase classname="gbp.deb.git.DebianGitRepository" name="_mangle_version" time="0.001"></testcase><testcase classname="gbp.deb.git.DebianGitRepository" name="_sanitize_version" time="0.001"></testcase><testcase classname="gbp.deb.git.DebianGitRepository" name="_unsanitize_version" time="0.001"></testcase><testcase classname="gbp.deb.git.DebianGitRepository" name="tag_to_version" time="0.002"></testcase><testcase classname="gbp.deb.git.DebianGitRepository" name="version_to_tag" time="0.001"></testcase><testcase classname="gbp.deb.policy" name="DebianPkgPolicy" time="0.001"></testcase><testcase classname="gbp.deb.policy.DebianPkgPolicy" name="build_tarball_name" time="0.001"></testcase><testcase classname="gbp.deb.uscan.Uscan" name="_parse" time="0.005"></testcase><testcase classname="gbp.deb.uscan.Uscan" name="_parse_uptodate" time="0.002"></testcase><testcase classname="gbp.deb.uscan.Uscan" name="_raise_error" time="0.003"></testcase><testcase classname="gbp.format" name="format_b" time="0.001"></testcase><testcase classname="gbp.format" name="format_str" time="0.002"></testcase><testcase classname="gbp.git.args" name="GitArgs" time="0.001"></testcase><testcase classname="gbp.git.commit.GitCommit" name="is_sha1" time="0.001"></testcase><testcase classname="gbp.git.modifier.GitModifier" name="get_author_env" time="0.001"></testcase><testcase classname="gbp.git.modifier.GitModifier" name="get_committer_env" time="0.001"></testcase><testcase classname="gbp.git.repository.GitRepository" name="strip_sha1" time="0.001"></testcase><testcase classname="gbp.git" name="rfc822_date_to_git" time="0.003"></testcase><testcase classname="gbp.patch_series.Patch" name="_get_subject_from_filename" time="0.001"></testcase><testcase classname="gbp.patch_series.PatchSeries" name="_get_topic" time="0.001"></testcase><testcase classname="gbp.patch_series.PatchSeries" name="_parse_line" time="0.001"></testcase><testcase classname="gbp.patch_series.PatchSeries" name="_read_series" time="0.001"></testcase><testcase classname="gbp.patch_series.PatchSeries" name="_split_strip" time="0.001"></testcase><testcase classname="gbp.patch_series.PatchSeries" name="_strip_comment" time="0.001"></testcase><testcase classname="gbp.pkg.archive.Archive" name="parse_filename" time="0.001"></testcase><testcase classname="gbp.pkg.compressor.Compressor" name="__repr__" time="0.001"></testcase><testcase classname="gbp.pkg.compressor.Compressor" name="cmdline" time="0.001"></testcase><testcase classname="gbp.pkg.git.PkgGitRepository" name="sanitize_prefix" time="0.002"></testcase><testcase classname="gbp.pkg.pkgpolicy.PkgPolicy" name="guess_upstream_src_version" time="0.003"></testcase><testcase classname="gbp.pkg.pkgpolicy.PkgPolicy" name="is_valid_packagename" time="0.001"></testcase><testcase classname="gbp.pkg.pkgpolicy.PkgPolicy" name="is_valid_upstreamversion" time="0.001"></testcase><testcase classname="gbp.rpm.git.RpmGitRepository" name="_sanitize_tag" time="0.001"></testcase><testcase classname="gbp.rpm.git.RpmGitRepository" name="version_to_tag" time="0.001"></testcase><testcase classname="gbp.rpm.linkedlist.LinkedList" name="append" time="0.001"></testcase><testcase classname="gbp.rpm.linkedlist.LinkedList" name="delete" time="0.001"></testcase><testcase classname="gbp.rpm.linkedlist.LinkedList" name="insert_after" time="0.001"></testcase><testcase classname="gbp.rpm.linkedlist.LinkedList" name="insert_before" time="0.001"></testcase><testcase classname="gbp.rpm.linkedlist.LinkedList" name="prepend" time="0.001"></testcase><testcase classname="gbp.rpm.linkedlist.LinkedListNode" name="set_data" time="0.001"></testcase><testcase classname="gbp.rpm.policy.RpmPkgPolicy" name="is_valid_orig_archive" time="0.001"></testcase><testcase classname="gbp.rpm" name="compose_version_str" time="0.001"></testcase><testcase classname="gbp.rpm" name="filter_version" time="0.001"></testcase><testcase classname="gbp.rpm" name="split_version_str" time="0.001"></testcase><testcase classname="gbp.rpm" name="string_to_int" time="0.001"></testcase><testcase classname="gbp.scripts.buildpackage" name="changes_file_suffix" time="0.015"></testcase><testcase classname="gbp.scripts.clone" name="repo_to_url" time="0.001"></testcase><testcase classname="gbp.scripts.common.pq" name="is_pq_branch" time="0.001"></testcase><testcase classname="gbp.scripts.common.pq" name="pq_branch_base" time="0.001"></testcase><testcase classname="gbp.scripts.common.pq" name="pq_branch_name" time="0.001"></testcase><testcase classname="gbp.scripts.common" name="is_download" time="0.001"></testcase><testcase classname="gbp.scripts.create_remote_repo" name="build_cmd" time="0.001"></testcase><testcase classname="gbp.scripts.create_remote_repo" name="print_config" time="0.001"></testcase><testcase classname="gbp.scripts.dch" name="snapshot_version" time="0.001"></testcase><testcase classname="gbp.scripts.import_dsc" name="is_download" time="0.001"></testcase><testcase classname="gbp.scripts.import_orig" name="find_upstream" time="0.001"></testcase><testcase classname="gbp.scripts.pq" name="compare_series" time="0.001"></testcase><testcase classname="gbp.scripts.pq" name="format_series_diff" time="0.001"></testcase><testcase classname="gbp.scripts.supercommand" name="pymod_to_cmd" time="0.001"></testcase><testcase classname="gbp.tristate.Tristate" name="__nonzero__" time="0.001"></testcase><testcase classname="gbp.tristate.Tristate" name="__repr__" time="0.001"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_argument_quoting" time="0.417"><system-out><![CDATA[../arg with spaces
+]]></system-out></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_broken_upstream_version" time="0.746"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_component_generation" time="0.710"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_debian_buildpackage" time="0.399"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_export_dir_additional_tar" time="0.862"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_export_dir_buildpackage" time="0.823"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_export_dir_overlay" time="0.294"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_export_wc_buildpackage" time="0.843"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_non_native_buildpackage" time="0.741"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_pristine_tar_commit" time="3.062"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_pristinetar_component_generation" time="5.472"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_sloppy_tarball_generation" time="0.818"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_tag_detached_head" time="0.713"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_tag_only" time="0.408"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_tag_pq_branch" time="0.748"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_tarball_default_compression" time="0.952"></testcase><testcase classname="tests.component.deb.test_buildpackage.TestBuildpackage" name="test_tarball_max_compression" time="0.854"></testcase><testcase classname="tests.component.deb.test_clone.TestClone" name="test_clone_github" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.component.deb.test_clone.TestClone" name="test_clone_native" time="0.403"></testcase><testcase classname="tests.component.deb.test_clone.TestClone" name="test_clone_nonempty" time="0.313"></testcase><testcase classname="tests.component.deb.test_clone.TestClone" name="test_clone_vcsgit_fail" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.component.deb.test_clone.TestClone" name="test_clone_vcsgit_ok" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.component.deb.test_dch.TestDch" name="test_postedit_hook" time="0.602"></testcase><testcase classname="tests.component.deb.test_dch.TestDch" name="test_user_customizations" time="0.911"></testcase><testcase classname="tests.component.deb.test_export_orig.TestExportOrig" name="test_component_generation" time="0.703"></testcase><testcase classname="tests.component.deb.test_export_orig.TestExportOrig" name="test_git_archive_tree_non_existent" time="0.702"></testcase><testcase classname="tests.component.deb.test_export_orig.TestExportOrig" name="test_pristine_tar_commit_non_existent" time="3.704"></testcase><testcase classname="tests.component.deb.test_export_orig.TestExportOrig" name="test_pristinetar_component_generation" time="5.443"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_bare" time="6.877"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_broken_download" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_create_branches" time="6.469"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_debian_branch_not_master" time="0.534"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_download" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_empty_repo" time="0.575"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_existing_dir" time="0.014"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_import_10" time="0.534"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_import_30_additional_tarball_pristine_tar" time="4.180"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_import_30_filters" time="0.514"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_import_30_pristine_tar" time="3.422"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_import_debian_native" time="1.010"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_import_environ" time="0.337"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_target_dir" time="0.526"></testcase><testcase classname="tests.component.deb.test_import_dsc.TestImportDsc" name="test_upstream_branch_is_master" time="0.674"></testcase><testcase classname="tests.component.deb.test_import_dscs.TestImportDscs" name="test_import_debian_native" time="0.625"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_download" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_filter_unpacked_dir" time="5.993"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_filter_with_component_tarballs" time="6.254"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_filter_with_orig_tarball" time="6.028"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_import_in_submodule" time="3.474"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_initial_import" time="2.840"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_initial_import_fail_create_debian_branch" time="2.767"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_initial_import_fail_create_upstream_tag" time="0.255"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_tag_exists" time="2.814"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_update" time="6.474"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_update_component_tarballs" time="11.208"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_update_fail_create_upstream_tag" time="6.280"></testcase><testcase classname="tests.component.deb.test_import_orig.TestImportOrig" name="test_update_fail_merge" time="6.286"></testcase><testcase classname="tests.component.deb.test_import_ref.TestImportRef" name="test_from_branch" time="3.199"></testcase><testcase classname="tests.component.deb.test_import_ref.TestImportRef" name="test_from_committish" time="3.213"></testcase><testcase classname="tests.component.deb.test_import_ref.TestImportRef" name="test_from_version" time="3.184"></testcase><testcase classname="tests.component.deb.test_pq.TestPq" name="test_empty_cycle" time="0.897"><system-out><![CDATA[Current branch patch-queue/master is up to date.
+]]></system-out></testcase><testcase classname="tests.component.deb.test_pq.TestPq" name="test_import" time="1.402"></testcase><testcase classname="tests.component.deb.test_pq.TestPq" name="test_import_poor_dep3_behaviour" time="1.343"></testcase><testcase classname="tests.component.deb.test_pq.TestPq" name="test_rebase_import" time="0.685"><system-out><![CDATA[Current branch patch-queue/master is up to date.
+]]></system-out></testcase><testcase classname="tests.component.deb.test_pq.TestPq" name="test_rename" time="0.739"></testcase><testcase classname="tests.component.deb.test_pq.TestPq" name="test_switch_import" time="0.635"></testcase><testcase classname="tests.component.deb.test_pristine_tar.TestPristineTar" name="test_run" time="3.085"></testcase><testcase classname="tests.component.deb.test_pristine_tar.TestPristineTar" name="test_run_component_tarball" time="3.719"></testcase><testcase classname="tests.component.deb.test_pull.TestPull" name="test_pull_all" time="1.147"></testcase><testcase classname="tests.component.deb.test_pull.TestPull" name="test_pull_default_remote" time="0.529"></testcase><testcase classname="tests.component.deb.test_pull.TestPull" name="test_pull_explicit_remote" time="0.513"></testcase><testcase classname="tests.component.deb.test_pull.TestPull" name="test_tracking" time="0.813"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_dont_push_unreleased" time="1.113"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_not_debian_branch" time="0.607"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_detached_head" time="3.964"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_failure" time="1.249"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_native" time="0.487"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_not_origin" time="1.051"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_not_origin_detect" time="1.078"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_pristine_tar" time="3.926"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_skip_upstream" time="1.052"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_tag_ne_branch" time="0.519"></testcase><testcase classname="tests.component.deb.test_push.TestPush" name="test_push_upstream" time="1.058"></testcase><testcase classname="tests.component.deb.test_tag.TestTag" name="test_tag" time="0.433"></testcase><testcase classname="tests.component.deb.test_tag.TestTag" name="test_tag_detached_head" time="0.752"></testcase><testcase classname="tests.component.deb.test_tag.TestTag" name="test_tag_pq_branch" time="0.787"></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_builddir_options" time="0.216"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.LCc90S
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_builddir_options_wsnzu1z2/foo/source/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.W04oib
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_builddir_options_wsnzu1z2/foo/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_builddir_options_wsnzu1z2/foo/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.w5abAt
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_builddir_options_wsnzu1z2/foo/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_builddir_options_wsnzu1z2/foo/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_builddir_options_wsnzu1z2/foo/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.ikIJ1L
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.uAPzt4
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_export_failure" time="0.319"></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_hook_options" time="0.778"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.ofgNEw
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.HA79FQ
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.9Vl1Ha
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.qWbuTu
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.2ucn5O
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.3imKFO
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.EWMBf9
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.RL7WPt
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.sKdMzO
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.Wh8Xj9
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.6pZQa3
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.VO9sjo
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.cnVvsJ
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_hook_options_f4pprl2w/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.ZgUsL4
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.wBtN4p
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_invalid_args" time="0.037"></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_native_build" time="0.344"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.Z5M5pm
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build_ar_4qh04/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.pt23fI
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build_ar_4qh04/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build_ar_4qh04/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.bYOt63
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build_ar_4qh04/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build_ar_4qh04/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build_ar_4qh04/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.l5zg7p
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.mrhn8L
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_native_build2" time="0.211"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.BgqkXB
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.cWREFY
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.DqB8nl
+Processing files: gbp-test-native2-2.0-0.noarch
+Provides: gbp-test-native2 = 2.0-0
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build2_zw07_urs/rpmbuild/BUILDROOT/gbp-test-native2-2.0-0.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build2_zw07_urs/rpmbuild/SRPMS/gbp-test-native2-2.0-0.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_native_build2_zw07_urs/rpmbuild/RPMS/noarch/gbp-test-native2-2.0-0.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.AnPwbI
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.zfVdZ4
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_non_native_build" time="0.525"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.dXKSzS
+drwxrwxr-x root/root 0 2012-11-07 08:15 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:15 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2012-11-07 08:15 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:15 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.0HnTUf
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.vcUogD
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.oZ8pQ0
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.SggJqo
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.Uc2Wic
+drwxrwxr-x root/root 0 2012-11-07 08:15 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:15 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2012-11-07 08:15 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:15 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.227TjA
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.S8JmlY
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_non_native_build_5cuuaq7l/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.DN4qzm
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.2TzPNK
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_builder" time="0.365"></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_cleaner" time="0.295"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.IzIScU
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_cleaner_8g6tc5oq/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.z4W7Zj
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_cleaner_8g6tc5oq/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_cleaner_8g6tc5oq/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.PwTUNJ
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_cleaner_8g6tc5oq/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_cleaner_8g6tc5oq/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_cleaner_8g6tc5oq/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.nQBDL9
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.XC0GJz
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_export" time="0.583"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.H5WMHb
+drwxrwxr-x root/root 0 2012-11-07 08:13 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:13 gbp-test/Makefile
+-rw-rw-r-- root/root 35 2012-11-07 08:13 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:13 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.jAuI8B
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_export_5xhrf5x3/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_export_5xhrf5x3/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.jMsbA2
+Processing files: gbp-test-1.0-1.noarch
+Provides: gbp-test = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_export_5xhrf5x3/rpmbuild/BUILDROOT/gbp-test-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_export_5xhrf5x3/rpmbuild/SRPMS/gbp-test-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_export_5xhrf5x3/rpmbuild/RPMS/noarch/gbp-test-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.4Gsdct
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.fkfAOT
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_native" time="0.512"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.QNQKFI
+drwxrwxr-x root/root 0 2012-11-13 12:31 /
+-rw-rw-r-- root/root 27 2012-11-13 12:31 /Makefile
+-rw-rw-r-- root/root 40 2012-11-13 12:31 /README
+-rwxrwxr-x root/root 34 2012-11-13 12:31 /dummy.sh
+Patch (my.patch):
+patching file dummy.sh
+Do things
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.914hBa
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/BUILD/gbp-test2-2.0'
+Ready (2.0)!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/BUILD/gbp-test2-2.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.9g9jxC
+Processing files: gbp-test2-2.0-1.noarch
+Provides: gbp-test2 = 1:2.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/BUILDROOT/gbp-test2-2.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/SRPMS/gbp-test2-2.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/RPMS/noarch/gbp-test2-2.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.owkED4
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.KnMkKw
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.YIJCuZ
+drwxrwxr-x root/root 0 2015-11-26 16:17 /
+-rw-rw-r-- root/root 346 2015-11-26 16:17 /.gbp.conf
+-rw-rw-r-- root/root 27 2015-11-26 16:17 /Makefile
+-rw-rw-r-- root/root 40 2015-11-26 16:17 /README
+-rwxrwxr-x root/root 34 2015-11-26 16:17 /dummy.sh
+drwxrwxr-x root/root 0 2015-11-26 16:17 /mydir/
+-rw-rw-r-- root/root 6 2015-11-26 16:17 /mydir/myfile.txt
+drwxrwxr-x root/root 0 2015-11-26 16:17 /packaging/
+-rw-rw-r-- root/root 177 2015-11-26 16:17 /packaging/bar.tar.gz
+-rw-rw-r-- root/root 56 2015-11-26 16:17 /packaging/foo.txt
+-rw-rw-r-- root/root 1006 2015-11-26 16:17 /packaging/gbp-test2-alt.spec
+-rw-rw-r-- root/root 905 2015-11-26 16:17 /packaging/gbp-test2.spec
+-rw-rw-r-- root/root 164 2015-11-26 16:17 /packaging/my.patch
+Patch (my.patch):
+patching file dummy.sh
+Do things
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.izLsZr
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/BUILD/gbp-test2-2.0'
+Ready (2.0)!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/BUILD/gbp-test2-2.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.7zQNuU
+Processing files: gbp-test2-2.0-1.noarch
+Provides: gbp-test2 = 1:2.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/BUILDROOT/gbp-test2-2.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/SRPMS/gbp-test2-2.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_native_u8h4t2lu/rpmbuild/RPMS/noarch/gbp-test2-2.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.8zyxfn
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.MxCz0P
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_notify" time="0.623"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.TRBE0h
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.kYY6fL
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.ROTZve
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.mMLqVH
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.Aq68kb
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.KGZ6Ow
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.AXbbA0
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.YIjNlu
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.PObuhY
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.nXPvds
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.xbB83K
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.4kKAlf
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.fwmxDJ
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_notify_paxtf7zo/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.U2Ll5d
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.BPttxI
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_packaging_dir" time="0.276"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.oeOIqW
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_packaging_dir_f49817bp/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.AA5arr
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_packaging_dir_f49817bp/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_packaging_dir_f49817bp/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.nx44rW
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_packaging_dir_f49817bp/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_packaging_dir_f49817bp/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_packaging_dir_f49817bp/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.adnFCr
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.FOGzNW
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_spec_file" time="0.408"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.MXO4xn
+drwxrwxr-x root/root 0 2012-11-13 12:31 /
+-rw-rw-r-- root/root 27 2012-11-13 12:31 /Makefile
+-rw-rw-r-- root/root 40 2012-11-13 12:31 /README
+-rwxrwxr-x root/root 34 2012-11-13 12:31 /dummy.sh
+Patch (my.patch):
+patching file dummy.sh
+Do things
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.8mWGrT
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_file_ky9d6ohp/rpmbuild/BUILD/gbp-test2-2.0'
+Ready (2.0)!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_file_ky9d6ohp/rpmbuild/BUILD/gbp-test2-2.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.QeUMlp
+Processing files: gbp-test2-2.0-1.noarch
+Provides: gbp-test2 = 1:2.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_file_ky9d6ohp/rpmbuild/BUILDROOT/gbp-test2-2.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_file_ky9d6ohp/rpmbuild/SRPMS/gbp-test2-2.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_file_ky9d6ohp/rpmbuild/RPMS/noarch/gbp-test2-2.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.OuC9pV
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.iJjOur
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_spec_vcs_tag" time="0.435"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.B6Wx9J
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.mEhzPg
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.Nxx2vN
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.Lgclmk
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.KNFXcR
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.sOg0LP
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.zDVaZm
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.ZZtTcU
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_spec_vcs_tag_mc9ystm9/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.nLRlAr
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.nkZ9XY
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_submodules" time="0.794"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.MnZKTe
+drwxrwxr-x root/root 0 2018-08-17 15:48 gbp-test/
+-rw-rw-r-- root/root 167 2018-08-17 15:48 gbp-test/.gitmodules
+-rw-rw-r-- root/root 21 2018-08-17 15:48 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2018-08-17 15:48 gbp-test/README
+-rwxrwxr-x root/root 30 2018-08-17 15:48 gbp-test/dummy.sh
+drwxrwxr-x root/root 0 2018-08-17 15:48 gbp-test/gbp-test-native.repo/
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.Etcn0M
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.t5ly7k
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.WU9ErT
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.H813Lr
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.sFd0sx
+drwxrwxr-x root/root 0 2018-08-17 15:48 gbp-test/
+-rw-rw-r-- root/root 167 2018-08-17 15:48 gbp-test/.gitmodules
+-rw-rw-r-- root/root 21 2018-08-17 15:48 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2018-08-17 15:48 gbp-test/README
+-rwxrwxr-x root/root 30 2018-08-17 15:48 gbp-test/dummy.sh
+drwxrwxr-x root/root 0 2018-08-17 15:48 gbp-test/gbp-test-native.repo/
+drwxrwxr-x root/root 0 2015-11-26 17:46 gbp-test/gbp-test-native.repo/
+-rw-rw-r-- root/root 144 2015-11-26 17:46 gbp-test/gbp-test-native.repo/.gbp.conf
+-rw-rw-r-- root/root 21 2015-11-26 17:46 gbp-test/gbp-test-native.repo/Makefile
+-rw-rw-r-- root/root 35 2015-11-26 17:46 gbp-test/gbp-test-native.repo/README
+-rwxrwxr-x root/root 30 2015-11-26 17:46 gbp-test/gbp-test-native.repo/dummy.sh
+drwxrwxr-x root/root 0 2015-11-26 17:46 gbp-test/gbp-test-native.repo/packaging/
+-rw-rw-r-- root/root 95 2015-11-26 17:46 gbp-test/gbp-test-native.repo/packaging/gbp-test-native.changes
+-rw-rw-r-- root/root 561 2015-11-26 17:46 gbp-test/gbp-test-native.repo/packaging/gbp-test-native.spec
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.PxH9e6
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.IXDR1E
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_itn1ks07/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.rDMC8d
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.hLQIfN
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_submodules_native" time="0.497"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.oqA4m2
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_native_oepss2rh/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+f99b0cfdbb2838ffdd03124b984dc6bfde9ac582
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ inflating: gbp-test-native-1.0/.gitmodules
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/gbp-test-native2.repo/
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+ inflating: gbp-test-native-1.0/gbp-test-native2.repo/.gbp.conf
+ extracting: gbp-test-native-1.0/gbp-test-native2.repo/foo.txt
+ inflating: gbp-test-native-1.0/gbp-test-native2.repo/gbp-test-native2.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.ZQOSwC
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_native_oepss2rh/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_native_oepss2rh/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.aGkdHc
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_native_oepss2rh/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_native_oepss2rh/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_submodules_native_oepss2rh/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.A7N94M
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.2pmttn
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_tmp_dir" time="0.191"></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_option_upstream_tree" time="1.064"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.GjUXXK
+drwxrwxr-x root/root 0 2012-11-07 08:15 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:15 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2012-11-07 08:15 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:15 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.pfQnPm
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.nNhhHY
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.r6b4LA
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.xs9cRc
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.hsKbSV
+drwxrwxr-x root/root 0 2018-08-17 15:48 gbp-test/
+-rw-rw-r-- root/root 21 2018-08-17 15:48 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2018-08-17 15:48 gbp-test/README
+-rwxrwxr-x root/root 30 2018-08-17 15:48 gbp-test/dummy.sh
+-rw-rw-r-- root/root 1 2018-08-17 15:48 gbp-test/new-file
+-rw-rw-r-- root/root 1 2018-08-17 15:48 gbp-test/new-file2
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.5Nxhly
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.bElTOa
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.ljUqvN
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.hjjjcq
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.4UMKQc
+drwxrwxr-x root/root 0 2018-08-17 15:48 gbp-test/
+-rw-rw-r-- root/root 21 2018-08-17 15:48 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2018-08-17 15:48 gbp-test/README
+-rwxrwxr-x root/root 30 2018-08-17 15:48 gbp-test/dummy.sh
+-rw-rw-r-- root/root 1 2018-08-17 15:48 gbp-test/new-file
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.MZQrVP
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.9pQD0s
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_option_upstream_tree_n4sh6pox/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.NJS6i6
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.92ZSBJ
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_options_ignore" time="0.288"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.Bj8DyS
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_options_ignore_xli9akfo/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.mnwHbx
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_options_ignore_xli9akfo/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_options_ignore_xli9akfo/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.PlfiPb
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_options_ignore_xli9akfo/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_options_ignore_xli9akfo/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_options_ignore_xli9akfo/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.jQPgCQ
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.NT6wpv
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_outside_repo" time="0.010"></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_packaging_branch_options" time="0.483"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.48v6p6
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_packaging_branch_options_gm2w38aj/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.eSp3QL
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_packaging_branch_options_gm2w38aj/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_packaging_branch_options_gm2w38aj/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.5jpAir
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_packaging_branch_options_gm2w38aj/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_packaging_branch_options_gm2w38aj/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_packaging_branch_options_gm2w38aj/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.fFQVT6
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.HE9xvM
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_pristine_tar" time="0.579"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.0IXHSL
+drwxrwxr-x root/root 0 2012-11-07 08:15 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:15 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2012-11-07 08:15 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:15 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.QDQTMs
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_l77_fj3z/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_l77_fj3z/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.tBGFH9
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_l77_fj3z/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_l77_fj3z/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_l77_fj3z/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.QLXtPQ
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.gMCzXx
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_pristine_tar_commit" time="1.126"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.n1ptsl
+drwxrwxr-x root/root 0 2012-11-07 08:13 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:13 gbp-test/Makefile
+-rw-rw-r-- root/root 35 2012-11-07 08:13 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:13 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.TIwYY3
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.3UaZvM
+Processing files: gbp-test-1.0-1.noarch
+Provides: gbp-test = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILDROOT/gbp-test-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/SRPMS/gbp-test-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/RPMS/noarch/gbp-test-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.gL2Odv
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.5lxWVd
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.4prWQU
+drwxrwxr-x root/root 0 2012-11-07 08:15 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:15 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2012-11-07 08:15 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:15 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.GakvwE
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.BEAyco
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.bUDq57
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.sIFAYR
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.pOyXJ3
+drwxrwxr-x root/root 0 2012-11-07 08:15 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:15 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2012-11-07 08:15 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:15 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.k3Ta3N
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.3SeUmy
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_pristine_tar_commit_v6yi_7zc/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.jc38Si
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.pCZGp3
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_tagging" time="1.121"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.CJgatm
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.m9BXq7
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.1LbdpS
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.NNb9wD
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.tmaqFo
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.5gxkDF
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.vIvdir
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.90uzXc
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.7z6MMY
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.dKjlCK
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.GukthT
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+2602690dc1870e491488bc59241d4735100aebd5
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.OUD0tF
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.E27YGr
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.vHyY2d
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.LO9lp0
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.mZrTka
+Archive: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SOURCES/gbp-test-native-1.0.zip
+66d8d14f6112eb144f4dbd877b07d64a33a14e33
+ creating: gbp-test-native-1.0/
+ inflating: gbp-test-native-1.0/.gbp.conf
+ extracting: gbp-test-native-1.0/Makefile
+ extracting: gbp-test-native-1.0/README
+ extracting: gbp-test-native-1.0/dummy.sh
+ creating: gbp-test-native-1.0/packaging/
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.changes
+ inflating: gbp-test-native-1.0/packaging/gbp-test-native.spec
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.sRAbpX
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILD/gbp-test-native-1.0'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.Cs9TtK
+Processing files: gbp-test-native-1.0-1.noarch
+Provides: gbp-test-native = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/BUILDROOT/gbp-test-native-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/SRPMS/gbp-test-native-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tagging_dafmjjwr/rpmbuild/RPMS/noarch/gbp-test-native-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.08FZHx
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.jCvqWk
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_buildpackage_rpm.TestGbpRpm" name="test_tarball_dir" time="0.478"><system-out><![CDATA[Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.S5whSy
+drwxrwxr-x root/root 0 2012-11-07 08:13 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:13 gbp-test/Makefile
+-rw-rw-r-- root/root 35 2012-11-07 08:13 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:13 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.dRCyNm
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.95piJa
+Processing files: gbp-test-1.0-1.noarch
+Provides: gbp-test = 1.0-1
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/BUILDROOT/gbp-test-1.0-1.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/SRPMS/gbp-test-1.0-1.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/RPMS/noarch/gbp-test-1.0-1.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.WUOHPY
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.FXrrWM
+Building target platforms: noarch
+Building for target noarch
+Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.VLM9UL
+drwxrwxr-x root/root 0 2012-11-07 08:15 gbp-test/
+-rw-rw-r-- root/root 21 2012-11-07 08:15 gbp-test/Makefile
+-rw-rw-r-- root/root 48 2012-11-07 08:15 gbp-test/README
+-rwxrwxr-x root/root 30 2012-11-07 08:15 gbp-test/dummy.sh
+-rw-r--r-- marquiz/users 56 2012-10-30 09:55 bar.txt
+Patch #0 (my.patch):
+patching file dummy.sh
+Patch #1 (my-gz.patch.gz):
+patching file AUTHORS
+Patch #2 (my-bzip2.patch.bz2):
+patching file NEWS
+Patch #10 (my2.patch):
+patching file mydir/myfile.txt
+Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.NgOlqA
+make[1]: Entering directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/BUILD/gbp-test'
+Ready!
+make[1]: Leaving directory '/var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/BUILD/gbp-test'
+Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.rxm6Vo
+Processing files: gbp-test-1.1-2.noarch
+Provides: gbp-test = 1.1-2
+Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
+Requires: /bin/sh
+Checking for unpackaged file(s): /usr/lib/rpm/check-files /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/BUILDROOT/gbp-test-1.1-2.noarch
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/SRPMS/gbp-test-1.1-2.src.rpm
+Wrote: /var/scratch/src/git-buildpackage/git-buildpackage/gbp_TestGbpRpm_y0wnb6tb/tmp_test_tarball_dir_rxefst_m/rpmbuild/RPMS/noarch/gbp-test-1.1-2.noarch.rpm
+Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.GLe9Dd
+Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.1ewum2
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestBareRepo" name="test_basic_import_to_bare_repo" time="0.306"><system-out><![CDATA[What will be the source package name? [gbp-test] What is the upstream version? [1.0] What will be the source package name? [gbp-test] What is the upstream version? [1.1] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestBareRepo" name="test_pristine_import_to_bare" time="0.148"><system-out><![CDATA[What will be the source package name? [gbp-test] What is the upstream version? [1.0] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_basic_filtering" time="0.245"><system-out><![CDATA[What will be the source package name? [gbp-test-1.1.with] What is the upstream version? [dotgit] What will be the source package name? [gbp-test-1.1.with] What is the upstream version? [dotgit] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_branch_update" time="0.387"><system-out><![CDATA[What will be the source package name? [gbp-test] What is the upstream version? [1.0] What will be the source package name? [gbp-test] What is the upstream version? [1.1] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_hook_error" time="0.166"><system-out><![CDATA[What will be the source package name? [gbp-test] What is the upstream version? [1.0] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_import_dir" time="0.351"><system-out><![CDATA[What will be the source package name? [] What is the upstream version? [] What will be the source package name? [gbp-test] What is the upstream version? [1.0] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_import_hooks" time="0.166"><system-out><![CDATA[What will be the source package name? [gbp-test] What is the upstream version? [1.0] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_import_outside_repo" time="0.011"></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_import_tars" time="0.316"><system-out><![CDATA[What will be the source package name? [gbp-test] What is the upstream version? [1.0] What will be the source package name? [gbp-test] What is the upstream version? [1.1] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_import_zip" time="0.161"><system-out><![CDATA[What will be the source package name? [gbp-test-native] What is the upstream version? [1.0] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_invalid_args" time="0.113"></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_invalid_config_file" time="0.036"></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_misc_options" time="0.415"><failure type="builtins.AssertionError" message="2 != 3&#10;-------------------- &gt;&gt; begin captured stdout &lt;&lt; ---------------------&#10;What will be the source package name? [gbp-test] What will be the source package name? [gbp-test-native] &#10;--------------------- &gt;&gt; end captured stdout &lt;&lt; ----------------------&#10;-------------------- &gt;&gt; begin captured logging &lt;&lt; --------------------&#10;gbp: info: Importing '/var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2' to branch 'orig'...&#10;gbp: info: Source package is gbp-test&#10;gbp: info: Upstream version is 0.8&#10;gbp: info: Successfully imported version 0.8 of /var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2&#10;gbp: info: Importing '/var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-native-1.0.zip' to branch 'orig'...&#10;gbp: info: Source package is gbp-test-native&#10;gbp: info: Upstream version is 0.9&#10;gbp: info: Merging to 'pack'&#10;gbp: info: Successfully imported version 0.9 of /var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-native-1.0.zip&#10;--------------------- &gt;&gt; end captured logging &lt;&lt; ---------------------"><![CDATA[ File "/usr/lib/python3.6/unittest/case.py", line 59, in testPartExecutor
+ yield
+ File "/usr/lib/python3.6/unittest/case.py", line 605, in run
+ testMethod()
+ File "/var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/test_import_orig_rpm.py", line 243, in test_misc_options
+ eq_(len(repo.get_commits(until='pack')), 3)
+ File "/usr/lib/python3/dist-packages/nose/tools/trivial.py", line 29, in eq_
+ raise AssertionError(msg or "%r != %r" % (a, b))
+2 != 3
+-------------------- >> begin captured stdout << ---------------------
+What will be the source package name? [gbp-test] What will be the source package name? [gbp-test-native]
+--------------------- >> end captured stdout << ----------------------
+-------------------- >> begin captured logging << --------------------
+gbp: info: Importing '/var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2' to branch 'orig'...
+gbp: info: Source package is gbp-test
+gbp: info: Upstream version is 0.8
+gbp: info: Successfully imported version 0.8 of /var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2
+gbp: info: Importing '/var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-native-1.0.zip' to branch 'orig'...
+gbp: info: Source package is gbp-test-native
+gbp: info: Upstream version is 0.9
+gbp: info: Merging to 'pack'
+gbp: info: Successfully imported version 0.9 of /var/scratch/src/git-buildpackage/git-buildpackage/tests/component/rpm/data/orig/gbp-test-native-1.0.zip
+--------------------- >> end captured logging << ---------------------]]></failure><system-out><![CDATA[What will be the source package name? [gbp-test] What will be the source package name? [gbp-test-native] ]]></system-out></testcase><testcase classname="tests.component.rpm.test_import_orig_rpm.TestImportOrig" name="test_noninteractive" time="0.206"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestBareRepo" name="test_basic_import_to_bare_repo" time="0.221"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestDownloadImport" name="test_invalid_url" time="0.007"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestDownloadImport" name="test_nonexistent_url" time="0.007"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestDownloadImport" name="test_urldownload" time="0.206"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_basic_import" time="0.212"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_basic_import2" time="0.217"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_basic_import_orphan" time="0.190"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_basic_native_import" time="0.159"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_filter" time="0.208"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_import_no_orig_src" time="0.142"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_import_to_existing" time="0.468"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_invalid_args" time="0.012"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_misc_options" time="0.420"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_multiple_versions" time="0.645"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_tagging" time="0.537"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_tagging_native" time="0.297"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportPacked" name="test_target_dir" time="0.226"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportUnPacked" name="test_import_dir" time="0.256"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportUnPacked" name="test_import_spec" time="0.228"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestImportUnPacked" name="test_missing_files" time="0.086"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestPristineTar" name="test_basic_import_pristine_tar" time="0.344"></testcase><testcase classname="tests.component.rpm.test_import_srpm.TestPristineTar" name="test_unsupported_archive" time="0.197"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_apply" time="0.223"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_drop_pq" time="0.183"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_export_with_merges" time="0.385"><system-out><![CDATA[On branch master
+Changes not staged for commit:
+ (use "git add/rm <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: gbp-test.spec
+ deleted: my-bzip2.patch.bz2
+ deleted: my-gz.patch.gz
+ deleted: my2.patch
+ deleted: my3.patch
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ 0002-my2.patch
+ 6450890-to-934e666.diff
+
+no changes added to commit (use "git add" and/or "git commit -a")
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_force_import" time="0.630"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_import_export" time="0.547"><system-out><![CDATA[On branch master
+Changes not staged for commit:
+ (use "git add/rm <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: gbp-test.spec
+ deleted: my-bzip2.patch.bz2
+ deleted: my-gz.patch.gz
+ deleted: my2.patch
+ deleted: my3.patch
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ 0001-my-gz.patch
+ 0002-my-bzip2.patch
+ 0003-my2.patch
+
+no changes added to commit (use "git add" and/or "git commit -a")
+On branch master
+Changes not staged for commit:
+ (use "git add/rm <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: gbp-test.spec
+ deleted: my-bzip2.patch.bz2
+ deleted: my-gz.patch.gz
+ deleted: my2.patch
+ deleted: my3.patch
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ 0001-my-gz.patch
+ 0002-my-bzip2.patch
+ 0003-my2.patch
+
+no changes added to commit (use "git add" and/or "git commit -a")
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_import_export2" time="0.335"><system-out><![CDATA[On branch master-orphan
+Changes not staged for commit:
+ (use "git add/rm <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: packaging/gbp-test2.spec
+ deleted: packaging/my2.patch
+ deleted: packaging/my3.patch
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ packaging/0001-PATCH-My-modification.patch
+
+no changes added to commit (use "git add" and/or "git commit -a")
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_import_outside_repo" time="0.010"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_import_unapplicable_patch" time="0.339"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_invalid_args" time="0.062"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_option_packaging_dir" time="0.378"><system-out><![CDATA[On branch master
+Changes not staged for commit:
+ (use "git add/rm <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: gbp-test.spec
+ deleted: my-bzip2.patch.bz2
+ deleted: my-gz.patch.gz
+ deleted: my2.patch
+ deleted: my3.patch
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ 0001-my-gz.patch
+ 0002-my-bzip2.patch
+ 0003-my2.patch
+
+no changes added to commit (use "git add" and/or "git commit -a")
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_option_patch_numbers" time="0.216"><system-out><![CDATA[On branch master
+Changes not staged for commit:
+ (use "git add/rm <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: gbp-test.spec
+ deleted: my-bzip2.patch.bz2
+ deleted: my-gz.patch.gz
+ modified: my2.patch
+ deleted: my3.patch
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ my-bzip2.patch
+ my-gz.patch
+
+no changes added to commit (use "git add" and/or "git commit -a")
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_option_spec_file" time="0.570"><system-out><![CDATA[On branch master
+Changes not staged for commit:
+ (use "git add/rm <file>..." to update what will be committed)
+ (use "git checkout -- <file>..." to discard changes in working directory)
+
+ modified: gbp-test.spec
+ deleted: my-bzip2.patch.bz2
+ deleted: my-gz.patch.gz
+ deleted: my2.patch
+ deleted: my3.patch
+
+Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+
+ 0001-my-gz.patch
+ 0002-my-bzip2.patch
+ 0003-my2.patch
+
+no changes added to commit (use "git add" and/or "git commit -a")
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_option_tmp_dir" time="0.191"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_option_upstream_tag" time="0.236"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_rebase" time="0.508"><system-out><![CDATA[First, rewinding head to replay your work on top of it...
+Applying: Auto-import file(s) from branch 'srcdata/gbp-test/master':
+Applying: my-gz
+Applying: my-bzip2
+Applying: my2
+First, rewinding head to replay your work on top of it...
+Applying: Auto-import file(s) from branch 'srcdata/gbp-test/master':
+Applying: my-gz
+Applying: my-bzip2
+Applying: my2
+First, rewinding head to replay your work on top of it...
+Applying: Auto-import file(s) from branch 'srcdata/gbp-test/master':
+Applying: my-gz
+Applying: my-bzip2
+Applying: my2
+First, rewinding head to replay your work on top of it...
+Applying: Auto-import file(s) from branch 'srcdata/gbp-test/master':
+Applying: my-gz
+Applying: my-bzip2
+Applying: my2
+]]></system-out></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_switch" time="0.165"></testcase><testcase classname="tests.component.rpm.test_pq_rpm.TestPqRpm" name="test_switch_drop" time="0.177"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_author" time="0.183"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_branch_options" time="0.115"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_commit_guessing" time="0.165"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_commit_guessing_fail" time="0.065"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_create_changes_file" time="0.117"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_create_spec_changelog" time="0.121"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_import_outside_repo" time="0.010"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_invalid_args" time="0.036"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_changelog_file" time="0.186"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_changelog_revision" time="0.162"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_editor_cmd" time="0.160"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_full" time="0.080"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_id_len" time="0.083"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_ignore_regex" time="0.083"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_no_release" time="0.089"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_packaging_dir" time="0.116"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_option_spec_file" time="0.151"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_paths" time="0.141"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_update_changes_file" time="0.094"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_update_spec_changelog" time="0.116"></testcase><testcase classname="tests.component.rpm.test_rpm_ch.TestRpmCh" name="test_user_customizations" time="0.102"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_parse_debian_only" time="0.048"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_parse_no_eopch" time="0.053"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_parse_eopch" time="0.054"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_parse_name" time="0.053"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_parse_last_mod" time="0.052"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_parse_sections" time="0.054"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_get_changes" time="0.143"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_add_section" time="0.185"></testcase><testcase classname="tests.doctests.test_Changelog" name="test_add_entry" time="0.270"></testcase><testcase classname="tests.doctests.test_Config" name="test_option_parser" time="0.022"></testcase><testcase classname="tests.doctests.test_Config" name="test_option_parser_debian" time="0.022"></testcase><testcase classname="tests.doctests.test_Config" name="test_option_group" time="0.023"></testcase><testcase classname="tests.doctests.test_Config" name="test_tristate" time="0.038"></testcase><testcase classname="tests.doctests.test_Config" name="test_filter" time="0.036"></testcase><testcase classname="tests.doctests.test_Config" name="test_filters" time="0.023"></testcase><testcase classname="tests.doctests.test_Control" name="test_parse_control" time="0.002"></testcase><testcase classname="tests.doctests.test_Control" name="test_no_control_error" time="0.001"></testcase><testcase classname="tests.doctests.test_GitModifier" name="test_author" time="0.001"></testcase><testcase classname="tests.doctests.test_GitModifier" name="test_date" time="0.002"></testcase><testcase classname="tests.doctests.test_GitModifier" name="test_dict" time="0.001"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_create" time="0.024"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_empty" time="0.038"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_subdir" time="0.022"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_add_files" time="0.077"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_rename_file" time="0.030"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_branch_master" time="0.025"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_clean" time="0.047"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_create_branch" time="0.030"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_delete_branch" time="0.048"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_set_branch" time="0.047"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_rename_branch" time="0.040"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_set_upstream_branch" time="0.114"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_get_upstream_branch" time="0.038"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_tag" time="0.054"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_describe" time="0.076"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_find_tag" time="0.028"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_find_branch_tag" time="0.031"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_move_tag" time="0.045"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_delete_tag" time="0.035"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_get_obj_type" time="0.045"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_list_files" time="0.079"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_get_commits" time="0.046"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_get_commit_info" time="0.037"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_diff" time="0.047"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_diff_status" time="0.027"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_mirror_clone" time="0.110"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_clone" time="0.121"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_get_remotes" time="0.028"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_merge" time="0.095"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_pull" time="0.077"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_fetch" time="0.172"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_create_bare" time="0.030"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_nonexistent" time="0.004"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_create_noperm" time="0.002"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_checkout" time="0.069"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_gc" time="0.072"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_grep_log" time="0.045"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_is_ff" time="0.037"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_update_ref" time="0.024"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_make_tree" time="0.042"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_update_submodules" time="0.014"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_get_merge_base" time="0.035"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_status" time="0.055"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_cmd_has_feature" time="0.565"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_set_user_name_and_email" time="0.027"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_set_config_and_get_config" time="0.022"></testcase><testcase classname="tests.doctests.test_GitRepository" name="test_git_dir" time="0.015"></testcase><testcase classname="tests.doctests.test_GitVfs" name="test_read" time="0.049"></testcase><testcase classname="tests.doctests.test_GitVfs" name="test_binary_read" time="0.048"></testcase><testcase classname="tests.doctests.test_GitVfs" name="test_content_manager" time="0.037"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_create" time="0.022"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_empty_repo" time="0.026"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_commit_dir" time="0.039"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_create_tarball" time="0.031"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_pristine_tar_commit" time="0.188"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_pristine_has_commit" time="0.055"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_pristine_tar_checkout" time="0.089"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_pristine_tar_verify" time="0.130"></testcase><testcase classname="tests.doctests.test_PristineTar" name="test_pristine_tar_checkout_nonexistent" time="0.074"></testcase><testcase classname="tests.doctests.test_create_remote_repo" name="test_build_remote_script" time="0.001"></testcase><testcase classname="tests.doctests.test_create_remote_repo" name="test_build_remote_script_template_dir" time="0.001"></testcase><testcase classname="tests.doctests.test_create_remote_repo" name="test_build_remote_script_bare" time="0.001"></testcase><testcase classname="tests.doctests.test_create_remote_repo" name="test_parse_url" time="0.003"></testcase><testcase classname="tests.01_test_help.TestHelp" name="testHelp" time="0.248"><system-out><![CDATA[Usage: gbp doesnotmatter [options]
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --git-ignore-new Build with uncommitted changes in the source tree,
+ default is 'False'
+ --git-no-ignore-new negates '--git-ignore-new'
+ --git-verbose verbose command execution
+ --git-color=COLOR Whether to use colored output, default is 'auto'
+ --git-color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --git-notify=NOTIFY Whether to send a desktop notification after the
+ build, default is 'auto'
+
+ tag options:
+ options related to git tag creation
+
+ --git-tag create a tag after a successful build
+ --git-tag-only don't build, only tag and run the posttag hook
+ --git-retag don't fail if the tag already exists
+ --git-sign-tags Whether to sign tags, default is 'True'
+ --git-no-sign-tags negates '--git-sign-tags'
+ --git-keyid=KEYID GPG keyid to sign tags with, default is
+ '0x25BF86524AFCC1E3'
+ --git-debian-tag=DEBIAN_TAG
+ Format string for debian tags, default is
+ 'debian/%(version)s'
+ --git-debian-tag-msg=DEBIAN_TAG_MSG
+ Format string for signed debian-tag messages, default
+ is '%(pkg)s Debian release %(version)s'
+ --git-upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+
+ orig tarball options:
+ options related to the creation of the orig tarball
+
+ --git-upstream-tree=UPSTREAM_TREE
+ Where to generate the upstream tarball from (tag or
+ branch), default is 'TAG'
+ --git-pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --git-no-pristine-tar
+ negates '--git-pristine-tar'
+ --git-pristine-tar-commit
+ When generating a tarball commit it to the pristine-
+ tar branch 'False' default is 'False'
+ --git-no-pristine-tar-commit
+ negates '--git-pristine-tar-commit'
+ --git-force-create force creation of orig tarball
+ --git-no-create-orig
+ don't create orig tarball
+ --git-tarball-dir=TARBALL_DIR
+ location to look for external tarballs
+ --git-compression=COMP_TYPE
+ Compression type, default is 'auto'
+ --git-compression-level=COMP_LEVEL
+ Compression level, default is ''
+ --git-component=COMPONENT
+ component name for additional tarballs
+
+ branch options:
+ branch layout options
+
+ --git-upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --git-debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --git-ignore-branch
+ Build although debian-branch != current branch,
+ default is 'False'
+ --git-no-ignore-branch
+ negates '--git-ignore-branch'
+ --git-submodules Transparently handle submodules in the upstream tree,
+ default is 'False'
+ --git-no-submodules
+ negates '--git-submodules'
+
+ external command options:
+ how and when to invoke external commands and hooks
+
+ --git-builder=BUILDER
+ command to build the Debian package, default is
+ 'debuild -i -I'
+ --git-cleaner=CLEANER
+ command to clean the working copy, default is
+ '/bin/true'
+ --git-prebuild=PREBUILD
+ hook to run before a build, default is ''
+ --git-postexport=POSTEXPORT
+ hook to run after exporting the source tree, default
+ is ''
+ --git-postbuild=POSTBUILD
+ hook run after a successful build, default is ''
+ --git-posttag=POSTTAG
+ hook run after a successful tag operation, default is
+ ''
+ --git-pbuilder Invoke git-pbuilder for building, default is 'True'
+ --git-no-pbuilder negates '--git-pbuilder'
+ --git-qemubuilder Invoke git-pbuilder with qemubuilder for building,
+ default is 'False'
+ --git-no-qemubuilder
+ negates '--git-qemubuilder'
+ --git-dist=PBUILDER_DIST
+ Build for this distribution when using git-pbuilder,
+ default is 'DEP14'
+ --git-arch=PBUILDER_ARCH
+ Build for this architecture when using git-pbuilder,
+ default is ''
+ --git-pbuilder-autoconf
+ Whether to configure pbuilder automatically, default
+ is 'True'
+ --git-no-pbuilder-autoconf
+ negates '--git-pbuilder-autoconf'
+ --git-pbuilder-options=PBUILDER_OPTIONS
+ Options to pass to pbuilder, default is ''
+ --git-hooks Enable running all hooks, default is True
+ --git-no-hooks negates '--git-hooks'
+
+ export build-tree options:
+ alternative build tree related options
+
+ --git-export-dir=EXPORT_DIR
+ before building the package export the source into
+ EXPORT_DIR, default is ''
+ --git-export=TREEISH
+ export treeish object TREEISH, default is 'HEAD'
+ --git-purge Purge exported package build directory. Default is
+ 'True'
+ --git-no-purge negates '--git-purge'
+ --git-overlay extract orig tarball when using export-dir option,
+ default is 'False'
+ --git-no-overlay negates '--git-overlay'
+Usage: gbp doesnotmatter [options] command[.optionname] - display configuration settings
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+Usage: gbp doesnotmatter [options] - create a remote git repository
+Actions:
+ create create the repository. This is the default when no action is
+ given.
+ list list available configuration templates for remote repositories
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --remote-name=NAME The name of the remote, default is 'origin'
+ --template-dir=TEMPLATE_DIR
+ Template directory used by git init, default is ''
+ --remote-config=REMOTE_CONFIG
+ Remote definition in gbp.conf used to create the
+ remote repository, default is ''
+
+ branch options:
+ branch layout and tracking options
+
+ --remote-url-pattern=REMOTE_URL
+ Remote url pattern to create the repo at, default is
+ 'ssh://git.debian.org/git/collab-maint/%(pkg)s.git'
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --no-pristine-tar negates '--pristine-tar'
+ --track Set up tracking for remote branches, default is 'True'
+ --no-track negates '--track'
+ --bare wether to create a bare repository on the remote side.
+ 'Default is 'True'.
+ --no-bare negates '--bare'
+Usage: gbp doesnotmatter [options] paths
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --ignore-branch Build although debian-branch != current branch,
+ default is 'False'
+ --no-ignore-branch negates '--ignore-branch'
+ --git-log=GIT_LOG options to pass to git-log, default is '--no-merges'
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --commit-msg=COMMIT_MSG
+ Format string for commit message used to commit, the
+ changelog, default is 'Update changelog for
+ %(version)s release'
+ -c, --commit commit changelog file after generating
+ --dch-opt=DCH_OPT option to pass to dch verbatim, can be given multiple
+ times
+
+ commit range options:
+ which commits to add to the changelog
+
+ -s SINCE, --since=SINCE
+ commit to start from (e.g. HEAD^^^, debian/0.4.3)
+ -a, --auto autocomplete changelog from last snapshot or tag
+
+ release & version number options:
+ what version number and release to use
+
+ -R, --release mark as release
+ -S, --snapshot mark as snapshot build
+ -D DISTRIBUTION, --distribution=DISTRIBUTION
+ Set distribution
+ --force-distribution
+ Force the provided distribution to be used, even if it
+ doesn't match the list of known distributions
+ -N NEW_VERSION, --new-version=NEW_VERSION
+ use this as base for the new version number
+ -U URGENCY, --urgency=URGENCY
+ Set urgency level, default is 'medium'
+ --bpo Increment the Debian release number for an upload to
+ backports, and add a backport upload changelog
+ comment.
+ --nmu Increment the Debian release number for a non-
+ maintainer upload
+ --qa Increment the Debian release number for a Debian QA
+ Team upload, and add a QA upload changelog comment.
+ --team Increment the Debian release number for a Debian Team
+ upload, and add a Team upload changelog comment.
+ --security Increment the Debian release number for a security
+ upload and add a security upload changelog comment.
+ --git-author Use name and email from git-config for changelog
+ trailer, default is 'False'
+ --no-git-author negates '--git-author'
+
+ commit message formatting:
+ howto format the changelog entries
+
+ --meta Parse meta tags in commit messages, default is 'True'
+ --no-meta negates '--meta'
+ --meta-closes=META_CLOSES
+ Meta tags for the bts close commands, default is
+ 'Closes|LP'
+ --meta-closes-bugnum=META_CLOSES_BUGNUM
+ Meta bug number format, default is
+ '(?:bug|issue)?\#?\s?\d+'
+ --full Include the full commit message instead of only the
+ first line, default is 'False'
+ --no-full negates '--full'
+ --id-length=N include N digits of the commit id in the changelog
+ entry, default is '0'
+ --ignore-regex=IGNORE_REGEX
+ Ignore commit lines matching regex, default is ''
+ --multimaint Note multiple maintainers, default is 'True'
+ --no-multimaint negates '--multimaint'
+ --multimaint-merge Merge commits by maintainer, default is 'False'
+ --no-multimaint-merge
+ negates '--multimaint-merge'
+ --spawn-editor=SPAWN_EDITOR
+ Whether to spawn an editor after adding the changelog
+ entry, default is 'release'
+
+ branch and tag naming:
+ branch names and tag formats
+
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+ --debian-tag=DEBIAN_TAG
+ Format string for debian tags, default is
+ 'debian/%(version)s'
+ --snapshot-number=SNAPSHOT_NUMBER
+ expression to determine the next snapshot number,
+ default is 'snapshot + 1'
+
+ customization:
+ options for customization
+
+ --customizations=CUSTOMIZATION_FILE
+ 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.
+ --postedit=POSTEDIT
+ Hook to run after changes to the changelog filehave
+ been finalized default is ''
+Usage: gbp doesnotmatter [options] /path/to/upstream-version.tar.gz | --uscan
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --interactive Run command interactively, default is 'True'
+ --no-interactive negates '--interactive'
+ --rollback Rollback repository changes when encountering an error
+ --no-rollback negates '--rollback'
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --uscan use uscan(1) to download the new tarball.
+ --download Ignored. Accepted for compatibility; see EXAMPLES in
+ gbp-import-orig(1).
+
+ import options:
+ pristine-tar and filtering
+
+ --filter=FILTERS Files to filter out during import (can be given
+ multiple times), default is []
+ --pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --no-pristine-tar negates '--pristine-tar'
+ --filter-pristine-tar
+ Filter pristine-tar when filter option is used,
+ default is 'False'
+ --no-filter-pristine-tar
+ negates '--filter-pristine-tar'
+ --import-msg=IMPORT_MSG
+ Format string for commit message used to commit the
+ upstream tarball, default is 'New upstream version
+ %(version)s'
+ --symlink-orig Whether to create a symlink from the upstream tarball
+ to the orig.tar.gz if needed, default is 'True'
+ --no-symlink-orig negates '--symlink-orig'
+ --component=COMPONENT
+ component name for additional tarballs
+
+ version and branch naming options:
+ version number and branch layout options
+
+ -u VERSION, --upstream-version=VERSION
+ Upstream Version
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --upstream-vcs-tag=VCS_TAG
+ Upstream VCS tag add to the merge commit
+ --merge After the import merge the result to the debian
+ branch, default is 'True'
+ --no-merge negates '--merge'
+ --merge-mode=MERGE_MODE
+ Howto merge the new upstream sources onto the debian
+ branch, default is 'auto'
+
+ tag options:
+ options related to git tag creation
+
+ --sign-tags Whether to sign tags, default is 'True'
+ --no-sign-tags negates '--sign-tags'
+ --keyid=KEYID GPG keyid to sign tags with, default is
+ '0x25BF86524AFCC1E3'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+
+ external command options:
+ how and when to invoke external commands and hooks
+
+ --postimport=POSTIMPORT
+ hook run after a successful import, default is ''
+Usage: gbp doesnotmatter [options] /path/to/upstream-version.tar.gz | --uscan
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --rollback Rollback repository changes when encountering an error
+ --no-rollback negates '--rollback'
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+
+ import options:
+ import related options
+
+ --import-msg=IMPORT_MSG
+ Format string for commit message used to commit the
+ upstream tarball, default is 'New upstream version
+ %(version)s'
+
+ version and branch naming options:
+ version number and branch layout options
+
+ -u VERSION, --upstream-version=VERSION
+ The version number to use for the new version, default
+ is ''
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --upstream-tree=UPSTREAM_TREE
+ Where to merge the upstream changes from.
+ --merge-mode=MERGE_MODE
+ Howto merge the new upstream sources onto the debian
+ branch, default is 'auto'
+
+ tag options:
+ tag related options
+
+ --sign-tags Whether to sign tags, default is 'True'
+ --no-sign-tags negates '--sign-tags'
+ --keyid=KEYID GPG keyid to sign tags with, default is
+ '0x25BF86524AFCC1E3'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+
+ external command options:
+ how and when to invoke external commands and hooks
+
+ --postimport=POSTIMPORT
+ hook run after a successful import, default is ''
+Usage: gbp doesnotmatter [options] /path/to/package.dsc [target]
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --repo-user=REPO_USER
+ Set repo username from the DEBFULLNAME and DEBEMAIL
+ environment variables ('DEBIAN') or fallback to the
+ git configuration ('GIT'), default is 'DEBIAN'
+ --repo-email=REPO_EMAIL
+ Set repo email from the DEBFULLNAME and DEBEMAIL
+ environment variables ('DEBIAN') or fallback to the
+ git configuration ('GIT'), default is 'DEBIAN'
+ --download Ignored. Accepted for compatibility; see EXAMPLES in
+ gbp-import-dsc(1).
+
+ import options:
+ pristine-tar and filtering
+
+ --filter=FILTERS Files to filter out during import (can be given
+ multiple times), default is []
+ --pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --no-pristine-tar negates '--pristine-tar'
+ --allow-same-version
+ allow import of already imported version
+ --author-is-committer
+ Use the authors's name also as the committer's name,
+ default is 'False'
+ --no-author-is-committer
+ negates '--author-is-committer'
+ --author-date-is-committer-date
+ Use the authors's date as the committer's date,
+ default is 'False'
+ --no-author-date-is-committer-date
+ negates '--author-date-is-committer-date'
+ --allow-unauthenticated
+ Don't verify integrity of downloaded source, default
+ is 'False'
+ --no-allow-unauthenticated
+ negates '--allow-unauthenticated'
+
+ version and branch naming options:
+ version number and branch layout options
+
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --create-missing-branches
+ Create missing branches automatically, default is
+ 'False'
+ --no-create-missing-branches
+ negates '--create-missing-branches'
+
+ tag options:
+ options related to git tag creation
+
+ --sign-tags Whether to sign tags, default is 'True'
+ --no-sign-tags negates '--sign-tags'
+ --keyid=KEYID GPG keyid to sign tags with, default is
+ '0x25BF86524AFCC1E3'
+ --debian-tag=DEBIAN_TAG
+ Format string for debian tags, default is
+ 'debian/%(version)s'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+ --skip-debian-tag Don't add a tag after importing the Debian patch
+Usage: gbp doesnotmatter [action] [options] /path/to/upstream-version.tar.gz
+
+Actions:
+ commit recreate the pristine-tar commits on the pristine-tar branch
+
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+ --component=COMPONENT
+ component name for additional tarballs
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+Usage: gbp doesnotmatter [options] [repo] - safely update a repository from remote
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+
+ branch options:
+ branch update and layout options
+
+ --ignore-branch Build although debian-branch != current branch,
+ default is 'False'
+ --no-ignore-branch negates '--ignore-branch'
+ --force force a branch update even if it can't be fast
+ forwarded
+ --all update all remote-tracking branches that have
+ identical name in the remote
+ --redo-pq redo the patch queue branch after a pull. Warning:
+ this drops the old patch-queue branch
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --no-pristine-tar negates '--pristine-tar'
+ --track-missing Track missing remote branches, default is 'False'
+ --no-track-missing negates '--track-missing'
+ --depth=DEPTH git history depth (for deepening shallow clones)
+Usage: gbp doesnotmatter [options]
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -d, --dry-run dry run, don't push.
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --debian-tag=DEBIAN_TAG
+ Format string for debian tags, default is
+ 'debian/%(version)s'
+ --pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --no-pristine-tar negates '--pristine-tar'
+ --ignore-branch Build although debian-branch != current branch,
+ default is 'False'
+ --no-ignore-branch negates '--ignore-branch'
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --verbose verbose command execution
+Usage: gbp doesnotmatter [options] action - maintain patches on a patch queue branch
+Actions:
+ export export the patch queue associated to the current branch
+ into a quilt patch series in debian/patches/ and update the
+ series file.
+ import create a patch queue branch from quilt patches in debian/patches.
+ rebase switch to patch queue branch associated to the current
+ branch and rebase against current branch.
+ drop drop (delete) the patch queue associated to the current branch.
+ apply apply a patch
+ switch switch to patch-queue branch and vice versa
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --patch-numbers Whether to number patch files, default is True
+ --no-patch-numbers negates '--patch-numbers'
+ --patch-num-format=PATCH_NUM_FORMAT
+ The format specifier for patch number prefixes,
+ default is %04d-
+ --renumber Whether to renumber patches exported from patch
+ queues, instead of preserving the number specified in
+ 'Gbp-Pq: Name' tags, default is False
+ --no-renumber negates '--renumber'
+ -v, --verbose verbose command execution
+ --topic=TOPIC in case of 'apply' topic (subdir) to put patch into
+ --time-machine=TIME_MACHINE
+ don't try to apply patch queue to head commit only.
+ Try at most TIME_MACHINE commits back, default is '1'
+ --drop In case of 'export' drop the patch-queue branch after
+ export. Default is 'False'
+ --no-drop negates '--drop'
+ --commit commit changes after export, Default is 'False'
+ --no-commit negates '--commit'
+ --abbrev=ABBREV abbreviate commits to this length. default is '7'
+ --force in case of import even import if the branch already
+ exists
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --meta-closes=META_CLOSES
+ Meta tags for the bts close commands, default is
+ 'Closes|LP'
+ --meta-closes-bugnum=META_CLOSES_BUGNUM
+ Meta bug number format, default is
+ '(?:bug|issue)?\#?\s?\d+'
+ --pq-from=PQ_FROM How to find the patch queue base. DEBIAN or TAG, the
+ default is 'DEBIAN'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+Usage: gbp doesnotmatter [options]
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --retag don't fail if the tag already exists
+ --debian-branch=DEBIAN_BRANCH
+ Branch the Debian package is being developed on,
+ default is 'master'
+ --debian-tag=DEBIAN_TAG
+ Format string for debian tags, default is
+ 'debian/%(version)s'
+ --debian-tag-msg=DEBIAN_TAG_MSG
+ Format string for signed debian-tag messages, default
+ is '%(pkg)s Debian release %(version)s'
+ --sign-tags Whether to sign tags, default is 'True'
+ --no-sign-tags negates '--sign-tags'
+ --keyid=KEYID GPG keyid to sign tags with, default is
+ '0x25BF86524AFCC1E3'
+ --posttag=POSTTAG hook run after a successful tag operation, default is
+ ''
+ --ignore-branch Build although debian-branch != current branch,
+ default is 'False'
+ --no-ignore-branch negates '--ignore-branch'
+ --ignore-new Build with uncommitted changes in the source tree,
+ default is 'False'
+ --no-ignore-new negates '--ignore-new'
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --verbose verbose command execution
+Usage: gbp doesnotmatter [options]
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --git-ignore-new Build with uncommitted changes in the source tree,
+ default is 'False'
+ --git-no-ignore-new negates '--git-ignore-new'
+ --git-verbose verbose command execution
+ --git-tmp-dir=TMP_DIR
+ Base directory under which temporary directories are
+ created, default is '/var/tmp/gbp/'
+ --git-color=COLOR Whether to use colored output, default is 'auto'
+ --git-color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --git-notify=NOTIFY Whether to send a desktop notification after the
+ build, default is 'auto'
+ --git-vendor=VENDOR Distribution vendor name, default is 'Downstream'
+ --git-native=NATIVE Treat this package as native, default is 'auto'
+
+ tag options:
+ options related to git tag creation
+
+ --git-tag create a tag after a successful build
+ --git-tag-only don't build, only tag and run the posttag hook
+ --git-retag don't fail if the tag already exists
+ --git-sign-tags Whether to sign tags, default is 'True'
+ --git-no-sign-tags negates '--git-sign-tags'
+ --git-keyid=KEYID GPG keyid to sign tags with, default is
+ '0x25BF86524AFCC1E3'
+ --git-packaging-tag=PACKAGING_TAG
+ Format string for packaging tags, RPM counterpart of
+ the 'debian-tag' option, default is
+ 'packaging/%(version)s'
+ --git-packaging-tag-msg=PACKAGING_TAG_MSG
+ Format string for packaging tag messages, default is
+ '%(pkg)s (vendor)s release %(version)s'
+ --git-upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+
+ orig tarball options:
+ options related to the creation of the orig tarball
+
+ --git-upstream-tree=UPSTREAM_TREE
+ Where to generate the upstream tarball from (tag or
+ branch), default is 'TAG'
+ --git-pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --git-no-pristine-tar
+ negates '--git-pristine-tar'
+ --git-pristine-tar-commit
+ When generating a tarball commit it to the pristine-
+ tar branch 'False' default is 'False'
+ --git-no-pristine-tar-commit
+ negates '--git-pristine-tar-commit'
+ --git-force-create force creation of upstream source tarball
+ --git-no-create-orig
+ don't create upstream source tarball
+ --git-tarball-dir=TARBALL_DIR
+ location to look for external tarballs
+ --git-compression-level=COMP_LEVEL
+ Compression level, default is ''
+
+ branch options:
+ branch layout options
+
+ --git-upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --git-packaging-branch=PACKAGING_BRANCH
+ Branch the packaging is being maintained on, rpm
+ counterpart of the 'debian-branch' option, default is
+ 'master'
+ --git-ignore-branch
+ Build although debian-branch != current branch,
+ default is 'False'
+ --git-no-ignore-branch
+ negates '--git-ignore-branch'
+ --git-submodules Transparently handle submodules in the upstream tree,
+ default is 'False'
+ --git-no-submodules
+ negates '--git-submodules'
+
+ external command options:
+ how and when to invoke external commands and hooks
+
+ --git-builder=BUILDER
+ command to build the package, default is 'rpmbuild'
+ --git-cleaner=CLEANER
+ command to clean the working copy, default is
+ '/bin/true'
+ --git-prebuild=PREBUILD
+ command to run before a build, default is ''
+ --git-postexport=POSTEXPORT
+ command to run after exporting the source tree,
+ default is ''
+ --git-postbuild=POSTBUILD
+ hook run after a successful build, default is ''
+ --git-posttag=POSTTAG
+ hook run after a successful tag operation, default is
+ ''
+ --git-mock Invoke mock for building using gbp-builder-mock,
+ default is 'False'
+ --git-no-mock negates '--git-mock'
+ --git-dist=MOCK_DIST
+ Build for this distribution when using mock. E.g.:
+ epel-6, default is 'DEP14'
+ --git-arch=MOCK_ARCH
+ Build for this architecture when using mock, default
+ is ''
+ --git-mock-root=MOCK_ROOT
+ The mock root (-r) name for building with mock:
+ <dist>-<arch>, default is ''
+ --git-mock-options=MOCK_OPTIONS
+ Options to pass to mock, default is ''
+ --git-hooks Enable running all hooks, default is True
+ --git-no-hooks negates '--git-hooks'
+
+ export build-tree options:
+ alternative build tree related options
+
+ --git-no-build Don't run builder or the associated hooks
+ --git-export-dir=EXPORT_DIR
+ Build topdir, also export the sources under
+ EXPORT_DIR, default is '../rpmbuild'
+ --git-export-specdir=EXPORT_SPECDIR
+ Subdir (under EXPORT_DIR) where package spec file is
+ exported default is 'SPECS'
+ --git-export-sourcedir=EXPORT_SOURCEDIR
+ Subdir (under EXPORT_DIR) where packaging sources
+ (other than the spec file) are exported, default is
+ 'SOURCES'
+ --git-export=TREEISH
+ export treeish object TREEISH, default is 'HEAD'
+ --git-packaging-dir=PACKAGING_DIR
+ Subdir for RPM packaging files, default is ''
+ --git-spec-file=SPEC_FILE
+ Spec file to use, causes the packaging-dir option to
+ be ignored, default is ''
+ --git-spec-vcs-tag=SPEC_VCS_TAG
+ Set/update the 'VCS:' tag in the spec file, empty
+ value removes the tag entirely, default is ''
+Usage: gbp doesnotmatter [options] /path/to/package.src.rpm [target]
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --tmp-dir=TMP_DIR Base directory under which temporary directories are
+ created, default is '/var/tmp/gbp/'
+ --vendor=VENDOR Distribution vendor name, default is 'Downstream'
+ --download download source package
+ --repo-user=REPO_USER
+ Set repo username from the DEBFULLNAME and DEBEMAIL
+ environment variables ('DEBIAN') or fallback to the
+ git configuration ('GIT'), default is 'DEBIAN'
+ --repo-email=REPO_EMAIL
+ Set repo email from the DEBFULLNAME and DEBEMAIL
+ environment variables ('DEBIAN') or fallback to the
+ git configuration ('GIT'), default is 'DEBIAN'
+
+ import options:
+ pristine-tar and filtering
+
+ --filter=FILTERS Files to filter out during import (can be given
+ multiple times), default is []
+ --pristine-tar Use pristine-tar to create orig tarball, default is
+ 'True'
+ --no-pristine-tar negates '--pristine-tar'
+ --allow-same-version
+ allow import of already imported version
+ --author-is-committer
+ Use the authors's name also as the committer's name,
+ default is 'False'
+ --no-author-is-committer
+ negates '--author-is-committer'
+ --packaging-dir=PACKAGING_DIR
+ Subdir for RPM packaging files, default is ''
+
+ version and branch naming options:
+ version number and branch layout options
+
+ --packaging-branch=PACKAGING_BRANCH
+ Branch the packaging is being maintained on, rpm
+ counterpart of the 'debian-branch' option, default is
+ 'master'
+ --upstream-branch=UPSTREAM_BRANCH
+ Upstream branch, default is 'upstream'
+ --upstream-vcs-tag=VCS_TAG
+ Upstream VCS tag on top of which to import the orig
+ sources
+ --create-missing-branches
+ Create missing branches automatically, default is
+ 'False'
+ --no-create-missing-branches
+ negates '--create-missing-branches'
+ --orphan-packaging The packaging branch doesn't base on upstream
+ --native This is a dist native package, no separate upstream
+ branch
+
+ tag options:
+ options related to git tag creation
+
+ --sign-tags Whether to sign tags, default is 'True'
+ --no-sign-tags negates '--sign-tags'
+ --keyid=KEYID GPG keyid to sign tags with, default is
+ '0x25BF86524AFCC1E3'
+ --packaging-tag=PACKAGING_TAG
+ Format string for packaging tags, RPM counterpart of
+ the 'debian-tag' option, default is
+ 'packaging/%(version)s'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+ --skip-packaging-tag
+ Don't add a tag after importing packaging files
+Usage: gbp doesnotmatter [options] paths
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -v, --verbose verbose command execution
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --tmp-dir=TMP_DIR Base directory under which temporary directories are
+ created, default is '/var/tmp/gbp/'
+ --vendor=VENDOR Distribution vendor name, default is 'Downstream'
+ --git-log=GIT_LOG options to pass to git-log, default is '--no-merges'
+ --ignore-branch Build although debian-branch != current branch,
+ default is 'False'
+ --no-ignore-branch negates '--ignore-branch'
+ --customizations=CUSTOMIZATION_FILE
+ Load Python code from CUSTOMIZATION_FILE. At the
+ moment, the only useful thing the code can do is
+ define a custom ChangelogEntryFormatter class.
+
+ commit range options:
+ which commits to add to the changelog
+
+ -s SINCE, --since=SINCE
+ commit to start from (e.g. HEAD^^^, release/0.1.2)
+
+ changelog entry formatting:
+ how to format the changelog entries
+
+ --no-release no release, just update the last changelog section
+ --git-author Use name and email from git-config for the changelog
+ header, default is 'False'
+ --no-git-author negates '--git-author'
+ --full Include the full commit message instead of only the
+ first line, default is 'False'
+ --no-full negates '--full'
+ --id-length=N include N digits of the commit id in the changelog
+ entry, default is '0'
+ --ignore-regex=IGNORE_REGEX
+ Ignore lines in commit message matching regex, default
+ is ''
+ --changelog-revision=CHANGELOG_REVISION
+ Format string for the revision field in the changelog
+ header. If empty or not defined the default from
+ packaging policy is used.
+ --spawn-editor=SPAWN_EDITOR
+ Whether to spawn an editor after adding the changelog
+ entry, default is 'always'
+ --editor-cmd=EDITOR_CMD
+ Editor command to use
+
+ naming:
+ branch names, tag formats, directory and file naming
+
+ --packaging-branch=PACKAGING_BRANCH
+ Branch the packaging is being maintained on, rpm
+ counterpart of the 'debian-branch' option, default is
+ 'master'
+ --packaging-tag=PACKAGING_TAG
+ Format string for packaging tags, RPM counterpart of
+ the 'debian-tag' option, default is
+ 'packaging/%(version)s'
+ --packaging-dir=PACKAGING_DIR
+ Subdir for RPM packaging files, default is ''
+ --changelog-file=CHANGELOG_FILE
+ Changelog file to be used, default is 'auto'
+ --spec-file=SPEC_FILE
+ Spec file to use, causes the packaging-dir option to
+ be ignored, default is ''
+Usage: gbp doesnotmatter [options] action - maintain patches on a patch queue branch
+Ations:
+export Export the patch queue / devel branch associated to the
+ current branch into a patch series in and update the spec file
+import Create a patch queue / devel branch from spec file
+ and patches in current dir.
+rebase Switch to patch queue / devel branch associated to the current
+ branch and rebase against upstream.
+drop Drop (delete) the patch queue /devel branch associated to
+ the current branch.
+apply Apply a patch
+switch Switch to patch-queue branch and vice versa.
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ --patch-numbers Whether to number patch files, default is True
+ --no-patch-numbers negates '--patch-numbers'
+ -v, --verbose Verbose command execution
+ --force In case of import even import if the branch already
+ exists
+ --drop In case of 'export' drop the patch-queue branch after
+ export. Default is 'False'
+ --no-drop negates '--drop'
+ --color=COLOR Whether to use colored output, default is 'auto'
+ --color-scheme=COLOR_SCHEME
+ Colors to use in output (when color is enabled),
+ format is '<debug>:<info>:<warning>:<error>', e.g.
+ 'cyan:34::'. Numerical values and color names are
+ accepted, empty fields indicate using the default.
+ --tmp-dir=TMP_DIR Base directory under which temporary directories are
+ created, default is '/var/tmp/gbp/'
+ --abbrev=ABBREV abbreviate commits to this length. default is '7'
+ --upstream-tag=UPSTREAM_TAG
+ Format string for upstream tags, default is
+ 'upstream/%(version)s'
+ --spec-file=SPEC_FILE
+ Spec file to use, causes the packaging-dir option to
+ be ignored, default is ''
+ --packaging-dir=PACKAGING_DIR
+ Subdir for RPM packaging files, default is ''
+]]></system-out></testcase><testcase classname="tests.02_test_upstream_source_tar_unpack.TestUnpack" name="test_upstream_source_type" time="0.026"></testcase><testcase classname="tests.02_test_upstream_source_tar_unpack.TestUnpack" name="test_upstream_source_unpack" time="0.022"></testcase><testcase classname="tests.02_test_upstream_source_tar_unpack.TestUnpack" name="test_upstream_source_unpack_filtered" time="0.026"></testcase><testcase classname="tests.02_test_upstream_source_tar_unpack.TestUnpack" name="test_upstream_source_unpack_no_filter" time="0.026"></testcase><testcase classname="tests.03_test_dch_guess_version.TestGuessVersionFromUpstream" name="test_guess_epoch" time="0.117"></testcase><testcase classname="tests.03_test_dch_guess_version.TestGuessVersionFromUpstream" name="test_guess_mangled_upstream_tag" time="0.116"></testcase><testcase classname="tests.03_test_dch_guess_version.TestGuessVersionFromUpstream" name="test_guess_no_epoch" time="0.112"></testcase><testcase classname="tests.03_test_dch_guess_version.TestGuessVersionFromUpstream" name="test_guess_upstream_tag_clash_with_non_upstream_tag" time="0.138"></testcase><testcase classname="tests.03_test_dch_guess_version.TestGuessVersionFromUpstream" name="test_guess_upstream_tag_zero_release" time="0.115"></testcase><testcase classname="tests.03_test_dch_guess_version.TestGuessVersionFromUpstream" name="test_no_changelog" time="0.069"></testcase><testcase classname="tests.04_test_submodules" name="test_empty_has_submodules" time="0.000"></testcase><testcase classname="tests.04_test_submodules" name="test_add_files" time="0.012"></testcase><testcase classname="tests.04_test_submodules" name="test_add_submodule_files" time="0.028"></testcase><testcase classname="tests.04_test_submodules" name="test_add_submodule" time="0.059"></testcase><testcase classname="tests.04_test_submodules" name="test_has_submodules" time="0.009"></testcase><testcase classname="tests.04_test_submodules" name="test_get_submodules" time="0.014"></testcase><testcase classname="tests.04_test_submodules" name="test_dump_tree" time="0.062"></testcase><testcase classname="tests.04_test_submodules" name="test_create_tarballs" time="0.067"></testcase><testcase classname="tests.04_test_submodules" name="test_create_zip_archives" time="0.036"></testcase><testcase classname="tests.04_test_submodules" name="test_check_tarfiles" time="0.003"></testcase><testcase classname="tests.04_test_submodules" name="test_add_whitespace_submodule" time="0.060"></testcase><testcase classname="tests.04_test_submodules" name="test_get_more_submodules" time="0.030"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_auto_bzip2" time="0.001"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_bz" time="0.001"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_bz2" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_bzip2" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_gz" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_gzip" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_lzma" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_no_pristine_tar_no_orig" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_no_pristine_tar_with_multiple_origs" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_guess_comp_type_no_pristine_tar_with_orig" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_has_orig_multiple_false" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_has_orig_multiple_true" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_has_orig_single_false" time="0.000"></testcase><testcase classname="tests.05_test_detection.TestDetection" name="test_has_orig_single_true" time="0.000"></testcase><testcase classname="tests.06_test_upstream_source.TestDir" name="test_directory" time="0.000"></testcase><testcase classname="tests.06_test_upstream_source.TestTar" name="test_pack_filtered" time="0.157"></testcase><testcase classname="tests.06_test_upstream_source.TestTar" name="test_pack_tar" time="0.162"></testcase><testcase classname="tests.06_test_upstream_source.TestZip" name="test_unpack" time="0.014"></testcase><testcase classname="tests.07_test_fastimport" name="test_init_fastimport" time="0.003"></testcase><testcase classname="tests.07_test_fastimport" name="test_add_file" time="0.013"></testcase><testcase classname="tests.07_test_fastimport" name="test_add_symlink" time="0.019"></testcase><testcase classname="tests.07_test_fastimport" name="test_close" time="0.003"></testcase><testcase classname="tests.07_test_fastimport" name="test_result" time="0.008"></testcase><testcase classname="tests.08_test_patch.TestDep3Patch" name="test_encoding" time="0.010"></testcase><testcase classname="tests.08_test_patch.TestPatch" name="test_filename" time="0.010"></testcase><testcase classname="tests.08_test_patch.TestPatch" name="test_header" time="0.004"></testcase><testcase classname="tests.09_test_git_repository.TestHasBranch" name="test_has_branch" time="0.049"></testcase><testcase classname="tests.09_test_git_repository.TestWriteTree" name="test_commit_tree" time="0.047"></testcase><testcase classname="tests.09_test_git_repository.TestWriteTree" name="test_write_tree" time="0.036"></testcase><testcase classname="tests.09_test_git_repository.TestWriteTree" name="test_write_tree_index_nonexistent" time="0.034"></testcase><testcase classname="tests.10_test_get_upstream_tree.TestGetUpstreamTree" name="test_invalid_tag" time="0.043"></testcase><testcase classname="tests.10_test_get_upstream_tree.TestGetUpstreamTree" name="test_invalid_tree" time="0.038"></testcase><testcase classname="tests.10_test_get_upstream_tree.TestGetUpstreamTree" name="test_invalid_upstream_branch" time="0.034"></testcase><testcase classname="tests.10_test_get_upstream_tree.TestGetUpstreamTree" name="test_valid_tag" time="0.047"></testcase><testcase classname="tests.10_test_get_upstream_tree.TestGetUpstreamTree" name="test_valid_tree" time="0.043"></testcase><testcase classname="tests.10_test_get_upstream_tree.TestGetUpstreamTree" name="test_valid_upstream_branch" time="0.049"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_create_changelog" time="0.462"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_closes_default" time="0.504"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_closes_non_debian_bug_numbers" time="0.589"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_git_author" time="0.450"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_increment_debian_version" time="0.486"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_increment_debian_version_with_auto" time="0.379"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_increment_debian_version_with_auto_release" time="0.466"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_increment_debian_version_with_auto_snapshot" time="0.434"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_increment_debian_version_with_release" time="0.484"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_increment_debian_version_with_snapshot" time="0.448"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_meta_closes_and_bug_numbers" time="0.580"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version" time="0.386"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_2_snapshots_auto" time="0.755"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_2_snapshots_auto_distribution" time="0.755"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_2_snapshots_commit_auto" time="0.938"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_2_snapshots_commit_auto_distribution" time="0.945"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_auto" time="0.387"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_auto_release" time="0.474"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_auto_snapshot" time="0.439"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_distribution" time="0.400"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_release" time="0.484"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_release_distribution" time="0.480"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_release_urgency" time="0.480"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_snapshot" time="0.444"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_snapshot_distribution" time="0.436"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_snapshot_release" time="0.118"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_snapshot_urgency" time="0.434"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_new_upstream_version_with_urgency" time="0.386"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_no_git_author" time="0.437"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_main_unreleased_debian_version_with_snapshot" time="0.707"></testcase><testcase classname="tests.11_test_dch_main.TestScriptDch" name="test_dch_subdir" time="0.380"></testcase><testcase classname="tests.12_test_deb.Test10DscNonNativeFile" name="test_dscfile_parse" time="0.001"></testcase><testcase classname="tests.12_test_deb.Test30DscFile" name="test_dscfile_parse" time="0.001"></testcase><testcase classname="tests.12_test_deb.TestDeb" name="test_get_arch" time="0.004"></testcase><testcase classname="tests.12_test_deb.TestDeb" name="test_get_vendor" time="0.033"></testcase><testcase classname="tests.12_test_deb.TestDpkgCompareVersions" name="testBadVersion" time="0.009"></testcase><testcase classname="tests.12_test_deb.TestDpkgCompareVersions" name="testGreaterThen" time="0.022"></testcase><testcase classname="tests.12_test_deb.TestDpkgCompareVersions" name="testLessThen" time="0.009"></testcase><testcase classname="tests.12_test_deb.TestDpkgCompareVersions" name="testSameVersion" time="0.022"></testcase><testcase classname="tests.13_test_gbp_pq.TestApplyAndCommit" name="test_apply_and_commit_patch" time="0.060"></testcase><testcase classname="tests.13_test_gbp_pq.TestApplyAndCommit" name="test_apply_and_commit_patch_preserve_subject" time="0.065"></testcase><testcase classname="tests.13_test_gbp_pq.TestApplyAndCommit" name="test_debian_missing_author" time="0.081"></testcase><testcase classname="tests.13_test_gbp_pq.TestApplyAndCommit" name="test_name" time="0.063"></testcase><testcase classname="tests.13_test_gbp_pq.TestApplyAndCommit" name="test_topic" time="0.065"></testcase><testcase classname="tests.13_test_gbp_pq.TestApplySinglePatch" name="test_apply_single_patch" time="0.077"></testcase><testcase classname="tests.13_test_gbp_pq.TestExport" name="test_commit" time="0.207"></testcase><testcase classname="tests.13_test_gbp_pq.TestExport" name="test_commit_dropped_patches" time="0.161"></testcase><testcase classname="tests.13_test_gbp_pq.TestExport" name="test_drop" time="0.119"></testcase><testcase classname="tests.13_test_gbp_pq.TestFromTAG" name="test_adding_patch" time="0.794"><system-out><![CDATA[rm 'debian/changelog'
+rm 'debian/control'
+Current branch patch-queue/master is up to date.
+[bar 7410805] added bar
+ Author: Gbp Tests <tests@example.com>
+ Date: Fri Aug 17 15:49:39 2018 +0200
+ 1 file changed, 1 insertion(+)
+ create mode 100644 bar
+Current branch patch-queue/master is up to date.
+]]></system-out></testcase><testcase classname="tests.13_test_gbp_pq.TestFromTAG" name="test_empty" time="0.403"><system-out><![CDATA[rm 'debian/changelog'
+rm 'debian/control'
+Current branch patch-queue/master is up to date.
+]]></system-out></testcase><testcase classname="tests.13_test_gbp_pq.TestParseGbpCommand" name="test_empty_body" time="0.001"></testcase><testcase classname="tests.13_test_gbp_pq.TestParseGbpCommand" name="test_filter_cmd" time="0.001"></testcase><testcase classname="tests.13_test_gbp_pq.TestParseGbpCommand" name="test_noarg_cmd" time="0.000"></testcase><testcase classname="tests.13_test_gbp_pq.TestWritePatch" name="test_generate_patches" time="0.123"></testcase><testcase classname="tests.13_test_gbp_pq.TestWritePatch" name="test_generate_patches_with_name_clashes" time="0.169"></testcase><testcase classname="tests.13_test_gbp_pq.TestWritePatch" name="test_generate_renumbered_patches" time="0.118"></testcase><testcase classname="tests.14_test_gbp_import_dscs.TestImportDscs" name="test_import_fail_first" time="0.037"></testcase><testcase classname="tests.14_test_gbp_import_dscs.TestImportDscs" name="test_import_fail_second" time="0.047"></testcase><testcase classname="tests.14_test_gbp_import_dscs.TestImportDscs" name="test_import_success" time="0.047"></testcase><testcase classname="tests.15_test_DebianSource.TestDebianSource" name="test_control" time="0.019"></testcase><testcase classname="tests.15_test_DebianSource.TestDebianSource" name="test_cur_dir_not_toplevel" time="0.120"></testcase><testcase classname="tests.15_test_DebianSource.TestDebianSource" name="test_is_native_fallback_file" time="0.068"></testcase><testcase classname="tests.15_test_DebianSource.TestDebianSource" name="test_is_native_file_3_file" time="0.022"></testcase><testcase classname="tests.15_test_DebianSource.TestDebianSource" name="test_is_native_file_3_git" time="0.052"></testcase><testcase classname="tests.15_test_DebianSource.TestDebianSource" name="test_is_not_releasable" time="0.066"></testcase><testcase classname="tests.15_test_DebianSource.TestDebianSource" name="test_is_releasable" time="0.068"></testcase><testcase classname="tests.16_test_supercommand.TestSuperCommand" name="test_help_command" time="0.000"><system-out><![CDATA[
+Usage:
+ gbp <command> [<args>]
+
+The most commonly used commands are:
+
+ buildpackage - build a Debian package
+ import-orig - import a new upstream tarball
+ import-dsc - import a single Debian source package
+ import-dscs - import multiple Debian source packages
+
+Use '--list-cmds' to list all available commands.
+
+]]></system-out></testcase><testcase classname="tests.16_test_supercommand.TestSuperCommand" name="test_import" time="0.001"></testcase><testcase classname="tests.16_test_supercommand.TestSuperCommand" name="test_invalid_command" time="0.000"><system-out><![CDATA[
+Usage:
+ gbp <command> [<args>]
+
+The most commonly used commands are:
+
+ buildpackage - build a Debian package
+ import-orig - import a new upstream tarball
+ import-dsc - import a single Debian source package
+ import-dscs - import multiple Debian source packages
+
+Use '--list-cmds' to list all available commands.
+
+
+Usage:
+ gbp <command> [<args>]
+
+The most commonly used commands are:
+
+ buildpackage - build a Debian package
+ import-orig - import a new upstream tarball
+ import-dsc - import a single Debian source package
+ import-dscs - import multiple Debian source packages
+
+Use '--list-cmds' to list all available commands.
+
+]]></system-out></testcase><testcase classname="tests.16_test_supercommand.TestSuperCommand" name="test_list_commands" time="0.001"></testcase><testcase classname="tests.16_test_supercommand.TestSuperCommand" name="test_missing_arg" time="0.000"><system-out><![CDATA[
+Usage:
+ gbp <command> [<args>]
+
+The most commonly used commands are:
+
+ buildpackage - build a Debian package
+ import-orig - import a new upstream tarball
+ import-dsc - import a single Debian source package
+ import-dscs - import multiple Debian source packages
+
+Use '--list-cmds' to list all available commands.
+
+]]></system-out></testcase><testcase classname="tests.17_test_dch_guess_documented_commit.TestGuessDocumentedCommit" name="test_01_from_snapshot_banner" time="0.063"></testcase><testcase classname="tests.17_test_dch_guess_documented_commit.TestGuessDocumentedCommit" name="test_02_from_tag" time="0.095"></testcase><testcase classname="tests.17_test_dch_guess_documented_commit.TestGuessDocumentedCommit" name="test_03_from_changelog_commit" time="0.109"></testcase><testcase classname="tests.17_test_dch_guess_documented_commit.TestGuessDocumentedCommit" name="test_04_not_touched" time="0.098"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_default" time="0.150"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_get_config_file_value" time="0.015"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_new_overrides_git" time="0.059"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_param_list" time="0.013"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_short_option" time="0.026"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_short_option_with_prefix" time="0.014"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_single_gbp_override" time="0.032"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_single_git_override" time="0.032"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_single_git_override_disabled_deprecations" time="0.031"></testcase><testcase classname="tests.18_test_Config.TestConfigParser" name="test_single_override" time="0.043"></testcase><testcase classname="tests.19_test_gbp_scripts_config.TestGbpConfigCommand" name="test_invocation_missing_value" time="0.025"></testcase><testcase classname="tests.19_test_gbp_scripts_config.TestGbpConfigCommand" name="test_invocation_single_value" time="0.030"><system-out><![CDATA[auto
+]]></system-out></testcase><testcase classname="tests.19_test_gbp_scripts_config.TestGbpConfigCommand" name="test_nonexistent_cmds" time="0.081"></testcase><testcase classname="tests.19_test_gbp_scripts_config.TestGbpConfigCommand" name="test_print_cmd_all_values" time="0.181"></testcase><testcase classname="tests.19_test_gbp_scripts_config.TestGbpConfigCommand" name="test_print_cmd_single_value_default" time="0.012"></testcase><testcase classname="tests.19_test_gbp_scripts_config.TestGbpConfigCommand" name="test_print_cmd_single_value_empty_default" time="0.024"></testcase><testcase classname="tests.19_test_gbp_scripts_config.TestGbpConfigCommand" name="test_print_cmd_single_value_override" time="0.013"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_changelog" time="0.005"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_modifying" time="0.012"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_modifying_err" time="0.004"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_parse_raw" time="0.003"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_patch_series" time="0.006"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_patch_series_autosetup" time="0.003"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_patch_series_quirks" time="0.008"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_quirks" time="0.006"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_spec" time="0.004"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_spec_2" time="0.004"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_spec_3" time="0.002"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_spec_4" time="0.002"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_tags" time="0.025"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_update_spec" time="0.006"></testcase><testcase classname="tests.20_test_rpm.TestSpecFile" name="test_update_spec2" time="0.005"></testcase><testcase classname="tests.20_test_rpm.TestSrcRpmFile" name="test_srpm" time="0.000"></testcase><testcase classname="tests.20_test_rpm.TestSrcRpmFile" name="test_srpm_2" time="0.000"></testcase><testcase classname="tests.20_test_rpm.TestSrcRpmFile" name="test_unpack_srpm" time="0.021"></testcase><testcase classname="tests.20_test_rpm.TestUtilityFunctions" name="test_guess_spec" time="0.005"></testcase><testcase classname="tests.20_test_rpm.TestUtilityFunctions" name="test_guess_spec_repo" time="0.092"></testcase><testcase classname="tests.21_test_command_wrappers.TestCommandWrapperFailures" name="test_log_default_error_msg" time="0.002"></testcase><testcase classname="tests.21_test_command_wrappers.TestCommandWrapperFailures" name="test_log_quote_format" time="0.001"></testcase><testcase classname="tests.21_test_command_wrappers.TestCommandWrapperFailures" name="test_log_use_err_or_reason_for_error_messge_error" time="0.001"></testcase><testcase classname="tests.21_test_command_wrappers.TestCommandWrapperFailures" name="test_log_use_err_or_reason_for_error_messge_reason" time="0.010"></testcase><testcase classname="tests.21_test_command_wrappers.TestCommandWrapperFailures" name="test_log_use_stderr_for_err_message" time="0.005"></testcase><testcase classname="tests.21_test_command_wrappers.TestCommandWrapperFailures" name="test_log_use_stdout_for_err_message" time="0.001"></testcase><testcase classname="tests.21_test_command_wrappers.TestCommandWrapperFailures" name="test_no_log_on_success" time="0.001"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_debian_master" time="0.061"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_debian_native" time="0.042"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_debian_sid" time="0.056"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_debian_suite" time="0.060"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_no_vendor" time="0.056"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_no_vendor_sid" time="0.065"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_too_many_slashes" time="0.081"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_vendor_sid" time="0.055"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_dep14_vendor_suite" time="0.054"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageDep14" name="test_get_pbuilder_dist_no_dep14" time="0.035"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageSetupPbuilder" name="test_setup_pbuilder" time="0.015"></testcase><testcase classname="tests.22_test_gbp_buildpackage.TestGbpBuildpackageSetupPbuilder" name="test_setup_pbuilder_arch" time="0.024"></testcase><testcase classname="tests.23_test_dch_extract_bts_cmds.TestExtractBTSCmds" name="test_debian_commands" time="0.000"></testcase><testcase classname="tests.23_test_dch_extract_bts_cmds.TestExtractBTSCmds" name="test_nondebian_commands" time="0.001"></testcase><testcase classname="tests.24_test_gbp_import_orig.TestImportOrigDownload" name="test_200_download" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.24_test_gbp_import_orig.TestImportOrigDownload" name="test_404_download" time="0.000"><skipped type="unittest.case.SkipTest" message="network tests disabled"><![CDATA[Exception: network tests disabled
+]]></skipped></testcase><testcase classname="tests.24_test_gbp_import_orig.TestIs30Quilt" name="test_30_quilt" time="0.044"></testcase><testcase classname="tests.24_test_gbp_import_orig.TestIs30Quilt" name="test_30_quilt_empty_repo" time="0.027"></testcase><testcase classname="tests.24_test_gbp_import_orig.TestIs30Quilt" name="test_no_format" time="0.019"></testcase><testcase classname="tests.24_test_gbp_import_orig.TestIs30Quilt" name="test_no_quilt" time="0.026"></testcase><testcase classname="tests.24_test_gbp_import_orig.TestMergeModeReplace" name="testDebianDir" time="0.112"></testcase><testcase classname="tests.25_test_broken_gbp_conf.TestBrokenConfig" name="testBrokenConf" time="0.152"></testcase><testcase classname="tests.26_test_dch_extract_thanks.TestExtractThanks" name="test_debian_commands" time="0.000"></testcase><testcase classname="tests.27_test_create_remote_repo.TestGbpCreateRemoteRepoCommand" name="test_list_config_templates" time="0.012"></testcase><testcase classname="tests.27_test_create_remote_repo.TestGbpCreateRemoteRepoCommand" name="test_no_config_templates" time="0.020"></testcase><testcase classname="tests.28_test_gbp_git_repository_commit_dir.TestGitRepositoryCommitDir" name="test_long_msg_854333" time="0.068"></testcase><testcase classname="tests.28_test_gbp_git_repository_commit_dir.TestGitRepositoryCommitDir" name="test_long_reflog" time="0.050"></testcase><testcase classname="tests.28_test_gbp_git_repository_commit_dir.TestGitRepositoryCommitDir" name="test_simple" time="0.048"></testcase><testcase classname="tests.29_test_gbp_clone.TestGbpClone" name="test_vcs_git_url" time="0.047"></testcase><testcase classname="tests.30_test_deb_changelog.Test" name="test_changelog_creation_full" time="0.122"></testcase><testcase classname="tests.30_test_deb_changelog.Test" name="test_changelog_creation_package" time="0.129"></testcase><testcase classname="tests.30_test_deb_changelog.Test" name="test_changelog_creation_version" time="0.133"></testcase><testcase classname="tests.30_test_deb_changelog.Test" name="test_changelog_exists" time="0.078"></testcase><testcase classname="tests.30_test_deb_changelog.Test" name="test_changelog_missing_dir" time="0.078"></testcase><testcase classname="tests.30_test_deb_changelog.TestQuoting" name="test_comma" time="0.052"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelog" name="basic_test" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelog" name="test_add_section" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogEntry" name="test_str_format" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogHeader" name="test_container" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogHeader" name="test_str_format" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogHeader" name="test_str_format_err" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogParser" name="test_parse_authors" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogParser" name="test_parse_changelog" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogParser" name="test_parse_changelog_fail" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogParser" name="test_parse_changelog_file" time="0.001"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogParser" name="test_parse_section_fail" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogSection" name="test_append_entry" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogSection" name="test_set_header" time="0.000"></testcase><testcase classname="tests.30_test_rpm_changelog.TestChangelogSection" name="test_str_format" time="0.000"></testcase><testcase classname="tests.31_test_uscan.TestUscan" name="test_uscan" time="0.001"></testcase><testcase classname="tests.test_RollbackDebianGitRepository.TestRollbackGitRepository" name="test_empty_rollback" time="0.024"></testcase><testcase classname="tests.test_RollbackDebianGitRepository.TestRollbackGitRepository" name="test_rrr_branch" time="0.020"></testcase><testcase classname="tests.test_RollbackDebianGitRepository.TestRollbackGitRepository" name="test_rrr_merge" time="0.024"></testcase><testcase classname="tests.test_RollbackDebianGitRepository.TestRollbackGitRepository" name="test_rrr_merge_abort" time="0.027"></testcase><testcase classname="tests.test_RollbackDebianGitRepository.TestRollbackGitRepository" name="test_rrr_tag" time="0.029"></testcase><testcase classname="tests.test_RollbackDebianGitRepository.TestRollbackGitRepository" name="test_rrr_unknown_action" time="0.020"></testcase></testsuite> \ No newline at end of file
diff --git a/packaging/git-buildpackage.spec b/packaging/git-buildpackage.spec
new file mode 100644
index 0000000..a41f4d4
--- /dev/null
+++ b/packaging/git-buildpackage.spec
@@ -0,0 +1,265 @@
+# Add --without docs rpmbuild option, i.e. docs are enabled by default
+%bcond_without docs
+
+Name: git-buildpackage
+Summary: Build packages from git
+Version: 0.9.10
+Release: 0
+Group: Development/Tools/Building
+License: GPLv2
+BuildArch: noarch
+URL: https://honk.sigxcpu.org/piki/projects/git-buildpackage/
+Source0: %{name}_%{version}.tar.gz
+
+# Conditional package names for requirements
+%if 0%{?fedora} || 0%{?centos_ver} >= 7
+%define dpkg_pkg_name dpkg-dev
+%else
+%if 0%{?centos_ver}
+%define dpkg_pkg_name dpkg-devel
+%else
+%define dpkg_pkg_name dpkg
+%endif
+%endif
+
+%if 0%{?fedora}
+%define man_pkg_name man-db
+%else
+%define man_pkg_name man
+%endif
+
+%if 0%{?suse_version}
+%define python_pkg_name python-base
+%else
+%define python_pkg_name python
+%endif
+
+%if 0%{?tizen_version:1}
+%define rpm_python_pkg_name python-rpm
+%else
+%define rpm_python_pkg_name rpm-python
+%endif
+
+Requires: %{name}-common = %{version}-%{release}
+Requires: %{dpkg_pkg_name}
+Requires: devscripts
+BuildRequires: python3
+BuildRequires: python3-setuptools
+
+%if %{with docs}
+BuildRequires: docbook2x
+BuildRequires: gtk-doc
+BuildRequires: libxslt-tools
+%if 0%{?fedora}
+BuildRequires: perl-podlators
+%endif
+%endif
+
+%if 0%{?do_unittests}
+BuildRequires: python3-coverage
+BuildRequires: python3-mock
+BuildRequires: python3-nose
+BuildRequires: git-core
+BuildRequires: %{man_pkg_name}
+BuildRequires: %{dpkg_pkg_name}
+BuildRequires: devscripts
+BuildRequires: rpm-build
+BuildRequires: %{rpm_python_pkg_name}
+BuildRequires: pristine-tar
+BuildRequires: unzip
+BuildRequires: /usr/bin/zipmerge
+BuildRequires: gnupg
+# Missing dep of dpkg in openSUSE
+%if 0%{?suse_version}
+BuildRequires: perl-TimeDate
+%endif
+%endif
+
+%description
+Set of tools from Debian that integrate the package build system with Git.
+This package contains the original Debian tools.
+
+
+%package common
+Summary: Common files for git-buildpackage debian and rpm tools
+Group: Development/Tools/Building
+Requires: git-core
+Requires: %{man_pkg_name}
+Requires: %{python_pkg_name}
+Requires: python3-setuptools
+Requires: python3-dateutil
+%if 0%{?centos_ver} && 0%{?centos_ver} <= 7
+Requires: unzip
+Requires: /usr/bin/zipmerge
+%else
+Recommends: unzip
+Recommends: /usr/bin/zipmerge
+Recommends: pristine-tar
+%endif
+
+%description common
+Common files and documentation, used by both git-buildpackage debian and rpm tools
+
+
+%package rpm
+Summary: Build RPM packages from git
+Group: Development/Tools/Building
+Requires: %{name}-common = %{version}-%{release}
+Requires: rpm
+Requires: %{rpm_python_pkg_name}
+%if 0%{?centos_ver} && 0%{?centos_ver} <= 7
+Requires: rpm-build
+%else
+Recommends: rpm-build
+%endif
+
+%description rpm
+Set of tools from Debian that integrate the package build system with Git.
+This package contains the tools for building RPM packages.
+
+%if %{with docs}
+%package doc
+Summary: Documentation for the git-buildpackage suite
+Group: Development/Tools/Building
+
+%description doc
+This package contains documentation for the git-buildpackage suite - both the
+Debian and the RPM tool set.
+%endif
+
+
+%prep
+%setup -q -n %{name}-%{version}
+
+
+
+%build
+WITHOUT_NOSETESTS=1 %{__python3} ./setup.py build
+
+%if %{with docs}
+# HTML docs
+HAVE_SGML2X=0 make -C docs/
+%endif
+
+
+%if 0%{?do_unittests}
+%check
+GIT_CEILING_DIRECTORIES=%{_builddir} \
+ GIT_AUTHOR_EMAIL=rpmbuild@example.com GIT_AUTHOR_NAME=rpmbuild \
+ GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL \
+ %{__python3} setup.py nosetests
+%endif
+
+
+%install
+rm -rf %{buildroot}
+WITHOUT_NOSETESTS=1 %{__python3} ./setup.py install --root=%{buildroot} --prefix=/usr --install-lib=%{python_sitelib}
+find %{buildroot} -name __pycache__ | xargs rm -r
+mkdir -p %{buildroot}/usr/share/%{name}
+mv %{buildroot}/usr/bin/gbp-builder-mock %{buildroot}/usr/share/%{name}/
+mkdir -p %{buildroot}/%{_sysconfdir}/git-buildpackage/
+mv %{buildroot}/usr/share/%{name}/gbp.conf %{buildroot}/%{_sysconfdir}/git-buildpackage/
+
+%if %{with docs}
+# Install man pages
+install -d %{buildroot}%{_mandir}/man1 %{buildroot}%{_mandir}/man5
+install docs/*.1 %{buildroot}%{_mandir}/man1/
+install docs/*.5 %{buildroot}%{_mandir}/man5/
+
+# Install html documentation
+mkdir -p %{buildroot}%{_docdir}/%{name}
+cp -r docs/manual-html %{buildroot}%{_docdir}/%{name}
+%endif
+
+cat > files.list << EOF
+%{_bindir}/git-pbuilder
+%{python_sitelib}/gbp/deb
+%{python_sitelib}/gbp/scripts/pq.py*
+%{python_sitelib}/gbp/scripts/buildpackage.py*
+%{python_sitelib}/gbp/scripts/dch.py*
+%{python_sitelib}/gbp/scripts/export_orig.py*
+%{python_sitelib}/gbp/scripts/import_dsc.py*
+%{python_sitelib}/gbp/scripts/import_dscs.py*
+%{python_sitelib}/gbp/scripts/import_orig.py*
+%{python_sitelib}/gbp/scripts/create_remote_repo.py*
+EOF
+
+%if %{with docs}
+cat >> files.list << EOF
+%{_mandir}/man1/gbp-buildpackage.1*
+%{_mandir}/man1/gbp-create-remote-repo.1*
+%{_mandir}/man1/gbp-dch.1*
+%{_mandir}/man1/gbp-export-orig.1*
+%{_mandir}/man1/gbp-import-dsc.1*
+%{_mandir}/man1/gbp-import-dscs.1*
+%{_mandir}/man1/gbp-import-orig.1*
+%{_mandir}/man1/gbp-pq.1*
+%{_mandir}/man1/git-pbuilder.1*
+EOF
+%endif
+
+# Disable the Debian tools for old CentOS
+%if 0%{?centos_ver} && 0%{?centos_ver} < 7
+for f in `cat files.list`; do
+ rm -rfv %{buildroot}/$f
+done
+
+%else
+
+%files -f files.list
+%defattr(-,root,root,-)
+%endif
+
+%files common
+%defattr(-,root,root,-)
+%{_bindir}/gbp
+%dir %{python_sitelib}/gbp
+%dir %{python_sitelib}/gbp/git
+%dir %{python_sitelib}/gbp/pkg
+%dir %{python_sitelib}/gbp/scripts
+%dir %{python_sitelib}/gbp/scripts/common
+%{python_sitelib}/gbp-*
+%{python_sitelib}/gbp/*.py*
+%{python_sitelib}/gbp/scripts/__init__.py*
+%{python_sitelib}/gbp/scripts/clone.py*
+%{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/tag.py*
+%{python_sitelib}/gbp/scripts/common/*.py*
+%{python_sitelib}/gbp/git/*.py*
+%{python_sitelib}/gbp/pkg/*.py*
+%config %{_sysconfdir}/git-buildpackage
+%if %{with docs}
+%{_mandir}/man1/gbp.1*
+%{_mandir}/man1/gbp-clone.1*
+%{_mandir}/man1/gbp-config.1*
+%{_mandir}/man1/gbp-pristine-tar.1*
+%{_mandir}/man1/gbp-pull.1*
+%{_mandir}/man1/gbp-push.1*
+%{_mandir}/man1/gbp-tag.1*
+%{_mandir}/man5/*.5*
+%endif
+
+
+%files rpm
+%defattr(-,root,root,-)
+%dir %{python_sitelib}/gbp/rpm
+%{python_sitelib}/gbp/scripts/*rpm*.py*
+%{python_sitelib}/gbp/rpm/*py*
+/usr/share/git-buildpackage/gbp-builder-mock
+%if %{with docs}
+%{_mandir}/man1/gbp-buildpackage-rpm.1*
+%{_mandir}/man1/gbp-pq-rpm.1*
+%{_mandir}/man1/gbp-import-srpm.1*
+%{_mandir}/man1/gbp-rpm-ch.1*
+%endif
+
+
+%if %{with docs}
+%files doc
+%defattr(-,root,root,-)
+%{_docdir}/%{name}/
+%endif
diff --git a/packaging/run-in-docker b/packaging/run-in-docker
new file mode 100755
index 0000000..9fdaf2f
--- /dev/null
+++ b/packaging/run-in-docker
@@ -0,0 +1,128 @@
+#!/bin/sh
+
+set -eu
+
+this="$(readlink -e ${0})"
+
+# assign defaults
+project_dir="$(dirname $(dirname ${this}))"
+debian_tag="${DEBIAN_TAG:-sid}"
+
+usage() {
+ cat << EOF
+Usage: $(basename ${this}) [OPTION]... [ACTION]
+Run tests or create package in a docker container
+
+Options:
+ -h print this usage and exit
+ -C PROJECT_DIR change to PROJECT_DIR (default: ${project_dir})
+ -t DEBIAN_TAG debian release tag (default: ${debian_tag})
+
+Actions:
+ help print this usage and exit
+ package create debian package artifacts
+ test run tests (default)
+
+Example:
+ $(basename ${this}) -C ~/code/git-buildpackage -t stretch package
+EOF
+}
+
+die() { { echo "ERROR: ${@}"; usage; } >&2; exit 1; }
+
+# convenience wrapper
+docker_build() {
+ docker build \
+ --force-rm=${DOCKER_BUILD_FORCE_RM:-false} \
+ --no-cache=${DOCKER_BUILD_NO_CACHE:-false} \
+ ${@}
+}
+
+# take path to project dir; build docker image for base
+base_build() {
+ (
+ cd ${project_dir}
+ [ -f .gitignore ] && cat .gitignore
+ [ -f .gitmodules ] && sed -nr 's|\s+path = (.+)|\1|gp' .gitmodules
+ cat <<EOF
+${this##${PWD}/}
+*.buildinfo
+*.changes
+*.deb
+*.dsc
+*.tar.xz
+.dockerignore
+.git*
+EOF
+ ) >${project_dir}/.dockerignore
+ docker_build \
+ --pull=${DOCKER_BUILD_PULL:-false} \
+ --build-arg=FROM_IMAGE=debian:${debian_tag} \
+ -t gbp-base:${debian_tag} -f- ${project_dir} <<'EOF'
+arg FROM_IMAGE
+from ${FROM_IMAGE}
+env DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true
+workdir /workdir/project
+run set -euvx \
+ && apt-get update -y \
+ && apt-get -y --no-install-recommends install \
+ build-essential devscripts equivs
+copy . .
+run yes | mk-build-deps -ir
+run groupadd luser && useradd -g luser luser && chown -R luser:luser ..
+user luser
+EOF
+ rm -vf ${project_dir}/.dockerignore
+}
+
+
+# run tests
+gbp_test() {
+ base_build
+ for L in C.UTF-8 C; do
+ docker run --rm -ie"TEST_LOCALE=${L}" gbp-base:${debian_tag} sh <<'EOF'
+set -euvx
+make all+net
+make -C docs
+EOF
+ done
+}
+
+# create debian package artifacts, copy to host
+gbp_package() {
+ base_build
+ docker_build \
+ --build-arg=FROM_IMAGE=gbp-base:${debian_tag} \
+ -t gbp-package:${debian_tag} -f- ${project_dir} <<'EOF'
+arg FROM_IMAGE
+from ${FROM_IMAGE}
+run dpkg-buildpackage -j$(nproc) -sa -us -uc
+EOF
+ docker run --rm -iu0:0 \
+ --mount type=bind,source="${PWD}",target=/mnt/host-volume \
+ gbp-package:${debian_tag} sh <<EOF
+set -euvx
+find .. -maxdepth 1 -mindepth 1 -type f \
+ -exec chown -v $(id -u):$(id -g) {} + \
+ -a -exec cp -vat /mnt/host-volume {} +
+EOF
+}
+
+while getopts ":hC:t:" opt; do
+ case $opt in
+ h) usage; exit 0;;
+ C) project_dir="$(readlink -e ${OPTARG})"
+ [ -d "${project_dir}" ] || die "bad project dir ${OPTARG}";;
+ t) debian_tag="${OPTARG}";;
+ :) die "missing argument: -${OPTARG}";;
+ \?) die "bad option: -${OPTARG}";;
+ esac
+done
+
+shift $((${OPTIND} - 1))
+case "${1:-test}" in
+ 'help') usage; exit 0;;
+ 'test') gbp_test; exit ${?};;
+ 'package') gbp_package; exit ${?};;
+ \?) die "bad action: ${1}";;
+esac
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0f08daa
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+python-dateutil
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..fd6cc5f
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,13 @@
+[nosetests]
+with-doctest=1
+with-xunit=1
+cover-package=gbp
+cover-erase=1
+exe=1
+verbosity=2
+
+[flake8]
+# E501: ignore line length
+# E265: block comment should start with '# '
+ignore=E501,E265
+builtins=unicode,execfile,raw_input
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..f505b39
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python3
+# vim: set fileencoding=utf-8 :
+# Copyright (C) 2006-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, please see
+# <http://www.gnu.org/licenses/>
+# END OF COPYRIGHT #
+
+import subprocess
+from setuptools import setup, find_packages
+import os
+
+
+def fetch_version():
+ """Get version from debian changelog and write it to gbp/version.py"""
+ version = "0.0"
+
+ try:
+ popen = subprocess.Popen('dpkg-parsechangelog', stdout=subprocess.PIPE)
+ out, ret = popen.communicate()
+ for line in out.decode('utf-8').split('\n'):
+ if line.startswith('Version:'):
+ version = line.split(' ')[1].strip()
+ break
+ except OSError:
+ pass # Failing is fine, we just can't print the version then
+
+ with open('gbp/version.py', 'w') as f:
+ f.write('"The current gbp version number"\n')
+ f.write('gbp_version = "%s"\n' % version)
+
+ return version
+
+
+def readme():
+ with open('README.md') as file:
+ return file.read()
+
+
+def setup_requires():
+ if os.getenv('WITHOUT_NOSETESTS'):
+ return []
+ else:
+ return ['nose>=0.11.1', 'coverage>=2.85', 'nosexcover>=1.0.7']
+
+
+setup(name="gbp",
+ version=fetch_version(),
+ author=u'Guido Günther',
+ author_email='agx@sigxcpu.org',
+ url='https://honk.sigxcpu.org/piki/projects/git-buildpackage/',
+ description='Suite to help with Debian packages in Git repositories',
+ license='GPLv2+',
+ long_description=readme(),
+ classifiers=[
+ 'Environment :: Console',
+ 'Programming Language :: Python :: 3',
+ 'Topic :: Software Development :: Version Control',
+ 'Operating System :: POSIX :: Linux',
+ ],
+ scripts=['bin/git-pbuilder',
+ 'bin/gbp-builder-mock'],
+ packages=find_packages(exclude=['tests', 'tests.*']),
+ data_files=[("share/git-buildpackage/", ["gbp.conf"]), ],
+ requires=['dateutil'],
+ install_requires=[
+ 'python-dateutil',
+ ],
+ setup_requires=setup_requires(),
+ python_requires='>=3.5',
+ entry_points={
+ 'console_scripts': ['gbp=gbp.scripts.supercommand:supercommand'],
+ },
+ )
diff --git a/tests/01_test_help.py b/tests/01_test_help.py
new file mode 100644
index 0000000..525c03b
--- /dev/null
+++ b/tests/01_test_help.py
@@ -0,0 +1,37 @@
+# vim: set fileencoding=utf-8 :
+
+"""Check if --help works"""
+
+from . import context # noqa: F401
+
+from .testutils.data import TestCaseWithData
+
+
+class TestHelp(TestCaseWithData):
+ """Test help output of gbp commands"""
+
+ deb_cmds = ['buildpackage',
+ 'config',
+ 'create_remote_repo',
+ 'dch',
+ 'import_orig',
+ 'import_dsc',
+ 'pristine_tar',
+ 'pull',
+ 'push',
+ 'pq',
+ 'tag']
+
+ rpm_cmds = ['buildpackage_rpm',
+ 'import_srpm',
+ 'rpm_ch',
+ 'pq_rpm']
+
+ @TestCaseWithData.feed(deb_cmds + rpm_cmds)
+ def testHelp(self, script):
+ module = 'gbp.scripts.%s' % script
+ m = __import__(module, globals(), locals(), ['main'], 0)
+ with self.assertRaises(SystemExit):
+ m.main(['doesnotmatter', '--help'])
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/02_test_upstream_source_tar_unpack.py b/tests/02_test_upstream_source_tar_unpack.py
new file mode 100644
index 0000000..7ce0286
--- /dev/null
+++ b/tests/02_test_upstream_source_tar_unpack.py
@@ -0,0 +1,81 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{UpstreamSource}'s tarball unpack"""
+
+from . import context
+
+import os
+import tarfile
+import unittest
+
+import gbp.pkg
+
+
+class TestUnpack(unittest.TestCase):
+ """Make sure we unpack gzip and bzip2 archives correctly"""
+ archive_prefix = "archive"
+
+ def _unpack_dir(self, compression):
+ return "%s-%s" % (self.archive_prefix, compression)
+
+ def _check_files(self, files, comp):
+ """Check if files exist in the unpacked dir"""
+ for f in files:
+ target = os.path.join(self._unpack_dir(comp), f)
+ assert os.path.exists(target), "%s does not exist" % target
+
+ def _create_archive(self, comp):
+ filelist = ['README.md', 'setup.py']
+
+ name = "%s_0.1.tar.%s" % (self.archive_prefix, comp)
+ t = tarfile.open(name=name, mode='w:%s' % comp)
+ for f in filelist:
+ t.add(os.path.join(self.top, f),
+ os.path.join(self._unpack_dir(comp), f))
+ t.close()
+ return name, filelist
+
+ def setUp(self):
+ self.dir = context.new_tmpdir(__name__)
+ self.top = context.projectdir
+ context.chdir(self.dir)
+ self.archives = {}
+ for ext in ["gz", "bz2"]:
+ self.archives[ext] = self._create_archive(ext)
+
+ def tearDown(self):
+ context.teardown()
+
+ def test_upstream_source_type(self):
+ for (comp, archive) in self.archives.items():
+ source = gbp.pkg.UpstreamSource(archive[0])
+ assert source.is_orig() is True
+ assert source.is_dir() is False
+ assert source.unpacked is None
+ source.unpack(".")
+ assert source.is_orig() is True
+ assert source.is_dir() is False
+ assert type(source.unpacked) == str
+
+ def test_upstream_source_unpack(self):
+ for (comp, archive) in self.archives.items():
+ source = gbp.pkg.UpstreamSource(archive[0])
+ source.unpack(".")
+ self._check_files(archive[1], comp)
+
+ def test_upstream_source_unpack_no_filter(self):
+ for (comp, archive) in self.archives.items():
+ source = gbp.pkg.UpstreamSource(archive[0])
+ source.unpack(".", [])
+ self._check_files(archive[1], comp)
+
+ def test_upstream_source_unpack_filtered(self):
+ exclude = "README.md"
+
+ for (comp, archive) in self.archives.items():
+ source = gbp.pkg.UpstreamSource(archive[0])
+ source.unpack(".", [exclude])
+ archive[1].remove(exclude)
+ self._check_files(archive[1], comp)
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/03_test_dch_guess_version.py b/tests/03_test_dch_guess_version.py
new file mode 100644
index 0000000..37f6da3
--- /dev/null
+++ b/tests/03_test_dch_guess_version.py
@@ -0,0 +1,136 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{Changelog}'s guess_version_from_upstream"""
+
+from . import context # noqa: F401
+from . import testutils
+
+from gbp.scripts import dch
+
+
+class TestGuessVersionFromUpstream(testutils.DebianGitTestRepo):
+ """Test guess_version_from_upstream"""
+
+ def test_guess_no_epoch(self):
+ """Guess the new version from the upstream tag"""
+ cp = testutils.MockedChangeLog('1.0-1')
+ tagformat = 'upstream/%(version)s'
+ uversion = '1.1'
+ upstream_branch = 'upstream'
+
+ self.add_file('doesnot', 'matter')
+ self.repo.create_branch('upstream')
+ tag = self.repo.version_to_tag(tagformat, uversion)
+ self.repo.create_tag(name=tag, msg="Upstream release %s" % uversion,
+ sign=False)
+ self.repo.set_branch("master")
+ guessed = dch.guess_version_from_upstream(self.repo,
+ tagformat,
+ upstream_branch,
+ cp)
+ self.assertEqual('1.1-1', guessed)
+
+ def test_guess_epoch(self):
+ """Check if we picked up the epoch correctly (#652366)"""
+ cp = testutils.MockedChangeLog('1:1.0-1')
+
+ tagformat = 'upstream/%(version)s'
+ uversion = '1.1'
+ upstream_branch = 'upstream'
+
+ self.add_file('doesnot', 'matter')
+ self.repo.create_branch('upstream')
+ tag = self.repo.version_to_tag(tagformat, uversion)
+ self.repo.create_tag(name=tag, msg="Upstream release %s" % uversion,
+ sign=False)
+
+ self.repo.set_branch("master")
+ guessed = dch.guess_version_from_upstream(self.repo,
+ tagformat,
+ upstream_branch,
+ cp)
+
+ self.assertEqual('1:1.1-1', guessed)
+
+ def test_guess_upstream_tag_clash_with_non_upstream_tag(self):
+ """Guess with clashing upstream- and non-upstream-tag"""
+ cp = testutils.MockedChangeLog('0.9-1')
+
+ tagformat = 'v%(version)s'
+ uversion = '1.0'
+ upstream_branch = 'upstream'
+
+ self.add_file('doesnot', 'matter')
+ self.repo.create_branch('upstream')
+ tag = self.repo.version_to_tag(tagformat, uversion)
+ self.repo.create_tag(name=tag, msg="Upstream release %s" % uversion,
+ sign=False)
+ self.repo.set_branch("master")
+ self.add_file("clash", "bar")
+ self.repo.create_tag("vyatta/something", msg="some non-upstream tag but not package release tag either")
+ self.add_file("clash2", "bar")
+
+ guessed = dch.guess_version_from_upstream(self.repo,
+ tagformat,
+ upstream_branch,
+ cp)
+
+ self.assertEqual('1.0-1', guessed)
+
+ def test_guess_upstream_tag_zero_release(self):
+ """Guess with existing -0... releases"""
+ cp = testutils.MockedChangeLog('0.9-0vyatta2')
+
+ tagformat = 'upstream/%(version)s'
+ uversion = '0.9'
+ upstream_branch = 'upstream'
+
+ self.add_file('doesnot', 'matter')
+ self.repo.create_branch('upstream')
+ tag = self.repo.version_to_tag(tagformat, uversion)
+ self.repo.create_tag(name=tag, msg="Upstream release %s" % uversion,
+ sign=False)
+ self.repo.set_branch('master')
+ self.add_file('doesnot2', 'matter')
+
+ guessed = dch.guess_version_from_upstream(self.repo,
+ tagformat,
+ upstream_branch,
+ cp)
+ self.assertEqual(None, guessed)
+
+ def test_guess_mangled_upstream_tag(self):
+ """Guess the new version from the upstream tag using a mangled tag format"""
+ cp = testutils.MockedChangeLog('1.0-1')
+ tagformat = 'upstream/%(version%~%-)s'
+ uversion = '1.1~rc1'
+ upstream_branch = 'upstream'
+
+ self.add_file('doesnot', 'matter')
+ self.repo.create_branch('upstream')
+ tag = self.repo.version_to_tag(tagformat, uversion)
+ self.repo.create_tag(name=tag, msg="Upstream release %s" % uversion,
+ sign=False)
+ self.repo.set_branch("master")
+ guessed = dch.guess_version_from_upstream(self.repo,
+ tagformat,
+ upstream_branch,
+ cp)
+ self.assertEqual('1.1~rc1-1', guessed)
+
+ def test_no_changelog(self):
+ tagformat = 'upstream/%(version)s'
+ uversion = '1.1'
+ upstream_branch = 'upstream'
+
+ self.add_file('doesnot', 'matter')
+ self.repo.create_branch('upstream')
+ tag = self.repo.version_to_tag(tagformat, uversion)
+ self.repo.create_tag(name=tag, msg="Upstream release %s" % uversion,
+ sign=False)
+ self.repo.set_branch("master")
+ guessed = dch.guess_version_from_upstream(self.repo,
+ tagformat,
+ upstream_branch,
+ None)
+ self.assertEqual('1.1-1', guessed)
diff --git a/tests/04_test_submodules.py b/tests/04_test_submodules.py
new file mode 100644
index 0000000..52e15dd
--- /dev/null
+++ b/tests/04_test_submodules.py
@@ -0,0 +1,193 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test submodule L{GitRepository} submodule methods"""
+
+from . import context
+
+import os
+import shutil
+import tarfile
+from nose.tools import eq_, ok_
+
+import gbp.log
+import gbp.git
+import gbp.command_wrappers
+
+from gbp.deb.policy import DebianPkgPolicy as Policy
+from gbp.deb.git import DebianGitRepository
+from gbp.pkg import Compressor
+
+from gbp.scripts import buildpackage
+from tests.testutils import ls_zip
+
+REPO = None
+REPODIR = None
+
+SUBMODULES = []
+SUBMODULE_NAMES = ["test_submodule", "sub module"]
+TMPDIR = None
+TESTFILE_NAME = "testfile"
+TESTDIR_NAME = "testdir"
+
+
+class Submodule(object):
+ """Class representing remote repo for Git submodule"""
+ def __init__(self, name, tmpdir):
+ self.name = name
+ self.dir = os.path.join(tmpdir, name)
+ self.repo = gbp.git.GitRepository.create(self.dir)
+
+
+def setup():
+ """Test module setup"""
+ global REPO, REPODIR, SUBMODULES, TMPDIR
+
+ TMPDIR = context.new_tmpdir(__name__)
+ REPODIR = TMPDIR.join('test_repo')
+ REPO = DebianGitRepository.create(REPODIR)
+
+ for name in SUBMODULE_NAMES:
+ SUBMODULES.append(Submodule(name, str(TMPDIR)))
+
+ context.chdir(REPODIR)
+
+
+def teardown():
+ """Test module teardown"""
+ context.teardown()
+
+
+def test_empty_has_submodules():
+ """Test empty repo for submodules"""
+ ok_(not REPO.has_submodules())
+
+
+def _add_dummy_data(repo, msg):
+ """Commit dummy data to a Git repository"""
+ shutil.copy(".git/HEAD", TESTFILE_NAME)
+ os.mkdir(TESTDIR_NAME)
+ shutil.copy(TESTFILE_NAME, os.path.join(TESTDIR_NAME, TESTFILE_NAME))
+ repo.add_files('.', force=True)
+ repo.commit_all(msg)
+
+
+def test_add_files():
+ """Add some dummy data"""
+ _add_dummy_data(REPO, "initial commit")
+ ok_(True)
+
+
+def test_add_submodule_files():
+ """Add some dummy data"""
+ for submodule in SUBMODULES:
+ os.chdir(submodule.dir)
+ _add_dummy_data(submodule.repo, "initial commit in submodule")
+ os.chdir(REPODIR)
+ ok_(True)
+
+
+def test_add_submodule():
+ """Add a submodule"""
+ REPO.add_submodule(SUBMODULES[0].dir)
+ REPO.commit_all(msg='Added submodule %s' % SUBMODULES[0].dir)
+
+
+def test_has_submodules():
+ """Check for submodules"""
+ ok_(REPO.has_submodules())
+ ok_(REPO.has_submodules('HEAD'))
+ ok_(not REPO.has_submodules('HEAD^'))
+
+
+def test_get_submodules():
+ """Check for submodules list of (name, hash)"""
+ modules = REPO.get_submodules("master")[0]
+ eq_(modules[0], 'test_submodule')
+ eq_(len(modules[1]), 40)
+
+
+def test_dump_tree():
+ """Dump the repository and check if files exist"""
+ dumpdir = TMPDIR.join("dump")
+ os.mkdir(dumpdir)
+ ok_(buildpackage.dump_tree(REPO, dumpdir, "master", True))
+ ok_(os.path.exists(os.path.join(dumpdir, TESTFILE_NAME)))
+ ok_(os.path.exists(os.path.join(dumpdir, TESTDIR_NAME, TESTFILE_NAME)))
+ ok_(os.path.exists(os.path.join(dumpdir, SUBMODULES[0].name,
+ TESTFILE_NAME)))
+ # No submodules or subdirs if recursive is False
+ dumpdir = TMPDIR.join("dump2")
+ os.mkdir(dumpdir)
+ ok_(buildpackage.dump_tree(REPO, dumpdir, "master", True, False))
+ ok_(os.path.exists(os.path.join(dumpdir, TESTFILE_NAME)))
+ ok_(not os.path.exists(os.path.join(dumpdir, TESTDIR_NAME)))
+ ok_(not os.path.exists(os.path.join(dumpdir, SUBMODULES[0].name)))
+
+
+def test_create_tarballs():
+ """Create an upstream tarball"""
+ class MockedSource:
+ def __init__(self, version):
+ self.name = 'test'
+ self.upstream_version = version
+
+ def upstream_tarball_name(self, compression, component=None):
+ return Policy.build_tarball_name(self.name,
+ self.upstream_version,
+ compression=compression)
+
+ comp = Compressor('bzip2')
+ # Tarball with submodules
+ s = MockedSource('0.1')
+ ok_(REPO.create_upstream_tarball_via_git_archive(s, str(TMPDIR), "HEAD", comp,
+ with_submodules=True))
+ # Tarball without submodules
+ s = MockedSource('0.2')
+ ok_(REPO.create_upstream_tarball_via_git_archive(s, str(TMPDIR), "HEAD", comp,
+ with_submodules=False))
+
+
+def test_create_zip_archives():
+ """Create an upstream zip archive"""
+ REPO.archive_comp('HEAD', 'with-submodules.zip', 'test',
+ None, format='zip', submodules=True)
+ # Check that submodules were included
+ contents = ls_zip('with-submodules.zip')
+ ok_('test/test_submodule/testfile' in contents)
+
+ REPO.archive_comp('HEAD', 'without-submodules.zip', 'test',
+ None, format='zip', submodules=False)
+ contents = ls_zip('without-submodules.zip')
+ ok_('test/test_submodule/testfile' not in contents)
+
+
+def test_check_tarfiles():
+ """Check the contents of the created tarfile"""
+ # Check tarball with submodules
+ tarobj = tarfile.open(TMPDIR.join("test_0.1.orig.tar.bz2"), 'r:*')
+ files = tarobj.getmembers()
+ ok_("test-0.1/.gitmodules" in [f.name for f in files])
+ eq_(len(files), 10)
+ # Check tarball without submodules
+ tarobj = tarfile.open(TMPDIR.join("test_0.2.orig.tar.bz2"), 'r:*')
+ files = tarobj.getmembers()
+ ok_(("test-0.2/%s" % TESTFILE_NAME) in [f.name for f in files])
+ eq_(len(files), 6)
+
+
+def test_add_whitespace_submodule():
+ """Add a second submodule with name containing whitespace"""
+ REPO.add_submodule(SUBMODULES[1].dir)
+ REPO.commit_all(msg='Added submodule %s' % SUBMODULES[0].dir)
+
+
+def test_get_more_submodules():
+ """Check for submodules list of (name, hash)"""
+ module = REPO.get_submodules("master")
+ eq_(len(module), len(SUBMODULE_NAMES))
+ for module in REPO.get_submodules("master"):
+ eq_(len(module[1]), 40)
+ ok_(os.path.basename(module[0]) in SUBMODULE_NAMES)
+
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/05_test_detection.py b/tests/05_test_detection.py
new file mode 100644
index 0000000..5e903db
--- /dev/null
+++ b/tests/05_test_detection.py
@@ -0,0 +1,138 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test tarball compression type detection"""
+
+from . import context
+
+import unittest
+
+from gbp.deb.source import DebianSource
+
+from gbp.scripts import export_orig
+from gbp.deb import DebianPkgPolicy
+from gbp.errors import GbpError
+
+
+class MockGitRepository:
+ def __init__(self, with_branch=False, subject=None):
+ self.with_branch = with_branch
+ self.subject = subject
+
+ def has_pristine_tar_branch(self):
+ return self.with_branch
+
+ def pristine_tar_branch(self):
+ 'pristine-tar'
+
+ def grep_log(self, regex, branch, merges=True):
+ return None
+
+ def get_commit_info(self, commit):
+ return {'subject': self.subject}
+
+
+class MockedSource(DebianSource):
+ def __init__(self):
+ pass
+
+ def is_native(self):
+ return False
+
+ @property
+ def name(self):
+ return 'source'
+
+ @property
+ def upstream_version(self):
+ return '1.2'
+
+
+class TestDetection(unittest.TestCase):
+ def setUp(self):
+ self.source = MockedSource()
+ self.tmpdir = context.new_tmpdir(__name__)
+
+ def tearDown(self):
+ context.teardown()
+
+ def test_guess_comp_type_no_pristine_tar_no_orig(self):
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'auto', self.source, repo, str(self.tmpdir))
+ self.assertEqual('gzip', guessed)
+
+ def test_guess_comp_type_no_pristine_tar_with_orig(self):
+ open(self.tmpdir.join('source_1.2.orig.tar.bz2'), "w").close()
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'auto', self.source, repo, str(self.tmpdir))
+ self.assertEqual('bzip2', guessed)
+
+ def test_guess_comp_type_no_pristine_tar_with_multiple_origs(self):
+ open(self.tmpdir.join('source_1.2.orig.tar.gz'), "w").close()
+ open(self.tmpdir.join('source_1.2.orig.tar.xz'), "w").close()
+ repo = MockGitRepository(with_branch=False)
+ with self.assertRaises(GbpError):
+ export_orig.guess_comp_type('auto', self.source, repo, str(self.tmpdir))
+
+ def test_guess_comp_type_auto_bzip2(self):
+ subject = 'pristine-tar data for source_1.2-3.orig.tar.bz2'
+ repo = MockGitRepository(with_branch=True, subject=subject)
+ guessed = export_orig.guess_comp_type(
+ 'auto', self.source, repo, str(self.tmpdir))
+ self.assertEqual("bzip2", guessed)
+
+ def test_has_orig_single_false(self):
+ self.assertFalse(DebianPkgPolicy.has_origs([self.source.upstream_tarball_name('gzip')], str(self.tmpdir)))
+
+ def test_has_orig_single_true(self):
+ open(self.tmpdir.join('source_1.2.orig.tar.gz'), "w").close()
+ self.assertTrue(DebianPkgPolicy.has_origs([self.source.upstream_tarball_name('gzip')], str(self.tmpdir)))
+
+ def test_has_orig_multiple_false(self):
+ orig_files = [self.source.upstream_tarball_name('gzip')] + \
+ [self.source.upstream_tarball_name('gzip', sub) for sub in ['foo', 'bar']]
+ self.assertFalse(DebianPkgPolicy.has_origs(orig_files, str(self.tmpdir)))
+
+ def test_has_orig_multiple_true(self):
+ for ext in ['', '-foo', '-bar']:
+ open(self.tmpdir.join('source_1.2.orig%s.tar.gz' % ext), "w").close()
+ orig_files = [self.source.upstream_tarball_name('gzip')] + \
+ [self.source.upstream_tarball_name('gzip', sub) for sub in ['foo', 'bar']]
+ self.assertTrue(DebianPkgPolicy.has_origs(orig_files, str(self.tmpdir)))
+
+ def test_guess_comp_type_bzip2(self):
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'bzip2', self.source, repo, None)
+ self.assertEqual("bzip2", guessed)
+
+ def test_guess_comp_type_gzip(self):
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'gzip', self.source, repo, None)
+ self.assertEqual("gzip", guessed)
+
+ def test_guess_comp_type_bz(self):
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'xz', self.source, repo, None)
+ self.assertEqual("xz", guessed)
+
+ def test_guess_comp_type_lzma(self):
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'lzma', self.source, repo, None)
+ self.assertEqual("lzma", guessed)
+
+ def test_guess_comp_type_bz2(self):
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'bz2', self.source, repo, None)
+ self.assertEqual("bzip2", guessed)
+
+ def test_guess_comp_type_gz(self):
+ repo = MockGitRepository(with_branch=False)
+ guessed = export_orig.guess_comp_type(
+ 'gz', self.source, repo, None)
+ self.assertEqual("gzip", guessed)
diff --git a/tests/06_test_upstream_source.py b/tests/06_test_upstream_source.py
new file mode 100644
index 0000000..18f6fbd
--- /dev/null
+++ b/tests/06_test_upstream_source.py
@@ -0,0 +1,96 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test the L{UpstreamSource} class"""
+
+from . import context
+
+import glob
+import os
+import tarfile
+import unittest
+import zipfile
+
+from gbp.pkg import UpstreamSource
+
+
+class TestDir(unittest.TestCase):
+ def setUp(self):
+ self.tmpdir = context.new_tmpdir(__name__)
+ self.upstream_dir = self.tmpdir.join('test-1.0')
+ os.mkdir(self.upstream_dir)
+
+ def test_directory(self):
+ """Upstream source is a directory"""
+ source = UpstreamSource(self.upstream_dir)
+ self.assertEqual(source.is_orig(), False)
+ self.assertEqual(source.path, self.upstream_dir)
+ self.assertEqual(source.unpacked, self.upstream_dir)
+ self.assertEqual(source.guess_version(), ('test', '1.0'))
+
+ def tearDown(self):
+ context.teardown()
+
+
+class TestTar(unittest.TestCase):
+ """Test if packing tar archives works"""
+ def _check_tar(self, us, positive=[], negative=[]):
+ t = tarfile.open(name=us.path, mode="r:bz2")
+ for f in positive:
+ i = t.getmember(f)
+ self.assertEqual(type(i), tarfile.TarInfo)
+
+ for f in negative:
+ try:
+ t.getmember(f)
+ self.fail("Found %s in archive" % f)
+ except KeyError:
+ pass
+ t.close()
+
+ def setUp(self):
+ self.tmpdir = context.new_tmpdir(__name__)
+ self.source = UpstreamSource(os.path.join(context.projectdir, "gbp"))
+
+ def tearDown(self):
+ context.teardown()
+
+ def test_pack_tar(self):
+ """Check if packing tar archives works"""
+ target = self.tmpdir.join("gbp_0.1.tar.bz2")
+ repacked = self.source.pack(target)
+ self.assertEqual(repacked.is_orig(), True)
+ self.assertEqual(repacked.is_dir(), False)
+ self.assertEqual(repacked.guess_version(), ('gbp', '0.1'))
+ self._check_tar(repacked, ["gbp/errors.py", "gbp/__init__.py"])
+
+ def test_pack_filtered(self):
+ """Check if filtering out files works"""
+ target = self.tmpdir.join("gbp_0.1.tar.bz2")
+ repacked = self.source.pack(target, ["__init__.py"])
+ self.assertEqual(repacked.is_orig(), True)
+ self.assertEqual(repacked.is_dir(), False)
+ self._check_tar(repacked, ["gbp/errors.py"],
+ ["gbp/__init__.py"])
+
+
+class TestZip(unittest.TestCase):
+ """Test if unpacking zip archives works"""
+ def setUp(self):
+ self.tmpdir = context.new_tmpdir(__name__)
+ self.zipfile = self.tmpdir.join("gbp-0.1.zip")
+ z = zipfile.ZipFile(self.zipfile, "w")
+ for f in glob.glob(os.path.join(context.projectdir, "gbp/*.py")):
+ z.write(f, f, zipfile.ZIP_DEFLATED)
+ z.close()
+
+ def tearDown(self):
+ context.teardown()
+
+ def test_unpack(self):
+ source = UpstreamSource(self.zipfile)
+ self.assertEqual(source.is_orig(), False)
+ self.assertEqual(source.is_dir(), False)
+ self.assertEqual(source.unpacked, None)
+ self.assertEqual(source.guess_version(), ('gbp', '0.1'))
+ source.unpack(str(self.tmpdir))
+ self.assertNotEqual(source.unpacked, None)
diff --git a/tests/07_test_fastimport.py b/tests/07_test_fastimport.py
new file mode 100644
index 0000000..c3ca818
--- /dev/null
+++ b/tests/07_test_fastimport.py
@@ -0,0 +1,66 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{FastImport} class"""
+
+from . import context
+
+import os
+
+import gbp.log
+import gbp.git
+
+repo = None
+fastimport = None
+tf_name = 'testfile'
+tl_name = 'a_testlink'
+
+
+def setup():
+ global repo
+
+ tmpdir = context.new_tmpdir(__name__)
+ repo = gbp.git.GitRepository.create(tmpdir.join('test_repo'))
+
+
+def teardown():
+ context.teardown()
+
+
+def test_init_fastimport():
+ """Create a fastimport object"""
+ global fastimport
+ fastimport = gbp.git.FastImport(repo)
+ assert fastimport, "Failed to init FastImport"
+
+
+def test_add_file():
+ """Add a file via fastimport"""
+ author = repo.get_author_info()
+ fastimport.start_commit('master', author, "a commit")
+ fastimport.deleteall()
+ testfile = os.path.join(repo.path, '.git', 'description')
+ fastimport.add_file(b'./testfile',
+ open(testfile, 'rb'),
+ os.path.getsize(testfile))
+
+
+def test_add_symlink():
+ """Add a symbolic link via fastimport"""
+ author = repo.get_author_info()
+ fastimport.start_commit('master', author, "a 2nd commit")
+ fastimport.add_symlink(tl_name, tf_name)
+
+
+def test_close():
+ fastimport.close()
+
+
+def test_result():
+ repo.force_head('master', hard=True)
+
+ testfile = os.path.join(repo.path, tf_name)
+ testlink = os.path.join(repo.path, tl_name)
+
+ assert os.path.exists(testfile), "%s doesn't exist" % testfile
+ assert os.path.lexists(testlink), "%s doesn't exist" % testlink
+ assert os.readlink(testlink) == tf_name
diff --git a/tests/08_test_patch.py b/tests/08_test_patch.py
new file mode 100644
index 0000000..3ed16eb
--- /dev/null
+++ b/tests/08_test_patch.py
@@ -0,0 +1,54 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{Patch} class"""
+
+from . import context # noqa: 401
+
+import os
+import unittest
+
+from gbp.patch_series import Patch, Dep3Patch
+
+
+class TestPatch(unittest.TestCase):
+ data_dir = os.path.splitext(__file__)[0] + '_data'
+
+ def test_filename(self):
+ """Get patch information from the filename"""
+ p = Patch(os.path.join(self.data_dir, "doesnotexist.diff"))
+ self.assertEqual('doesnotexist', p.subject)
+ self.assertEqual({}, p.info)
+ p = Patch(os.path.join(self.data_dir, "doesnotexist.patch"))
+ self.assertEqual('doesnotexist', p.subject)
+ p = Patch(os.path.join(self.data_dir, "doesnotexist"))
+ self.assertEqual('doesnotexist', p.subject)
+ self.assertEqual(None, p.author)
+ self.assertEqual(None, p.email)
+ self.assertEqual(None, p.date)
+
+ def test_header(self):
+ """Get the patch information from a patch header"""
+ patchfile = os.path.join(self.data_dir, "patch1.diff")
+ self.assertTrue(os.path.exists(patchfile))
+ p = Patch(patchfile)
+ self.assertEqual('This is patch1', p.subject)
+ self.assertEqual("foo", p.author)
+ self.assertEqual("foo@example.com", p.email)
+ self.assertEqual("This is the long description.\n"
+ "It can span several lines.\n",
+ p.long_desc)
+ self.assertEqual('Sat, 24 Dec 2011 12:05:53 +0100', p.date)
+
+
+class TestDep3Patch(unittest.TestCase):
+ data_dir = os.path.splitext(__file__)[0] + '_data'
+
+ def test_encoding(self):
+ """Make sure broken encoding does no affect import"""
+ patchfile = os.path.join(self.data_dir, "dep3-iso8859-1.patch")
+ self.assertTrue(os.path.exists(patchfile))
+ p = Dep3Patch(patchfile)
+ self.assertEqual('Replace all -- in man page by \-\- to make lintian happy.', p.subject)
+ self.assertEqual("Roland Rosenfeld", p.author)
+ self.assertEqual("roland@debian.org", p.email)
+ self.assertEqual("", p.long_desc)
diff --git a/tests/08_test_patch_data/dep3-iso8859-1.patch b/tests/08_test_patch_data/dep3-iso8859-1.patch
new file mode 100644
index 0000000..518ce55
--- /dev/null
+++ b/tests/08_test_patch_data/dep3-iso8859-1.patch
@@ -0,0 +1,15 @@
+Author: Roland Rosenfeld <roland@debian.org>
+Description: Replace all -- in man page by \-\- to make lintian happy.
+
+--- a/GNUmakefile.in
++++ b/GNUmakefile.in
+@@ -484,8 +484,7 @@ man: dok-release
+ perl -pi.bak -e "s/\[ /\[/g;s/á/\\\\['a]/g;s/é/\\\\['e]/g" $(MAN_PAGE); \
+ perl -pi.bak -e "s/ö/\\\\[:o]/g" $(MAN_PAGE); \
+ perl -pi.bak -e 's/([ {])-([a-z])/$$1\\-$$2/g' $(MAN_PAGE); \
+- perl -pi.bak -e 's/ --([a-z])/ \\-\\-$$1/g' $(MAN_PAGE); \
+- perl -pi.bak -e 's/\\fB--/\\fB\\-\\-/g' $(MAN_PAGE); \
++ perl -pi.bak -e 's/--/\\-\\-/g' $(MAN_PAGE); \
+ $(DB) ../privoxy-man-page.sgml && $(MV) -f $(MAN_PAGE) ../../../$(MAN_PAGE)
+
+ # For those with man2html ala RH7s.
diff --git a/tests/08_test_patch_data/patch1.diff b/tests/08_test_patch_data/patch1.diff
new file mode 100644
index 0000000..5521b73
--- /dev/null
+++ b/tests/08_test_patch_data/patch1.diff
@@ -0,0 +1,7 @@
+From: foo <foo@example.com>
+Date: Sat, 24 Dec 2011 12:05:53 +0100
+Subject: This is
+ patch1
+
+This is the long description.
+It can span several lines.
diff --git a/tests/09_test_git_repository.py b/tests/09_test_git_repository.py
new file mode 100644
index 0000000..ed487ce
--- /dev/null
+++ b/tests/09_test_git_repository.py
@@ -0,0 +1,81 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{GitRepository}
+
+Test things here that don't fit nicely into the doctests that
+also make up the API documentation.
+"""
+
+from . import context # noqa: 401
+from . import testutils
+
+import os
+
+import gbp.log
+import gbp.git
+import gbp.errors
+
+
+class TestWriteTree(testutils.DebianGitTestRepo):
+ def _write_testtree(self):
+ """Write a test tree"""
+ paths = []
+ for i in range(4):
+ path = os.path.join(self.repo.path, 'testfile%d' % i)
+ with open(path, 'w') as f:
+ print("testdata %d" % i, file=f)
+ paths.append(path)
+ return paths
+
+ def test_write_tree_index_nonexistent(self):
+ """Write out index file to non-existent dir"""
+ paths = self._write_testtree()
+ self.repo.add_files(paths)
+ with self.assertRaises(gbp.git.GitRepositoryError):
+ self.repo.write_tree('/does/not/exist')
+
+ def test_write_tree(self):
+ """Write out index file to alternate index file"""
+ index = os.path.join(self.repo.git_dir, 'gbp_index')
+ expected_sha1 = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
+
+ paths = self._write_testtree()
+ self.repo.add_files(paths)
+ sha1 = self.repo.write_tree(index)
+ self.assertTrue(os.path.exists(index))
+ self.assertEqual(sha1, expected_sha1)
+ self.assertTrue(self.repo.has_treeish(expected_sha1))
+
+ def test_commit_tree(self):
+ """Commit a tree"""
+ expected_sha1 = 'ea63fcee40675a5f82ea6bedbf29ca86d89c5f63'
+ paths = self._write_testtree()
+ self.repo.add_files(paths)
+ sha1 = self.repo.write_tree()
+ self.assertEqual(sha1, expected_sha1)
+ self.assertTrue(self.repo.has_treeish(expected_sha1))
+ commit = self.repo.commit_tree(sha1, "first commit", parents=[],
+ committer=dict(name='foo',
+ email='foo@example.com'),
+ author=dict(name='bar',
+ email='bar@example.com'),
+ )
+ self.assertEqual(len(commit), 40)
+ # commit the same tree again using the previous commit as parent
+ self.repo.commit_tree(sha1, "second commit", parents=[commit])
+ # commit the same tree again using a non-existent parent
+ with self.assertRaises(gbp.errors.GbpError):
+ self.repo.commit_tree(sha1, "failed commit", ['doesnotexist'])
+
+
+class TestHasBranch(testutils.DebianGitTestRepo):
+ def test_has_branch(self):
+ self.add_file('whatever')
+ self.repo.create_branch("foo")
+ self.assertTrue(self.repo.has_branch("foo"))
+ # Don't be too sloppy on (#813298)
+ self.repo.create_branch("refs/heads/bar")
+ self.assertFalse(self.repo.has_branch("bar"))
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/10_test_get_upstream_tree.py b/tests/10_test_get_upstream_tree.py
new file mode 100644
index 0000000..cd65057
--- /dev/null
+++ b/tests/10_test_get_upstream_tree.py
@@ -0,0 +1,76 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{export_orig}'s git_archive_get_upstream_tree method"""
+
+from . import context # noqa: 401
+from . import testutils
+
+import gbp.errors
+import gbp.scripts.export_orig as export_orig
+
+
+class MockOptions(object):
+ def __init__(self,
+ upstream_branch=None,
+ upstream_tree=None,
+ upstream_tag=None):
+ self.upstream_branch = upstream_branch
+ self.upstream_tree = upstream_tree
+ self.upstream_tag = upstream_tag
+
+
+class TestGetUpstreamTree(testutils.DebianGitTestRepo):
+ class source:
+ upstream_version = '1.0~rc3'
+
+ def test_valid_upstream_branch(self):
+ """Get upstream tree from a valid upstream branch"""
+ self.add_file('foo')
+ self.repo.create_branch('upstream')
+ options = MockOptions(upstream_tree='BRANCH',
+ upstream_branch='upstream')
+ t = export_orig.git_archive_get_upstream_tree(self.repo, None, options)
+ self.assertEqual(t, 'upstream')
+
+ def test_invalid_upstream_branch(self):
+ """Getting upstream tree from a invalid upstream branch must fail"""
+ self.add_file('foo')
+ options = MockOptions(upstream_tree='BRANCH',
+ upstream_branch='upstream')
+ with self.assertRaises(gbp.errors.GbpError):
+ export_orig.git_archive_get_upstream_tree(self.repo, None, options)
+
+ def test_valid_tree(self):
+ """Get upstream tree from a valid upstream tree"""
+ self.add_file('foo')
+ tree = self.repo.rev_parse('master')
+ options = MockOptions(upstream_tree=tree)
+ t = export_orig.git_archive_get_upstream_tree(self.repo, None, options)
+ self.assertEqual(t, tree)
+
+ def test_invalid_tree(self):
+ """Getting upstream tree from an invalid tree must fail"""
+ self.add_file('foo')
+ options = MockOptions(upstream_tree='doesnotexist')
+ with self.assertRaises(gbp.errors.GbpError):
+ export_orig.git_archive_get_upstream_tree(self.repo, None, options)
+
+ def test_valid_tag(self):
+ """Get upstream tree from a valid tag"""
+ self.add_file('foo')
+ self.repo.rev_parse('master')
+ self.repo.create_tag('upstream/1.0_rc3')
+ options = MockOptions(upstream_tree="TAG",
+ upstream_tag="upstream/%(version)s")
+ tag = export_orig.git_archive_get_upstream_tree(self.repo, self.source, options)
+ self.assertEqual(tag, "upstream/1.0_rc3")
+
+ def test_invalid_tag(self):
+ """Getting upstream tree from an invalid tag must fail"""
+ self.add_file('foo')
+ options = MockOptions(upstream_tree="TAG",
+ upstream_tag="upstream/%(version)s")
+ with self.assertRaises(gbp.errors.GbpError):
+ export_orig.git_archive_get_upstream_tree(self.repo, self.source, options)
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/11_test_dch_main.py b/tests/11_test_dch_main.py
new file mode 100644
index 0000000..7fc8260
--- /dev/null
+++ b/tests/11_test_dch_main.py
@@ -0,0 +1,413 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{gbp.scripts.dch} main"""
+
+from . import context
+from .testutils import (DebianGitTestRepo, OsReleaseFile, skip_without_cmd,
+ get_dch_default_urgency, capture_stderr)
+
+from gbp.scripts import dch
+
+import os
+import re
+
+# Older dch compatibility
+default_urgency = get_dch_default_urgency()
+
+# For Ubuntu compatibility
+os_release = OsReleaseFile('/etc/lsb-release')
+
+# OS release codename and snapshot of version 0.9-2~1
+if os_release['DISTRIB_ID'] == 'Ubuntu':
+ os_codename = os_release['DISTRIB_CODENAME']
+ snap_header_0_9 = r'^test-package\s\(0.9-1ubuntu1~1\.gbp([0-9a-f]{6})\)\sUNRELEASED;\surgency=%s' % default_urgency
+ new_version_0_9 = '0.9-1ubuntu1'
+else:
+ os_codename = 'unstable'
+ snap_header_0_9 = r'^test-package\s\(0.9-2~1\.gbp([0-9a-f]{6})\)\sUNRELEASED;\surgency=%s' % default_urgency
+ new_version_0_9 = '0.9-2'
+# Snapshot of version 1.0-1~1
+snap_header_1 = r'^test-package\s\(1.0-1~1\.gbp([0-9a-f]{6})\)\sUNRELEASED;\surgency=%s' % default_urgency
+# Snapshot of version 1.0-1~2
+snap_header_1_2 = r'^test-package\s\(1.0-1~2\.gbp([0-9a-f]{6})\)\sUNRELEASED;\surgency=%s' % default_urgency
+
+snap_mark = r'\s{2}\*{2}\sSNAPSHOT\sbuild\s@'
+
+deb_tag = "debian/0.9-1"
+deb_tag_msg = "Pre stable release version 0.9-1"
+
+cl_debian = """test-package (0.9-1) unstable; urgency=%s
+
+ [ Debian Maintainer ]
+ * New pre stable upstream release
+
+ -- Debian Maintainer <maint@debian.org> Mon, 17 Oct 2011 10:15:22 +0200
+""" % default_urgency
+
+
+@skip_without_cmd('debchange')
+class TestScriptDch(DebianGitTestRepo):
+ """Test git-dch"""
+
+ def setUp(self):
+ DebianGitTestRepo.setUp(self)
+ self.add_file("foo", "bar")
+ self.repo.create_tag("upstream/0.9", msg="upstream version 0.9")
+ self.add_file("bar", "foo")
+ self.repo.create_tag("upstream/1.0", msg="upstream version 1.0")
+ self.repo.create_branch("upstream")
+ self.repo.create_branch("debian")
+ self.repo.set_branch("debian")
+ self.upstream_tag = "upstream/%(version)s"
+ self.top = os.path.abspath(os.path.curdir)
+ os.mkdir(os.path.join(self.repo.path, "debian"))
+ context.chdir(self.repo.path)
+ self.add_file("debian/changelog", cl_debian)
+ self.add_file("debian/control", """Source: test-package\nSection: test\n""")
+ self.options = ["--upstream-tag=%s" % self.upstream_tag, "--debian-branch=debian",
+ "--upstream-branch=upstream", "--id-length=0", "--spawn-editor=/bin/true"]
+ self.repo.create_tag(deb_tag, msg=deb_tag_msg, commit="HEAD~1")
+ self.repo.set_user_name("gbp test user")
+ self.repo.set_user_email("gbp@example.com")
+
+ def tearDown(self):
+ DebianGitTestRepo.tearDown(self)
+
+ def run_dch(self, dch_options=None):
+ # Take care to copy the list
+ options = self.options[:]
+ if dch_options is not None:
+ options.extend(dch_options)
+ ret = dch.main(options)
+ self.assertEqual(ret, 0)
+ cl = os.path.join(self.repo.path, 'debian/changelog')
+ return open(cl, encoding='utf-8').readlines()
+
+ def test_dch_main_new_upstream_version(self):
+ """Test dch.py like gbp dch script does: new upstream version"""
+ lines = self.run_dch()
+ self.assertEqual("test-package (1.0-1) UNRELEASED; urgency=%s\n" % default_urgency, lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_release(self):
+ """Test dch.py like gbp dch script does: new upstream version - release"""
+ options = ["--release"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (1.0-1) %s; urgency=%s\n" % (os_codename, default_urgency), lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_auto(self):
+ """Test dch.py like gbp dch script does: new upstream version - guess last commit"""
+ options = ["--auto"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (1.0-1) UNRELEASED; urgency=%s\n" % default_urgency, lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_snapshot(self):
+ """Test dch.py like gbp dch script does: new upstream version - snapshot mode"""
+ options = ["--snapshot"]
+ lines = self.run_dch(options)
+ header = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header)
+ self.assertEqual(header.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_2_snapshots_auto(self):
+ """Test dch.py like gbp dch script does: new upstream version - two snapshots - auto"""
+ options = ["--snapshot"]
+ lines = self.run_dch(options)
+ header1 = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header1)
+ self.assertEqual(header1.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header1.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+ # New snapshot, use auto to guess last one
+ self.add_file("debian/compat", "9")
+ options.append("--auto")
+ lines = self.run_dch(options)
+ header2 = re.search(snap_header_1_2, lines[0])
+ self.assertIsNotNone(header2)
+ self.assertEqual(header2.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header2.group(1), lines[2]))
+ # First snapshot entry must be concatenated with the last one
+ self.assertNotIn(header1.group(0) + "\n", lines)
+ self.assertIn(""" * added debian/control\n""", lines)
+ self.assertIn(""" * added debian/compat\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_2_snapshots_commit_auto(self):
+ """Test dch.py like gbp dch script does: new upstream version - two committed snapshots - auto"""
+ options = ["--commit"]
+ options.append("--commit-msg=TEST-COMMITTED-SNAPSHOT")
+ options.append("--snapshot")
+ lines = self.run_dch(options)
+ header1 = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header1)
+ self.assertEqual(header1.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header1.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+ # New snapshot, use auto to guess last one
+ self.add_file("debian/compat", "9")
+ options.append("--auto")
+ lines = self.run_dch(options)
+ header2 = re.search(snap_header_1_2, lines[0])
+ self.assertIsNotNone(header2)
+ self.assertEqual(header2.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header2.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+ self.assertIn(""" * added debian/compat\n""", lines)
+ # First snapshot entry must have disappeared
+ self.assertNotIn(header1.group(0) + "\n", lines)
+ # But its changelog must be included in the new one
+ self.assertIn(""" * TEST-COMMITTED-SNAPSHOT\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_auto_release(self):
+ """Test dch.py like gbp dch script does: new upstream version - auto - release"""
+ options = ["--auto", "--release"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (1.0-1) %s; urgency=%s\n" % (os_codename, default_urgency), lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_auto_snapshot(self):
+ """Test dch.py like gbp dch script does: new upstream version - auto - snapshot mode"""
+ options = ["--auto", "--snapshot"]
+ options.append("--snapshot")
+ lines = self.run_dch(options)
+ header = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header)
+ self.assertEqual(header.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_snapshot_release(self):
+ """Test dch.py like gbp dch script does: new upstream version - snapshot - release"""
+ options = ["--snapshot", "--release"]
+ with capture_stderr() as c:
+ with self.assertRaises(SystemExit):
+ self.run_dch(options)
+ self.assertTrue("'--snapshot' and '--release' are incompatible options" in c.output())
+
+ def test_dch_main_new_upstream_version_with_distribution(self):
+ """Test dch.py like gbp dch script does: new upstream version - set distribution"""
+ options = ["--distribution=testing", "--force-distribution"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (1.0-1) testing; urgency=%s\n" % default_urgency, lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_release_distribution(self):
+ """Test dch.py like gbp dch script does: new upstream version - release - set distribution"""
+ options = ["--release", "--distribution=testing", "--force-distribution"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (1.0-1) testing; urgency=%s\n" % default_urgency, lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_snapshot_distribution(self):
+ """Test dch.py like gbp dch script does: new upstream version - snapshot mode - do not set distribution"""
+ options = ["--snapshot", "--distribution=testing"]
+ lines = self.run_dch(options)
+ header = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header)
+ self.assertEqual(header.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_2_snapshots_auto_distribution(self):
+ """Test dch.py like gbp dch script does: new upstream version - two snapshots - do not set distribution"""
+ options = ["--snapshot", "--distribution=testing"]
+ lines = self.run_dch(options)
+ header1 = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header1)
+ self.assertEqual(header1.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header1.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+ # New snapshot, use auto to guess last one
+ self.add_file("debian/compat", "9")
+ options.append("--auto")
+ lines = self.run_dch(options)
+ header2 = re.search(snap_header_1_2, lines[0])
+ self.assertIsNotNone(header2)
+ self.assertEqual(header2.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header2.group(1), lines[2]))
+ # First snapshot entry must be concatenated with the last one
+ self.assertNotIn(header1.group(0) + "\n", lines)
+ self.assertIn(""" * added debian/control\n""", lines)
+ self.assertIn(""" * added debian/compat\n""", lines)
+ # But its changelog must not be included in the new one since
+ # we do not commit
+ self.assertNotIn(""" * TEST-COMMITTED-SNAPSHOT\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_2_snapshots_commit_auto_distribution(self):
+ """Test dch.py like gbp dch script does: new upstream version - two committed snapshots - do not set distribution"""
+ options = ["--commit"]
+ options.append("--commit-msg=TEST-COMMITTED-SNAPSHOT")
+ options.append("--snapshot")
+ options.append("--distribution=testing")
+ lines = self.run_dch(options)
+ header1 = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header1)
+ self.assertEqual(header1.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header1.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+ # New snapshot, use auto to guess last one
+ self.add_file("debian/compat", "9")
+ options.append("--auto")
+ lines = self.run_dch(options)
+ header2 = re.search(snap_header_1_2, lines[0])
+ self.assertIsNotNone(header2)
+ self.assertEqual(header2.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header2.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+ self.assertIn(""" * added debian/compat\n""", lines)
+ # First snapshot entry must have disappeared
+ self.assertNotIn(header1.group(0) + "\n", lines)
+ # But its changelog must be included in the new one
+ self.assertIn(""" * TEST-COMMITTED-SNAPSHOT\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_urgency(self):
+ """Test dch.py like gbp dch script does: new upstream version - set urgency"""
+ options = ["--urgency=emergency"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (1.0-1) UNRELEASED; urgency=emergency\n", lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_release_urgency(self):
+ """Test dch.py like gbp dch script does: new upstream version - release - set urgency"""
+ options = ["--release", "--urgency=emergency"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (1.0-1) %s; urgency=emergency\n" % os_codename, lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_new_upstream_version_with_snapshot_urgency(self):
+ """Test dch.py like gbp dch script does: new upstream version - snapshot mode - set urgency"""
+ options = ["--snapshot", "--urgency=emergency"]
+ lines = self.run_dch(options)
+ header = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header)
+ self.assertEqual(header.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_increment_debian_version(self):
+ """Test dch.py like gbp dch script does: increment debian version"""
+ self.repo.delete_tag("debian/0.9-1")
+ self.repo.create_tag("debian/0.9-1", msg="Pre stable release version 0.9-1", commit="HEAD~2")
+ self.repo.delete_tag("upstream/1.0")
+ lines = self.run_dch()
+ self.assertEqual("test-package (%s) UNRELEASED; urgency=%s\n" % (new_version_0_9, default_urgency), lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_increment_debian_version_with_release(self):
+ """Test dch.py like gbp dch script does: increment debian version - release"""
+ self.repo.delete_tag("upstream/1.0")
+ options = ["--release"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (%s) %s; urgency=%s\n" % (new_version_0_9, os_codename, default_urgency), lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_increment_debian_version_with_auto(self):
+ """Test dch.py like gbp dch script does: increment debian version - guess last commit"""
+ self.repo.delete_tag("upstream/1.0")
+ options = ["--auto"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (%s) UNRELEASED; urgency=%s\n" % (new_version_0_9, default_urgency), lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_increment_debian_version_with_snapshot(self):
+ """Test dch.py like gbp dch script does: increment debian version - snapshot mode"""
+ self.repo.delete_tag("upstream/1.0")
+ options = ["--snapshot"]
+ lines = self.run_dch(options)
+ header = re.search(snap_header_0_9, lines[0])
+ self.assertIsNotNone(header)
+ self.assertEqual(header.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_increment_debian_version_with_auto_release(self):
+ """Test dch.py like gbp dch script does: increment debian version - auto - release"""
+ self.repo.delete_tag("upstream/1.0")
+ options = ["--auto", "--release"]
+ lines = self.run_dch(options)
+ self.assertEqual("test-package (%s) %s; urgency=%s\n" % (new_version_0_9, os_codename, default_urgency), lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_increment_debian_version_with_auto_snapshot(self):
+ """Test dch.py like gbp dch script does: increment debian version - auto - snapshot mode"""
+ self.repo.delete_tag("upstream/1.0")
+ options = ["--auto", "--snapshot"]
+ lines = self.run_dch(options)
+ header = re.search(snap_header_0_9, lines[0])
+ self.assertIsNotNone(header)
+ self.assertEqual(header.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_unreleased_debian_version_with_snapshot(self):
+ """Test dch.py like gbp dch script does: snapshot mode with unreleased debian version"""
+ new_version_1_0 = '1.0-1'
+ options = ["--commit"]
+ options.append("--commit-msg=UNRELEASED-version")
+ lines = self.run_dch()
+ header = re.search(r"\(%s\) UNRELEASED" % new_version_1_0, lines[0])
+ self.assertIsNotNone(header)
+ options = ["--snapshot", "--auto"]
+ lines = self.run_dch(options)
+ header = re.search(snap_header_1, lines[0])
+ self.assertIsNotNone(header)
+ self.assertEqual(header.lastindex, 1)
+ self.assertIsNotNone(re.search(snap_mark + header.group(1), lines[2]))
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_main_closes_default(self):
+ options = ["--meta"]
+ self.add_file("closes", "test file",
+ msg="""test debian closes commit\n\nCloses: #123456""")
+ lines = self.run_dch(options)
+ self.assertIn(""" * test debian closes commit (Closes: #123456)\n""",
+ lines)
+
+ def test_dch_main_closes_non_debian_bug_numbers(self):
+ self.add_file("closes", "test file",
+ msg="""test non-debian closes 1\n\nCloses: EX-123""")
+ self.add_file("closes1", "test file",
+ msg="""test non-debian closes 2\n\nCloses: EX-5678""")
+ options = ["--meta", '--meta-closes-bugnum=ex-\d+']
+ lines = self.run_dch(options)
+ self.assertIn(""" * test non-debian closes 1 (Closes: EX-123)\n""",
+ lines)
+ self.assertIn(""" * test non-debian closes 2 (Closes: EX-5678)\n""",
+ lines)
+
+ def test_dch_main_meta_closes_and_bug_numbers(self):
+ self.add_file("closes", "test file",
+ msg="""test non-debian closes 1\n\nExample: EX-123""")
+ self.add_file("closes1", "test file",
+ msg="""test non-debian closes 2\n\nExample: EX-5678""")
+ options = ["--meta", '--meta-closes-bugnum=ex-\d+',
+ '--meta-closes=Example']
+ lines = self.run_dch(options)
+ self.assertIn(""" * test non-debian closes 1 (Example: EX-123)\n""",
+ lines)
+ self.assertIn(""" * test non-debian closes 2 (Example: EX-5678)\n""",
+ lines)
+
+ def test_dch_main_git_author(self):
+ options = ["--git-author", '-S', '-a']
+ lines = self.run_dch(options)
+ self.assertIn("-- gbp test user <gbp@example.com>", lines[7] + lines[9])
+
+ def test_dch_main_no_git_author(self):
+ options = ["--no-git-author", '-S', '-a']
+ lines = self.run_dch(options)
+ self.assertNotIn("-- gbp test user", "\n".join(lines))
+
+ def test_dch_subdir(self):
+ os.chdir('debian/')
+ lines = self.run_dch()
+ self.assertEqual("test-package (1.0-1) UNRELEASED; urgency=%s\n" % default_urgency, lines[0])
+ self.assertIn(""" * added debian/control\n""", lines)
+
+ def test_dch_create_changelog(self):
+ os.unlink('debian/changelog')
+ lines = self.run_dch()
+ self.assertEqual("test-package (1.0-1) UNRELEASED; urgency=%s\n" % default_urgency, lines[0])
diff --git a/tests/12_test_deb.py b/tests/12_test_deb.py
new file mode 100644
index 0000000..4a2388e
--- /dev/null
+++ b/tests/12_test_deb.py
@@ -0,0 +1,177 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{gbp.deb}"""
+
+from . import context # noqa: 401
+from . import testutils
+
+import os
+import tempfile
+import platform
+import unittest
+
+import gbp.deb
+
+from gbp.deb.dscfile import DscFile
+from gbp.command_wrappers import CommandExecFailed
+
+
+class Test30DscFile(unittest.TestCase):
+ """Test L{gbp.deb.DscFile}"""
+
+ content = """Format: 3.0 (quilt)
+Source: libvirt
+Binary: libvirt-bin, libvirt0, libvirt0-dbg, libvirt-doc, libvirt-dev, python-libvirt
+Architecture: any all
+Version: 0.9.12-4
+Maintainer: Debian Libvirt Maintainers <pkg-libvirt-maintainers@lists.alioth.debian.org>
+Uploaders: Guido Günther <agx@sigxcpu.org>, Laurent Léonard <laurent@open-minds.org>
+Dm-Upload-Allowed: yes
+Homepage: http://libvirt.org
+Standards-Version: 3.9.3
+Vcs-Browser: http://git.debian.org/?p=pkg-libvirt/libvirt.git
+Vcs-Git: git://git.debian.org/git/pkg-libvirt/libvirt.git
+Build-Depends: cdbs (>= 0.4.90~), debhelper (>= 7), libxml2-dev, libncurses5-dev, libreadline-dev, zlib1g-dev, libgcrypt11-dev, libgnutls-dev, python-all-dev (>= 2.6.6-3~), libavahi-client-dev, libsasl2-dev, libxen-dev [i386 amd64], lvm2 [linux-any], open-iscsi [linux-any], libparted0-dev (>= 2.2), parted (>= 2.2), libdevmapper-dev [linux-any], uuid-dev, libudev-dev [linux-any], libhal-dev [!linux-any], libpciaccess-dev, module-init-tools [linux-any], policykit-1, libcap-ng-dev [linux-any], libnl-dev [linux-any], libyajl-dev, libpcap0.8-dev, libnuma-dev [amd64 i386 ia64 mips mipsel powerpc], radvd [linux-any], libnetcf-dev [linux-any], dwarves, libxml2-utils, dnsmasq-base, openssh-client, netcat-openbsd
+Build-Conflicts: dpkg-dev (= 1.15.3)
+Package-List:
+ libvirt-bin deb admin optional
+ libvirt-dev deb libdevel optional
+ libvirt-doc deb doc optional
+ libvirt0 deb libs optional
+ libvirt0-dbg deb debug extra
+ python-libvirt deb python optional
+Checksums-Sha1:
+ 3743dc4f3e58d5912a98f568c3e854d97d81f216 20054618 libvirt_0.9.12.orig.tar.gz
+ 7dc0f3bfe8a63a0259affe4fe3d3cc5b3180a72b 240 libvirt_0.9.12.orig.tar.gz.asc
+ 3743dc4f3e58d5912a98f568c3e854d97d81f123 20054618 libvirt_0.9.12.orig-foo.tar.gz
+ 7dc0f3bfe8a63a0259affe4fe3d3cc5b3180a72b 240 libvirt_0.9.12.orig-foo.tar.gz.asc
+ 3743dc4f3e58d5912a98f568c3e854d97d81f123 20054618 libvirt_0.9.12.orig-bar.tar.gz
+ 7dc0f3bfe8a63a0259affe4fe3d3cc5b3180a72b 240 libvirt_0.9.12.orig-bar.tar.gz.asc
+ 3743dc4f3e58d5912a98f568c3e854d97d81f12c 20054618 libvirt_0.9.12.orig-upper-CASE.tar.gz
+ a7ffa64c18a5ee448c98b1dc894a0a27e1670357 35935 libvirt_0.9.12-4.debian.tar.gz
+Checksums-Sha256:
+ 298ffc7f2a6d6e78aae46f11a0980f4bc17fa2928f5de6cd9e8abaf5990336e7 20054618 libvirt_0.9.12.orig.tar.gz
+ 2496f435c029673dd7cad49cdf27935d261ef1b3b245118a431556b7f40a7967 240 libvirt_0.9.12.orig.tar.gz.asc
+ 298ffc7f2a6d6e78aae46f11a0980f4bc17fa2928f5de6cd9e8abaf599033123 20054618 libvirt_0.9.12.orig-foo.tar.gz
+ 2496f435c029673dd7cad49cdf27935d261ef1b3b245118a431556b7f40a7967 240 libvirt_0.9.12.orig-foo.tar.gz.asc
+ 298ffc7f2a6d6e78aae46f11a0980f4bc17fa2928f5de6cd9e8abaf599033123 20054618 libvirt_0.9.12.orig-bar.tar.gz
+ 2496f435c029673dd7cad49cdf27935d261ef1b3b245118a431556b7f40a7967 240 libvirt_0.9.12.orig-bar.tar.gz.asc
+ 298ffc7f2a6d6e78aae46f11a0980f4bc17fa2928f5de6cd9e8abaf59903312c 20054618 libvirt_0.9.12.orig-upper-CASE.tar.gz
+ e75110c493995ba5366e751f20f3842f30674c3918357fa6eb83175d0afbec31 35935 libvirt_0.9.12-4.debian.tar.gz
+Files:
+ 5e842bc55733ceba60c64767580ff3e4 20054618 libvirt_0.9.12.orig.tar.gz
+ ddfefbf64ffa1b1d7e0819501d096544 240 libvirt_0.9.12.orig.tar.gz.asc
+ 5e842bc55733ceba60c64767580ff123 20054618 libvirt_0.9.12.orig-foo.tar.gz
+ ddfefbf64ffa1b1d7e0819501d096544 240 libvirt_0.9.12.orig-foo.tar.gz.asc
+ 5e842bc55733ceba60c64767580ff123 20054618 libvirt_0.9.12.orig-bar.tar.gz
+ ddfefbf64ffa1b1d7e0819501d096544 240 libvirt_0.9.12.orig-bar.tar.gz.asc
+ 5e842bc55733ceba60c64767580ff12c 20054618 libvirt_0.9.12.orig-upper-CASE.tar.gz
+ f328960d25e7c843f3ac5f9ba5064251 35935 libvirt_0.9.12-4.debian.tar.gz
+"""
+
+ def setUp(self):
+ with tempfile.NamedTemporaryFile(delete=False) as self.dscfile:
+ self.dscfile.write(self.content.encode())
+
+ def tearDown(self):
+ os.unlink(self.dscfile.name)
+
+ def test_dscfile_parse(self):
+ """Test parsing a valid dsc file"""
+ dsc = DscFile.parse(self.dscfile.name)
+ self.assertEqual(dsc.version, '0.9.12-4')
+ self.assertEqual(dsc.native, False)
+ self.assertEqual(os.path.basename(dsc.tgz), 'libvirt_0.9.12.orig.tar.gz')
+ self.assertEqual(os.path.basename(dsc.diff), '')
+ self.assertEqual(os.path.basename(dsc.deb_tgz), 'libvirt_0.9.12-4.debian.tar.gz')
+ for s in ['foo', 'bar', 'upper-CASE']:
+ self.assertEqual(os.path.basename(dsc.additional_tarballs[s]),
+ 'libvirt_0.9.12.orig-%s.tar.gz' % s)
+ self.assertEquals(sorted(dsc.sigs), ['/tmp/libvirt_0.9.12.orig-bar.tar.gz.asc',
+ '/tmp/libvirt_0.9.12.orig-foo.tar.gz.asc',
+ '/tmp/libvirt_0.9.12.orig.tar.gz.asc'])
+
+
+class Test10DscNonNativeFile(unittest.TestCase):
+ """Test L{gbp.deb.DscFile}"""
+
+ content = """Format: 1.0
+Source: latencytop
+Binary: latencytop
+Architecture: any
+Version: 0.5
+Maintainer: Giacomo Catenazzi <cate@debian.org>
+Homepage: http://www.latencytop.org/
+Standards-Version: 3.8.2
+Build-Depends: cdbs, debhelper (>= 5), pkg-config, libncursesw5-dev, libglib2.0-dev, libgtk2.0-dev
+Package-List:
+ latencytop deb utils extra arch=any
+Checksums-Sha1:
+ cfd8a83fa40e630cf680d96a186ff4fdbf6f22c8 25374 latencytop_0.5.orig.tar.gz
+ 1fa907254c61c73679fd173c828327e9a2273c31 1978 latencytop_0.5.diff.gz
+Checksums-Sha256:
+ 9e7f72fbea7bd918e71212a1eabaad8488d2c602205d2e3c95d62cd57e9203ef 25374 latencytop_0.5.orig.tar.gz
+ 66342c4d55ae31e529bdcdf88d41a7d114b355f438b0d10efb107f3aef1a0db6 1978 latencytop_0.5.diff.gz
+Files:
+ 73bb3371c6ee0b0e68e25289027e865c 25374 latencytop_0.5.orig.tar.gz
+ bf7afb3e0d68b0e33e5abf4f1542af71 1978 latencytop_0.5.diff.gz
+"""
+
+ def setUp(self):
+ with tempfile.NamedTemporaryFile(delete=False) as self.dscfile:
+ self.dscfile.write(self.content.encode())
+
+ def tearDown(self):
+ os.unlink(self.dscfile.name)
+
+ def test_dscfile_parse(self):
+ """Test parsing a a 1.0 non-native dsc file without debian revision"""
+ dsc = DscFile.parse(self.dscfile.name)
+ self.assertEqual(dsc.version, '0.5')
+ self.assertEqual(dsc.native, False)
+ self.assertEqual(os.path.basename(dsc.tgz), 'latencytop_0.5.orig.tar.gz')
+ self.assertEqual(os.path.basename(dsc.deb_tgz), '')
+ self.assertEqual(os.path.basename(dsc.diff), 'latencytop_0.5.diff.gz')
+ self.assertEqual(dsc.additional_tarballs, {}),
+ self.assertEquals(dsc.sigs, [])
+
+
+@testutils.skip_without_cmd('dpkg')
+class TestDpkgCompareVersions(unittest.TestCase):
+ """Test L{gbp.deb.DpkgCompareVersions}"""
+
+ def setUp(self):
+ self.cmp = gbp.deb.DpkgCompareVersions()
+
+ def testLessThen(self):
+ ret = self.cmp('1', '2')
+ self.assertEqual(ret, -1)
+
+ def testGreaterThen(self):
+ ret = self.cmp('2', '1')
+ self.assertEqual(ret, 1)
+
+ def testSameVersion(self):
+ ret = self.cmp('2', '2')
+ self.assertEqual(ret, 0)
+
+ def testBadVersion(self):
+ with self.assertRaises(CommandExecFailed):
+ self.cmp('_', '_ _')
+
+
+@testutils.skip_without_cmd('dpkg')
+class TestDeb(unittest.TestCase):
+ """Test L{gbp.deb.__init__} """
+
+ @unittest.skipUnless(platform.machine() == "x86_64", "not on amd64")
+ def test_get_arch(self):
+ arch = gbp.deb.get_arch()
+ self.assertTrue(isinstance(arch, str))
+ self.assertEquals(arch, "amd64")
+
+ @unittest.skipUnless(testutils.OsReleaseFile()['ID'] == 'debian', "not on Debian")
+ def test_get_vendor(self):
+ vendor = gbp.deb.get_vendor()
+ self.assertTrue(isinstance(vendor, str))
+ self.assertEquals(vendor, "Debian")
diff --git a/tests/13_test_gbp_pq.py b/tests/13_test_gbp_pq.py
new file mode 100644
index 0000000..dc53c26
--- /dev/null
+++ b/tests/13_test_gbp_pq.py
@@ -0,0 +1,437 @@
+# vim: set fileencoding=utf-8 :
+# (C) 2012,2015 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, please see
+# <http://www.gnu.org/licenses/>
+"""Test L{gbp.pq}"""
+
+from . import context
+from . import testutils
+
+import os
+import unittest
+
+from gbp.command_wrappers import GitCommand
+from gbp.scripts.pq import (generate_patches, export_patches,
+ import_quilt_patches, rebase_pq,
+ switch_pq,
+ SERIES_FILE)
+import gbp.scripts.common.pq as pq
+import gbp.patch_series
+
+
+class TestPqOptions(object):
+ abbrev = 7
+ drop = False
+ patch_num_format = '%04d-'
+ patch_numbers = False
+ renumber = False
+
+
+class TestApplyAndCommit(testutils.DebianGitTestRepo):
+ """Test L{gbp.pq}'s apply_and_commit"""
+
+ def setUp(self):
+ testutils.DebianGitTestRepo.setUp(self)
+ self.add_file('bar')
+
+ def test_apply_and_commit_patch(self):
+ """Test applying a single patch"""
+ patch = gbp.patch_series.Patch(_patch_path('foo.patch'))
+
+ pq.apply_and_commit_patch(self.repo, patch, None)
+ self.assertIn(b'foo', self.repo.list_files())
+
+ def test_apply_and_commit_patch_preserve_subject(self):
+ """Test applying a patch preserves the subject"""
+ patch = gbp.patch_series.Patch(_patch_path('brackets-in-subject.patch'))
+
+ pq.apply_and_commit_patch(self.repo, patch, None)
+ self.assertIn(b'foo', self.repo.list_files())
+ info = self.repo.get_commit_info('HEAD')
+ self.assertEquals('[text] foobar', info['subject'])
+
+ def test_topic(self):
+ """Test if setting a topic works"""
+ patch = gbp.patch_series.Patch(_patch_path('foo.patch'))
+
+ pq.apply_and_commit_patch(self.repo, patch, None, topic='foobar')
+ info = self.repo.get_commit_info('HEAD')
+ self.assertIn('Gbp-Pq: Topic foobar', info['body'])
+
+ def test_name(self):
+ """Test if setting a name works"""
+ patch = gbp.patch_series.Patch(_patch_path('foo.patch'))
+
+ pq.apply_and_commit_patch(self.repo, patch, None, name='foobar')
+ info = self.repo.get_commit_info('HEAD')
+ self.assertIn('Gbp-Pq: Name foobar', info['body'])
+
+ @testutils.skip_without_cmd('dpkg')
+ def test_debian_missing_author(self):
+ """
+ Check if we parse the author from debian control if it's missing in the patch.
+ """
+ def _check_log(msg):
+ self.assertEqual(msg, "Patch 'foo.patch' has no authorship "
+ "information, using 'Guido Günther <gg@godiug.net>'")
+
+ patch = gbp.patch_series.Patch(_patch_path('foo.patch'))
+
+ # Overwrite data parsed from patch:
+ patch.author
+ patch.info['author'] = None
+ patch.info['email'] = None
+
+ # Fake a control file
+ self.add_file("debian/control",
+ "Maintainer: Guido Günther <gg@godiug.net>".encode('utf-8'),
+ mode='wb+')
+
+ maintainer = pq.get_maintainer_from_control(self.repo)
+ orig_warn = gbp.log.warn
+ gbp.log.warn = _check_log
+ pq.apply_and_commit_patch(self.repo, patch, maintainer)
+ gbp.log.warn = orig_warn
+ info = self.repo.get_commit_info('HEAD')
+ self.assertEqual(info['author'].email, 'gg@godiug.net')
+ self.assertIn(b'foo', self.repo.list_files())
+
+
+class TestApplySinglePatch(testutils.DebianGitTestRepo):
+ """Test L{gbp.pq}'s apply_single_patch"""
+
+ def setUp(self):
+ testutils.DebianGitTestRepo.setUp(self)
+ self.add_file('bar')
+
+ def test_apply_single_patch(self):
+ """Test applying a single patch"""
+
+ patch = gbp.patch_series.Patch(_patch_path('foo.patch'))
+
+ self.repo.create_branch(pq.pq_branch_name('master'))
+ pq.apply_single_patch(self.repo, 'master', patch, None)
+ self.assertIn(b'foo', self.repo.list_files())
+
+
+class TestWritePatch(testutils.DebianGitTestRepo):
+ """Test L{gbp.pq}'s write_patch """
+
+ def setUp(self):
+ testutils.DebianGitTestRepo.setUp(self)
+ self.add_file('bar', 'bar')
+
+ def tearDown(self):
+ context.teardown()
+
+ def _test_generate_patches(self, changes, expected_patches, opts):
+ self.assertEqual(len(changes), len(expected_patches))
+
+ d = context.new_tmpdir(__name__)
+ expected_paths = [os.path.join(str(d), n) for n in expected_patches]
+
+ # Commit changes
+ for c in changes:
+ self.add_file(c[0], c[1], c[2])
+
+ # Write it out as patch and check its existence
+ origin = 'HEAD~%d' % len(changes)
+ patchfiles = generate_patches(self.repo, origin, 'HEAD', str(d), opts)
+ for expected in expected_paths:
+ self.assertIn(expected, patchfiles)
+ self.assertTrue(os.path.exists(expected))
+
+ # Reapply the patch to a new branch
+ self.repo.create_branch('testapply', origin)
+ self.repo.set_branch('testapply')
+ for expected in expected_paths:
+ self.repo.apply_patch(expected)
+ self.repo.commit_all("foo")
+ diff = self.repo.diff('master', 'testapply')
+ # Branches must be identical afterwards
+ self.assertEqual(b'', diff)
+
+ def test_generate_patches(self):
+ """Test generation of patches"""
+
+ expected_patches = ['gbptest/added-foo.patch',
+ 'gbptest/patchname.diff']
+
+ changes = [('foo', 'foo', ("added foo\n\n"
+ "Gbp-Pq: Topic gbptest")),
+ ('baz', 'baz', ("added bar\n\n"
+ "Gbp-Pq: Topic gbptest\n"
+ "Gbp-Pq: Name patchname.diff"))]
+
+ opts = TestPqOptions()
+ opts.patch_num_format = '%04d-'
+ self._test_generate_patches(changes, expected_patches, opts)
+
+ def test_generate_renumbered_patches(self):
+ """Test generation of renumbered patches"""
+
+ expected_patches = ['gbptest/01_added-foo.patch',
+ 'gbptest/02_patchname.diff']
+
+ changes = [('foo', 'foo', ("added foo\n\n"
+ "Gbp-Pq: Topic gbptest")),
+ ('baz', 'baz', ("added bar\n\n"
+ "Gbp-Pq: Topic gbptest\n"
+ "Gbp-Pq: Name 099-patchname.diff"))]
+
+ opts = TestPqOptions()
+ opts.patch_num_format = '%02d_'
+ opts.renumber = True
+ opts.patch_numbers = True
+ self._test_generate_patches(changes, expected_patches, opts)
+
+ def test_generate_patches_with_name_clashes(self):
+ """Test generation of patches which have name clashes"""
+
+ expected_patches = ['gbptest/added-foo.patch',
+ 'gbptest/patchname.diff',
+ 'gbptest/patchname-1.diff',
+ 'gbptest/patchname-2.diff']
+
+ changes = [('foo', 'foo', ("added foo\n\n"
+ "Gbp-Pq: Topic gbptest")),
+ ('baz', 'baz', ("added bar\n\n"
+ "Gbp-Pq: Topic gbptest\n"
+ "Gbp-Pq: Name 099-patchname.diff")),
+ ('qux', 'qux', ("added qux\n\n"
+ "Gbp-Pq: Topic gbptest\n"
+ "Gbp-Pq: Name 100-patchname.diff")),
+ ('norf', 'norf', ("added norf\n\n"
+ "Gbp-Pq: Topic gbptest\n"
+ "Gbp-Pq: Name 101-patchname.diff"))]
+
+ opts = TestPqOptions()
+ opts.renumber = True
+ opts.patch_num_format = '%02d_'
+ self._test_generate_patches(changes, expected_patches, opts)
+
+
+class TestExport(testutils.DebianGitTestRepo):
+ class Options(TestPqOptions):
+ commit = False
+ meta_closes = False
+ meta_closes_bugnum = ''
+ pq_from = 'DEBIAN'
+
+ def setUp(self):
+ testutils.DebianGitTestRepo.setUp(self)
+ self.add_file('bar', 'bar')
+
+ def test_drop(self):
+ """Test if we drop the patch-queue branch with --drop"""
+ repo = self.repo
+ start = repo.get_branch()
+ pq_branch = os.path.join('patch-queue', start)
+ opts = TestExport.Options()
+ opts.drop = True
+
+ repo.create_branch(pq.pq_branch_name('master'))
+ switch_pq(repo, start, opts)
+ self.assertEqual(repo.get_branch(), pq_branch)
+ export_patches(repo, pq_branch, opts)
+ self.assertEqual(repo.get_branch(), start)
+ self.assertFalse(repo.has_branch(pq_branch))
+
+ def test_commit(self):
+ """Test if we commit the patch-queue branch with --commit"""
+ repo = self.repo
+ start = repo.get_branch()
+ pq_branch = os.path.join('patch-queue', start)
+ opts = TestExport.Options()
+ opts.commit = True
+ repo.create_branch(pq.pq_branch_name('master'))
+ switch_pq(repo, start, opts)
+ self.assertEqual(len(repo.get_commits()), 1)
+ self.assertEqual(repo.get_branch(), pq_branch)
+ self.add_file('foo', 'foo')
+ export_patches(repo, pq_branch, opts)
+ self.assertEqual(repo.get_branch(), start)
+ self.assertTrue(repo.has_branch(pq_branch))
+ self.assertEqual(len(repo.get_commits()), 2,
+ "Export did not create commit")
+ export_patches(repo, pq_branch, opts)
+ self.assertEqual(repo.get_branch(), start)
+ self.assertEqual(len(repo.get_commits()), 2,
+ "Export must not create another commit")
+
+ def test_commit_dropped_patches(self):
+ """Test if we commit the patch-queue branch with all patches dropped"""
+ repo = self.repo
+ start = repo.get_branch()
+ pq_branch = os.path.join('patch-queue', start)
+ opts = TestExport.Options()
+ opts.commit = True
+ patch_dir = os.path.join(repo.path, 'debian/patches')
+ os.makedirs(patch_dir)
+ with open(os.path.join(patch_dir, 'series'), 'w') as f:
+ f.write("patch1.diff\n")
+ f.write("patch2.diff\n")
+ repo.add_files('debian/patches')
+ repo.commit_all('Add series file')
+ repo.create_branch(pq.pq_branch_name('master'))
+ switch_pq(repo, start, opts)
+ self.assertEqual(len(repo.get_commits()), 2)
+ self.assertEqual(repo.get_branch(), pq_branch)
+ export_patches(repo, pq_branch, opts)
+ self.assertEqual(repo.get_branch(), start)
+ self.assertTrue(repo.has_branch(pq_branch))
+ self.assertEqual(len(repo.get_commits()), 3,
+ "Export did not create commit")
+ self.assertIn(b"Drop patch1.diff:", repo.show('HEAD'))
+ self.assertIn(b"Drop patch2.diff:", repo.show('HEAD'))
+
+
+class TestParseGbpCommand(unittest.TestCase):
+ def test_empty_body(self):
+ """Test command filtering with an empty body"""
+ info = {'body': ''}
+ (cmds, body) = pq.parse_gbp_commands(info, ['tag'], ['cmd1'], ['cmd2'])
+ self.assertEquals(cmds, {})
+ self.assertEquals(body, '')
+
+ def test_noarg_cmd(self):
+ orig_body = '\n'.join(["Foo",
+ "tag: cmd1"])
+ info = {'body': orig_body}
+ (cmds, body) = pq.parse_gbp_commands(info, 'tag', ['cmd'], ['argcmd'])
+ self.assertEquals(cmds, {'cmd': None})
+ self.assertEquals(body, orig_body)
+
+ def test_filter_cmd(self):
+ orig_body = '\n'.join(["Foo",
+ "tag: cmd1"])
+ info = {'body': orig_body}
+ (cmds, body) = pq.parse_gbp_commands(info, 'tag', ['cmd'], ['argcmd'], ['cmd'])
+ self.assertEquals(cmds, {'cmd': None})
+ self.assertEquals(body, 'Foo')
+
+
+class TestFromTAG(testutils.DebianGitTestRepo):
+ """Test L{gbp.pq}'s pq-from=TAG"""
+
+ class Options(TestPqOptions):
+ commit = True
+ force = False
+ meta_closes = 'Closes|LP'
+ meta_closes_bugnum = r'(?:bug|issue)?\#?\s?\d+'
+ pq_from = 'TAG'
+ upstream_tag = 'upstream/%(version)s'
+
+ def git_create_empty_branch(self, branch):
+ GitCommand('checkout', cwd=self.repo.path)(['-q', '--orphan', branch])
+ GitCommand('rm', cwd=self.repo.path)(['-rf', '.'])
+
+ def setUp(self):
+ testutils.DebianGitTestRepo.setUp(self)
+ self.add_file('debian/control')
+ self.add_file(
+ 'debian/changelog',
+ 'foo (0.0.1-1) UNRELEASED; urgency=medium\n'
+ '\n'
+ ' * Initial foo\n'
+ '\n'
+ ' -- Mr. T. S. <t@example.com> '
+ 'Thu, 01 Jan 1970 00:00:00 +0000\n'
+ )
+ self.git_create_empty_branch('bar')
+ self.add_file('foo', 'foo')
+ self.repo.create_tag('upstream/0.0.1')
+ self.repo.set_branch('master')
+
+ def test_empty(self):
+
+ import_quilt_patches(self.repo,
+ branch=self.repo.get_branch(),
+ series=SERIES_FILE,
+ tries=1,
+ force=TestFromTAG.Options.force,
+ pq_from=TestFromTAG.Options.pq_from,
+ upstream_tag=TestFromTAG.Options.upstream_tag)
+ diff = self.repo.diff(self.repo.get_branch(), 'upstream/0.0.1')
+ self.assertEqual(b'', diff)
+ diff = self.repo.diff(self.repo.get_branch(), 'master')
+ self.assertNotEqual(b'', diff)
+
+ rebase_pq(self.repo,
+ branch=self.repo.get_branch(),
+ options=TestFromTAG.Options)
+ diff = self.repo.diff(self.repo.get_branch(), 'upstream/0.0.1')
+ self.assertEqual(b'', diff)
+ diff = self.repo.diff(self.repo.get_branch(), 'master')
+ self.assertNotEqual(b'', diff)
+
+ export_patches(self.repo,
+ branch=self.repo.get_branch(),
+ options=TestFromTAG.Options())
+ diff = self.repo.diff(self.repo.get_branch(), self.repo.head)
+ self.assertEqual(b'', diff)
+
+ def test_adding_patch(self):
+ import_quilt_patches(self.repo,
+ branch=self.repo.get_branch(),
+ series=SERIES_FILE,
+ tries=1,
+ force=TestFromTAG.Options.force,
+ pq_from=TestFromTAG.Options.pq_from,
+ upstream_tag=TestFromTAG.Options.upstream_tag)
+ self.add_file('bar', 'bar', 'added bar')
+ export_patches(self.repo,
+ branch=self.repo.get_branch(),
+ options=TestFromTAG.Options())
+ self.assertTrue(os.path.exists(os.path.join(self.repo.path,
+ os.path.dirname(SERIES_FILE),
+ 'added-bar.patch')))
+ switch_pq(self.repo, 'master', TestFromTAG.Options)
+ rebase_pq(self.repo,
+ branch=self.repo.get_branch(),
+ options=TestFromTAG.Options())
+ export_patches(self.repo,
+ branch=self.repo.get_branch(),
+ options=TestFromTAG.Options())
+ self.assertTrue(os.path.exists(os.path.join(self.repo.path,
+ os.path.dirname(SERIES_FILE),
+ 'added-bar.patch')))
+ # New upstream release
+ self.repo.set_branch('bar')
+ GitCommand('cherry-pick', cwd=self.repo.path)(['patch-queue/master'])
+ self.repo.create_tag('upstream/0.0.2')
+ self.repo.set_branch('master')
+ self.add_file(
+ 'debian/changelog',
+ 'foo (0.0.2-1) UNRELEASED; urgency=medium\n'
+ '\n'
+ ' * Initial foo\n'
+ '\n'
+ ' -- Mr. T. S. <t@example.com> '
+ 'Thu, 01 Jan 1970 00:00:00 +0000\n'
+ )
+ switch_pq(self.repo, 'master', TestFromTAG.Options())
+ rebase_pq(self.repo,
+ branch=self.repo.get_branch(),
+ options=TestFromTAG.Options())
+ export_patches(self.repo,
+ branch=self.repo.get_branch(),
+ options=TestFromTAG.Options())
+ self.assertFalse(os.path.exists(os.path.join(self.repo.path,
+ os.path.dirname(SERIES_FILE),
+ 'added-bar.patch')))
+
+
+def _patch_path(name):
+ return os.path.join(context.projectdir, 'tests/data', name)
diff --git a/tests/14_test_gbp_import_dscs.py b/tests/14_test_gbp_import_dscs.py
new file mode 100644
index 0000000..f7fac34
--- /dev/null
+++ b/tests/14_test_gbp_import_dscs.py
@@ -0,0 +1,102 @@
+# vim: set fileencoding=utf-8 :
+# (C) 2012 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, please see
+# <http://www.gnu.org/licenses/>
+"""Test L{gbp.pq}"""
+
+from . import context
+from . import testutils
+
+import gbp.log
+import gbp.scripts.import_dscs as import_dscs
+
+from gbp.errors import GbpError
+
+
+class StubGitImportDsc(object):
+ """
+ A Stub for GitImportDsc.
+ """
+ def __init__(self, args):
+ self.failfile = None
+ for arg in args:
+ if arg.startswith('--failfile'):
+ self.failfile = "%s.dsc" % arg.split('=')[1]
+
+ def importdsc(self, dsc):
+ """
+ Stub the dsc import and fail if we were told to do
+ so by the --failfile option.
+ """
+ return 1 if dsc.filename == self.failfile else 0
+
+
+class DscStub(object):
+ def __init__(self, filename, version):
+ self.filename = filename
+ self.version = version
+ self.dscfile = filename
+
+ @classmethod
+ def parse(cls, filename):
+ # filename is like file1.dsc, file2.dsc, use
+ # the digit as version number
+ version = filename[4]
+ return cls(filename, version)
+
+
+class TestImportDscs(testutils.DebianGitTestRepo):
+ """Test L{gbp.scripts.import_dscs}'s """
+
+ def setUp(self):
+ testutils.DebianGitTestRepo.setUp(self)
+ context.chdir(self.repo.path)
+ self.orig_err = gbp.log.err
+ gbp.log.err = self._check_err_msg
+
+ self.safed_GitImportDsc = import_dscs.GitImportDsc
+ self.safed_DscFile = import_dscs.DscFile
+ import_dscs.GitImportDsc = StubGitImportDsc
+ import_dscs.DscFile = DscStub
+
+ def _check_err_msg(self, err):
+ self.assertIsInstance(err, GbpError)
+ self.assertIn("Failed to import", str(err))
+
+ def test_import_success(self):
+ """Test importing success with stub"""
+ ret = import_dscs.main(['argv0', 'file1.dsc', 'file2.dsc'])
+ self.assertEqual(ret, 0)
+
+ def test_import_fail_first(self):
+ ret = import_dscs.main(['argv0',
+ '--failfile=file1',
+ 'file1.dsc'])
+ self.assertEqual(ret, 1)
+
+ def test_import_fail_second(self):
+ ret = import_dscs.main(['argv0',
+ '--failfile=file1',
+ '--failfile=file2',
+ 'file1.dsc',
+ 'file2.dsc'])
+ self.assertEqual(ret, 1)
+
+ def tearDown(self):
+ gbp.log.err = self.orig_err
+ testutils.DebianGitTestRepo.tearDown(self)
+ context.teardown()
+
+ import_dscs.GitImportDsc = self.safed_GitImportDsc
+ import_dscs.DscFile = self.safed_DscFile
diff --git a/tests/15_test_DebianSource.py b/tests/15_test_DebianSource.py
new file mode 100644
index 0000000..809feaa
--- /dev/null
+++ b/tests/15_test_DebianSource.py
@@ -0,0 +1,143 @@
+# vim: set fileencoding=utf-8 :
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""Test L{gbp.pq}"""
+
+from . import context
+from . import testutils
+
+import os
+
+from gbp.deb.source import DebianSource, DebianSourceError
+from gbp.deb.format import DebianSourceFormat
+from gbp.git.vfs import GitVfs
+
+
+class TestDebianSource(testutils.DebianGitTestRepo):
+ """Test L{gbp.deb.source}'s """
+
+ def setUp(self):
+ testutils.DebianGitTestRepo.setUp(self)
+ context.chdir(self.repo.path)
+
+ def test_is_native_file_3_file(self):
+ """Test native package of format 3"""
+ source = DebianSource('.')
+ os.makedirs('debian/source')
+ with self.assertRaises(DebianSourceError):
+ source.is_native()
+
+ dsf = DebianSourceFormat.from_content("3.0", "native")
+ self.assertEqual(dsf.type, 'native')
+ self.assertTrue(source.is_native())
+
+ dsf = DebianSourceFormat.from_content("3.0", "quilt")
+ self.assertEqual(dsf.type, 'quilt')
+ self.assertFalse(source.is_native())
+
+ def test_is_native_fallback_file(self):
+ """Test native package without a debian/source/format file"""
+ source = DebianSource('.')
+ os.makedirs('debian/')
+ with self.assertRaises(DebianSourceError):
+ source.is_native()
+
+ with open('debian/changelog', 'w') as f:
+ f.write("""git-buildpackage (0.2.3) git-buildpackage; urgency=low
+
+ * git doesn't like '~' in tag names so replace this with a dot when tagging
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 2 Oct 2006 18:30:20 +0200
+""")
+ source = DebianSource('.')
+ self.assertTrue(source.is_native())
+
+ def _commit_format(self, version, format):
+ # Commit a format file to disk
+ if not os.path.exists('debian/source'):
+ os.makedirs('debian/source')
+ dsf = DebianSourceFormat.from_content(version, format)
+ self.assertEqual(dsf.type, format)
+ self.repo.add_files('.')
+ self.repo.commit_all('foo')
+ os.unlink('debian/source/format')
+ self.assertFalse(os.path.exists('debian/source/format'))
+
+ def test_is_native_file_3_git(self):
+ """Test native package of format 3 from git"""
+ self._commit_format('3.0', 'native')
+ source = DebianSource(GitVfs(self.repo))
+ self.assertTrue(source.is_native())
+
+ self._commit_format('3.0', 'quilt')
+ source = DebianSource(GitVfs(self.repo))
+ self.assertFalse(source.is_native())
+
+ def test_is_releasable(self):
+ os.makedirs('debian/')
+ with open('debian/changelog', 'w') as f:
+ f.write("""git-buildpackage (0.2.3) unstable; urgency=low
+
+ * git doesn't like '~' in tag names so replace this with a dot when tagging
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 2 Oct 2006 18:30:20 +0200
+""")
+ source = DebianSource('.')
+ self.assertEquals(source.changelog.distribution, "unstable")
+ self.assertTrue(source.is_releasable())
+
+ def test_is_not_releasable(self):
+ os.makedirs('debian/')
+ with open('debian/changelog', 'w') as f:
+ f.write("""git-buildpackage (0.2.3) UNRELEASED; urgency=low
+
+ * git doesn't like '~' in tag names so replace this with a dot when tagging
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 2 Oct 2006 18:30:20 +0200
+""")
+ source = DebianSource('.')
+ self.assertEquals(source.changelog.distribution, "UNRELEASED")
+ self.assertFalse(source.is_releasable())
+
+ def test_control(self):
+ os.makedirs('debian/')
+ with open('debian/control', 'w') as f:
+ f.write("Source: foo")
+ source = DebianSource('.')
+ self.assertIsNotNone(source.control)
+ self.assertEquals(source.control.name, "foo")
+
+ def test_cur_dir_not_toplevel(self):
+ """
+ Check if we can parse files if workdir != debian toplevel dir
+ """
+ os.makedirs('debian/')
+ with open('debian/changelog', 'w') as f:
+ f.write("""foo (0.2.3) unstable; urgency=low
+
+ * git doesn't like '~' in tag names so replace this with a dot when tagging
+
+ -- Guido Guenther <agx@sigxcpu.org> Mon, 2 Oct 2006 18:30:20 +0200
+""")
+ with open('debian/control', 'w') as f:
+ f.write("Source: foo")
+ os.chdir('debian/')
+ source = DebianSource('..')
+ self.assertEquals(source.changelog.name, "foo")
+ self.assertEquals(source.control.name, "foo")
+
+ source = DebianSource(os.path.abspath('..'))
+ self.assertEquals(source.changelog.name, "foo")
+ self.assertEquals(source.control.name, "foo")
diff --git a/tests/16_test_supercommand.py b/tests/16_test_supercommand.py
new file mode 100644
index 0000000..f9172df
--- /dev/null
+++ b/tests/16_test_supercommand.py
@@ -0,0 +1,59 @@
+# vim: set fileencoding=utf-8 :
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+"""Test L{gbp} command wrapper"""
+
+import unittest
+import gbp.scripts.supercommand
+
+from tests.testutils import capture_stdout, capture_stderr
+
+
+class TestSuperCommand(unittest.TestCase):
+
+ def test_import(self):
+ """Test the importer itself"""
+ with self.assertRaises(ImportError):
+ gbp.scripts.supercommand.import_command('not.allowed')
+ with self.assertRaises(ImportError):
+ gbp.scripts.supercommand.import_command('not/allowed')
+ with self.assertRaises(ImportError):
+ gbp.scripts.supercommand.import_command('0notallowed')
+ self.assertIsNotNone(gbp.scripts.supercommand.import_command('pq'))
+
+ def test_invalid_command(self):
+ """Test if we fail correctly with an invalid command"""
+ with capture_stderr():
+ self.assertEqual(gbp.scripts.supercommand.supercommand(
+ ['argv0', 'asdf']), 2)
+ self.assertEqual(gbp.scripts.supercommand.supercommand(
+ ['argv0', 'asdf', '--verbose']), 2)
+
+ def test_list_commands(self):
+ """Invoking with --list-cmds must not raise an error"""
+ with capture_stdout() as out:
+ self.assertEqual(gbp.scripts.supercommand.supercommand(['argv0',
+ '--list-cmds']), 0)
+ for cmd in ['import-orig', 'create-remote-repo', 'pq']:
+ self.assertIn("%s - " % cmd, out.output())
+
+ def test_help_command(self):
+ """Invoking with --help must not raise an error"""
+ self.assertEqual(gbp.scripts.supercommand.supercommand(
+ ['argv0', '--help']), 0)
+
+ def test_missing_arg(self):
+ self.assertEqual(gbp.scripts.supercommand.supercommand(
+ ['argv0']), 1)
diff --git a/tests/17_test_dch_guess_documented_commit.py b/tests/17_test_dch_guess_documented_commit.py
new file mode 100644
index 0000000..146ede8
--- /dev/null
+++ b/tests/17_test_dch_guess_documented_commit.py
@@ -0,0 +1,73 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{Changelog}'s guess_version_from_upstream"""
+
+from . import context # noqa: 401
+from . import testutils
+
+from gbp.scripts import dch
+
+
+class TestGuessDocumentedCommit(testutils.DebianGitTestRepo):
+ def setUp(self):
+ self.version = '1.0-1'
+ self.tagformat = 'debian/%(version)s'
+
+ testutils.DebianGitTestRepo.setUp(self)
+
+ def test_01_from_snapshot_banner(self):
+ """
+ Guess the commit to start from from the snapshot banner
+ """
+ cp = testutils.MockedChangeLog(self.version,
+ "*** SNAPSHOT build @12345 ***")
+ guessed_commit = dch.guess_documented_commit(cp, None, None)
+ self.assertEqual(guessed_commit, '12345')
+
+ def test_02_from_tag(self):
+ """
+ Guess the commit to start from from the tag matching
+ the topmost version in the changelog
+ """
+ cp = testutils.MockedChangeLog(self.version)
+
+ self.add_file('doesnot', 'matter')
+ tag = self.repo.version_to_tag(self.tagformat,
+ self.version)
+ self.repo.create_tag(name=tag,
+ msg="Debian release %s" % self.version,
+ sign=False)
+ commit = self.repo.rev_parse('%s^0' % tag)
+ guessed_commit = dch.guess_documented_commit(cp,
+ self.repo,
+ self.tagformat)
+ self.assertEqual(guessed_commit, commit)
+
+ def test_03_from_changelog_commit(self):
+ """
+ Guess the commit to start from from the commit that
+ last touched the changelog
+ """
+ cp = testutils.MockedChangeLog(self.version)
+
+ self.add_file('debian/changelog', 'foo')
+ commit = self.repo.head
+ self.add_file('doesnot', 'matter')
+ guessed_commit = dch.guess_documented_commit(cp,
+ self.repo,
+ self.tagformat)
+ self.assertEqual(guessed_commit, commit)
+
+ def test_04_not_touched(self):
+ """
+ None of the above matched so we want to start from
+ the beginning of history
+ """
+ cp = testutils.MockedChangeLog(self.version)
+
+ self.add_file('doesnot', 'matter')
+ self.add_file('doesnot', 'mattereither')
+ guessed_commit = dch.guess_documented_commit(cp,
+ self.repo,
+ self.tagformat)
+ self.assertIsNone(guessed_commit)
diff --git a/tests/18_test_Config.py b/tests/18_test_Config.py
new file mode 100644
index 0000000..60cd9cf
--- /dev/null
+++ b/tests/18_test_Config.py
@@ -0,0 +1,134 @@
+# vim: set fileencoding=utf-8 :
+
+import os
+import unittest
+from gbp.config import GbpOptionParser, GbpOptionGroup
+from .testutils import GbpLogTester
+
+
+class TestConfigParser(unittest.TestCase, GbpLogTester):
+ def __init__(self, methodName='runTest'):
+ unittest.TestCase.__init__(self, methodName)
+ GbpLogTester.__init__(self)
+
+ def setUp(self):
+ self.conffiles_save = os.environ.get('GBP_CONF_FILES')
+ self.confname = 'tests/data/test1.conf'
+ self.assertTrue(os.stat(self.confname))
+ os.environ['GBP_CONF_FILES'] = self.confname
+ self._capture_log(True)
+
+ def tearDown(self):
+ if self.conffiles_save:
+ os.environ['GBP_CONF_FILES'] = self.conffiles_save
+ self._capture_log(False)
+
+ def test_default(self):
+ """
+ A value only in the default section should be available in all commands
+ """
+ for n in range(1, 5):
+ for prefix in ['', 'git-', 'gbp-']:
+ parser = GbpOptionParser('%scmd%d' % (prefix, n))
+ self.assertEqual(parser.config['default_option'], 'default_default1')
+
+ def test_single_override(self):
+ """
+ A value in any command section should override the default
+ """
+ for prefix in ['', 'git-', 'gbp-']:
+ parser = GbpOptionParser('%scmd1' % prefix)
+ self.assertEqual(parser.config['single_override_option1'], 'single_override_value1')
+ # No deprecation warning since the test1.conf section is [cmd1]
+ self._check_log_empty()
+
+ def test_single_git_override(self):
+ """
+ A value in any git-command section should override the default
+ """
+ for prefix in ['', 'git-']:
+ parser = GbpOptionParser('%scmd2' % prefix)
+ self.assertEqual(parser.config['single_git_override_option1'], 'single_git_override_value1')
+ for line in range(0, 2):
+ self._check_log(line, ".*Old style config section \[git-cmd2\] found please rename to \[cmd2\]")
+
+ def test_single_gbp_override(self):
+ """
+ A value in any gbp-command section should override the default
+ """
+ for prefix in ['', 'gbp-']:
+ parser = GbpOptionParser('%scmd3' % prefix)
+ self.assertEqual(parser.config['single_gbp_override_option1'], 'single_gbp_override_value1')
+ for line in range(0, 2):
+ self._check_log(line, ".*Old style config section \[gbp-cmd3\] found please rename to \[cmd3\]")
+
+ def test_single_git_override_disabled_deprecations(self):
+ """
+ With disabled deprecations we shouldn't see a log line
+ """
+ for prefix in ['', 'git-']:
+ os.environ['GBP_DISABLE_SECTION_DEPRECATION'] = 'true'
+ parser = GbpOptionParser('%scmd2' % prefix)
+ self.assertEqual(parser.config['single_git_override_option1'], 'single_git_override_value1')
+ for line in range(0, 2):
+ self._check_log_empty()
+ os.environ.pop('GBP_DISABLE_SECTION_DEPRECATION')
+
+ def test_new_overrides_git(self):
+ """
+ A value in the cmd section should override the old git-cmd section independent from
+ how we're invoked
+ """
+ for n in range(4, 6):
+ for prefix in ['', 'git-']:
+ cmd = '%scmd%d' % (prefix, n)
+ parser = GbpOptionParser(cmd)
+ actual = parser.config['new_overrides_git_option1']
+ expected = 'new_overrides_git_value1'
+ self.assertEqual(actual, expected, "%s != %s for %s" % (actual, expected, cmd))
+
+ def test_get_config_file_value(self):
+ """
+ Read a single value from the parsed config
+ """
+ parser = GbpOptionParser('cmd4')
+ self.assertEqual(parser.get_config_file_value('new_overrides_git_option1'),
+ 'new_overrides_git_value1')
+ self.assertEqual(parser.get_config_file_value('doesnotexist'), None)
+
+ def test_param_list(self):
+ parser = GbpOptionParser('cmd4')
+
+ branch_group = GbpOptionGroup(parser, "branch options", "branch update and layout options")
+ 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("debian-branch", dest="upstream_branch")
+ parser.add_config_file_option(option_name="color", dest="color", type='tristate')
+
+ params = parser.valid_options
+ self.assertTrue('upstream-branch' in params)
+ self.assertTrue('debian-branch' in params)
+ self.assertTrue('color' in params)
+
+ def test_short_option_with_prefix(self):
+ """Options with short options can't have a prefix"""
+ class TestOptonParser(GbpOptionParser):
+ list_opts = []
+ defaults = {'withshort': 'foo'}
+ short_opts = {'withshort': '-S'}
+ parser = TestOptonParser('cmd', prefix='p')
+ with self.assertRaisesRegexp(ValueError, "Options with prefix cannot have a short option"):
+ parser.add_config_file_option(option_name="withshort", dest="with_short", help="foo")
+
+ def test_short_option(self):
+ class TestOptionParser(GbpOptionParser):
+ list_opts = []
+ defaults = {'withshort': 'foo'}
+ short_opts = {'withshort': '-S'}
+
+ parser = TestOptionParser('cmd')
+ parser.add_config_file_option(option_name="withshort", dest="with_short", help="foo")
+ self.assertEquals('withshort', parser.valid_options[0])
+ self.assertEquals(len(parser.valid_options), 1)
+ self.assertTrue(parser.has_option("--withshort"))
+ self.assertTrue(parser.has_option("-S"))
diff --git a/tests/19_test_gbp_scripts_config.py b/tests/19_test_gbp_scripts_config.py
new file mode 100644
index 0000000..3c85b8b
--- /dev/null
+++ b/tests/19_test_gbp_scripts_config.py
@@ -0,0 +1,111 @@
+# vim: set fileencoding=utf-8 :
+# (C) 2014,2016 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, please see
+# <http://www.gnu.org/licenses/>
+"""Test the L{gbp} config command"""
+
+import os
+import unittest
+import gbp.scripts.config
+
+
+class TestGbpConfigCommand(unittest.TestCase):
+ class SingleValuePrintStub(object):
+ def __init__(self):
+ self.result = None
+
+ def __call__(self, arg):
+ self.result = arg
+
+ class AllValuesPrintStub(object):
+ def __init__(self, cmd):
+ self.cmd = cmd
+ self.result = {}
+
+ def __call__(self, arg):
+ k, v = arg.split('=', 1)
+ self.result[k] = v
+
+ def setUp(self):
+ self.conffiles_save = os.environ.get('GBP_CONF_FILES')
+ self.confname = 'tests/data/gbp_config.conf'
+ self.assertTrue(os.path.exists(self.confname))
+ os.environ['GBP_CONF_FILES'] = self.confname
+
+ def tearDown(self):
+ if self.conffiles_save:
+ os.environ['GBP_CONF_FILES'] = self.conffiles_save
+
+ def test_invocation_single_value(self):
+ """Can invoke it for a sngle value without error"""
+ ret = gbp.scripts.config.main(['argv0', 'config.color'])
+ self.assertEqual(ret, 0)
+
+ def test_invocation_missing_value(self):
+ """Can we detect a missing value"""
+ ret = gbp.scripts.config.main(['argv0', 'config.doesnotexist'])
+ self.assertEqual(ret, 2)
+
+ def test_print_cmd_single_value_default(self):
+ """Can we fetch a single configuration value that is at it's default"""
+ printstub = self.SingleValuePrintStub()
+ query = 'config.color'
+ ret = gbp.scripts.config.print_cmd_values(query, printstub)
+ self.assertEqual(printstub.result, 'auto')
+ self.assertEqual(ret, 0)
+
+ def test_print_cmd_single_value_empty_default(self):
+ """Can we fetch a single configuration value that is at it's default which is empty"""
+ printstub = self.SingleValuePrintStub()
+ query = 'buildpackage.keyid'
+ ret = gbp.scripts.config.print_cmd_values(query, printstub)
+ self.assertEqual(printstub.result, '')
+ self.assertEqual(ret, 0)
+
+ def test_print_cmd_single_value_override(self):
+ """Can we fetch a single configuration value that is overridden by config"""
+ printstub = self.SingleValuePrintStub()
+ query = 'config.color-scheme'
+ ret = gbp.scripts.config.print_cmd_values(query, printstub)
+ self.assertEqual(printstub.result, 'checkcheck')
+ self.assertEqual(ret, 0)
+
+ def test_print_cmd_all_values(self):
+ """Can we fetch the configuration for all commands"""
+ for cmd in ['buildpackage',
+ 'buildpackage_rpm',
+ 'clone',
+ 'config',
+ 'create_remote_repo',
+ 'dch',
+ 'import_dsc',
+ 'import_orig',
+ 'import_srpm',
+ 'pq',
+ 'pq_rpm',
+ 'pull',
+ 'rpm_ch']:
+ printstub = self.AllValuesPrintStub(cmd)
+ ret = gbp.scripts.config.print_cmd_values(cmd, printstub)
+ self.assertIn('%s.color' % cmd, printstub.result.keys())
+ self.assertEquals(printstub.result['%s.color' % cmd], 'auto')
+ self.assertEqual(ret, 0)
+
+ def test_nonexistent_cmds(self):
+ """Non-existing commands should print no values"""
+ for cmd in ["import_dscs", "supercommand", "nonexistent"]:
+ printstub = self.AllValuesPrintStub(cmd)
+ ret = gbp.scripts.config.print_cmd_values(cmd, printstub)
+ self.assertEquals(printstub.result, dict())
+ self.assertEqual(ret, 2)
diff --git a/tests/20_test_rpm.py b/tests/20_test_rpm.py
new file mode 100644
index 0000000..a2cd5e0
--- /dev/null
+++ b/tests/20_test_rpm.py
@@ -0,0 +1,422 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Test the classes under L{gbp.rpm}"""
+
+import filecmp
+import os
+import shutil
+import tempfile
+from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
+
+from gbp.errors import GbpError
+from gbp.rpm import (SpecFile, SrcRpmFile, NoSpecError, guess_spec,
+ guess_spec_repo, spec_from_repo)
+from gbp.git.repository import GitRepository
+
+# Disable "Method could be a function"
+# pylint: disable=R0201
+
+
+DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data',
+ 'rpm')
+SRPM_DIR = os.path.join(DATA_DIR, 'srpms')
+SPEC_DIR = os.path.join(DATA_DIR, 'specs')
+
+
+class SpecFileTester(SpecFile):
+ """Helper class for testing"""
+
+ def protected(self, name):
+ """Get a protected member"""
+ return super(SpecFileTester, self).__getattribute__(name)
+
+
+class RpmTestBase(object):
+ """Test base class"""
+ def __init__(self):
+ self.tmpdir = None
+
+ def setup(self):
+ """Test case setup"""
+ self.tmpdir = tempfile.mkdtemp(prefix='gbp_%s_' % __name__, dir='.')
+
+ def teardown(self):
+ """Test case teardown"""
+ shutil.rmtree(self.tmpdir)
+
+
+class TestSrcRpmFile(RpmTestBase):
+ """Test L{gbp.rpm.SrcRpmFile}"""
+
+ def test_srpm(self):
+ """Test parsing of a source rpm"""
+ srpm = SrcRpmFile(os.path.join(SRPM_DIR, 'gbp-test-1.0-1.src.rpm'))
+ eq_(srpm.version, {'release': '1', 'upstreamversion': '1.0'})
+ eq_(srpm.name, 'gbp-test')
+ eq_(srpm.upstreamversion, '1.0')
+ eq_(srpm.packager, None)
+
+ def test_srpm_2(self):
+ """Test parsing of another source rpm"""
+ srpm = SrcRpmFile(os.path.join(SRPM_DIR, 'gbp-test2-3.0-0.src.rpm'))
+ eq_(srpm.version, {'release': '0', 'upstreamversion': '3.0',
+ 'epoch': '2'})
+ eq_(srpm.packager, 'Markus Lehtonen <markus.lehtonen@linux.intel.com>')
+
+ def test_unpack_srpm(self):
+ """Test unpacking of a source rpm"""
+ srpm = SrcRpmFile(os.path.join(SRPM_DIR, 'gbp-test-1.0-1.src.rpm'))
+ srpm.unpack(self.tmpdir)
+ for fn in ['gbp-test-1.0.tar.bz2', 'foo.txt', 'bar.tar.gz', 'my.patch',
+ 'my2.patch', 'my3.patch']:
+ ok_(os.path.exists(os.path.join(self.tmpdir, fn)),
+ "%s not found" % fn)
+
+
+class TestSpecFile(RpmTestBase):
+ """Test L{gbp.rpm.SpecFile}"""
+
+ def test_spec(self):
+ """Test parsing of a valid spec file"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test.spec')
+ spec = SpecFileTester(spec_filepath)
+
+ # Test basic properties
+ eq_(spec.specfile, os.path.basename(spec_filepath))
+ eq_(spec.specdir, os.path.dirname(spec_filepath))
+ eq_(spec.specpath, spec_filepath)
+
+ eq_(spec.name, 'gbp-test')
+ eq_(spec.packager, None)
+
+ eq_(spec.upstreamversion, '1.0')
+ eq_(spec.release, '1')
+ eq_(spec.epoch, None)
+ eq_(spec.version, {'release': '1', 'upstreamversion': '1.0'})
+
+ orig = spec.orig_src
+ eq_(orig['filename'], 'gbp-test-1.0.tar.bz2')
+ eq_(orig['uri'], 'gbp-test-1.0.tar.bz2')
+ eq_(orig['filename_base'], 'gbp-test-1.0')
+ eq_(orig['archive_fmt'], 'tar')
+ eq_(orig['compression'], 'bzip2')
+ eq_(orig['prefix'], 'gbp-test/')
+
+ def test_spec_2(self):
+ """Test parsing of another valid spec file"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test2.spec')
+ spec = SpecFile(spec_filepath)
+
+ # Test basic properties
+ eq_(spec.name, 'gbp-test2')
+ eq_(spec.packager, 'Markus Lehtonen <markus.lehtonen@linux.intel.com>')
+
+ eq_(spec.epoch, '2')
+ eq_(spec.version, {'release': '0', 'upstreamversion': '3.0',
+ 'epoch': '2'})
+
+ orig = spec.orig_src
+ eq_(orig['filename'], 'gbp-test2-3.0.tar.gz')
+ eq_(orig['uri'], 'ftp://ftp.host.com/gbp-test2-3.0.tar.gz')
+ eq_(orig['archive_fmt'], 'tar')
+ eq_(orig['compression'], 'gzip')
+ eq_(orig['prefix'], '')
+
+ def test_spec_3(self):
+ """Test parsing of yet another valid spec file"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-native.spec')
+ spec = SpecFile(spec_filepath)
+
+ # Test basic properties
+ eq_(spec.name, 'gbp-test-native')
+ orig = spec.orig_src
+ eq_(orig['filename'], 'gbp-test-native-1.0.zip')
+ eq_(orig['archive_fmt'], 'zip')
+ eq_(orig['compression'], None)
+ eq_(orig['prefix'], 'gbp-test-native-1.0/')
+
+ def test_spec_4(self):
+ """Test parsing of spec without orig tarball"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-native2.spec')
+ spec = SpecFile(spec_filepath)
+
+ # Test basic properties
+ eq_(spec.name, 'gbp-test-native2')
+ eq_(spec.orig_src, None)
+
+ def test_parse_raw(self):
+ """Test parsing of a valid spec file"""
+ with assert_raises(NoSpecError):
+ SpecFile(None, None)
+ with assert_raises(NoSpecError):
+ SpecFile('filename', 'filedata')
+
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test.spec')
+ with open(spec_filepath, 'r') as spec_fd:
+ spec_data = spec_fd.read()
+ spec = SpecFile(filedata=spec_data)
+
+ # Test basic properties
+ eq_(spec.specfile, None)
+ eq_(spec.specdir, None)
+ eq_(spec.name, 'gbp-test')
+
+ def test_update_spec(self):
+ """Test spec autoupdate functionality"""
+ # Create temporary spec file
+ tmp_spec = os.path.join(self.tmpdir, 'gbp-test.spec')
+ shutil.copy2(os.path.join(SPEC_DIR, 'gbp-test.spec'), tmp_spec)
+
+ reference_spec = os.path.join(SPEC_DIR, 'gbp-test-reference.spec')
+ spec = SpecFile(tmp_spec)
+ spec.update_patches(['new.patch'], {})
+ spec.write_spec_file()
+ eq_(filecmp.cmp(tmp_spec, reference_spec), True)
+
+ # Test adding the VCS tag and adding changelog
+ reference_spec = os.path.join(SPEC_DIR, 'gbp-test-reference2.spec')
+ spec.set_tag('VCS', None, 'myvcstag')
+ spec.set_changelog("* Wed Feb 05 2014 Name <email> 1\n- New entry\n")
+ spec.write_spec_file()
+ eq_(filecmp.cmp(tmp_spec, reference_spec), True)
+
+ def test_update_spec2(self):
+ """Another test for spec autoupdate functionality"""
+ tmp_spec = os.path.join(self.tmpdir, 'gbp-test2.spec')
+ shutil.copy2(os.path.join(SPEC_DIR, 'gbp-test2.spec'), tmp_spec)
+
+ reference_spec = os.path.join(SPEC_DIR, 'gbp-test2-reference2.spec')
+ spec = SpecFile(tmp_spec)
+ spec.update_patches(['1.patch', '2.patch'],
+ {'1.patch': {'if': 'true'},
+ '2.patch': {'ifarch': '%ix86'}})
+ spec.set_tag('VCS', None, 'myvcstag')
+ spec.write_spec_file()
+ eq_(filecmp.cmp(tmp_spec, reference_spec), True)
+
+ # Test updating patches again, removing the VCS tag and re-writing
+ # changelog
+ reference_spec = os.path.join(SPEC_DIR, 'gbp-test2-reference.spec')
+ spec.update_patches(['new.patch'], {'new.patch': {'if': '1'}})
+ spec.set_tag('VCS', None, '')
+ spec.set_changelog("* Wed Feb 05 2014 Name <email> 2\n- New entry\n\n")
+ spec.write_spec_file()
+ eq_(filecmp.cmp(tmp_spec, reference_spec), True)
+
+ def test_modifying(self):
+ """Test updating/deleting of tags and macros"""
+ tmp_spec = os.path.join(self.tmpdir, 'gbp-test.spec')
+ shutil.copy2(os.path.join(SPEC_DIR, 'gbp-test-updates.spec'), tmp_spec)
+ reference_spec = os.path.join(SPEC_DIR,
+ 'gbp-test-updates-reference.spec')
+ spec = SpecFileTester(tmp_spec)
+
+ # Mangle tags
+ prev = spec.protected('_delete_tag')('Vendor', None)
+ spec.protected('_set_tag')('License', None, 'new license', prev)
+ spec.protected('_delete_tag')('source', 0)
+ eq_(spec.sources(), {})
+ spec.protected('_delete_tag')('patch', 0)
+ spec.protected('_delete_tag')('patch', -1)
+ eq_(spec.protected('_patches')(), {})
+ prev = spec.protected('_delete_tag')('invalidtag', None)
+
+ with assert_raises(GbpError):
+ # Check that setting empty value fails
+ spec.protected('_set_tag')('Version', None, '', prev)
+ with assert_raises(GbpError):
+ # Check that setting invalid tag with public method fails
+ spec.set_tag('invalidtag', None, 'value')
+
+ # Mangle macros
+ prev = spec.protected('_delete_special_macro')('patch', -1)
+ spec.protected('_delete_special_macro')('patch', 123)
+ spec.protected('_set_special_macro')('patch', 0, 'my new args', prev)
+ with assert_raises(GbpError):
+ spec.protected('_delete_special_macro')('invalidmacro', 0)
+ with assert_raises(GbpError):
+ spec.protected('_set_special_macro')('invalidmacro', 0, 'args',
+ prev)
+
+ # Check resulting spec file
+ spec.write_spec_file()
+ eq_(filecmp.cmp(tmp_spec, reference_spec), True)
+
+ def test_modifying_err(self):
+ """Test error conditions of modification methods"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test2.spec')
+ spec = SpecFileTester(spec_filepath)
+
+ # Unknown/invalid section name
+ with assert_raises(GbpError):
+ spec.protected('_set_section')('patch', 'new content\n')
+
+ # Multiple sections with the same name
+ with assert_raises(GbpError):
+ spec.protected('_set_section')('files', '%{_sysconfdir}/foo\n')
+
+ def test_changelog(self):
+ """Test changelog methods"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test2.spec')
+ spec = SpecFile(spec_filepath)
+
+ # Read changelog
+ eq_(spec.get_changelog(),
+ "* Tue Feb 04 2014 Name <email> 1\n- My change\n\n\n")
+
+ # Set changelog and check again
+ new_text = "* Wed Feb 05 2014 Name <email> 2\n- New entry\n\n\n"
+ spec.set_changelog(new_text)
+ eq_(spec.get_changelog(), new_text)
+
+ def test_quirks(self):
+ """Test spec that is broken/has anomalities"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-quirks.spec')
+ spec = SpecFile(spec_filepath)
+
+ # Check that we quess orig source and prefix correctly
+ eq_(spec.orig_src['prefix'], 'foobar/')
+
+ def test_tags(self):
+ """Test parsing of all the different tags of spec file"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-tags.spec')
+ spec = SpecFileTester(spec_filepath)
+
+ # Check all the tags
+ for name, val in spec.protected('_tags').items():
+ rval = None
+ if name in ('version', 'release', 'epoch'):
+ rval = '0'
+ elif name in ('autoreq', 'autoprov', 'autoreqprov'):
+ rval = 'No'
+ elif name not in spec.protected('_listtags'):
+ rval = 'my_%s' % name
+ if rval:
+ eq_(val['value'], rval, ("'%s:' is '%s', expecting '%s'" %
+ (name, val['value'], rval)))
+ eq_(spec.ignorepatches, [])
+ # Check patch numbers and patch filenames
+ patches = {}
+ for patch in spec.protected('_tags')['patch']['lines']:
+ patches[patch['num']] = patch['linevalue']
+
+ eq_(patches, {0: 'my_patch0', -1: 'my_patch'})
+
+ def test_patch_series(self):
+ """Test the getting the patches as a patchseries"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-native.spec')
+ spec = SpecFileTester(spec_filepath)
+
+ eq_(len(spec.patchseries()), 0)
+ spec.update_patches(['1.patch', '2.patch', '3.patch'], {})
+ eq_(len(spec.patchseries()), 3)
+ spec.protected('_gbp_tags')['ignore-patches'].append({'args': "0"})
+ spec.update_patches(['4.patch'], {})
+ eq_(len(spec.patchseries()), 1)
+ eq_(len(spec.patchseries(ignored=True)), 2)
+ spec.protected('_delete_special_macro')('patch', 0)
+ eq_(len(spec.patchseries(ignored=True)), 1)
+ series = spec.patchseries(unapplied=True, ignored=True)
+ eq_(len(series), 2)
+ eq_(os.path.basename(series[-1].path), '1.patch')
+
+ def test_patch_series_autosetup(self):
+ """Check patch series functionalitu with %autostup"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test3.spec')
+ spec = SpecFileTester(spec_filepath)
+
+ eq_(len(spec.patchseries()), 2)
+ eq_(len(spec.patchseries(ignored=True)), 3)
+ spec.update_patches(['1.patch'], {})
+ eq_(len(spec.patchseries()), 1)
+ eq_(len(spec.patchseries(ignored=True)), 2)
+ eq_(spec.protected('_special_directives')['patch'], [])
+
+ def test_patch_series_quirks(self):
+ """Patches are applied in order different from the patch numbering"""
+ spec_filepath = os.path.join(SPEC_DIR, 'gbp-test-quirks.spec')
+ spec = SpecFileTester(spec_filepath)
+
+ # Check series is returned in the order the patches are applied
+ files = [os.path.basename(patch.path) for patch in spec.patchseries()]
+ eq_(files, ['05.patch', '01.patch'])
+ # Also ignored patches are returned in the correct order
+ files = [os.path.basename(patch.path) for patch in
+ spec.patchseries(ignored=True)]
+ eq_(files, ['05.patch', '02.patch', '01.patch'])
+ # Unapplied patches are added to the end of the series
+ files = [os.path.basename(patch.path) for patch in
+ spec.patchseries(unapplied=True)]
+ eq_(files, ['05.patch', '01.patch', '03.patch'])
+ # Return all patches (for which tag is found)
+ files = [os.path.basename(patch.path) for patch in
+ spec.patchseries(unapplied=True, ignored=True)]
+ eq_(files, ['05.patch', '02.patch', '01.patch', '03.patch', '04.patch'])
+
+
+class TestUtilityFunctions(RpmTestBase):
+ """Test utility functions of L{gbp.rpm}"""
+
+ def test_guess_spec(self):
+ """Test guess_spec() function"""
+ # Spec not found
+ with assert_raises(NoSpecError):
+ guess_spec(DATA_DIR, recursive=False)
+ # Multiple spec files
+ with assert_raises(NoSpecError):
+ guess_spec(DATA_DIR, recursive=True)
+ with assert_raises(NoSpecError):
+ guess_spec(SPEC_DIR, recursive=False)
+ # Spec found
+ spec = guess_spec(SPEC_DIR, recursive=False,
+ preferred_name='gbp-test2.spec')
+ eq_(spec.specfile, 'gbp-test2.spec')
+ eq_(spec.specdir, SPEC_DIR)
+
+ def test_guess_spec_repo(self):
+ """Test guess_spec_repo() and spec_from_repo() functions"""
+ # Create dummy repository with some commits
+ repo = GitRepository.create(self.tmpdir)
+ with open(os.path.join(repo.path, 'foo.txt'), 'w') as fobj:
+ fobj.write('bar\n')
+ repo.add_files('foo.txt')
+ repo.commit_all('Add dummy file')
+ os.mkdir(os.path.join(repo.path, 'packaging'))
+ shutil.copy(os.path.join(SPEC_DIR, 'gbp-test.spec'),
+ os.path.join(repo.path, 'packaging'))
+ repo.add_files('packaging/gbp-test.spec')
+ repo.commit_all('Add spec file')
+
+ # Spec not found
+ with assert_raises(NoSpecError):
+ guess_spec_repo(repo, 'HEAD~1', recursive=True)
+ with assert_raises(NoSpecError):
+ guess_spec_repo(repo, 'HEAD', recursive=False)
+ # Spec found
+ spec = guess_spec_repo(repo, 'HEAD', 'packaging', recursive=False)
+ spec = guess_spec_repo(repo, 'HEAD', recursive=True)
+ eq_(spec.specfile, 'gbp-test.spec')
+ eq_(spec.specdir, 'packaging')
+ eq_(spec.specpath, 'packaging/gbp-test.spec')
+
+ # Test spec_from_repo()
+ with assert_raises(NoSpecError):
+ spec_from_repo(repo, 'HEAD~1', 'packaging/gbp-test.spec')
+ spec = spec_from_repo(repo, 'HEAD', 'packaging/gbp-test.spec')
+ eq_(spec.specfile, 'gbp-test.spec')
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/21_test_command_wrappers.py b/tests/21_test_command_wrappers.py
new file mode 100644
index 0000000..3d0ed96
--- /dev/null
+++ b/tests/21_test_command_wrappers.py
@@ -0,0 +1,80 @@
+# vim: set fileencoding=utf-8 :
+"""Test L{gbp.command_wrappers.Command}'s tarball unpack"""
+
+import unittest
+
+from gbp.command_wrappers import Command, CommandExecFailed
+from . testutils import GbpLogTester, patch_popen
+
+
+class TestCommandWrapperFailures(unittest.TestCase, GbpLogTester):
+ def setUp(self):
+ self.false = Command('/does/not/matter')
+ self.log_tester = GbpLogTester()
+ self.log_tester._capture_log(True)
+
+ def tearDown(self):
+ self.log_tester._capture_log(False)
+
+ @patch_popen(stdout=b'', stderr=b'', returncode=1)
+ def test_log_default_error_msg(self, create_mock):
+ with self.assertRaises(CommandExecFailed):
+ self.false.__call__()
+ self.log_tester._check_log(0, "gbp:error: '/does/not/matter' failed: it exited with 1")
+ self.assertEqual(self.false.retcode, 1)
+ self.assertEqual(self.false.stderr, '')
+ self.assertEqual(self.false.stdout, '')
+
+ @patch_popen(stdout=b'', stderr=b'we have a problem', returncode=1)
+ def test_log_use_stderr_for_err_message(self, create_mock):
+ self.false.capture_stderr = True
+ self.false.run_error = "Erpel {stderr}"
+ with self.assertRaises(CommandExecFailed):
+ self.false.__call__()
+ self.log_tester._check_log(0, "gbp:error: Erpel we have a problem")
+ self.assertEqual(self.false.retcode, 1)
+ self.assertEqual(self.false.stderr, 'we have a problem')
+ self.assertEqual(self.false.stdout, '')
+
+ @patch_popen(stdout=b'', stderr=b'we have a problem', returncode=1)
+ def test_log_quote_format(self, create_mock):
+ self.false = Command('/does/{not}/matter')
+ self.false.capture_stderr = True
+ with self.assertRaises(CommandExecFailed):
+ self.false.__call__()
+ self.log_tester._check_log(0, "gbp:error: '/does/{not}/matter' failed: it exited with 1")
+ self.assertEqual(self.false.retcode, 1)
+ self.assertEqual(self.false.stderr, 'we have a problem')
+ self.assertEqual(self.false.stdout, '')
+
+ @patch_popen(stdout=b'we have a problem', stderr=b'', returncode=1)
+ def test_log_use_stdout_for_err_message(self, create_mock):
+ self.false.capture_stdout = True
+ self.false.run_error = "Erpel {stdout}"
+ with self.assertRaises(CommandExecFailed):
+ self.false.__call__()
+ self.log_tester._check_log(0, "gbp:error: Erpel we have a problem")
+ self.assertEqual(self.false.retcode, 1)
+ self.assertEqual(self.false.stderr, '')
+ self.assertEqual(self.false.stdout, 'we have a problem')
+
+ def test_log_use_err_or_reason_for_error_messge_reason(self):
+ self.false.run_error = "AFAIK {stderr_or_reason}"
+ with self.assertRaises(CommandExecFailed):
+ self.false.__call__()
+ self.log_tester._check_log(0, "gbp:error: AFAIK execution failed: .Errno 2. No such file or directory")
+ self.assertEqual(self.false.retcode, 1)
+
+ @patch_popen(stderr=b'we have a problem', returncode=1)
+ def test_log_use_err_or_reason_for_error_messge_error(self, create_mock):
+ self.false.run_error = "Erpel {stderr_or_reason}"
+ with self.assertRaises(CommandExecFailed):
+ self.false.__call__()
+ self.log_tester._check_log(0, "gbp:error: Erpel we have a problem")
+ self.assertEqual(self.false.retcode, 1)
+
+ @patch_popen(returncode=0)
+ def test_no_log_on_success(self, create_mock):
+ self.false.__call__()
+ self.log_tester._check_log_empty()
+ self.assertEqual(self.false.retcode, 0)
diff --git a/tests/22_test_gbp_buildpackage.py b/tests/22_test_gbp_buildpackage.py
new file mode 100644
index 0000000..9f7eca6
--- /dev/null
+++ b/tests/22_test_gbp_buildpackage.py
@@ -0,0 +1,126 @@
+# vim: set fileencoding=utf-8 :
+"""Test L{gbp.command_wrappers.Command}'s tarball unpack"""
+
+from gbp.scripts.buildpackage import (get_pbuilder_dist,
+ setup_pbuilder,
+ GbpError)
+from . testutils import DebianGitTestRepo
+
+from mock import patch
+
+
+class TestGbpBuildpackageDep14(DebianGitTestRepo):
+ class Options(object):
+ pass
+
+ def setUp(self):
+ DebianGitTestRepo.setUp(self)
+ self.add_file('doesnotmatter')
+ self.options = self.Options()
+ self.options.pbuilder_dist = 'DEP14'
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_no_dep14(self, patch):
+ self.options.pbuilder_dist = 'notdep14'
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo),
+ self.options.pbuilder_dist)
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_debian_sid(self, patch):
+ branch = 'debian/sid'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo), '')
+ patch.assert_called_once_with()
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_debian_master(self, patch):
+ branch = 'debian/master'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo), '')
+ patch.assert_called_once_with()
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_debian_suite(self, patch):
+ branch = 'debian/squeeze-lts'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo), 'squeeze-lts')
+ patch.assert_called_once_with()
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_debian_native(self, patch):
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo, True), '')
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_vendor_sid(self, patch):
+ branch = 'downstream/sid'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo), 'downstream_sid')
+ patch.assert_called_once_with()
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_vendor_suite(self, patch):
+ branch = 'downstream/mies-lts'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo), 'downstream_mies-lts')
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_no_vendor_sid(self, patch):
+ branch = 'sid'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo), '')
+ patch.assert_called_once_with()
+
+ @patch('gbp.deb.get_vendor', return_value='Debian')
+ def test_get_pbuilder_dist_dep14_no_vendor(self, patch):
+ branch = 'wheezy'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ self.assertEqual(get_pbuilder_dist(self.options, self.repo), 'wheezy')
+ patch.assert_called_once_with()
+
+ def test_get_pbuilder_dist_dep14_too_many_slashes(self):
+ branch = 'too/many/slashes'
+ self.repo.create_branch(branch)
+ self.repo.set_branch(branch)
+ with self.assertRaisesRegexp(GbpError,
+ "DEP14 DIST: Current branch 'too/many/slashes' does not match vendor/suite"):
+ get_pbuilder_dist(self.options, self.repo)
+
+
+class TestGbpBuildpackageSetupPbuilder(DebianGitTestRepo):
+ class Options(object):
+ use_pbuilder = True
+ pbuilder_dist = 'sid'
+ pbuilder_arch = ''
+ use_qemubuilder = False
+ pbuilder_autoconf = True
+ pbuilder_options = ''
+
+ def setUp(self):
+ DebianGitTestRepo.setUp(self)
+ self.options = self.Options()
+
+ def test_setup_pbuilder(self):
+ self.assertEqual(setup_pbuilder(self.options,
+ self.repo,
+ True),
+ ({'GBP_PBUILDER_DIST': 'sid', 'DIST': 'sid'},
+ {'GBP_PBUILDER_DIST': 'sid'}))
+
+ def test_setup_pbuilder_arch(self):
+ self.options.pbuilder_arch = 'arm64'
+ self.assertEqual(setup_pbuilder(self.options,
+ self.repo,
+ True),
+ ({'ARCH': 'arm64',
+ 'DIST': 'sid',
+ 'GBP_PBUILDER_ARCH': 'arm64',
+ 'GBP_PBUILDER_DIST': 'sid'},
+ {'GBP_PBUILDER_ARCH': 'arm64',
+ 'GBP_PBUILDER_DIST': 'sid'}))
diff --git a/tests/23_test_dch_extract_bts_cmds.py b/tests/23_test_dch_extract_bts_cmds.py
new file mode 100644
index 0000000..e1fb7e0
--- /dev/null
+++ b/tests/23_test_dch_extract_bts_cmds.py
@@ -0,0 +1,55 @@
+# (C) 2015 Jonathan Toppins <jtoppins@cumulusnetworks.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Test gbp.dch.extract_bts_cmds()"""
+
+import unittest
+
+from gbp.dch import extract_bts_cmds
+
+
+class OptionsStub:
+ def __init__(self):
+ self.meta_closes = "Closes|LP"
+ self.meta_closes_bugnum = r'(?:bug|issue)?\#?\s?\d+'
+
+
+class TestExtractBTSCmds(unittest.TestCase):
+ def test_debian_commands(self):
+ """Test default BTS command extraction that is applicable to Debian"""
+ options = OptionsStub()
+ lines = """This is a test commit
+
+Closes: bug#12345
+Closes: 456
+"""
+ bugs, dummy = extract_bts_cmds(lines.split('\n'), options)
+ self.assertEquals(bugs, {'Closes': ['bug#12345', '456']})
+
+ def test_nondebian_commands(self):
+ """Test non-default BTS commands. We use the example given in the
+ documentation manpages."""
+ options = OptionsStub()
+ options.meta_closes_bugnum = "(?:bug)?\s*ex-\d+"
+ lines = """This is a test commit
+some more lines...
+
+Closes: bug EX-12345
+Closes: ex-01273
+Closes: bug ex-1ab
+Closes: EX--12345
+"""
+ bugs, dummy = extract_bts_cmds(lines.split('\n'), options)
+ self.assertEquals(bugs, {'Closes': ['bug EX-12345', 'ex-01273',
+ 'bug ex-1']})
diff --git a/tests/24_test_gbp_import_orig.py b/tests/24_test_gbp_import_orig.py
new file mode 100644
index 0000000..fadff28
--- /dev/null
+++ b/tests/24_test_gbp_import_orig.py
@@ -0,0 +1,90 @@
+# vim: set fileencoding=utf-8 :
+"""Test L{gbp.scripts.import_orig}"""
+
+import os
+import unittest
+
+from collections import namedtuple
+
+from gbp.scripts.import_orig import (debian_branch_merge_by_replace,
+ GbpError,
+ is_30_quilt)
+from gbp.scripts.common.import_orig import download_orig
+from . testutils import DebianGitTestRepo
+
+
+@unittest.skipUnless(os.getenv("GBP_NETWORK_TESTS"), "network tests disabled")
+class TestImportOrigDownload(DebianGitTestRepo):
+ HOST = 'git.sigxcpu.org'
+
+ def setUp(self):
+ DebianGitTestRepo.setUp(self)
+ os.chdir(self.repo.path)
+
+ def test_404_download(self):
+ with self.assertRaisesRegexp(GbpError, "404 Client Error: Not Found for url"):
+ download_orig("https://{host}/does_not_exist".format(host=self.HOST))
+
+ def test_200_download(self):
+ pkg = 'hello-debhelper_2.6.orig.tar.gz'
+ url = "https://{host}/cgit/gbp/deb-testdata/tree/dsc-3.0/{pkg}".format(host=self.HOST,
+ pkg=pkg)
+ self.assertEqual(download_orig(url).path, '../%s' % pkg)
+
+
+class TestIs30Quilt(DebianGitTestRepo):
+ Options = namedtuple('Options', 'debian_branch')
+ format_file = 'debian/source/format'
+
+ def setUp(self):
+ DebianGitTestRepo.setUp(self)
+ os.chdir(self.repo.path)
+ os.makedirs('debian/source/')
+
+ def test_30_quilt(self):
+ options = self.Options(debian_branch='master')
+ with open(self.format_file, 'w') as f:
+ f.write('3.0 (quilt)\n')
+ self.repo.add_files([self.format_file])
+ self.repo.commit_all("Add %s" % self.format_file)
+ self.assertEquals(self.repo.branch, options.debian_branch)
+ self.assertTrue(is_30_quilt(self.repo, options))
+
+ def test_no_format(self):
+ options = self.Options(debian_branch='master')
+ self.assertFalse(os.path.exists(self.format_file))
+ self.assertFalse(is_30_quilt(self.repo, options))
+
+ def test_no_quilt(self):
+ options = self.Options(debian_branch='master')
+ with open(self.format_file, 'w') as f:
+ f.write('3.0 (nonexistent)')
+ self.assertFalse(is_30_quilt(self.repo, options))
+
+ def test_30_quilt_empty_repo(self):
+ options = self.Options(debian_branch='master')
+ self.assertFalse(is_30_quilt(self.repo, options))
+
+
+class TestMergeModeReplace(DebianGitTestRepo):
+ debian_branch = 'master'
+
+ def setUp(self):
+ DebianGitTestRepo.setUp(self)
+ os.chdir(self.repo.path)
+
+ def testDebianDir(self):
+ """Test that dropping upstream's debian/ workd (#881750)"""
+ self.add_file("debian/control")
+ self.repo.create_branch("upstream")
+ self.repo.set_branch("upstream")
+ self.add_file("upstream_file")
+ self.add_file("debian/changelog")
+ self.repo.set_branch("master")
+ self.repo.create_tag('upstream/1.0', "Upstream 1.0", "upstream")
+ debian_branch_merge_by_replace(self.repo, "upstream/1.0", "1.0", self)
+ self.assertTrue(os.path.exists("debian/control"))
+ # Upstream files must end up on debian branch…
+ self.assertTrue(os.path.exists("upstream_file"))
+ # … but upsream's debian dir must not
+ self.assertFalse(os.path.exists("debian/changelog"))
diff --git a/tests/25_test_broken_gbp_conf.py b/tests/25_test_broken_gbp_conf.py
new file mode 100644
index 0000000..79d551f
--- /dev/null
+++ b/tests/25_test_broken_gbp_conf.py
@@ -0,0 +1,58 @@
+# vim: set fileencoding=utf-8 :
+
+"""Check if we fail correcdtly on broken gbp.conf"""
+
+from . import context
+
+from . testutils.data import TestCaseWithData
+from . testutils.gbplogtester import GbpLogTester
+
+import os
+import unittest
+
+
+class TestBrokenConfig(TestCaseWithData, GbpLogTester):
+ """Test that broken config gives a sensible error for all commands"""
+
+ cmds = ['buildpackage',
+ 'clone',
+ 'config',
+ 'create_remote_repo',
+ 'dch',
+ 'import_orig',
+ 'import_dsc',
+ 'pull',
+ 'pq',
+ 'import_srpm',
+ 'buildpackage_rpm',
+ 'pq_rpm',
+ 'rpm_ch']
+
+ def __init__(self, methodName='runTest'):
+ unittest.TestCase.__init__(self, methodName)
+ GbpLogTester.__init__(self)
+
+ def setUp(self):
+ tmpdir = str(context.new_tmpdir('bar'))
+ confname = os.path.join(tmpdir, 'gbp.conf')
+ with open(confname, 'w') as f:
+ f.write("this is a broken config\n")
+ os.environ['GBP_CONF_FILES'] = confname
+ self._capture_log(True)
+
+ def tearDown(self):
+ del os.environ['GBP_CONF_FILES']
+
+ @TestCaseWithData.feed(cmds)
+ def testBrokenConf(self, cmd):
+ module = 'gbp.scripts.%s' % cmd
+ try:
+ m = __import__(module, globals(), locals(), ['main'], 0)
+ ret = m.main([cmd, '--help'])
+ self.assertEquals(ret, 3)
+ except Exception as e:
+ self.assertTrue(False, "Caught '%s'" % e)
+ self._check_log(-1, "See 'man gbp.conf' for the format.")
+ self._clear_log()
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/26_test_dch_extract_thanks.py b/tests/26_test_dch_extract_thanks.py
new file mode 100644
index 0000000..f0da01d
--- /dev/null
+++ b/tests/26_test_dch_extract_thanks.py
@@ -0,0 +1,40 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2015 Jonathan Toppins <jtoppins@cumulusnetworks.com>
+# (C) 2016 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, please see
+# <http://www.gnu.org/licenses/>
+"""Test gbp.dch.extract_thanks_info()"""
+
+import unittest
+
+from gbp.dch import extract_thanks_info
+
+
+class OptionsStub:
+ def __init__(self):
+ self.meta_closes = "Closes|LP"
+ self.meta_closes_bugnum = r'(?:bug|issue)?\#?\s?\d+'
+
+
+class TestExtractThanks(unittest.TestCase):
+ def test_debian_commands(self):
+ """Test default thanks extraction"""
+ lines = """
+thAnks: a lot
+Thanks: everyone"""
+
+ lines += " \n" # Add some trailing whitespace
+ bugs, dummy = extract_thanks_info(lines.split('\n'), None)
+ self.assertEquals(bugs, ['a lot', 'everyone'])
diff --git a/tests/27_test_create_remote_repo.py b/tests/27_test_create_remote_repo.py
new file mode 100644
index 0000000..7ab3c51
--- /dev/null
+++ b/tests/27_test_create_remote_repo.py
@@ -0,0 +1,47 @@
+# vim: set fileencoding=utf-8 :
+# (C) 2016 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, please see
+# <http://www.gnu.org/licenses/>
+"""Test the L{gbp} create_remote_repo command"""
+
+import os
+import unittest
+import gbp.scripts.create_remote_repo as create_remote_repo
+
+
+class TestGbpCreateRemoteRepoCommand(unittest.TestCase):
+ def setUp(self):
+ self.conffiles_save = os.environ.get('GBP_CONF_FILES')
+
+ def tearDown(self):
+ if self.conffiles_save:
+ os.environ['GBP_CONF_FILES'] = self.conffiles_save
+
+ def test_no_config_templates(self):
+ self.confname = 'tests/data/gbp_nonexistent.conf'
+ self.assertFalse(os.path.exists(self.confname))
+ os.environ['GBP_CONF_FILES'] = self.confname
+
+ _, _, sections = create_remote_repo.parse_args(['create-remote-repo'])
+ self.assertEqual(create_remote_repo.get_config_names(sections),
+ [])
+
+ def test_list_config_templates(self):
+ self.confname = 'tests/data/gbp_create_remote_repo.conf'
+ self.assertTrue(os.path.exists(self.confname))
+ os.environ['GBP_CONF_FILES'] = self.confname
+
+ _, _, sections = create_remote_repo.parse_args(['create-remote-repo'])
+ self.assertEqual(create_remote_repo.get_config_names(sections),
+ ['config1', 'config2'])
diff --git a/tests/28_test_gbp_git_repository_commit_dir.py b/tests/28_test_gbp_git_repository_commit_dir.py
new file mode 100644
index 0000000..7fcea51
--- /dev/null
+++ b/tests/28_test_gbp_git_repository_commit_dir.py
@@ -0,0 +1,42 @@
+# vim: set fileencoding=utf-8 :
+
+import os
+
+from . testutils import DebianGitTestRepo
+from gbp.git.repository import GitRepositoryError
+
+
+class TestGitRepositoryCommitDir(DebianGitTestRepo):
+ def setUp(self):
+ DebianGitTestRepo.setUp(self)
+ self.content = os.path.join(str(self.tmpdir), 'new')
+ os.mkdir(self.content)
+ with open(os.path.join(self.content, 'file1'), 'w') as f:
+ f.write('content1')
+
+ def test_simple(self):
+ self.repo.commit_dir(self.content,
+ 'new content',
+ 'master',
+ create_missing_branch=True)
+ self.assertEquals(self.repo.show('master:file1'), b'content1')
+
+ def test_long_reflog(self):
+ """Make sure we fail on onverly long msg resulting in an
+ overly long reflog enry"""
+ with self.assertRaises(GitRepositoryError):
+ self.repo.commit_dir(self.content,
+ 'foo' * 100000,
+ 'master',
+ create_missing_branch=True)
+
+ def test_long_msg_854333(self):
+ """Make sure we shorten the reflog entry properly"""
+ self.repo.commit_dir(self.content,
+ 'foo\n' * 100000,
+ 'master',
+ create_missing_branch=True)
+ self.assertEquals(self.repo.show('master:file1'), b'content1')
+ out, dummy, ret = self.repo._git_inout('reflog', [])
+ self.assertEquals(ret, 0)
+ self.assertIn(b'HEAD@{0}: gbp: foo\n', out)
diff --git a/tests/29_test_gbp_clone.py b/tests/29_test_gbp_clone.py
new file mode 100644
index 0000000..f1ac392
--- /dev/null
+++ b/tests/29_test_gbp_clone.py
@@ -0,0 +1,34 @@
+# vim: set fileencoding=utf-8 :
+from gbp.scripts.clone import vcs_git_url
+
+import unittest
+from mock import patch
+
+from . testutils import skip_without_cmd
+
+
+class TestGbpClone(unittest.TestCase):
+ show_src = """
+Version: 0.6.22
+Standards-Version: 3.9.4
+Vcs-Git: git://honk.sigxcpu.org/git/git-buildpackage.git
+
+Version: 0.8.14
+Standards-Version: 3.9.8
+Vcs-Git: https://git.sigxcpu.org/cgit/git-buildpackage/ -b foo
+
+Version: 0.8.12.2
+Standards-Version: 3.9.8
+Vcs-Git: https://git.sigxcpu.org/cgit/git-buildpackage/
+
+Version: 0.6.0~git20120601
+Standards-Version: 3.9.3
+Vcs-Git: git://honk.sigxcpu.org/git/git-buildpackage.git
+
+"""
+
+ @skip_without_cmd('dpkg')
+ @patch('gbp.scripts.clone.apt_showsrc', return_value=show_src)
+ def test_vcs_git_url(self, patch):
+ self.assertEqual(vcs_git_url('git-buildpackage'),
+ 'https://git.sigxcpu.org/cgit/git-buildpackage/')
diff --git a/tests/30_test_deb_changelog.py b/tests/30_test_deb_changelog.py
new file mode 100644
index 0000000..9028621
--- /dev/null
+++ b/tests/30_test_deb_changelog.py
@@ -0,0 +1,67 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{gbp.deb.changelog.Changelog}
+
+Test things here that don't fit nicely into the doctests that
+also make up the API documentation.
+"""
+
+from . import context # noqa: 401
+from . testutils import skip_without_cmd
+import os
+import unittest
+
+from gbp.deb.changelog import ChangeLog
+from gbp.command_wrappers import CommandExecFailed
+
+
+class TestQuoting(unittest.TestCase):
+ def test_comma(self):
+ """Test we properly parse maitainers with comma #737623"""
+ changes = """git-buildpackage (0.9.2) unstable; urgency=low
+
+ * List of changes
+
+ -- Guido Günther, aftercomma <agx@sigxcpu.org> Sun, 12 Nov 2017 19:00:00 +0200
+"""
+ cl = ChangeLog(changes)
+ self.assertEquals(cl.author, 'Guido Günther, aftercomma')
+ self.assertEquals(cl.email, 'agx@sigxcpu.org')
+
+
+@skip_without_cmd('debchange')
+class Test(unittest.TestCase):
+ def setUp(self):
+ self.tmpdir = context.new_tmpdir(__name__)
+ context.chdir(self.tmpdir)
+ os.mkdir('debian/')
+
+ def tearDown(self):
+ context.teardown()
+
+ def test_changelog_creation_full(self):
+ cp = ChangeLog.create('package', '1.0')
+ self.assertEquals(cp.name, 'package')
+ self.assertEquals(cp.version, '1.0')
+
+ def test_changelog_creation_version(self):
+ cp = ChangeLog.create(version='1.0')
+ self.assertEquals(cp.name, 'PACKAGE')
+ self.assertEquals(cp.version, '1.0')
+
+ def test_changelog_creation_package(self):
+ cp = ChangeLog.create(package='package')
+ self.assertEquals(cp.name, 'package')
+ self.assertEquals(cp.version, 'unknown')
+
+ def test_changelog_missing_dir(self):
+ os.rmdir('debian/')
+ with self.assertRaisesRegexp(CommandExecFailed, "Cannot find debian directory"):
+ ChangeLog.create('package', '1.0')
+
+ def test_changelog_exists(self):
+ with open('debian/changelog', 'w') as f:
+ f.write('')
+ with self.assertRaisesRegexp(CommandExecFailed, "File debian/changelog already exists"):
+ ChangeLog.create('package', '1.0')
diff --git a/tests/30_test_rpm_changelog.py b/tests/30_test_rpm_changelog.py
new file mode 100644
index 0000000..85142a4
--- /dev/null
+++ b/tests/30_test_rpm_changelog.py
@@ -0,0 +1,226 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2014-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Test RPM changelog classes and parsing"""
+
+from datetime import datetime
+from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
+from tempfile import NamedTemporaryFile
+
+from gbp.rpm.changelog import _ChangelogHeader, _ChangelogEntry
+from gbp.rpm.changelog import _ChangelogSection, Changelog
+from gbp.rpm.changelog import ChangelogParser, ChangelogError
+from gbp.rpm.policy import RpmPkgPolicy
+
+
+class TestChangelogHeader(object):
+ """Test the _ChangelogHeader class"""
+
+ def test_str_format(self):
+ """Basic test for header"""
+ time = datetime(2014, 1, 29, 12, 13, 14)
+ header = _ChangelogHeader(RpmPkgPolicy, time, name="John Doe",
+ email="user@host.com", revision="1")
+ eq_(str(header), "* Wed Jan 29 2014 John Doe <user@host.com> 1\n")
+
+ def test_str_format_err(self):
+ """Test missing properties"""
+ time = datetime(2014, 1, 29, 12, 13, 14)
+ header = _ChangelogHeader(RpmPkgPolicy, time, name="John", revision="1")
+ with assert_raises(ChangelogError):
+ str(header)
+
+ def test_container(self):
+ """Test the container methods of the class"""
+ header = _ChangelogHeader(RpmPkgPolicy, datetime(2014, 1, 1), name="N",
+ revision="1")
+ # Test __getitem__()
+ eq_(header['name'], "N")
+ eq_(header['email'], None)
+ # Test __contains__()
+ ok_('name' in header)
+ ok_('foo' not in header)
+
+
+class TestChangelogEntry(object):
+ """Test the _ChangelogEntry class"""
+
+ def test_str_format(self):
+ """Basic test"""
+ entry = _ChangelogEntry(RpmPkgPolicy, author="John Doe",
+ text="- foo\n bar")
+ eq_(str(entry), "- foo\n bar\n")
+
+
+class TestChangelogSection(object):
+ """Test the _ChangelogSection class"""
+
+ def setup(self):
+ """Initialize test"""
+ time = datetime(2014, 1, 29, 12, 13, 14)
+ self.default_sect = _ChangelogSection(RpmPkgPolicy, time, name="J. D.",
+ email="u@h", revision="1")
+ entry = _ChangelogEntry(RpmPkgPolicy, "J. D.", "- my change")
+ self.default_sect.entries = [entry]
+
+ def test_str_format(self):
+ """Basic test"""
+ section = self.default_sect
+ eq_(str(section), "* Wed Jan 29 2014 J. D. <u@h> 1\n- my change\n\n")
+
+ def test_append_entry(self):
+ """Test add_entry() method"""
+ section = self.default_sect
+ entry = _ChangelogEntry(RpmPkgPolicy, author="",
+ text="- another\n change")
+ new_entry = section.append_entry(entry)
+ eq_(str(section), "* Wed Jan 29 2014 J. D. <u@h> 1\n- my change\n"
+ "- another\n change\n\n")
+ eq_(new_entry, section.entries[-1])
+
+ def test_set_header(self):
+ """Test set_header() method"""
+ section = self.default_sect
+ time = datetime(2014, 1, 30)
+ section.set_header(time=time, name="Jane", email="u@h", revision="1.1")
+ eq_(str(section), "* Thu Jan 30 2014 Jane <u@h> 1.1\n- my change\n\n")
+
+
+class TestChangelogParser(object):
+ """Test the default changelog parser"""
+
+ cl_default_style = """\
+* Wed Jan 29 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.3-1
+- Version bump
+- Drop foo.patch
+
+* Tue Jan 28 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.2
+- Update to 0.2
+
+* Mon Jan 27 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.1
+- Initial version
+"""
+ cl_with_authors = """\
+* Wed Jan 29 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.3-1
+[Markus Lehtonen]
+- Version bump
+[John Doe]
+- Bug fix
+"""
+ # Invalid timestamp / name
+ cl_broken_header_1 = """\
+* Wed Jan 29 2014Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.3-1
+- Version bump
+"""
+ # Whitespace before the asterisk in the header
+ cl_broken_header_2 = """\
+ * Wed Jan 29 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.3-1
+- Version bump
+"""
+ # Invalid timestamp
+ cl_broken_header_3 = """\
+* Wed Jan 32 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.3-1
+- Version bump
+"""
+ # Missing email
+ cl_broken_header_4 = """\
+* Wed Jan 29 2014 Markus Lehtonen 0.3-1
+- Version bump
+"""
+ # Garbage before section header
+ cl_broken_header_5 = """\
+---garbage---
+* Wed Jan 29 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 0.3-1
+- Version bump
+"""
+
+ parser = ChangelogParser(RpmPkgPolicy)
+
+ def test_parse_changelog(self):
+ """Basic tests for successful parsing"""
+ # Raw parsing of changelog
+ changelog = self.parser.raw_parse_string(self.cl_default_style)
+ eq_(len(changelog.sections), 3)
+
+ # Check that re-creating the changelog doesn't mangle it
+ eq_(str(changelog), self.cl_default_style)
+
+ # Parse and check section
+ section = self.parser.parse_section(changelog.sections[0])
+
+ eq_(section.header['time'], datetime(2014, 1, 29))
+ eq_(section.header['name'], "Markus Lehtonen")
+ eq_(section.header['email'], "markus.lehtonen@linux.intel.com")
+ eq_(section.header['revision'], "0.3-1")
+
+ # Check that re-creating section doesn't mangle it
+ eq_(str(section), changelog.sections[0])
+
+ def test_parse_authors(self):
+ """Test parsing of authors from changelog entries"""
+ section = self.parser.parse_section(self.cl_with_authors)
+ eq_(section.entries[0].author, "Markus Lehtonen")
+ eq_(section.entries[1].author, "John Doe")
+
+ def test_parse_changelog_file(self):
+ """Basic tests for parsing a file"""
+ # Create file and parse it
+ tmpfile = NamedTemporaryFile(mode='w')
+ tmpfile.write(self.cl_default_style)
+ tmpfile.file.flush()
+ changelog = self.parser.raw_parse_file(tmpfile.name)
+ # Check parsing results
+ eq_(len(changelog.sections), 3)
+ eq_(str(changelog), self.cl_default_style)
+ # Cleanup
+ tmpfile.close()
+
+ def test_parse_section_fail(self):
+ """Basic tests for failures of changelog section parsing"""
+ with assert_raises(ChangelogError):
+ self.parser.parse_section(self.cl_broken_header_1)
+
+ with assert_raises(ChangelogError):
+ self.parser.parse_section(self.cl_broken_header_2)
+
+ with assert_raises(ChangelogError):
+ self.parser.parse_section(self.cl_broken_header_3)
+
+ with assert_raises(ChangelogError):
+ self.parser.parse_section(self.cl_broken_header_4)
+
+ def test_parse_changelog_fail(self):
+ """Basic tests for changelog parsing failures"""
+ with assert_raises(ChangelogError):
+ self.parser.raw_parse_string(self.cl_broken_header_5)
+
+
+class TestChangelog(object):
+ """Unit tests for the Changelog class"""
+
+ def basic_test(self):
+ """Test basic initialization"""
+ changelog = Changelog(RpmPkgPolicy)
+ eq_(str(changelog), "")
+
+ def test_add_section(self):
+ """Test the add_section() method"""
+ changelog = Changelog(RpmPkgPolicy)
+ time = datetime(2014, 1, 30)
+ new_section = changelog.add_section(time=time, name="Jane Doe",
+ email="j@doe.com", revision="1.2")
+ eq_(str(changelog), "* Thu Jan 30 2014 Jane Doe <j@doe.com> 1.2\n\n")
+ eq_(new_section, changelog.sections[0])
diff --git a/tests/31_test_uscan.py b/tests/31_test_uscan.py
new file mode 100644
index 0000000..5bd0e5e
--- /dev/null
+++ b/tests/31_test_uscan.py
@@ -0,0 +1,44 @@
+# vim: set fileencoding=utf-8 :
+
+"""Test L{gbp.deb}"""
+
+from . import context # noqa: 401
+from . import testutils
+
+import unittest
+
+from gbp.deb.uscan import Uscan
+
+
+class TestUscan(unittest.TestCase):
+ """Test L{gbp.deb.uscan}"""
+
+ uscan_ok = b"""<dehs>
+uscan: Newest version of virt-what on remote site is 1.18, local version is 1.15
+uscan: => Newer package available from
+ https://people.redhat.com/~rjones/virt-what/files/virt-what-1.18.tar.gz
+gpgv: Signature made Mo 31 Jul 2017 11:36:08 ADT
+gpgv: using RSA key 91738F73E1B768A0
+gpgv: Good signature from "Richard W.M. Jones <rjones@redhat.com>"
+gpgv: aka "Richard W.M. Jones <rich@annexia.org>"
+<package>virt-what</package>
+<debian-uversion>1.15</debian-uversion>
+<debian-mangled-uversion>1.15</debian-mangled-uversion>
+<upstream-version>1.18</upstream-version>
+<upstream-url>https://people.redhat.com/~rjones/virt-what/files/virt-what-1.18.tar.gz</upstream-url>
+<status>newer package available</status>
+<target>virt-what_1.18.orig.tar.gz</target>
+<target-path>../virt-what_1.18.orig.tar.gz</target-path>
+<messages>Not downloading, using existing file: virt-what-1.18.tar.gz
+</messages>
+<messages>Leaving ../virt-what_1.18.orig.tar.gz where it is.
+</messages>
+</dehs>"""
+
+ @testutils.patch_popen(stdout=uscan_ok, stderr=b'', returncode=0)
+ def test_uscan(self, uscan_mock):
+ """Test parsing a valid uscan file"""
+ uscan = Uscan()
+ self.assertTrue(uscan.scan())
+ self.assertFalse(uscan.uptodate)
+ self.assertEquals(uscan.tarball, '../virt-what_1.18.orig.tar.gz')
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..7349719
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,20 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2009, 2010,2011, 2012 Guido Günther <agx@sigxcpu.org>
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Unit tests for git-buildpackage"""
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/component/__init__.py b/tests/component/__init__.py
new file mode 100644
index 0000000..c670851
--- /dev/null
+++ b/tests/component/__init__.py
@@ -0,0 +1,270 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 2013,2017 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, please see
+# <http://www.gnu.org/licenses/>
+"""
+Module for testing individual command line tools of the git-buildpackage suite
+"""
+
+import hashlib
+import os
+import shutil
+import tempfile
+import unittest
+from unittest import skipUnless
+from nose import SkipTest
+from nose.tools import eq_, ok_ # pylint: disable=E0611
+from .. testutils import GbpLogTester
+
+from gbp.git import GitRepository, GitRepositoryError
+
+
+__all__ = ['ComponentTestGitRepository', 'ComponentTestBase', 'GbpLogTester', 'skipUnless']
+
+
+class ComponentTestGitRepository(GitRepository):
+ """Git repository class for component tests"""
+ def submodule_status(self):
+ """
+ Determine submodules and their status
+ """
+ out, err, ret = self._git_inout('submodule', ['status'],
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Cannot get submodule status: %s" %
+ err.strip())
+ submodules = {}
+ for line in out.decode().splitlines():
+ module = line.strip()
+ # Uninitialized
+ status = module[0]
+ if status == '-':
+ sha1, path = module[1:].rsplit(' ', 1)
+ else:
+ commitpath = module[1:].rsplit(' ', 1)[0]
+ sha1, path = commitpath.split(' ', 1)
+ submodules[path] = (status, sha1)
+ return submodules
+
+ @classmethod
+ def check_testdata(cls, data):
+ """Check whether the testdata is current"""
+ try:
+ repo = cls('.')
+ except GitRepositoryError:
+ raise SkipTest("Skipping '%s', since this is not a git checkout."
+ % __name__)
+
+ submodules = repo.submodule_status()
+ try:
+ status = submodules[data]
+ except KeyError:
+ raise SkipTest("Skipping '%s', testdata directory not a known "
+ "submodule." % __name__)
+
+ if status[0] == '-':
+ raise SkipTest("Skipping '%s', testdata directory not initialized. "
+ "Consider doing 'git submodule update'" % __name__)
+
+ def ls_tree(self, treeish):
+ """List contents (blobs) in a git treeish"""
+ objs = self.list_tree(treeish, True)
+ blobs = [obj[3] for obj in objs if obj[1] == 'blob']
+ return set(blobs)
+
+ def get_head_author_subject(self):
+ out, err, ret = self._git_inout('format-patch', ['-1', '--stdout', '--subject-prefix='],
+ capture_stderr=True)
+ if ret:
+ raise GitRepositoryError("Cannot get head author/subject: %s" %
+ err.strip())
+
+ output = out.decode('utf-8')
+ for line in output.split('\n'):
+ line = line.strip()
+ if not line:
+ # end of headers
+ break
+ if line.startswith('From:'):
+ author = line.replace('From:', '').strip()
+ elif line.startswith('Subject:'):
+ subject = line.replace('Subject:', '').strip()
+ return author, subject
+
+
+class ComponentTestBase(unittest.TestCase, GbpLogTester):
+ """Base class for testing cmdline tools of git-buildpackage"""
+
+ @classmethod
+ def setUpClass(cls):
+ """Test class case setup"""
+ # Don't let git see that we're (possibly) under a git directory
+ cls.orig_env = os.environ.copy()
+ os.environ['GIT_CEILING_DIRECTORIES'] = os.getcwd()
+ # Create a top-level tmpdir for the test
+ cls._tmproot = tempfile.mkdtemp(prefix='gbp_%s_' % cls.__name__,
+ dir='.')
+ cls._tmproot = os.path.abspath(cls._tmproot)
+ # Prevent local config files from messing up the tests
+ os.environ['GBP_CONF_FILES'] = ':'.join(['%(top_dir)s/.gbp.conf',
+ '%(top_dir)s/debian/gbp.conf',
+ '%(git_dir)s/gbp.conf'])
+
+ @classmethod
+ def tearDownClass(cls):
+ """Test class case teardown"""
+ # Return original environment
+ os.environ.clear()
+ os.environ.update(cls.orig_env)
+ # Remove top-level tmpdir
+ if not os.getenv("GBP_TESTS_NOCLEAN"):
+ shutil.rmtree(cls._tmproot)
+
+ def __init__(self, methodName='runTest'):
+ """Object initialization"""
+ self._orig_dir = None
+ self._tmpdir = None
+ unittest.TestCase.__init__(self, methodName)
+ GbpLogTester.__init__(self)
+
+ def setUp(self):
+ """Test case setup"""
+ # Change to a temporary directory
+ self._orig_dir = os.getcwd()
+ self._tmpdir = tempfile.mkdtemp(prefix='tmp_%s_' % self._testMethodName,
+ dir=self._tmproot)
+ os.chdir(self._tmpdir)
+
+ self._capture_log(True)
+
+ def tearDown(self):
+ """Test case teardown"""
+ # Restore original working dir
+ os.chdir(self._orig_dir)
+ if not os.getenv("GBP_TESTS_NOCLEAN"):
+ shutil.rmtree(self._tmpdir)
+
+ self._capture_log(False)
+
+ @staticmethod
+ def check_files(reference, filelist):
+ """Compare two file lists"""
+ extra = set(filelist) - set(reference)
+ missing = set(reference) - set(filelist)
+ assert_msg = "Unexpected files: %s, Missing files: %s" % \
+ (list(extra), list(missing))
+ assert not extra and not missing, assert_msg
+
+ @classmethod
+ def check_tags(cls, repo, tags):
+ local_tags = repo.tags
+ assert_msg = "Tags: expected %s, found %s" % (tags,
+ local_tags)
+ eq_(set(local_tags), set(tags), assert_msg)
+
+ @classmethod
+ def _check_repo_state(cls, repo, current_branch, branches, files=None,
+ dirs=None, tags=None, clean=True):
+ """
+ Check that repository is clean and given branches, tags, files
+ and dirs exist
+ """
+ branch = repo.branch
+ eq_(branch, current_branch)
+ ok_(repo.is_clean())
+ local_branches = repo.get_local_branches()
+ assert_msg = "Branches: expected %s, found %s" % (branches,
+ local_branches)
+ eq_(set(local_branches), set(branches), assert_msg)
+
+ if files is not None or dirs is not None:
+ # Get files of the working copy recursively
+ local_f = set()
+ local_d = set()
+ for dirpath, dirnames, filenames in os.walk(repo.path):
+ # Skip git dir(s)
+ if '.git' in dirnames:
+ dirnames.remove('.git')
+ for filename in filenames:
+ local_f.add(os.path.relpath(os.path.join(dirpath, filename),
+ repo.path))
+ for dirname in dirnames:
+ local_d.add(os.path.relpath(os.path.join(dirpath, dirname),
+ repo.path) + '/')
+ if files is not None:
+ cls.check_files(files, local_f)
+ if dirs is not None:
+ cls.check_files(dirs, local_d)
+ if tags is not None:
+ cls.check_tags(repo, tags)
+ if clean:
+ clean, files = repo.is_clean()
+ ok_(clean, "Repo has uncommitted files %s" % files)
+
+ @classmethod
+ def rem_refs(cls, repo, refs):
+ """Remember the SHA1 of the given refs"""
+ rem = []
+ for name in refs:
+ rem.append((name, repo.rev_parse(name)))
+ return rem
+
+ @classmethod
+ def check_refs(cls, repo, rem):
+ """
+ Check that the heads given n (head, sha1) tuples are
+ still pointing to the given sha1
+ """
+ for (h, s) in rem:
+ n = repo.rev_parse(h)
+ ok_(n == s, "Head '%s' points to %s' instead of '%s'" % (h, n, s))
+
+ @staticmethod
+ def hash_file(filename):
+ h = hashlib.md5()
+ with open(filename, 'rb') as f:
+ buf = f.read()
+ h.update(buf)
+ return h.hexdigest()
+
+ @staticmethod
+ def check_hook_vars(name, expected):
+ """
+ Check that a hook had the given vars in
+ it's environment.
+ This assumes the hook was set too
+ printenv > hookname.out
+ """
+ with open('%s.out' % name, encoding='utf-8') as f:
+ parsed = dict([line[:-1].split('=', 1) for line in f.readlines() if line.startswith("GBP_")])
+
+ for var in expected:
+ if len(var) == 2:
+ k, v = var
+ else:
+ k, v = var, None
+ ok_(k in parsed, "%s not found in %s" % (k, parsed))
+ if v is not None:
+ ok_(v == parsed[k],
+ "Got %s not expected value %s for %s" % (parsed[k], v, k))
+
+ @staticmethod
+ def add_file(repo, name, content=None):
+ with open(name, 'w') as f:
+ f.write(' ' or content)
+ repo.add_files(name)
+ repo.commit_files(name, 'New file %s' % name)
diff --git a/tests/component/deb/__init__.py b/tests/component/deb/__init__.py
new file mode 100644
index 0000000..65486e8
--- /dev/null
+++ b/tests/component/deb/__init__.py
@@ -0,0 +1,31 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# (C) 2013 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import ComponentTestGitRepository
+
+DEB_TEST_SUBMODULE = os.path.join('tests', 'component', 'deb', 'data')
+DEB_TEST_DATA_DIR = os.path.abspath(DEB_TEST_SUBMODULE)
+DEB_TEST_DOWNLOAD_URL = 'https://git.sigxcpu.org/cgit/gbp/deb-testdata/plain/'
+
+
+def setup():
+ """Test Module setup"""
+ ComponentTestGitRepository.check_testdata(DEB_TEST_SUBMODULE)
diff --git a/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.diff.gz b/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.diff.gz
new file mode 100644
index 0000000..daf3667
--- /dev/null
+++ b/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.diff.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.dsc b/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.dsc
new file mode 100644
index 0000000..7560221
--- /dev/null
+++ b/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6-2.dsc
@@ -0,0 +1,20 @@
+Format: 1.0
+Source: hello-debhelper
+Binary: hello-debhelper
+Architecture: any
+Version: 2.6-2
+Maintainer: Santiago Vila <sanvila@debian.org>
+Homepage: http://www.gnu.org/software/hello/
+Standards-Version: 3.9.1
+Build-Depends: debhelper (>= 8)
+Package-List:
+ hello-debhelper deb devel extra arch=any
+Checksums-Sha1:
+ d0bcb2df85485a3bc7838b490df951c95a7e69b4 594257 hello-debhelper_2.6.orig.tar.gz
+ c925344d14a1a4a9448b0e670f338a45e70a60da 14104 hello-debhelper_2.6-2.diff.gz
+Checksums-Sha256:
+ 747aa9c3a0d105ad649af2f8ab95fb3526b280f2c1d04233d87d9ecce794cec0 594257 hello-debhelper_2.6.orig.tar.gz
+ 7e50c231fa60ffe9d986850fdd645989688ea23a0f9cd74827c2244736d6c78b 14104 hello-debhelper_2.6-2.diff.gz
+Files:
+ 066ba7113eed3554cde6bc34b7b43e9b 594257 hello-debhelper_2.6.orig.tar.gz
+ 7471f786fca191e79c5520ed8f750384 14104 hello-debhelper_2.6-2.diff.gz
diff --git a/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6.orig.tar.gz b/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6.orig.tar.gz
new file mode 100644
index 0000000..2b560eb
--- /dev/null
+++ b/tests/component/deb/data/dsc-1.0/hello-debhelper_2.6.orig.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.debian.tar.gz b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.debian.tar.gz
new file mode 100644
index 0000000..0b605d8
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.debian.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.dsc b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.dsc
new file mode 100644
index 0000000..70d1edc
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8-1.dsc
@@ -0,0 +1,38 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+Format: 3.0 (quilt)
+Source: hello-debhelper
+Binary: hello-debhelper
+Architecture: any
+Version: 2.8-1
+Maintainer: Santiago Vila <sanvila@debian.org>
+Homepage: http://www.gnu.org/software/hello/
+Standards-Version: 3.9.2
+Build-Depends: debhelper (>= 9.20120311)
+Package-List:
+ hello-debhelper deb devel extra
+Checksums-Sha1:
+ 5a32898ddce0586bb3f4035fc63324a6a515ce38 697483 hello-debhelper_2.8.orig.tar.gz
+ 272c3ae004959649941c6b0ccd4453d66683b107 697483 hello-debhelper_2.8.orig-foo.tar.gz
+ 272c3ae004959649941c6b0ccd4453d66683b107 5980 hello-debhelper_2.8-1.debian.tar.gz
+Checksums-Sha256:
+ e6b77f81f7cf7daefad4a9f5b65de6cae9c3f13b8cfbaea8cb53bb5ea5460d73 697483 hello-debhelper_2.8.orig.tar.gz
+ e6b77f81f7cf7daefad4a9f5b65de6cae9c3f13b8cfbaea8cb53bb5ea5460d73 697483 hello-debhelper_2.8.orig-foo.tar.gz
+ b6b5722a2e353dc91282b8fc7231b014a1d26678960affa0dcfa74d84236aa0a 5980 hello-debhelper_2.8-1.debian.tar.gz
+Files:
+ 6a67cbbbc0420061ef938a9a2736fbd6 697483 hello-debhelper_2.8.orig.tar.gz
+ 6a67cbbbc0420061ef938a9a2736fbd6 697483 hello-debhelper_2.8.orig-foo.tar.gz
+ b2b4b3a45587caa115a27944557ce29d 5980 hello-debhelper_2.8-1.debian.tar.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.12 (GNU/Linux)
+
+iQEcBAEBCAAGBQJPwpzUAAoJEEHOfwufG4syx1gH/1FiR+Wts28apKS3oSVDBaWL
+8Qznm+V+Vb9WVAbWc67dmwZhObYaqDKztaI8qXPSWXDEq0FeldPnQzAmLqmwqeO3
+aEu2VqMwgoCwF0oBFyiedfKqWpv9zUWd1wPsHXCI5tSTBGzNEoZLmu8KPtoMVgaa
+04w3tEnkQzYVX/dde7JiOpGk2nkLedVXATxW7Ts3HdF2CMivMsG4Rfc3/k5c+TmJ
++qUiyZm/XUjwzdqO/Ko/F/3qlt0C/J2KTo03oop0B57Nv9jq53/0Dr3OZhIQfYx+
+wnv55jgh80PbPfGiS6ITtveFHQ9E/z/ufDZ6Jh/gVL7h34U+9aY6P8ReIoreYGI=
+=Tt6n
+-----END PGP SIGNATURE-----
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig-foo.tar.gz b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig-foo.tar.gz
new file mode 100644
index 0000000..6014744
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig-foo.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig.tar.gz b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig.tar.gz
new file mode 100644
index 0000000..731541e
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.8.orig.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.debian.tar.gz b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.debian.tar.gz
new file mode 100644
index 0000000..0b605d8
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.debian.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.dsc b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.dsc
new file mode 100644
index 0000000..a428200
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9-1.dsc
@@ -0,0 +1,38 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+Format: 3.0 (quilt)
+Source: hello-debhelper
+Binary: hello-debhelper
+Architecture: any
+Version: 2.8-1
+Maintainer: Santiago Vila <sanvila@debian.org>
+Homepage: http://www.gnu.org/software/hello/
+Standards-Version: 3.9.2
+Build-Depends: debhelper (>= 9.20120311)
+Package-List:
+ hello-debhelper deb devel extra
+Checksums-Sha1:
+ 5a32898ddce0586bb3f4035fc63324a6a515ce38 697483 hello-debhelper_2.9.orig.tar.gz
+ 272c3ae004959649941c6b0ccd4453d66683b107 697483 hello-debhelper_2.9.orig-foo.tar.gz
+ 272c3ae004959649941c6b0ccd4453d66683b107 5980 hello-debhelper_2.9-1.debian.tar.gz
+Checksums-Sha256:
+ e6b77f81f7cf7daefad4a9f5b65de6cae9c3f13b8cfbaea8cb53bb5ea5460d73 697483 hello-debhelper_2.9.orig.tar.gz
+ e6b77f81f7cf7daefad4a9f5b65de6cae9c3f13b8cfbaea8cb53bb5ea5460d73 697483 hello-debhelper_2.9.orig-foo.tar.gz
+ b6b5722a2e353dc91282b8fc7231b014a1d26678960affa0dcfa74d84236aa0a 5980 hello-debhelper_2.9-1.debian.tar.gz
+Files:
+ 6a67cbbbc0420061ef938a9a2736fbd6 697483 hello-debhelper_2.9.orig.tar.gz
+ 6a67cbbbc0420061ef938a9a2736fbd6 697483 hello-debhelper_2.9.orig-foo.tar.gz
+ b2b4b3a45587caa115a27944557ce29d 5980 hello-debhelper_2.9-1.debian.tar.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.12 (GNU/Linux)
+
+iQEcBAEBCAAGBQJPwpzUAAoJEEHOfwufG4syx1gH/1FiR+Wts28apKS3oSVDBaWL
+8Qznm+V+Vb9WVAbWc67dmwZhObYaqDKztaI8qXPSWXDEq0FeldPnQzAmLqmwqeO3
+aEu2VqMwgoCwF0oBFyiedfKqWpv9zUWd1wPsHXCI5tSTBGzNEoZLmu8KPtoMVgaa
+04w3tEnkQzYVX/dde7JiOpGk2nkLedVXATxW7Ts3HdF2CMivMsG4Rfc3/k5c+TmJ
++qUiyZm/XUjwzdqO/Ko/F/3qlt0C/J2KTo03oop0B57Nv9jq53/0Dr3OZhIQfYx+
+wnv55jgh80PbPfGiS6ITtveFHQ9E/z/ufDZ6Jh/gVL7h34U+9aY6P8ReIoreYGI=
+=Tt6n
+-----END PGP SIGNATURE-----
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig-foo.tar.gz b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig-foo.tar.gz
new file mode 100644
index 0000000..38c18f0
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig-foo.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig.tar.gz b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig.tar.gz
new file mode 100644
index 0000000..731541e
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0-additional-tarballs/hello-debhelper_2.9.orig.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.debian.tar.gz b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.debian.tar.gz
new file mode 100644
index 0000000..190c809
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.debian.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.dsc b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.dsc
new file mode 100644
index 0000000..8dcd63e
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-1.dsc
@@ -0,0 +1,33 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+Format: 3.0 (quilt)
+Source: hello-debhelper
+Binary: hello-debhelper
+Architecture: any
+Version: 2.6-1
+Maintainer: Santiago Vila <sanvila@debian.org>
+Homepage: http://www.gnu.org/software/hello/
+Standards-Version: 3.9.1
+Build-Depends: debhelper (>= 7)
+Checksums-Sha1:
+ d0bcb2df85485a3bc7838b490df951c95a7e69b4 594257 hello-debhelper_2.6.orig.tar.gz
+ 5cc9239d660189a023b4a8967a3b6f5480f2d5cf 6074 hello-debhelper_2.6-1.debian.tar.gz
+Checksums-Sha256:
+ 747aa9c3a0d105ad649af2f8ab95fb3526b280f2c1d04233d87d9ecce794cec0 594257 hello-debhelper_2.6.orig.tar.gz
+ ba54eb0cc3c87ce6f7ea63f460957b272cadb3455019b23791a589889cb6f282 6074 hello-debhelper_2.6-1.debian.tar.gz
+Files:
+ 066ba7113eed3554cde6bc34b7b43e9b 594257 hello-debhelper_2.6.orig.tar.gz
+ f4bd9cbb191ede400f6afab286c8b33f 6074 hello-debhelper_2.6-1.debian.tar.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iQEcBAEBCAAGBQJMXHE3AAoJEEHOfwufG4syp4AH/jw5AN8ea7OjnA02bn4OHMv6
+r3FrnwnF49tgw7BBAwekFjK66+Q+fgrFhwTQAFP14fjKJDXdUQm7xRL9dPqqgkVd
++W773Q507RMhTPNqXermHyvpywfKmbgk1KVHffL8AQ2zr4MaS+5KCGKQ0dAc4EVe
+uWaS+SVhXNyW6Vav5wLaBakKW33sgzerl49j5K3ofUCpc66Z0KNQGPl/EfplDqqx
+FXFvFZv6Yu/tUqkZ9tShInJLg8qL/WsIoqCWy52F4Cdwi6fy66qcPpTKgMVhfPoK
+Sd49HTNg4UiTmX/QVcIjnnSxW5/voMbEK1MvmnK0cvNCBouMpPTGBamq3QgKRs4=
+=9/5A
+-----END PGP SIGNATURE-----
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.debian.tar.gz b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.debian.tar.gz
new file mode 100644
index 0000000..fdc8404
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.debian.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.dsc b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.dsc
new file mode 100644
index 0000000..1aff516
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6-2.dsc
@@ -0,0 +1,33 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+Format: 3.0 (quilt)
+Source: hello-debhelper
+Binary: hello-debhelper
+Architecture: any
+Version: 2.6-2
+Maintainer: Santiago Vila <sanvila@debian.org>
+Homepage: http://www.gnu.org/software/hello/
+Standards-Version: 3.9.1
+Build-Depends: debhelper (>= 8)
+Checksums-Sha1:
+ d0bcb2df85485a3bc7838b490df951c95a7e69b4 594257 hello-debhelper_2.6.orig.tar.gz
+ ba298e87a4964c094096befe0f9c5ee4f3b44d84 5996 hello-debhelper_2.6-2.debian.tar.gz
+Checksums-Sha256:
+ 747aa9c3a0d105ad649af2f8ab95fb3526b280f2c1d04233d87d9ecce794cec0 594257 hello-debhelper_2.6.orig.tar.gz
+ f24e69b58fc67ccd1ae76fc943efccc7adfe168f0bbb73cc80246bd7b6a4b003 5996 hello-debhelper_2.6-2.debian.tar.gz
+Files:
+ 066ba7113eed3554cde6bc34b7b43e9b 594257 hello-debhelper_2.6.orig.tar.gz
+ dfb73f956947a94537f62f858aff50d1 5996 hello-debhelper_2.6-2.debian.tar.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iQEcBAEBCAAGBQJMa+z8AAoJEEHOfwufG4syP3cH/i5PtLhXOjkLumiMypdkkWXW
+hcH6yXVYn/DumlXXAYPw3zT70F6xxNfqGYTP11FAZzxpxtb5K9XMmmtuxExUhoJS
+sx6DDpzVTSxr5g7tgN6VHniqH9R3gMxf5extG0/UuoFvMvnmDxEl6UqePCNaT12W
+PhFUxTexD8laAwq+zLbQ23GGPOJ7GVDzKcWJEAz9Q6zKKXLg8C3Hqg903e6iXgUy
+P/M8A09rSHjADRRhQ40z1vzMXbCFivrKHvGfU4PGEKFWkLjpubJUs8X+ObtNg2Ua
+1nxV7d5IrmclPXbGoXSIpjuiQNo52t02QuSp09mR7mfmv/P3AUKKs+reLQ5eL1s=
+=U/Cv
+-----END PGP SIGNATURE-----
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6.orig.tar.gz b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6.orig.tar.gz
new file mode 100644
index 0000000..2b560eb
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.6.orig.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.debian.tar.gz b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.debian.tar.gz
new file mode 100644
index 0000000..0b605d8
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.debian.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.dsc b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.dsc
new file mode 100644
index 0000000..8968e5e
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8-1.dsc
@@ -0,0 +1,35 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+Format: 3.0 (quilt)
+Source: hello-debhelper
+Binary: hello-debhelper
+Architecture: any
+Version: 2.8-1
+Maintainer: Santiago Vila <sanvila@debian.org>
+Homepage: http://www.gnu.org/software/hello/
+Standards-Version: 3.9.2
+Build-Depends: debhelper (>= 9.20120311)
+Package-List:
+ hello-debhelper deb devel extra
+Checksums-Sha1:
+ 5a32898ddce0586bb3f4035fc63324a6a515ce38 697483 hello-debhelper_2.8.orig.tar.gz
+ 272c3ae004959649941c6b0ccd4453d66683b107 5980 hello-debhelper_2.8-1.debian.tar.gz
+Checksums-Sha256:
+ e6b77f81f7cf7daefad4a9f5b65de6cae9c3f13b8cfbaea8cb53bb5ea5460d73 697483 hello-debhelper_2.8.orig.tar.gz
+ b6b5722a2e353dc91282b8fc7231b014a1d26678960affa0dcfa74d84236aa0a 5980 hello-debhelper_2.8-1.debian.tar.gz
+Files:
+ 6a67cbbbc0420061ef938a9a2736fbd6 697483 hello-debhelper_2.8.orig.tar.gz
+ b2b4b3a45587caa115a27944557ce29d 5980 hello-debhelper_2.8-1.debian.tar.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.12 (GNU/Linux)
+
+iQEcBAEBCAAGBQJPwpzUAAoJEEHOfwufG4syx1gH/1FiR+Wts28apKS3oSVDBaWL
+8Qznm+V+Vb9WVAbWc67dmwZhObYaqDKztaI8qXPSWXDEq0FeldPnQzAmLqmwqeO3
+aEu2VqMwgoCwF0oBFyiedfKqWpv9zUWd1wPsHXCI5tSTBGzNEoZLmu8KPtoMVgaa
+04w3tEnkQzYVX/dde7JiOpGk2nkLedVXATxW7Ts3HdF2CMivMsG4Rfc3/k5c+TmJ
++qUiyZm/XUjwzdqO/Ko/F/3qlt0C/J2KTo03oop0B57Nv9jq53/0Dr3OZhIQfYx+
+wnv55jgh80PbPfGiS6ITtveFHQ9E/z/ufDZ6Jh/gVL7h34U+9aY6P8ReIoreYGI=
+=Tt6n
+-----END PGP SIGNATURE-----
diff --git a/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8.orig.tar.gz b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8.orig.tar.gz
new file mode 100644
index 0000000..731541e
--- /dev/null
+++ b/tests/component/deb/data/dsc-3.0/hello-debhelper_2.8.orig.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.dsc b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.dsc
new file mode 100644
index 0000000..9c69ee7
--- /dev/null
+++ b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.dsc
@@ -0,0 +1,12 @@
+Format: 1.0
+Source: git-buildpackage
+Binary: git-buildpackage
+Architecture: all
+Version: 0.4.14
+Maintainer: Guido Guenther <agx@sigxcpu.org>
+Homepage: http://sigxcpu.org/projects.html
+Standards-Version: 3.7.3
+Vcs-Git: http://honk.sigxcpu.org/git/git-buildpackage.git/
+Build-Depends: cdbs, debhelper (>= 5), docbook-utils, gtk-doc-tools, jade, pychecker, python-dev, python-support (>= 0.3), sgml2x
+Files:
+ 84332fed12f59ce5969f16016987f2c9 33598 git-buildpackage_0.4.14.tar.gz
diff --git a/tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.tar.gz b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.tar.gz
new file mode 100644
index 0000000..393cbc3
--- /dev/null
+++ b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.14.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.dsc b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.dsc
new file mode 100644
index 0000000..79b378b
--- /dev/null
+++ b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.dsc
@@ -0,0 +1,23 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.0
+Source: git-buildpackage
+Binary: git-buildpackage
+Architecture: all
+Version: 0.4.15
+Maintainer: Guido Guenther <agx@sigxcpu.org>
+Homepage: http://sigxcpu.org/projects.html
+Standards-Version: 3.7.3
+Vcs-Git: http://honk.sigxcpu.org/git/git-buildpackage.git/
+Build-Depends: cdbs, debhelper (>= 5), docbook-utils, gtk-doc-tools, jade, pychecker, python-dev, python-support (>= 0.3), sgml2x
+Files:
+ f5c6ea7b3de02af9bfd80f9ab886d448 34151 git-buildpackage_0.4.15.tar.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.6 (GNU/Linux)
+
+iD8DBQFHvZ8dn88szT8+ZCYRAuJqAJoCa0685ix1mxl297fDT4QQMlY3HQCfTu+P
+Ns2j8Vrv6/yZZLHWZm6buJA=
+=TT+4
+-----END PGP SIGNATURE-----
diff --git a/tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.tar.gz b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.tar.gz
new file mode 100644
index 0000000..8d56f16
--- /dev/null
+++ b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.15.tar.gz
Binary files differ
diff --git a/tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.dsc b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.dsc
new file mode 100644
index 0000000..fc18996
--- /dev/null
+++ b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.dsc
@@ -0,0 +1,23 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+Format: 1.0
+Source: git-buildpackage
+Binary: git-buildpackage
+Architecture: all
+Version: 0.4.16
+Maintainer: Guido Guenther <agx@sigxcpu.org>
+Homepage: http://sigxcpu.org/projects.html
+Standards-Version: 3.7.3
+Vcs-Git: http://honk.sigxcpu.org/git/git-buildpackage.git/
+Build-Depends: cdbs, debhelper (>= 5), docbook-utils, gtk-doc-tools, jade, pychecker, python-dev, python-support (>= 0.3), sgml2x
+Files:
+ 561b5f58f11d8a379fcdfb7a0cacbcb3 34720 git-buildpackage_0.4.16.tar.gz
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.6 (GNU/Linux)
+
+iD8DBQFHwYNKn88szT8+ZCYRAguiAJ9nepj+PoioMy3BFrB5l/2YqfN6sQCeIGoE
+QNCBzBWqdlopGrpttwwnEzU=
+=UYM6
+-----END PGP SIGNATURE-----
diff --git a/tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.tar.gz b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.tar.gz
new file mode 100644
index 0000000..49f9d9b
--- /dev/null
+++ b/tests/component/deb/data/dsc-native/git-buildpackage_0.4.16.tar.gz
Binary files differ
diff --git a/tests/component/deb/fixtures.py b/tests/component/deb/fixtures.py
new file mode 100644
index 0000000..78ba1e9
--- /dev/null
+++ b/tests/component/deb/fixtures.py
@@ -0,0 +1,146 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from functools import wraps
+from tests.component import (ComponentTestBase,
+ ComponentTestGitRepository)
+from tests.component.deb import DEB_TEST_DATA_DIR
+
+from nose.tools import eq_, ok_
+
+from gbp.command_wrappers import UnpackTarArchive
+from gbp.git import GitRepository
+from gbp.deb.dscfile import DscFile
+from gbp.scripts.import_dsc import main as import_dsc
+
+DEFAULT_NATIVE = os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-native',
+ 'git-buildpackage_%s.dsc' % '0.4.14')
+
+DEFAULT_QUILT30 = os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-3.0',
+ 'hello-debhelper_%s.dsc' % '2.8-1')
+
+DEFAULT_ADDITIONAL_TAR = os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-3.0-additional-tarballs',
+ 'hello-debhelper_%s.dsc' % '2.8-1')
+
+DEFAULT_OVERLAY = os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-3.0-additional-tarballs',
+ 'hello-debhelper_%s.debian.tar.gz' % '2.8-1')
+
+
+class RepoFixtures(object):
+ @classmethod
+ def native(cls, dsc=DEFAULT_NATIVE, opts=None):
+ """Decorator to be used as Debian native test fixture"""
+ def wrapper(fn):
+ @wraps(fn)
+ def _native_repo(*args):
+ repo = cls.import_native(dsc, opts)
+ return fn(*args, repo=repo)
+ return _native_repo
+ return wrapper
+
+ @classmethod
+ def quilt30(cls, dsc=DEFAULT_QUILT30, opts=None):
+ """Decorator to be used as 3.0 (quilt) test fixture"""
+ def wrapper(fn):
+ @wraps(fn)
+ def _quilt30_repo(*args):
+ repo = cls.import_quilt30(dsc, opts)
+ return fn(*args, repo=repo)
+ return _quilt30_repo
+ return wrapper
+
+ @classmethod
+ def quilt30_additional_tarball(cls, dsc=DEFAULT_ADDITIONAL_TAR, opts=None):
+ """Decorator to be used as 3.0 (quilt) with additional tarball test fixture"""
+ def wrapper(fn):
+ @wraps(fn)
+ def _quilt30_additional_tar_repo(*args):
+ repo = cls.import_quilt30_additional_tarball(dsc, opts)
+ return fn(*args, repo=repo)
+ return _quilt30_additional_tar_repo
+ return wrapper
+
+ @classmethod
+ def overlay(cls, debian=DEFAULT_OVERLAY, opts=None):
+ """Decorator to be used as overay mode test fixture"""
+ def wrapper(fn):
+ @wraps(fn)
+ def _overlay_mode_repo(*args):
+ repo = cls.import_debian_tarball(debian, opts)
+ return fn(*args, repo=repo)
+ return _overlay_mode_repo
+ return wrapper
+
+ @classmethod
+ def _import_one(cls, dsc, opts):
+ opts = opts or []
+ assert import_dsc(['arg0'] + opts + [dsc]) == 0
+ parsed = DscFile(dsc)
+ return ComponentTestGitRepository(parsed.pkg)
+
+ @classmethod
+ def import_native(cls, dsc=DEFAULT_NATIVE, opts=None):
+ """Import a Debian native package, verify and change into repo"""
+ repo = cls._import_one(dsc, opts)
+ ComponentTestBase._check_repo_state(repo, 'master', ['master'])
+ eq_(len(repo.get_commits()), 1)
+ os.chdir(repo.path)
+ return repo
+
+ @classmethod
+ def import_quilt30(cls, dsc=DEFAULT_QUILT30, opts=None):
+ """Import a 3.0 (quilt) package, verify and change into repo"""
+ repo = cls._import_one(dsc, opts)
+ expected_branches = ['master', 'upstream']
+ if opts and '--pristine-tar' in opts:
+ expected_branches.append('pristine-tar')
+ ComponentTestBase._check_repo_state(repo, 'master', expected_branches)
+ eq_(len(repo.get_commits()), 2)
+ os.chdir(repo.path)
+ return repo
+
+ @classmethod
+ def import_quilt30_additional_tarball(cls, dsc=DEFAULT_ADDITIONAL_TAR, opts=None):
+ """Import a 3.0 (quilt) package with additional tarball, verify and change into repo"""
+ repo = cls._import_one(dsc, opts)
+ expected_branches = ['master', 'upstream']
+ if opts and '--pristine-tar' in opts:
+ expected_branches.append('pristine-tar')
+ ComponentTestBase._check_repo_state(repo, 'master', expected_branches)
+ eq_(len(repo.get_commits()), 2)
+ os.chdir(repo.path)
+ ok_(os.path.exists('./foo'))
+ return repo
+
+ @classmethod
+ def import_debian_tarball(cls, debian=DEFAULT_OVERLAY, opts=None):
+ """Import a 3.0 (quilt) debian dir for overlay mode"""
+ repo = GitRepository.create(os.path.split('/')[-1].split('_')[0])
+ UnpackTarArchive(debian, repo.path)()
+ repo.add_files('.')
+ repo.commit_files('.', msg="debian dir")
+ expected_branches = ['master']
+ ComponentTestBase._check_repo_state(repo, 'master', expected_branches)
+ eq_(len(repo.get_commits()), 1)
+ os.chdir(repo.path)
+ return repo
diff --git a/tests/component/deb/test_buildpackage.py b/tests/component/deb/test_buildpackage.py
new file mode 100644
index 0000000..7aa8577
--- /dev/null
+++ b/tests/component/deb/test_buildpackage.py
@@ -0,0 +1,305 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2015-2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import glob
+import hashlib
+import os
+import subprocess
+import tarfile
+
+from tests.component import (ComponentTestBase,
+ ComponentTestGitRepository)
+from tests.component.deb import DEB_TEST_DATA_DIR
+from tests.component.deb.fixtures import (RepoFixtures,
+ DEFAULT_OVERLAY)
+from tests.testutils import skip_without_cmd
+
+from nose.tools import ok_, eq_, assert_false, assert_true
+
+from gbp.scripts.import_dsc import main as import_dsc
+from gbp.scripts.buildpackage import main as buildpackage
+from gbp.scripts.pq import main as pq
+
+from gbp.deb.changelog import ChangeLog
+
+
+class TestBuildpackage(ComponentTestBase):
+ """Test building a debian package"""
+
+ @staticmethod
+ def _dsc_name(pkg, version, dir):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ dir,
+ '%s_%s.dsc' % (pkg, version))
+
+ def _test_buildpackage(self, repo, opts=[]):
+ prebuild_out = os.path.join(repo.path, '..', 'prebuild.out')
+ postbuild_out = os.path.join(repo.path, '..', 'postbuild.out')
+ args = ['arg0',
+ '--git-prebuild=printenv > %s' % prebuild_out,
+ '--git-postbuild=printenv > %s' % postbuild_out,
+ '--git-builder=/bin/true',
+ '--git-cleaner=/bin/true'] + opts
+ os.chdir(repo.path)
+ ret = buildpackage(args)
+ ok_(ret == 0, "Building the package failed")
+ eq_(os.path.exists(prebuild_out), True)
+ eq_(os.path.exists(postbuild_out), True)
+
+ self.check_hook_vars('../prebuild', ["GBP_BUILD_DIR",
+ "GBP_GIT_DIR",
+ "GBP_BUILD_DIR"])
+
+ self.check_hook_vars('../postbuild', ["GBP_CHANGES_FILE",
+ "GBP_BUILD_DIR",
+ "GBP_CHANGES_FILE",
+ "GBP_BUILD_DIR"])
+
+ @RepoFixtures.native()
+ def test_debian_buildpackage(self, repo):
+ """Test that building a native debian package works"""
+ self._test_buildpackage(repo)
+
+ @RepoFixtures.quilt30()
+ def test_non_native_buildpackage(self, repo):
+ """Test that building a source 3.0 debian package works"""
+ self._test_buildpackage(repo)
+
+ @RepoFixtures.native()
+ def test_tag_only(self, repo):
+ """Test that only tagging a native debian package works"""
+ repo.delete_tag('debian/0.4.14') # make sure we can tag again
+ ret = buildpackage(['arg0',
+ '--git-tag-only',
+ '--git-posttag=printenv > ../posttag.out',
+ '--git-builder=touch ../builder-run.stamp',
+ '--git-cleaner=/bin/true'])
+ ok_(ret == 0, "Building the package failed")
+ eq_(os.path.exists('../posttag.out'), True)
+ eq_(os.path.exists('../builder-run.stamp'), False)
+ self.check_hook_vars('../posttag', [("GBP_TAG", "debian/0.4.14"),
+ ("GBP_BRANCH", "master"),
+ "GBP_SHA1"])
+
+ def test_component_generation(self):
+ """Test that generating tarball and additional tarball works without pristine-tar"""
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.8-1', 'dsc-3.0-additional-tarballs')
+ tarballs = ["../%s_2.8.orig-foo.tar.gz" % pkg,
+ "../%s_2.8.orig.tar.gz" % pkg]
+
+ assert import_dsc(['arg0', '--no-pristine-tar', dsc]) == 0
+ repo = ComponentTestGitRepository(pkg)
+ os.chdir(pkg)
+ assert_false(repo.has_branch('pristine-tar'), "Pristine-tar branch must not exist")
+ for t in tarballs:
+ self.assertFalse(os.path.exists(t), "Tarball %s must not exist" % t)
+ ret = buildpackage(['arg0',
+ '--git-component=foo',
+ '--git-no-pristine-tar',
+ '--git-posttag=printenv > posttag.out',
+ '--git-builder=touch builder-run.stamp',
+ '--git-cleaner=/bin/true'])
+ ok_(ret == 0, "Building the package failed")
+ for t in tarballs:
+ self.assertTrue(os.path.exists(t), "Tarball %s not found" % t)
+
+ def test_pristinetar_component_generation(self):
+ """Test that generating tarball and additional tarball works with pristine-tar"""
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.8-1', 'dsc-3.0-additional-tarballs')
+ tarballs = ["../%s_2.8.orig-foo.tar.gz" % pkg,
+ "../%s_2.8.orig.tar.gz" % pkg]
+
+ assert import_dsc(['arg0', '--pristine-tar', dsc]) == 0
+ repo = ComponentTestGitRepository(pkg)
+ os.chdir(pkg)
+ assert_true(repo.has_branch('pristine-tar'), "Pristine-tar branch must exist")
+ for t in tarballs:
+ self.assertFalse(os.path.exists(t), "Tarball %s must not exist" % t)
+ # Make sure the tree object for importing the main tarball is recreated
+ repo.collect_garbage(prune='all', aggressive=True)
+ ret = buildpackage(['arg0',
+ '--git-component=foo',
+ '--git-pristine-tar',
+ '--git-posttag=printenv > posttag.out',
+ '--git-builder=touch builder-run.stamp',
+ '--git-cleaner=/bin/true'])
+ ok_(ret == 0, "Building the package failed")
+ for t in tarballs:
+ self.assertTrue(os.path.exists(t), "Tarball %s not found" % t)
+
+ @RepoFixtures.quilt30()
+ def test_pristine_tar_commit(self, repo):
+ """Test that committing to pristine-tar branch after building tarballs works"""
+ assert_false(repo.has_branch('pristine-tar'), "Pristine-tar branch must not exist")
+ ret = buildpackage(['arg0',
+ '--git-builder=/bin/true',
+ '--git-pristine-tar-commit'])
+ ok_(ret == 0, "Building the package failed")
+ assert_true(repo.has_branch('pristine-tar'), "Pristine-tar branch must exist")
+ eq_(repo.ls_tree('pristine-tar'), {b'hello-debhelper_2.8.orig.tar.gz.id',
+ b'hello-debhelper_2.8.orig.tar.gz.delta'})
+
+ @RepoFixtures.quilt30()
+ def test_sloppy_tarball_generation(self, repo):
+ """Test that generating tarball from Debian branch works"""
+ tarball = '../hello-debhelper_2.8.orig.tar.gz'
+ self.add_file(repo, 'foo.txt')
+ self._test_buildpackage(repo, ['--git-force-create',
+ '--git-upstream-tree=SLOPPY'])
+ self.assertTrue(os.path.exists(tarball))
+ t = tarfile.open(name=tarball, mode="r:gz")
+ names = t.getnames()
+ for f in ['hello-debhelper-2.8/build-aux',
+ 'hello-debhelper-2.8/foo.txt']:
+ self.assertIn(f, names)
+ self.assertNotIn('hello-debhelper-2.8/debian', names)
+
+ @RepoFixtures.quilt30()
+ def test_export_dir_buildpackage(self, repo):
+ """Test that building with a export dir works"""
+ self._test_buildpackage(repo, ['--git-export-dir=../foo/bar'])
+ ok_(os.path.exists('../foo/bar'))
+
+ @RepoFixtures.quilt30_additional_tarball()
+ def test_export_dir_additional_tar(self, repo):
+ """Test that building with a export dir and additional tarball works"""
+ self._test_buildpackage(repo, ['--git-export-dir=../foo/bar',
+ '--git-no-purge',
+ '--git-component=foo'])
+ # Check that all needed tarballs end up in the build-area
+ eq_(sorted(glob.glob('../foo/bar/*')), ['../foo/bar/hello-debhelper-2.8',
+ '../foo/bar/hello-debhelper_2.8.orig-foo.tar.gz',
+ '../foo/bar/hello-debhelper_2.8.orig.tar.gz'])
+ # Check that directories from additional tarballs get exported too
+ ok_(os.path.exists('../foo/bar/hello-debhelper-2.8/foo'))
+
+ @RepoFixtures.overlay()
+ def test_export_dir_overlay(self, repo):
+ """Test that building in overlay mode with export dir works"""
+ tarball_dir = os.path.dirname(DEFAULT_OVERLAY)
+ self._test_buildpackage(repo, ['--git-overlay',
+ '--git-compression=auto',
+ '--git-tarball-dir=%s' % tarball_dir,
+ '--git-no-purge',
+ '--git-component=foo',
+ '--git-export-dir=../overlay'])
+ # Check if main tarball got unpacked
+ ok_(os.path.exists('../overlay/hello-debhelper-2.8/configure'))
+ # Check if debian dir is there
+ ok_(os.path.exists('../overlay/hello-debhelper-2.8/debian/changelog'))
+ # Check if additional tarball got unpacked
+ ok_(os.path.exists('../overlay/hello-debhelper-2.8/foo/test1'))
+ # Check if upstream tarballs is in export_dir
+ eq_(sorted(glob.glob('../overlay/*')), ['../overlay/hello-debhelper-2.8',
+ '../overlay/hello-debhelper_2.8.orig-foo.tar.gz',
+ '../overlay/hello-debhelper_2.8.orig.tar.gz'])
+
+ @RepoFixtures.quilt30()
+ def test_export_wc_buildpackage(self, repo):
+ """Test that exporting working copy works and it ignores
+ modifications the source tree """
+ with open(os.path.join(repo.path, 'foo.txt'), 'w') as f:
+ f.write("foo")
+ self._test_buildpackage(repo, ['--git-export=WC',
+ '--git-export-dir=../foo/bar'])
+ ok_(os.path.exists('../foo/bar'))
+
+ @RepoFixtures.native()
+ def test_argument_quoting(self, repo):
+ """Test that we quote arguments to builder (#850869)"""
+ with open('../arg with spaces', 'w'):
+ pass
+ # We use ls as builder to look for a file with spaces. This
+ # will fail if build arguments are not properly quoted and
+ # therefore split up
+ ret = buildpackage(['arg0',
+ '--git-builder=ls',
+ '--git-cleaner=/bin/true',
+ '../arg with spaces'])
+ ok_(ret == 0, "Building the package failed")
+
+ @RepoFixtures.quilt30()
+ def test_tarball_default_compression(self, repo):
+ """Test that we use defaults for compression if not given (#820846)"""
+ self._test_buildpackage(repo, ['--git-no-pristine-tar'])
+ tarball = "../hello-debhelper_2.8.orig.tar.gz"
+ out = subprocess.check_output(["file", tarball])
+ ok_(b"max compression" not in out)
+ m1 = hashlib.md5(open(tarball, 'rb').read()).hexdigest()
+ os.unlink(tarball)
+ eq_(buildpackage(['arg0',
+ '--git-ignore-new',
+ '--git-builder=/bin/true',
+ '--git-cleaner=/bin/true',
+ '../arg with spaces']), 0)
+ m2 = hashlib.md5(open(tarball, 'rb').read()).hexdigest()
+ eq_(m1, m2, "Regenerated tarball has different checksum")
+
+ @RepoFixtures.quilt30()
+ def test_tarball_max_compression(self, repo):
+ """Test that passing max compression works (#820846)"""
+ self._test_buildpackage(repo, ['--git-no-pristine-tar', '--git-compression-level=9'])
+ out = subprocess.check_output(["file", "../hello-debhelper_2.8.orig.tar.gz"])
+ ok_(b"max compression" in out)
+
+ @RepoFixtures.quilt30()
+ def test_tag_pq_branch(self, repo):
+ ret = pq(['argv0', 'import'])
+ eq_(repo.rev_parse('master'), repo.rev_parse('debian/2.8-1^{}'))
+ eq_(ret, 0)
+ eq_(repo.branch, 'patch-queue/master')
+ self.add_file(repo, 'foo.txt')
+ ret = buildpackage(['argv0',
+ '--git-tag-only',
+ '--git-retag',
+ '--git-ignore-branch'])
+ eq_(ret, 0)
+ eq_(repo.branch, 'patch-queue/master')
+ eq_(repo.rev_parse('patch-queue/master^{}^'), repo.rev_parse('debian/2.8-1^{}'))
+
+ @RepoFixtures.quilt30()
+ def test_tag_detached_head(self, repo):
+ """
+ Test that tagging works with an detached head (#863167)
+ """
+ eq_(repo.rev_parse('master^{}'), repo.rev_parse('debian/2.8-1^{}'))
+ self.add_file(repo, 'debian/foo.txt')
+ repo.checkout("HEAD~")
+ ret = buildpackage(['argv0',
+ '--git-tag-only',
+ '--git-retag',
+ '--git-ignore-branch'])
+ eq_(ret, 0)
+ repo.checkout("master")
+ eq_(repo.rev_parse('master~^{}'), repo.rev_parse('debian/2.8-1^{}'))
+
+ @skip_without_cmd('debchange')
+ @RepoFixtures.quilt30()
+ def test_broken_upstream_version(self, repo):
+ cl = ChangeLog(filename='debian/changelog')
+ cl.add_section(["broken versionnumber"],
+ "unstable",
+ version={'version': "3.0"})
+ ret = buildpackage(['argv0',
+ '--git-ignore-new',
+ '--git-builder=/bin/true',
+ '--git-tarball-dir=../tarballs'])
+ eq_(ret, 1)
+ self._check_log(-1, "gbp:error: Non-native package 'hello-debhelper' has invalid version '3.0'")
diff --git a/tests/component/deb/test_clone.py b/tests/component/deb/test_clone.py
new file mode 100644
index 0000000..db5e2f9
--- /dev/null
+++ b/tests/component/deb/test_clone.py
@@ -0,0 +1,84 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2016 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import (ComponentTestBase,
+ ComponentTestGitRepository,
+ skipUnless)
+from tests.component.deb.fixtures import RepoFixtures
+
+from nose.tools import ok_
+
+from gbp.scripts.clone import main as clone
+
+
+class TestClone(ComponentTestBase):
+ """Test cloning from a remote"""
+
+ @RepoFixtures.native()
+ def test_clone_nonempty(self, repo):
+ """Test that cloning into an existing dir fails"""
+ os.chdir('..')
+ ok_(clone(['arg0', repo.path]) == 1,
+ "Cloning did no fail as expected")
+ self._check_log(-2,
+ "gbp:error: Git command failed: Error "
+ "running git clone: fatal: destination path "
+ "'git-buildpackage' already exists and is not "
+ "an empty directory.")
+
+ @RepoFixtures.native()
+ def test_clone_native(self, repo):
+ """Test that cloning of debian native packages works"""
+ dest = os.path.join(self._tmpdir,
+ 'cloned_repo')
+ clone(['arg0',
+ '--postclone=printenv > ../postclone.out',
+ repo.path, dest])
+ cloned = ComponentTestGitRepository(dest)
+ self._check_repo_state(cloned, 'master', ['master'])
+ assert len(cloned.get_commits()) == 1
+ self.check_hook_vars('../postclone', ["GBP_GIT_DIR"])
+
+ @skipUnless(os.getenv("GBP_NETWORK_TESTS"), "network tests disabled")
+ def test_clone_vcsgit_ok(self):
+ """Test that cloning from vcs-git urls works"""
+ dest = os.path.join(self._tmpdir,
+ 'cloned_repo')
+ ret = clone(['arg0', "vcsgit:libvirt-glib", dest])
+ self.assertEquals(ret, 0)
+ cloned = ComponentTestGitRepository(dest)
+ self._check_repo_state(cloned, 'debian/sid', ['debian/sid', 'upstream/latest'])
+
+ @skipUnless(os.getenv("GBP_NETWORK_TESTS"), "network tests disabled")
+ def test_clone_vcsgit_fail(self):
+ """Test that cloning from vcs-git urls fails as expected"""
+ ret = clone(['arg0', "vcsgit:doesnotexist"])
+ self.assertEquals(ret, 1)
+ self._check_log(-1, "gbp:error: Can't find any vcs-git URL for 'doesnotexist'")
+
+ @skipUnless(os.getenv("GBP_NETWORK_TESTS"), "network tests disabled")
+ def test_clone_github(self):
+ """Test that cloning from github urls works"""
+ dest = os.path.join(self._tmpdir,
+ 'cloned_repo')
+ ret = clone(['arg0', "github:agx/git-buildpackage", dest])
+ self.assertEquals(ret, 0)
+ cloned = ComponentTestGitRepository(dest)
+ self._check_repo_state(cloned, 'master', ['master'])
diff --git a/tests/component/deb/test_dch.py b/tests/component/deb/test_dch.py
new file mode 100644
index 0000000..1a1fc2d
--- /dev/null
+++ b/tests/component/deb/test_dch.py
@@ -0,0 +1,72 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import ComponentTestBase
+from tests.component.deb import DEB_TEST_DATA_DIR
+from tests.component.deb.fixtures import RepoFixtures
+from tests.testutils import skip_without_cmd
+
+import gbp.scripts.dch
+from gbp.scripts.dch import main as dch
+
+from nose.tools import eq_, ok_
+
+
+def _dsc_file(pkg, version, dir='dsc-3.0'):
+ return os.path.join(DEB_TEST_DATA_DIR, dir, '%s_%s.dsc' % (pkg, version))
+
+
+DEFAULT_DSC = _dsc_file('hello-debhelper', '2.6-2')
+
+
+@skip_without_cmd('debchange')
+class TestDch(ComponentTestBase):
+ """Test importing of new upstream versions"""
+ pkg = "hello-debhelper"
+ def_branches = ['master', 'upstream', 'pristine-tar']
+
+ @RepoFixtures.quilt30(DEFAULT_DSC)
+ def test_user_customizations(self, repo):
+ os.chdir(repo.path)
+ # Non-existent customization file
+ ok_(dch(['arg0', '--customizations=customizations.py']) == 1,
+ "dch did no fail as expected")
+
+ # Create user customizations file
+ with open('customizations.py', 'w') as fobj:
+ fobj.write("""def format_changelog_entry(commit_info, options, last_commit=False):
+ return ['testentry']
+""")
+ # Add the file so we have a change
+ repo.add_files(['customizations.py'])
+ repo.commit_all(msg="test customizations")
+ ok_(dch(['arg0', '-S', '-a', '--customizations=customizations.py']) == 0,
+ "dch did no succeed as expected")
+ with open("debian/changelog", encoding='utf-8') as f:
+ cl = f.read()
+ ok_('* testentry\n' in cl)
+ del gbp.scripts.dch.user_customizations['format_changelog_entry']
+
+ @RepoFixtures.native()
+ def test_postedit_hook(self, repo):
+ os.chdir(repo.path)
+ eq_(dch(['arg0', '-N', '1.2.3', '--postedit', 'echo $GBP_DEBIAN_VERSION > foo.txt']), 0)
+ with open('foo.txt') as f:
+ eq_(f.read(), '1.2.3\n')
diff --git a/tests/component/deb/test_export_orig.py b/tests/component/deb/test_export_orig.py
new file mode 100644
index 0000000..e42a1fc
--- /dev/null
+++ b/tests/component/deb/test_export_orig.py
@@ -0,0 +1,113 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import (ComponentTestBase,
+ ComponentTestGitRepository)
+from tests.component.deb import DEB_TEST_DATA_DIR
+
+from nose.tools import ok_, assert_false, assert_true
+
+from gbp.scripts.import_dsc import main as import_dsc
+from gbp.scripts.export_orig import main as export_orig
+
+
+class TestExportOrig(ComponentTestBase):
+ """Test exporting of orig tarballs"""
+
+ @staticmethod
+ def _dsc_name(pkg, version, dir):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ dir,
+ '%s_%s.dsc' % (pkg, version))
+
+ def test_component_generation(self):
+ """Test that generating tarball and additional tarball works without pristine-tar"""
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.8-1', 'dsc-3.0-additional-tarballs')
+ tarballs = ["../%s_2.8.orig-foo.tar.gz" % pkg,
+ "../%s_2.8.orig.tar.gz" % pkg]
+
+ assert import_dsc(['arg0', '--no-pristine-tar', dsc]) == 0
+ repo = ComponentTestGitRepository(pkg)
+ os.chdir(pkg)
+ assert_false(repo.has_branch('pristine-tar'), "Pristine-tar branch must not exist")
+ for t in tarballs:
+ self.assertFalse(os.path.exists(t), "Tarball %s must not exist" % t)
+ ret = export_orig(['arg0',
+ '--component=foo',
+ '--no-pristine-tar'])
+ ok_(ret == 0, "Exporting tarballs failed")
+ for t in tarballs:
+ self.assertTrue(os.path.exists(t), "Tarball %s not found" % t)
+
+ def test_pristinetar_component_generation(self):
+ """Test that generating tarball and additional tarball works with pristine-tar"""
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.8-1', 'dsc-3.0-additional-tarballs')
+ tarballs = ["../%s_2.8.orig-foo.tar.gz" % pkg,
+ "../%s_2.8.orig.tar.gz" % pkg]
+
+ assert import_dsc(['arg0', '--pristine-tar', dsc]) == 0
+ repo = ComponentTestGitRepository(pkg)
+ os.chdir(pkg)
+ assert_true(repo.has_branch('pristine-tar'), "Pristine-tar branch must exist")
+ for t in tarballs:
+ self.assertFalse(os.path.exists(t), "Tarball %s must not exist" % t)
+ # Make sure the tree object for importing the main tarball is recreated
+ repo.collect_garbage(prune='all', aggressive=True)
+ ret = export_orig(['arg0',
+ '--component=foo',
+ '--pristine-tar'])
+ ok_(ret == 0, "Exporting tarballs failed")
+ for t in tarballs:
+ self.assertTrue(os.path.exists(t), "Tarball %s not found" % t)
+
+ def test_git_archive_tree_non_existent(self):
+ """Test that we're failing tarball generation when commits are missing"""
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.8-1', 'dsc-3.0-additional-tarballs')
+
+ assert import_dsc(['arg0', '--no-pristine-tar', dsc]) == 0
+ repo = ComponentTestGitRepository(pkg)
+ os.chdir(pkg)
+ assert_false(repo.has_branch('pristine-tar'), "Pristine-tar branch not must exist")
+ ret = export_orig(['arg0',
+ '--component=bar', # non-existing component
+ '--no-pristine-tar'])
+ ok_(ret == 1, "Exporting tarballs must fail")
+ self._check_log(-1, "gbp:error: No tree for 'bar' found in "
+ "'upstream/2.8' to create additional tarball from")
+
+ def test_pristine_tar_commit_non_existent(self):
+ """Test that we're failing if pristine-tar commit is missing"""
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.8-1', 'dsc-3.0-additional-tarballs')
+
+ assert import_dsc(['arg0', '--pristine-tar', dsc]) == 0
+ repo = ComponentTestGitRepository(pkg)
+ os.chdir(pkg)
+ assert_true(repo.has_branch('pristine-tar'), "Pristine-tar branch must exist")
+ repo.delete_branch("pristine-tar")
+ repo.create_branch("pristine-tar") # create a nonsense pristine-tar branch
+ ret = export_orig(['arg0',
+ '--component=foo',
+ '--pristine-tar'])
+ ok_(ret == 1, "Exporting tarballs must fail")
+ self._check_log(-1, ".*git show refs/heads/pristine-tar:.*failed")
diff --git a/tests/component/deb/test_import_dsc.py b/tests/component/deb/test_import_dsc.py
new file mode 100644
index 0000000..a3b0aea
--- /dev/null
+++ b/tests/component/deb/test_import_dsc.py
@@ -0,0 +1,369 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013,2014,2015,2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import (ComponentTestBase,
+ ComponentTestGitRepository,
+ skipUnless)
+from tests.component.deb import DEB_TEST_DATA_DIR, DEB_TEST_DOWNLOAD_URL
+from tests.component.deb.fixtures import RepoFixtures
+
+from nose.tools import ok_, eq_
+
+from gbp.scripts.import_dsc import main as import_dsc
+from gbp.deb.pristinetar import DebianPristineTar
+from gbp.deb.dscfile import DscFile
+from gbp.git.repository import GitRepository
+
+
+class TestImportDsc(ComponentTestBase):
+ """Test importing of debian source packages"""
+
+ def _dsc30(self, version):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-3.0',
+ 'hello-debhelper_%s.dsc' % version)
+
+ def _check_reflog(self, repo):
+ reflog, ret = repo._git_getoutput('reflog')
+ # Recent (>= 2.10) git versions create a reflog entry for "git
+ # update-ref HEAD HEAD" while older ones (2.9.4) don't so
+ # reflog[0] is either there or not.
+ if len(reflog) == 3:
+ ok_(b"gbp: Import Debian changes" in reflog[1])
+ ok_(b"gbp: Import Upstream version 2.6" in reflog[2])
+ elif len(reflog) == 2:
+ # Furthermore some older git versions (<2.10) fail to set
+ # the reflog correctly on the initial commit so only check
+ # the second
+ ok_(b"gbp: Import Debian changes" in reflog[0])
+ else:
+ ok_(len(reflog) > 3, "Reflog %s has too many lines" % reflog)
+ ok_(len(reflog) < 2, "Reflog %s has too few lines" % reflog)
+
+ def test_import_debian_native(self):
+ """Test that importing of debian native packages works"""
+ def _dsc(version):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-native',
+ 'git-buildpackage_%s.dsc' % version)
+
+ dsc = _dsc('0.4.14')
+ assert import_dsc(['arg0', dsc]) == 0
+ repo = ComponentTestGitRepository('git-buildpackage')
+ self._check_repo_state(repo, 'master', ['master'])
+ assert len(repo.get_commits()) == 1
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ ok_("git-buildpackage (0.01) unstable; urgency=low" in commitmsg)
+ ok_("git-buildpackage (0.4.14) unstable; urgency=low" in commitmsg)
+
+ os.chdir('git-buildpackage')
+ dsc = _dsc('0.4.15')
+ assert import_dsc(['arg0', dsc]) == 0
+ self._check_repo_state(repo, 'master', ['master'])
+ assert len(repo.get_commits()) == 2
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ ok_("git-buildpackage (0.4.14) unstable; urgency=low" not in commitmsg)
+ ok_("git-buildpackage (0.4.15) unstable; urgency=low" in commitmsg)
+
+ dsc = _dsc('0.4.16')
+ assert import_dsc(['arg0', dsc]) == 0
+ self._check_repo_state(repo, 'master', ['master'])
+ assert len(repo.get_commits()) == 3
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ ok_("git-buildpackage (0.4.14) unstable; urgency=low" not in commitmsg)
+ ok_("git-buildpackage (0.4.15) unstable; urgency=low" not in commitmsg)
+ ok_("git-buildpackage (0.4.16) unstable; urgency=low" in commitmsg)
+
+ @skipUnless(os.getenv("GBP_NETWORK_TESTS"), "network tests disabled")
+ def test_download(self):
+ def _dsc(version):
+ return os.path.join(DEB_TEST_DOWNLOAD_URL,
+ 'dsc-native',
+ 'git-buildpackage_%s.dsc' % version)
+ dsc = _dsc('0.4.14')
+ assert import_dsc(['arg0',
+ '--allow-unauthenticated',
+ dsc]) == 0
+ repo = ComponentTestGitRepository('git-buildpackage')
+ self._check_repo_state(repo, 'master', ['master'])
+ assert len(repo.get_commits()) == 1
+
+ @skipUnless(os.getenv("GBP_NETWORK_TESTS"), "network tests disabled")
+ def test_broken_download(self):
+ def _not_a_dsc(version):
+ return os.path.join(DEB_TEST_DOWNLOAD_URL,
+ 'dsc-3.0',
+ 'hello-debhelper_%s.orig.tar.gz' % version)
+
+ f = _not_a_dsc('2.6')
+ assert import_dsc(['arg0',
+ '--allow-unauthenticated',
+ f]) == 1
+ self._check_log(-1, "gbp:error: Did not find a dsc file at")
+
+ def test_create_branches(self):
+ """Test that creating missing branches works"""
+ dsc = self._dsc30('2.6-2')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ dsc]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ os.chdir('hello-debhelper')
+ assert len(repo.get_commits()) == 2
+ self._check_reflog(repo)
+ self._check_repo_state(repo, 'master', ['master', 'pristine-tar', 'upstream'])
+ dsc = self._dsc30('2.8-1')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=foo',
+ '--upstream-branch=bar',
+ '--create-missing-branches',
+ dsc]) == 0
+ self._check_repo_state(repo, 'master', ['bar', 'foo', 'master', 'pristine-tar', 'upstream'])
+ commits, expected = len(repo.get_commits()), 2
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+
+ def test_import_30_pristine_tar(self):
+ dscfile = self._dsc30('2.6-1')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ dscfile]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ self._check_repo_state(repo, 'master', ['master', 'pristine-tar', 'upstream'])
+ commits, expected = len(repo.get_commits()), 2
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ eq_("hello-debhelper (2.6-1) unstable; urgency=low", commitmsg.split('\n')[0])
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+
+ os.chdir(repo.path)
+ dscfile = self._dsc30('2.6-2')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ dscfile]) == 0
+ commits, expected = len(repo.get_commits()), 3
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ eq_("hello-debhelper (2.6-2) unstable; urgency=medium", commitmsg.split('\n')[0])
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+
+ commits, expected = len(repo.get_commits(until='pristine-tar')), 1
+ ok_(commits == expected, "Found %d pristine-tar commits instead of %d" % (commits, expected))
+
+ def test_import_30_additional_tarball_pristine_tar(self):
+ """Test that importing a package with additional tarballs works"""
+ def _dsc(version):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-3.0-additional-tarballs',
+ 'hello-debhelper_%s.dsc' % version)
+
+ dscfile = _dsc('2.8-1')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ dscfile]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ self._check_repo_state(repo, 'master', ['master', 'pristine-tar', 'upstream'])
+ commits, expected = len(repo.get_commits()), 2
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ ok_("hello-debhelper (2.8-1) unstable; urgency=low" in commitmsg)
+ ok_("hello (1.3-7) experimental; urgency=LOW" in commitmsg)
+
+ for file in [b'foo/test1', b'foo/test2']:
+ ok_(file in repo.ls_tree('HEAD'),
+ "Could not find component tarball file %s in %s" % (file, repo.ls_tree('HEAD')))
+
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+
+ dsc = DscFile.parse(dscfile)
+ # Check if we can rebuild the tarball and component
+ ptars = [('hello-debhelper_2.8.orig.tar.gz', 'pristine-tar', '', dsc.tgz),
+ ('hello-debhelper_2.8.orig-foo.tar.gz', 'pristine-tar^', 'foo', dsc.additional_tarballs['foo'])]
+
+ p = DebianPristineTar(repo)
+ outdir = os.path.abspath('.')
+ for f, w, s, o in ptars:
+ eq_(repo.get_subject(w), 'pristine-tar data for %s' % f)
+ old = self.hash_file(o)
+ p.checkout('hello-debhelper', '2.8', 'gzip', outdir, component=s)
+ new = self.hash_file(os.path.join(outdir, f))
+ eq_(old, new, "Checksum %s of regenerated tarball %s does not match original %s" %
+ (f, old, new))
+
+ def test_existing_dir(self):
+ """
+ Importing outside of git repository with existing target
+ dir must fail
+ """
+
+ # Create directory we should stumble upon
+ os.makedirs('hello-debhelper')
+ dsc = self._dsc30('2.8-1')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ dsc]) == 1
+ self._check_log(0, "gbp:error: Directory 'hello-debhelper' already exists. If you want to import into it, "
+ "please change into this directory otherwise move it away first")
+
+ def test_import_10(self):
+ """Test that importing a 1.0 source format package works"""
+ def _dsc(version):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-1.0',
+ 'hello-debhelper_%s.dsc' % version)
+
+ dsc = _dsc('2.6-2')
+ assert import_dsc(['arg0', dsc]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'],
+ tags=['upstream/2.6', 'debian/2.6-2'])
+ assert len(repo.get_commits()) == 2
+
+ def test_target_dir(self):
+ """Test that setting the target dir works"""
+ dsc = self._dsc30('2.6-2')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--no-pristine-tar',
+ dsc,
+ 'targetdir']) == 0
+ assert os.path.exists('targetdir')
+ repo = ComponentTestGitRepository('targetdir')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+
+ def test_bare(self):
+ """Test that importing into bare repository works"""
+ dsc = self._dsc30('2.6-2')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ dsc]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ os.chdir('hello-debhelper')
+ assert len(repo.get_commits()) == 2
+ self._check_reflog(repo)
+ self._check_repo_state(repo, 'master', ['master', 'pristine-tar', 'upstream'])
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ ok_("hello-debhelper (2.6-2) unstable; urgency=medium" in commitmsg)
+ ok_("hello (1.3-7) experimental; urgency=LOW" in commitmsg)
+
+ dsc = self._dsc30('2.8-1')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ dsc]) == 0
+ commits, expected = len(repo.get_commits()), 4
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ ok_("hello-debhelper (2.8-1) unstable; urgency=low" in commitmsg)
+ ok_("ello-debhelper (2.7-1) unstable; urgency=low" in commitmsg)
+ ok_("hello-debhelper (2.6-2) unstable; urgency=medium" not in commitmsg)
+
+ def test_import_environ(self):
+ """Test that environment variables influence git configuration"""
+ os.environ['DEBFULLNAME'] = 'testing tester'
+ os.environ['DEBEMAIL'] = 'gbp-tester@debian.invalid'
+ repo = RepoFixtures.import_native()
+ got = repo.get_config("user.email")
+ want = os.environ['DEBEMAIL']
+ ok_(got == want, "unexpected git config user.email: got %s, want %s" % (got, want))
+
+ got = repo.get_config("user.name")
+ want = os.environ['DEBFULLNAME']
+ ok_(got == want, "unexpected git config user.name: got %s, want %s" % (got, want))
+
+ def test_debian_branch_not_master(self):
+ """Make sure we only have debian-branch and upstream-branch after an initial import"""
+ dsc = self._dsc30('2.6-2')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--no-pristine-tar',
+ '--debian-branch=pk4',
+ '--upstream-branch=upstream',
+ dsc]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ self._check_repo_state(repo, 'pk4', ['pk4', 'upstream'])
+ commits, expected = len(repo.get_commits()), 2
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+
+ def test_empty_repo(self):
+ """Make sure we can import into an empty repository"""
+ dsc = self._dsc30('2.6-2')
+ repo = GitRepository.create("hello-debhelper")
+ self._check_repo_state(repo, None, [])
+ os.chdir('hello-debhelper')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--no-pristine-tar',
+ '--debian-branch=pk4',
+ '--upstream-branch=upstream',
+ dsc]) == 0
+ self._check_repo_state(repo, 'pk4', ['pk4', 'upstream'])
+ commits, expected = len(repo.get_commits()), 2
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+
+ def test_upstream_branch_is_master(self):
+ """Make sure we can import when upstream-branch == master (#750962)"""
+ dsc = self._dsc30('2.6-2')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--no-pristine-tar',
+ '--debian-branch=debian',
+ '--upstream-branch=master',
+ dsc]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ self._check_repo_state(repo, 'debian', ['debian', 'master'])
+ commits, expected = len(repo.get_commits()), 2
+ ok_(commits == expected, "Found %d commit instead of %d" % (commits, expected))
+
+ def test_import_30_filters(self):
+ dscfile = self._dsc30('2.6-1')
+ assert import_dsc(['arg0',
+ '--verbose',
+ '--no-pristine-tar',
+ '--debian-branch=master',
+ '--upstream-branch=upstream',
+ '--filter=debian/patches/*',
+ '--filter=AUTHORS',
+ dscfile]) == 0
+ repo = ComponentTestGitRepository('hello-debhelper')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ os.chdir('hello-debhelper')
+ ok_(os.path.exists("./debian/changelog"))
+ ok_(os.path.exists("./configure.ac"))
+ ok_(not os.path.exists("./debian/patches/series"))
+ ok_(not os.path.exists("./debian/patches/AUTHORS"))
diff --git a/tests/component/deb/test_import_dscs.py b/tests/component/deb/test_import_dscs.py
new file mode 100644
index 0000000..480d3de
--- /dev/null
+++ b/tests/component/deb/test_import_dscs.py
@@ -0,0 +1,46 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import (ComponentTestBase,
+ ComponentTestGitRepository)
+from tests.component.deb import DEB_TEST_DATA_DIR
+from nose.tools import ok_
+
+from gbp.scripts.import_dscs import main as import_dscs
+
+
+class TestImportDscs(ComponentTestBase):
+ """Test importing of debian source packages"""
+
+ def test_import_debian_native(self):
+ """Test that importing of debian native packages works"""
+ def _dsc(version):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ 'dsc-native',
+ 'git-buildpackage_%s.dsc' % version)
+
+ dsc1 = _dsc('0.4.14')
+ dsc2 = _dsc('0.4.15')
+ assert import_dscs(['arg0', dsc1, dsc2]) == 0
+ repo = ComponentTestGitRepository('git-buildpackage')
+ self._check_repo_state(repo, 'master', ['master'])
+ assert len(repo.get_commits()) == 2
+ commitmsg = repo.get_commit_info('HEAD')['body']
+ ok_("git-buildpackage (0.4.15) unstable; urgency=low" in commitmsg)
diff --git a/tests/component/deb/test_import_orig.py b/tests/component/deb/test_import_orig.py
new file mode 100644
index 0000000..58b72c5
--- /dev/null
+++ b/tests/component/deb/test_import_orig.py
@@ -0,0 +1,355 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2015,2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+import shutil
+import tarfile
+
+from mock import patch, DEFAULT
+
+from tests.component import (ComponentTestBase, skipUnless)
+from tests.component.deb import DEB_TEST_DATA_DIR, DEB_TEST_DOWNLOAD_URL
+from tests.component.deb.fixtures import RepoFixtures
+
+from gbp.scripts.import_orig import main as import_orig
+from gbp.deb.pristinetar import DebianPristineTar
+from gbp.deb.dscfile import DscFile
+from gbp.git.repository import GitRepository, GitRepositoryError
+from gbp.paths import to_bin
+from gbp.command_wrappers import UnpackTarArchive
+
+from nose.tools import ok_, eq_, assert_raises
+
+
+def raise_if_tag_match(match):
+ def wrapped(*args, **kwargs):
+ if len(args) > 0 and args[0].startswith(match) or kwargs.get('name', '').startswith(match):
+ raise GitRepositoryError('this is a create tag error mock for %s %s' % (match, kwargs))
+ return DEFAULT
+ return wrapped
+
+
+def _dsc_file(pkg, version, dir='dsc-3.0'):
+ return os.path.join(DEB_TEST_DATA_DIR, dir, '%s_%s.dsc' % (pkg, version))
+
+
+DEFAULT_DSC = _dsc_file('hello-debhelper', '2.6-2')
+
+
+class TestImportOrig(ComponentTestBase):
+ """Test importing of new upstream versions"""
+ pkg = "hello-debhelper"
+ def_branches = ['master', 'upstream', 'pristine-tar']
+
+ def _orig(self, version, dir='dsc-3.0'):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ dir,
+ '%s_%s.orig.tar.gz' % (self.pkg, version))
+
+ def _download_url(self, version, dir='dsc-3.0'):
+ return os.path.join(DEB_TEST_DOWNLOAD_URL,
+ dir,
+ '%s_%s.orig.tar.gz' % (self.pkg, version))
+
+ def test_initial_import(self):
+ """Test that importing into an empty repo works"""
+ repo = GitRepository.create(self.pkg)
+ os.chdir(self.pkg)
+ orig = self._orig('2.6')
+ ok_(import_orig(['arg0', '--no-interactive', '--pristine-tar', orig]) == 0)
+ self._check_repo_state(repo, 'master', self.def_branches,
+ tags=['upstream/2.6'])
+
+ @skipUnless(os.getenv("GBP_NETWORK_TESTS"), "network tests disabled")
+ def test_download(self):
+ """Test that importing via download works"""
+ repo = GitRepository.create(self.pkg)
+ os.chdir(self.pkg)
+ orig = self._download_url('2.6')
+ ok_(import_orig(['arg0', '--no-interactive', '--pristine-tar', orig]) == 0)
+ self._check_repo_state(repo, 'master', self.def_branches,
+ tags=['upstream/2.6'])
+
+ def _check_component_tarballs(self, repo, files):
+ for file in files:
+ ok_(to_bin(file) in repo.ls_tree('HEAD'),
+ "Could not find component tarball file %s in %s" % (file, repo.ls_tree('HEAD')))
+ ok_(to_bin(file) in repo.ls_tree('upstream'),
+ "Could not find component tarball file %s in %s" % (file, repo.ls_tree('upstream')))
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_update(self, repo):
+ """
+ Test that importing a new version works
+ """
+ orig = self._orig('2.8')
+ ok_(import_orig(['arg0',
+ '--postimport=printenv > ../postimport.out',
+ '--no-interactive', '--pristine-tar', orig]) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6', 'upstream/2.8'])
+ ok_(os.path.exists('debian/changelog'))
+ ok_(os.path.exists('../postimport.out'))
+ self.check_hook_vars('../postimport', [("GBP_BRANCH", "master"),
+ ("GBP_TAG", "upstream/2.8"),
+ ("GBP_UPSTREAM_VERSION", "2.8"),
+ ("GBP_DEBIAN_VERSION", "2.8-1")])
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_update_component_tarballs(self, repo):
+ """
+ Test importing new version with additional tarballs works
+ """
+ # Import 2.8
+ orig = self._orig('2.8', dir='dsc-3.0-additional-tarballs')
+ ok_(import_orig(['arg0', '--component=foo', '--no-interactive', '--pristine-tar', orig]) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6', 'upstream/2.8'])
+ self._check_component_tarballs(repo, [b'foo/test1', b'foo/test2'])
+ ok_(os.path.exists('debian/changelog'))
+
+ dsc = DscFile.parse(_dsc_file(self.pkg, '2.8-1', dir='dsc-3.0-additional-tarballs'))
+ # Check if we can rebuild the upstream tarball and additional tarball
+ ptars = [('hello-debhelper_2.8.orig.tar.gz', 'pristine-tar', '', dsc.tgz),
+ ('hello-debhelper_2.8.orig-foo.tar.gz', 'pristine-tar^', 'foo', dsc.additional_tarballs['foo'])]
+
+ p = DebianPristineTar(repo)
+ outdir = os.path.abspath('.')
+ for f, w, s, o in ptars:
+ eq_(repo.get_subject(w), 'pristine-tar data for %s' % f)
+ old = self.hash_file(o)
+ p.checkout('hello-debhelper', '2.8', 'gzip', outdir, component=s)
+ out = os.path.join(outdir, f)
+ new = self.hash_file(out)
+ eq_(old, new, "Checksum %s of regenerated tarball %s does not match original %s" %
+ (f, old, new))
+ os.unlink(out)
+
+ # Import 2.9
+ orig = self._orig('2.9', dir='dsc-3.0-additional-tarballs')
+ ok_(import_orig(['arg0', '--component=foo', '--no-interactive', '--pristine-tar', orig]) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6', 'upstream/2.8', 'upstream/2.9'])
+ self._check_component_tarballs(repo, ['foo/test1', 'foo/test2', 'foo/test3'])
+ ok_(os.path.exists('debian/changelog'))
+
+ dsc = DscFile.parse(_dsc_file(self.pkg, '2.9-1', dir='dsc-3.0-additional-tarballs'))
+ # Check if we can rebuild the upstream tarball and additional tarball
+ ptars = [('hello-debhelper_2.9.orig.tar.gz', 'pristine-tar', '', dsc.tgz),
+ ('hello-debhelper_2.9.orig-foo.tar.gz', 'pristine-tar^', 'foo', dsc.additional_tarballs['foo'])]
+
+ p = DebianPristineTar(repo)
+ outdir = os.path.abspath('.')
+ for f, w, s, o in ptars:
+ eq_(repo.get_subject(w), 'pristine-tar data for %s' % f)
+ old = self.hash_file(o)
+ p.checkout('hello-debhelper', '2.9', 'gzip', outdir, component=s)
+ new = self.hash_file(os.path.join(outdir, f))
+ eq_(old, new, "Checksum %s of regenerated tarball %s does not match original %s" %
+ (f, old, new))
+
+ def test_tag_exists(self):
+ """Test that importing an already imported version fails"""
+ repo = GitRepository.create(self.pkg)
+ os.chdir(repo.path)
+ orig = self._orig('2.6')
+ # First import
+ ok_(import_orig(['arg0', '--no-interactive', '--pristine-tar', orig]) == 0)
+ heads = self.rem_refs(repo, self.def_branches)
+ # Second import must fail
+ ok_(import_orig(['arg0', '--no-interactive', '--pristine-tar', orig]) == 1)
+ self._check_log(0, "gbp:error: Upstream tag 'upstream/2.6' already exists")
+ # Check that the second import didn't change any refs
+ self.check_refs(repo, heads)
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_update_fail_create_upstream_tag(self, repo):
+ """
+ Test that we can rollback from a failure to create the upstream
+ tag
+ """
+ heads = self.rem_refs(repo, self.def_branches)
+ orig = self._orig('2.8')
+ with patch('gbp.git.repository.GitRepository.create_tag',
+ side_effect=GitRepositoryError('this is a create tag error mock')):
+ ok_(import_orig(['arg0', '--no-interactive', '--pristine-tar', orig]) == 1)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6'])
+ self.check_refs(repo, heads)
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_update_fail_merge(self, repo):
+ """
+ Test that we can rollback from a failed merge
+ """
+ heads = self.rem_refs(repo, self.def_branches)
+ orig = self._orig('2.8')
+ with patch('gbp.scripts.import_orig.debian_branch_merge',
+ side_effect=GitRepositoryError('this is a fail merge error mock')):
+ ok_(import_orig(['arg0', '--no-interactive', '--pristine-tar', orig]) == 1)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6'])
+ self.check_refs(repo, heads)
+
+ @patch('gbp.git.repository.GitRepository.create_tag',
+ side_effect=raise_if_tag_match('upstream/'))
+ def test_initial_import_fail_create_upstream_tag(self, RepoMock):
+ """
+ Test that we can rollback from a failure to create the upstream
+ tag on initial import
+ """
+ repo = GitRepository.create(self.pkg)
+ os.chdir(repo.path)
+ orig = self._orig('2.6')
+ ok_(import_orig(['arg0', '--no-interactive', orig]) == 1)
+
+ self._check_repo_state(repo, None, [], tags=[])
+
+ def test_initial_import_fail_create_debian_branch(self):
+ """
+ Test that we can rollback from creating the Debian branch on
+ initial import
+ """
+ repo = GitRepository.create(self.pkg)
+ os.chdir(self.pkg)
+ orig = self._orig('2.6')
+
+ with patch('gbp.git.repository.GitRepository.create_branch',
+ side_effect=GitRepositoryError('this is a create branch error mock')):
+ ok_(import_orig(['arg0', '--no-interactive', '--pristine-tar', orig]) == 1)
+
+ self._check_repo_state(repo, None, [], tags=[])
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_filter_with_component_tarballs(self, repo):
+ """
+ Test that using a filter works with component tarballs (#840602)
+ """
+ # copy data since we don't want the repacked tarball to end up in DEB_TEST_DATA_DIR
+ os.mkdir('../tarballs')
+ for f in ['hello-debhelper_2.8.orig-foo.tar.gz', 'hello-debhelper_2.8.orig.tar.gz']:
+ src = os.path.join(DEB_TEST_DATA_DIR, 'dsc-3.0-additional-tarballs', f)
+ shutil.copy(src, '../tarballs')
+
+ ok_(import_orig(['arg0',
+ '--component=foo',
+ '--no-interactive',
+ '--pristine-tar',
+ '--filter-pristine-tar',
+ '--filter=README*',
+ '../tarballs/hello-debhelper_2.8.orig.tar.gz']) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6', 'upstream/2.8'])
+ self._check_component_tarballs(repo, ['foo/test1', 'foo/test2'])
+
+ ok_('README' not in repo.ls_tree('HEAD'),
+ "README not filtered out of %s" % repo.ls_tree('HEAD'))
+ tar = '../hello-debhelper_2.8.orig.tar.gz'
+
+ # Check if tar got filtered properly
+ ok_(os.path.exists(tar))
+ t = tarfile.open(name=tar, mode="r:gz")
+ for f in ['hello-2.8/configure']:
+ i = t.getmember(f)
+ eq_(type(i), tarfile.TarInfo)
+ for f in ['hello-2.8/README']:
+ with assert_raises(KeyError):
+ t.getmember(f)
+ t.close()
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_filter_with_orig_tarball(self, repo):
+ """
+ Test that using a filter works with an upstream tarball that has
+ already the correct name (#558777)
+ """
+ f = 'hello-debhelper_2.8.orig.tar.gz'
+ src = os.path.join(DEB_TEST_DATA_DIR, 'dsc-3.0', f)
+ shutil.copy(src, '..')
+
+ ok_(import_orig(['arg0',
+ '--no-interactive',
+ '--pristine-tar',
+ '--filter-pristine-tar',
+ '--filter=README*',
+ '../hello-debhelper_2.8.orig.tar.gz']) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6', 'upstream/2.8'])
+
+ filtered = os.path.join('..', f)
+ ok_(os.path.exists(filtered))
+ eq_(os.readlink(filtered).split('/')[-1],
+ 'hello-debhelper_2.8.orig.gbp.tar.gz')
+ # Check if tar got filtered properly
+ t = tarfile.open(name=filtered, mode="r:gz")
+ for f in ['hello-2.8/configure']:
+ i = t.getmember(f)
+ eq_(type(i), tarfile.TarInfo)
+ for f in ['hello-2.8/README']:
+ with assert_raises(KeyError):
+ t.getmember(f)
+ t.close()
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_filter_unpacked_dir(self, repo):
+ """
+ Test that importing and filtering unpacked upstream source works.
+ """
+ f = 'hello-debhelper_2.8.orig.tar.gz'
+ src = os.path.join(DEB_TEST_DATA_DIR, 'dsc-3.0', f)
+
+ # Create an unpacked tarball we can import
+ UnpackTarArchive(src, '..')()
+ ok_(os.path.exists('../hello-2.8'))
+
+ ok_(import_orig(['arg0',
+ '--no-interactive',
+ '--pristine-tar',
+ '--filter-pristine-tar',
+ '--filter=README*',
+ '../hello-2.8']) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'],
+ tags=['debian/2.6-2', 'upstream/2.6', 'upstream/2.8'])
+
+ filtered = os.path.join('..', f)
+ ok_(os.path.exists(filtered))
+ # Check if tar got filtered properly
+ t = tarfile.open(name=filtered, mode="r:gz")
+ for f in ['hello-2.8/configure']:
+ i = t.getmember(f)
+ eq_(type(i), tarfile.TarInfo)
+ for f in ['hello-2.8/README']:
+ with assert_raises(KeyError):
+ t.getmember(f)
+ t.close()
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--pristine-tar'])
+ def test_import_in_submodule(self, repo):
+ """
+ Test that importing works if repo is a git submodule (#674015)
+ """
+ parent_repo = GitRepository.create('../parent')
+ parent_repo.add_submodule(repo.path)
+ parent_repo.update_submodules(init=True, recursive=True)
+ submodule = GitRepository(os.path.join(parent_repo.path,
+ 'hello-debhelper'))
+ ok_(submodule.path.endswith, 'parent/hello-debhelper')
+ os.chdir(submodule.path)
+ orig = self._orig('2.8')
+ submodule.create_branch('upstream', 'origin/upstream')
+ ok_(import_orig(['arg0', '--no-interactive', orig]) == 0)
diff --git a/tests/component/deb/test_pq.py b/tests/component/deb/test_pq.py
new file mode 100644
index 0000000..9dd985a
--- /dev/null
+++ b/tests/component/deb/test_pq.py
@@ -0,0 +1,188 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import (ComponentTestBase)
+
+from tests.component.deb import DEB_TEST_DATA_DIR
+from tests.component.deb.fixtures import RepoFixtures
+
+from nose.tools import ok_, eq_
+
+from gbp.scripts.pq import main as pq
+from gbp.scripts.import_dsc import main as import_dsc
+
+
+class TestPq(ComponentTestBase):
+ """Test gbp pq"""
+
+ def _test_pq(self, repo, action, opts=[]):
+ args = ['arg0', action] + opts
+ os.chdir(os.path.abspath(repo.path))
+ ret = pq(args)
+ ok_(ret == 0, "Running gbp pq %s failed" % action)
+
+ @RepoFixtures.quilt30()
+ def test_rebase_import(self, repo):
+ """Test that rebase imports patches first"""
+ eq_(repo.branch, 'master')
+ eq_(repo.has_branch('patch-queue/master'), False)
+ self._test_pq(repo, 'rebase')
+ eq_(repo.has_branch('patch-queue/master'), True)
+
+ @RepoFixtures.quilt30()
+ def test_switch_import(self, repo):
+ """Test that switch imports patches first"""
+ eq_(repo.branch, 'master')
+ eq_(repo.has_branch('patch-queue/master'), False)
+ self._test_pq(repo, 'switch')
+ eq_(repo.has_branch('patch-queue/master'), True)
+
+ @RepoFixtures.quilt30()
+ def test_empty_cycle(self, repo):
+ eq_(repo.has_branch('patch-queue/master'), False)
+ eq_(repo.branch, 'master')
+ self._test_pq(repo, 'import')
+ eq_(repo.has_branch('patch-queue/master'), True)
+ eq_(repo.branch, 'patch-queue/master')
+ self._test_pq(repo, 'rebase')
+ eq_(repo.branch, 'patch-queue/master')
+ self._test_pq(repo, 'export')
+ eq_(repo.has_branch('patch-queue/master'), True)
+ eq_(repo.branch, 'master')
+ self._test_pq(repo, 'drop')
+ eq_(repo.has_branch('patch-queue/master'), False)
+
+ @RepoFixtures.quilt30()
+ def test_rename(self, repo):
+ patch = os.path.join(repo.path, 'debian/patches/0001-Rename.patch')
+
+ repo.set_config('diff.renames', 'true')
+ self._test_pq(repo, 'import')
+ repo.rename_file('configure.ac', 'renamed')
+ repo.commit_all("Rename")
+ self._test_pq(repo, 'export')
+ self.assertTrue(
+ os.path.exists(patch))
+ # Check the file was removed and added, not renamed
+ with open(patch) as f:
+ self.assertTrue('rename from' not in f.read())
+ self.assertTrue('rename to' not in f.read())
+
+ @staticmethod
+ def _dsc_name(pkg, version, dir):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ dir,
+ '%s_%s.dsc' % (pkg, version))
+
+ @staticmethod
+ def _append_patch(repo, name, contents):
+ with open(os.path.join(repo.path, 'debian/patches/series'), 'a') as series_file:
+ series_file.write('{}.patch\n'.format(name))
+
+ with open(os.path.join(repo.path, 'debian/patches/{}.patch'.format(name)), 'w') as patch:
+ patch.write(contents)
+
+ repo.add_files('debian/patches/{}.patch'.format(name))
+ repo.commit_files(msg='Add patch: {}.patch'.format(name),
+ files=['debian/patches/series',
+ 'debian/patches/{}.patch'.format(name)])
+
+ @RepoFixtures.quilt30()
+ def test_import(self, repo):
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.6-2', 'dsc-3.0')
+ eq_(import_dsc(['arg0', dsc]), 0)
+ self._test_pq(repo, 'import')
+
+ author, subject = repo.get_head_author_subject()
+ eq_(author, 'Santiago Vila <sanvila@debian.org>')
+ eq_(subject, 'Modified doc/Makefile.in to avoid '
+ '/usr/share/info/dir.gz')
+
+ self._test_pq(repo, 'switch')
+
+ self._append_patch(repo, 'foo', '''\
+Author: Mr. T. St <t@example.com>
+Description: Short DEP3 description
+ Long DEP3 description
+ .
+ Continued
+--- /dev/null
++++ b/foo
+@@ -0,0 +1 @@
++foo
+''')
+ self._test_pq(repo, 'import', ['--force'])
+
+ author, subject = repo.get_head_author_subject()
+ eq_(subject, 'Short DEP3 description')
+ eq_(author, '"Mr. T. St" <t@example.com>')
+
+ @RepoFixtures.quilt30()
+ def test_import_poor_dep3_behaviour(self, repo):
+ """Demonstrate the issues with the current DEP3 support"""
+
+ pkg = 'hello-debhelper'
+ dsc = self._dsc_name(pkg, '2.6-2', 'dsc-3.0')
+ eq_(import_dsc(['arg0', dsc]), 0)
+
+ self._append_patch(repo, 'foo', '''\
+Author: Mr. T. St <t@example.com>
+Description: A very long description with wrapp-
+ ing to increase readability in the file, which
+ is currently split into a short and long description.
+Origin: https://twitter.com/MrT/status/941789967361097728
+Forwarded: not-needed
+--- /dev/null
++++ b/foo
+@@ -0,0 +1 @@
++foo
+''')
+ self._test_pq(repo, 'import', ['--force'])
+
+ _, subject = repo.get_head_author_subject()
+ eq_(subject, 'A very long description with wrapp-')
+
+ self._test_pq(repo, 'export')
+
+ relevant_parts_of_patch = ''
+ with open('debian/patches/foo.patch') as patch_file:
+ for line in patch_file:
+ # skip the date as it's currently set to now(),
+ # not a deterministic value
+ if line.startswith('Date: '):
+ continue
+
+ # stop reading after the main part of the description;
+ # i.e. ignore the bit that git(1) fully controls.
+ if line.startswith('---'):
+ break
+
+ relevant_parts_of_patch += line
+
+ eq_(relevant_parts_of_patch, '''\
+From: "Mr. T. St" <t@example.com>
+Subject: A very long description with wrapp-
+
+ ing to increase readability in the file, which
+ is currently split into a short and long description.
+Origin: https://twitter.com/MrT/status/941789967361097728
+Forwarded: not-needed
+''')
diff --git a/tests/component/deb/test_pristine_tar.py b/tests/component/deb/test_pristine_tar.py
new file mode 100644
index 0000000..68148c6
--- /dev/null
+++ b/tests/component/deb/test_pristine_tar.py
@@ -0,0 +1,71 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import ComponentTestBase
+from tests.component.deb.fixtures import RepoFixtures
+from tests.component.deb import DEB_TEST_DATA_DIR
+
+from gbp.scripts.pristine_tar import main as pristine_tar
+
+from nose.tools import ok_, eq_
+
+
+def _dsc_file(pkg, version, dir='dsc-3.0'):
+ return os.path.join(DEB_TEST_DATA_DIR, dir, '%s_%s.dsc' % (pkg, version))
+
+
+DEFAULT_DSC = _dsc_file('hello-debhelper', '2.6-2')
+
+
+class TestPristineTar(ComponentTestBase):
+ """Test pristine-tar commit tool"""
+ pkg = "hello-debhelper"
+ def_branches = ['master', 'upstream', 'pristine-tar']
+
+ def _orig(self, version, dir='dsc-3.0'):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ dir,
+ '%s_%s.orig.tar.gz' % (self.pkg, version))
+
+ @RepoFixtures.quilt30(DEFAULT_DSC, opts=['--no-pristine-tar'])
+ def test_run(self, repo):
+ """
+ Test that adding pristine-tar commit wotks
+ """
+ orig = self._orig('2.6')
+ ok_(pristine_tar(['arg0', 'commit', orig]) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'])
+
+ @RepoFixtures.quilt30(_dsc_file('hello-debhelper',
+ '2.8-1',
+ dir='dsc-3.0-additional-tarballs'),
+ opts=['--no-pristine-tar'])
+ def test_run_component_tarball(self, repo):
+ """
+ Test that adding pristine-tar commits with additional tarballs works
+ """
+ orig = self._orig('2.8', dir='dsc-3.0-additional-tarballs')
+ ok_(pristine_tar(['arg0', 'commit', '--component=foo', orig]) == 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream', 'pristine-tar'])
+
+ ptars = [('hello-debhelper_2.8.orig.tar.gz', 'pristine-tar'),
+ ('hello-debhelper_2.8.orig-foo.tar.gz', 'pristine-tar^')]
+ for f, w in ptars:
+ eq_(repo.get_subject(w), 'pristine-tar data for %s' % f)
diff --git a/tests/component/deb/test_pull.py b/tests/component/deb/test_pull.py
new file mode 100644
index 0000000..3b34ce4
--- /dev/null
+++ b/tests/component/deb/test_pull.py
@@ -0,0 +1,106 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import (ComponentTestBase,
+ ComponentTestGitRepository)
+from tests.component.deb.fixtures import RepoFixtures
+
+from nose.tools import eq_
+
+from gbp.scripts.clone import main as clone
+from gbp.scripts.pull import main as pull
+
+
+class TestPull(ComponentTestBase):
+ """Test pulling from a remote"""
+
+ @RepoFixtures.native()
+ def test_pull_explicit_remote(self, repo):
+ """Test that pulling of debian native packages works (explicit remote)"""
+ dest = os.path.join(self._tmpdir, 'cloned_repo')
+ clone(['arg0', repo.path, dest])
+ cloned = ComponentTestGitRepository(dest)
+ self._check_repo_state(cloned, 'master', ['master'])
+ eq_(pull(['argv0', 'origin']), 0)
+ assert len(repo.get_commits()) == 1
+
+ @RepoFixtures.native()
+ def test_pull_default_remote(self, repo):
+ """Test that pulling of debian native packages works (default remote)"""
+ dest = os.path.join(self._tmpdir, 'cloned_repo')
+ clone(['arg0', repo.path, dest])
+ cloned = ComponentTestGitRepository(dest)
+ self._check_repo_state(cloned, 'master', ['master'])
+ eq_(pull(['argv0']), 0)
+ assert len(repo.get_commits()) == 1
+
+ @RepoFixtures.quilt30()
+ def test_pull_all(self, repo):
+ """Test the '--all' commandline option"""
+ # Create new branch in repo
+ repo.create_branch('foob')
+
+ # Clone and create new commits in origin
+ dest = os.path.join(self._tmpdir, 'cloned_repo')
+ clone(['arg0', '--all', repo.path, dest])
+ cloned = ComponentTestGitRepository(dest)
+ tmp_workdir = os.path.join(self._tmpdir, 'tmp_workdir')
+ os.mkdir(tmp_workdir)
+ with open(os.path.join(tmp_workdir, 'new_file'), 'w'):
+ pass
+ repo.commit_dir(tmp_workdir, 'New commit in master', branch='master')
+ repo.commit_dir(tmp_workdir, 'New commit in foob', branch='foob')
+
+ # Check that the branch is not updated when --all is not used
+ eq_(pull(['argv0']), 0)
+ eq_(len(cloned.get_commits(until='master')), 3)
+ eq_(len(cloned.get_commits(until='upstream')), 1)
+ eq_(len(cloned.get_commits(until='foob')), 2)
+
+ # Check that --all updates all branches
+ repo.commit_dir(tmp_workdir, 'New commit in upstream', branch='upstream')
+ eq_(pull(['argv0', '--all']), 0)
+ eq_(len(cloned.get_commits(until='foob')), 3)
+ eq_(len(cloned.get_commits(until='upstream')), 2)
+
+ @RepoFixtures.native()
+ def test_tracking(self, repo):
+ """Test that --track-missing picks up missing branches"""
+ dest = os.path.join(self._tmpdir, 'cloned_repo')
+ clone(['arg0', repo.path, dest])
+ cloned = ComponentTestGitRepository(dest)
+ os.chdir(cloned.path)
+ self._check_repo_state(cloned, 'master', ['master'])
+ # Pull initially
+ eq_(pull(['argv0']), 0)
+ assert len(repo.get_commits()) == 1
+ self._check_repo_state(cloned, 'master', ['master'])
+
+ # Pick up missing branches (none exist yet)
+ eq_(pull(['argv0', '--track-missing']), 0)
+ assert len(repo.get_commits()) == 1
+ self._check_repo_state(cloned, 'master', ['master'])
+
+ # Pick up missing branches
+ repo.create_branch('pristine-tar')
+ repo.create_branch('upstream')
+ eq_(pull(['argv0', '--track-missing', '--pristine-tar']), 0)
+ assert len(repo.get_commits()) == 1
+ self._check_repo_state(cloned, 'master', ['master', 'pristine-tar', 'upstream'])
diff --git a/tests/component/deb/test_push.py b/tests/component/deb/test_push.py
new file mode 100644
index 0000000..0dd571c
--- /dev/null
+++ b/tests/component/deb/test_push.py
@@ -0,0 +1,152 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+import subprocess
+
+from tests.component import ComponentTestBase
+
+from tests.component.deb.fixtures import RepoFixtures
+from tests.testutils import skip_without_cmd
+
+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.quilt30(opts=['--pristine-tar'])
+ def test_push_detached_head(self, repo):
+ repo.checkout("HEAD^{commit}")
+ repo.add_remote_repo('origin', self.target.path)
+ self.assertEquals(push(['argv0', '--ignore-branch']), 0)
+ # Since branch head is detached we don't push it but upstream
+ # branch and tags must be there:
+ self._check_repo_state(self.target, None,
+ ['upstream'],
+ tags=['debian/2.8-1', 'upstream/2.8'])
+
+ @RepoFixtures.quilt30()
+ def test_push_skip_upstream(self, repo):
+ repo.add_remote_repo('origin', self.target.path)
+ self.assertEquals(push(['argv0', '--upstream-branch=']), 0)
+ self._check_repo_state(self.target, 'master',
+ ['master'],
+ 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'")
+
+ @skip_without_cmd('debchange')
+ @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)
+
+ @RepoFixtures.quilt30()
+ def test_push_failure(self, repo):
+ """
+ Check that in case of failure we push all other branches/tags
+ """
+ # Create a broken tag so pushing to it fails:
+ tag = os.path.join(self.target.path, 'refs', 'tags', 'debian', '2.8-1')
+ os.mkdir(os.path.dirname(tag))
+ with open(tag, 'w') as f:
+ f.write("broken_tag")
+
+ repo.add_remote_repo('origin', self.target.path)
+ self.assertEquals(push(['argv0']), 1)
+ self._check_repo_state(self.target, 'master',
+ ['master', 'upstream'],
+ tags=['upstream/2.8'])
+ self.assertEquals(repo.head, self.target.head)
+ self._check_in_log('.*Error running git push: .*refs/tags/debian/2.8-1')
+ self._check_log(-1, ".*Failed to push some refs")
diff --git a/tests/component/deb/test_tag.py b/tests/component/deb/test_tag.py
new file mode 100644
index 0000000..f56dabb
--- /dev/null
+++ b/tests/component/deb/test_tag.py
@@ -0,0 +1,77 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2015-2017 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, please see
+# <http://www.gnu.org/licenses/>
+
+import os
+
+from tests.component import ComponentTestBase
+from tests.component.deb import DEB_TEST_DATA_DIR
+from tests.component.deb.fixtures import RepoFixtures
+
+from nose.tools import ok_, eq_
+
+from gbp.scripts.tag import main as tag
+from gbp.scripts.pq import main as pq
+
+
+class TestTag(ComponentTestBase):
+ """Test tagging a debian package"""
+
+ @staticmethod
+ def _dsc_name(pkg, version, dir):
+ return os.path.join(DEB_TEST_DATA_DIR,
+ dir,
+ '%s_%s.dsc' % (pkg, version))
+
+ @RepoFixtures.native()
+ def test_tag(self, repo):
+ """Test that tagging a native debian package works"""
+ repo.delete_tag('debian/0.4.14') # make sure we can tag again
+ eq_(repo.has_tag('debian/0.4.14'), False)
+ ret = tag(['arg0',
+ '--posttag=printenv > posttag.out'])
+ ok_(ret == 0, "Tagging the package failed")
+ eq_(os.path.exists('posttag.out'), True)
+ self.check_hook_vars('posttag', [("GBP_TAG", "debian/0.4.14"),
+ ("GBP_BRANCH", "master"),
+ "GBP_SHA1"])
+ eq_(repo.head, repo.rev_parse('debian/0.4.14^{}'))
+
+ @RepoFixtures.quilt30()
+ def test_tag_pq_branch(self, repo):
+ ret = pq(['argv0', 'import'])
+ eq_(repo.rev_parse('master'), repo.rev_parse('debian/2.8-1^{}'))
+ eq_(ret, 0)
+ eq_(repo.branch, 'patch-queue/master')
+ self.add_file(repo, 'foo.txt')
+ ret = tag(['argv0', '--retag', '--ignore-branch'])
+ eq_(ret, 0)
+ eq_(repo.branch, 'patch-queue/master')
+ eq_(repo.rev_parse('patch-queue/master^{}^'), repo.rev_parse('debian/2.8-1^{}'))
+
+ @RepoFixtures.quilt30()
+ def test_tag_detached_head(self, repo):
+ """
+ Test that tagging works with an detached head (#863167)
+ """
+ eq_(repo.rev_parse('master^{}'), repo.rev_parse('debian/2.8-1^{}'))
+ self.add_file(repo, 'debian/foo.txt')
+ repo.checkout("HEAD~")
+ ret = tag(['argv0', '--retag', '--ignore-branch'])
+ eq_(ret, 0)
+ repo.checkout("master")
+ eq_(repo.rev_parse('master~^{}'), repo.rev_parse('debian/2.8-1^{}'))
diff --git a/tests/component/rpm/__init__.py b/tests/component/rpm/__init__.py
new file mode 100644
index 0000000..db776e3
--- /dev/null
+++ b/tests/component/rpm/__init__.py
@@ -0,0 +1,64 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Test module for RPM command line tools of the git-buildpackage suite"""
+
+from nose.tools import nottest
+import os
+import shutil
+from glob import glob
+
+from gbp.command_wrappers import Command
+
+from tests.component import ComponentTestBase, ComponentTestGitRepository
+
+
+RPM_TEST_DATA_SUBMODULE = os.path.join('tests', 'component', 'rpm', 'data')
+RPM_TEST_DATA_DIR = os.path.abspath(RPM_TEST_DATA_SUBMODULE)
+
+
+def setup():
+ """Test Module setup"""
+ ComponentTestGitRepository.check_testdata(RPM_TEST_DATA_SUBMODULE)
+
+
+class RpmRepoTestBase(ComponentTestBase):
+ """Baseclass for tests run in a Git repository with packaging data"""
+
+ @classmethod
+ def setUpClass(cls):
+ """Initializations only made once per test run"""
+ super(RpmRepoTestBase, cls).setUpClass()
+
+ # Initialize test data repositories
+ cmd = Command('./manage.py', cwd=RPM_TEST_DATA_DIR, capture_stderr=True)
+ cmd(['import-repo', '-o', cls._tmproot])
+
+ cls.orig_repos = {}
+ for path in glob(cls._tmproot + '/*.repo'):
+ prj = os.path.basename(path).rsplit('.', 1)[0]
+ cls.orig_repos[prj] = ComponentTestGitRepository(path)
+
+ @classmethod
+ @nottest
+ def init_test_repo(cls, pkg_name):
+ """Initialize git repository for testing"""
+ dirname = os.path.basename(cls.orig_repos[pkg_name].path)
+ shutil.copytree(cls.orig_repos[pkg_name].path, dirname)
+ os.chdir(dirname)
+ return ComponentTestGitRepository('.')
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/component/rpm/data/README b/tests/component/rpm/data/README
new file mode 100644
index 0000000..89c6d82
--- /dev/null
+++ b/tests/component/rpm/data/README
@@ -0,0 +1,2 @@
+Data for testing git-buildpackage-rpm. Used in nose unittests of the RPM
+variants of the git-buildpackage tools.
diff --git a/tests/component/rpm/data/gbp-test-1.0-1.other.src.rpm b/tests/component/rpm/data/gbp-test-1.0-1.other.src.rpm
new file mode 100644
index 0000000..4b725c7
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-1.0-1.other.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test-1.0-1.src.rpm b/tests/component/rpm/data/gbp-test-1.0-1.src.rpm
new file mode 100644
index 0000000..0378bcc
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-1.0-1.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test-1.1-1.src.rpm b/tests/component/rpm/data/gbp-test-1.1-1.src.rpm
new file mode 100644
index 0000000..f2a02cf
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-1.1-1.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test-1.1-2.src.rpm b/tests/component/rpm/data/gbp-test-1.1-2.src.rpm
new file mode 100644
index 0000000..1c27373
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-1.1-2.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test-native-1.0-1.src.rpm b/tests/component/rpm/data/gbp-test-native-1.0-1.src.rpm
new file mode 100644
index 0000000..188e1ba
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native-1.0-1.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test-native.data/Add-changelog-file.patch b/tests/component/rpm/data/gbp-test-native.data/Add-changelog-file.patch
new file mode 100644
index 0000000..b9f96aa
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native.data/Add-changelog-file.patch
@@ -0,0 +1,17 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 25 Feb 2014 10:57:25 +0200
+Subject: [PATCH] Add changelog file
+
+Add an "OBS-style" separate changelog file.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/packaging/gbp-test-native.changes b/packaging/gbp-test-native.changes
+new file mode 100644
+index 0000000..e9886a4
+--- /dev/null
++++ b/packaging/gbp-test-native.changes
+@@ -0,0 +1,3 @@
++* Tue Feb 25 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 1.0-1
++- Initial packaging
++
diff --git a/tests/component/rpm/data/gbp-test-native.data/Add-dummy-Makefile.patch b/tests/component/rpm/data/gbp-test-native.data/Add-dummy-Makefile.patch
new file mode 100644
index 0000000..9d7f2b1
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native.data/Add-dummy-Makefile.patch
@@ -0,0 +1,14 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 7 Nov 2012 09:13:25 +0200
+Subject: [PATCH] Add dummy Makefile
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/Makefile b/Makefile
+new file mode 100644
+index 0000000..ecbc49e
+--- /dev/null
++++ b/Makefile
+@@ -0,0 +1,2 @@
++all:
++ @echo "Ready!"
diff --git a/tests/component/rpm/data/gbp-test-native.data/Add-dummy-files.patch b/tests/component/rpm/data/gbp-test-native.data/Add-dummy-files.patch
new file mode 100644
index 0000000..2d2b3a8
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native.data/Add-dummy-files.patch
@@ -0,0 +1,22 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 7 Nov 2012 09:13:14 +0200
+Subject: [PATCH] Add dummy files
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/README b/README
+new file mode 100644
+index 0000000..a1311cb
+--- /dev/null
++++ b/README
+@@ -0,0 +1 @@
++Just for testing git-buildpackage.
+diff --git a/dummy.sh b/dummy.sh
+new file mode 100755
+index 0000000..8c33db6
+--- /dev/null
++++ b/dummy.sh
+@@ -0,0 +1,3 @@
++#!/bin/sh
++
++echo "Hello world"
diff --git a/tests/component/rpm/data/gbp-test-native.data/Add-gbp.conf.patch b/tests/component/rpm/data/gbp-test-native.data/Add-gbp.conf.patch
new file mode 100644
index 0000000..5ec6d61
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native.data/Add-gbp.conf.patch
@@ -0,0 +1,17 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 19:49:51 +0200
+Subject: [PATCH] Add gbp.conf
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+new file mode 100644
+index 0000000..13aa70c
+--- /dev/null
++++ b/.gbp.conf
+@@ -0,0 +1,5 @@
++[DEFAULT]
++vendor = Test-Distro
++packaging-branch = master
++packaging-tag = release/%(version)s
++packaging-tag-msg = %(vendor)s release %(version)s
diff --git a/tests/component/rpm/data/gbp-test-native.data/Add-packaging-files.patch b/tests/component/rpm/data/gbp-test-native.data/Add-packaging-files.patch
new file mode 100644
index 0000000..6a0596a
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native.data/Add-packaging-files.patch
@@ -0,0 +1,46 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 14:33:51 +0200
+Subject: [PATCH] Add packaging files
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/packaging/gbp-test-native.spec b/packaging/gbp-test-native.spec
+new file mode 100644
+index 0000000..38b07e4
+--- /dev/null
++++ b/packaging/gbp-test-native.spec
+@@ -0,0 +1,34 @@
++Name: gbp-test-native
++Summary: Test package for git-buildpackage
++Version: 1.0
++Release: 1
++Group: Development/Libraries
++License: GPLv2
++Source1: %{name}-%{version}.zip
++BuildRequires: unzip
++
++%description
++Package for testing the RPM functionality of git-buildpackage.
++Mimics a "native" package
++
++
++%prep
++unzip %{SOURCE1}
++%setup -T -D
++
++
++%build
++make
++
++
++%install
++rm -rf %{buildroot}
++mkdir -p %{buildroot}/%{_datadir}/%{name}
++cp -R * %{buildroot}/%{_datadir}/%{name}
++
++
++
++%files
++%defattr(-,root,root,-)
++%dir %{_datadir}/%{name}
++%{_datadir}/%{name}
diff --git a/tests/component/rpm/data/gbp-test-native.data/Repository-initialization.patch b/tests/component/rpm/data/gbp-test-native.data/Repository-initialization.patch
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native.data/Repository-initialization.patch
diff --git a/tests/component/rpm/data/gbp-test-native.data/manifest.json b/tests/component/rpm/data/gbp-test-native.data/manifest.json
new file mode 100644
index 0000000..994677b
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native.data/manifest.json
@@ -0,0 +1,81 @@
+{
+ "HEAD": "ref: refs/heads/master\n",
+ "commits": {
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "message": "Repository initialization\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "patchfile": "Repository-initialization.patch",
+ "sha1": "0579e0753b78476ad822c54b6ea07e2e80c0cdd2",
+ "tree": "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+ },
+ "2602690dc1870e491488bc59241d4735100aebd5": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352378031 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448556394 +0200",
+ "message": "Add packaging files\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "3d679b81b4c7847ac217515b2878d1e4680d50d7"
+ ],
+ "patchfile": "Add-packaging-files.patch",
+ "sha1": "2602690dc1870e491488bc59241d4735100aebd5",
+ "tree": "8bfe92bfb18c69580d94b50b5f1afa99f7391819"
+ },
+ "3d679b81b4c7847ac217515b2878d1e4680d50d7": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352396991 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448556393 +0200",
+ "message": "Add gbp.conf\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "cb5596d72659212d947f0cb882d758edb70a69b8"
+ ],
+ "patchfile": "Add-gbp.conf.patch",
+ "sha1": "3d679b81b4c7847ac217515b2878d1e4680d50d7",
+ "tree": "cc4847f78e3c9e0937668dd7473c428c7fe79a3d"
+ },
+ "3da491d0deb6f39fec98b3ca2e946126052e5134": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272394 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272394 +0200",
+ "message": "Add dummy files\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2"
+ ],
+ "patchfile": "Add-dummy-files.patch",
+ "sha1": "3da491d0deb6f39fec98b3ca2e946126052e5134",
+ "tree": "ad6c786edb64f4bf1be4c38db1bf4dde485fd5c6"
+ },
+ "66d8d14f6112eb144f4dbd877b07d64a33a14e33": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1393318645 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448556394 +0200",
+ "message": "Add changelog file\n\nAdd an \"OBS-style\" separate changelog file.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "2602690dc1870e491488bc59241d4735100aebd5"
+ ],
+ "patchfile": "Add-changelog-file.patch",
+ "sha1": "66d8d14f6112eb144f4dbd877b07d64a33a14e33",
+ "tree": "937e32d31f1b11231476693eae588d27404cee75"
+ },
+ "cb5596d72659212d947f0cb882d758edb70a69b8": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272405 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272405 +0200",
+ "message": "Add dummy Makefile\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "3da491d0deb6f39fec98b3ca2e946126052e5134"
+ ],
+ "patchfile": "Add-dummy-Makefile.patch",
+ "sha1": "cb5596d72659212d947f0cb882d758edb70a69b8",
+ "tree": "4d95f43faa3efb7b914f2c384853f0776649d567"
+ }
+ },
+ "refs": {
+ "refs/heads/master": "66d8d14f6112eb144f4dbd877b07d64a33a14e33",
+ "refs/tags/release/1.0-1": "03fa45b9f680c75a7195c60d2893797f4fc49f47"
+ },
+ "tags": {
+ "03fa45b9f680c75a7195c60d2893797f4fc49f47": {
+ "message": "Test-Distro release 1.0-1",
+ "object": "2602690dc1870e491488bc59241d4735100aebd5",
+ "tag": "release/1.0-1",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448556424 +0200",
+ "type": "commit"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/component/rpm/data/gbp-test-native2-2.0-0.src.rpm b/tests/component/rpm/data/gbp-test-native2-2.0-0.src.rpm
new file mode 100644
index 0000000..cda8b48
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native2-2.0-0.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test-native2.data/Add-gbp.conf.patch b/tests/component/rpm/data/gbp-test-native2.data/Add-gbp.conf.patch
new file mode 100644
index 0000000..5ec6d61
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native2.data/Add-gbp.conf.patch
@@ -0,0 +1,17 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 19:49:51 +0200
+Subject: [PATCH] Add gbp.conf
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+new file mode 100644
+index 0000000..13aa70c
+--- /dev/null
++++ b/.gbp.conf
+@@ -0,0 +1,5 @@
++[DEFAULT]
++vendor = Test-Distro
++packaging-branch = master
++packaging-tag = release/%(version)s
++packaging-tag-msg = %(vendor)s release %(version)s
diff --git a/tests/component/rpm/data/gbp-test-native2.data/Add-packaging-files.patch b/tests/component/rpm/data/gbp-test-native2.data/Add-packaging-files.patch
new file mode 100644
index 0000000..26d39ec
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native2.data/Add-packaging-files.patch
@@ -0,0 +1,56 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 14:33:51 +0200
+Subject: [PATCH] Add packaging files
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/foo.txt b/foo.txt
+new file mode 100644
+index 0000000..25ed442
+--- /dev/null
++++ b/foo.txt
+@@ -0,0 +1,3 @@
++FOO:
++
++file for testing rpm support of git-buildpackage.
+diff --git a/gbp-test-native2.spec b/gbp-test-native2.spec
+new file mode 100644
+index 0000000..34fd33d
+--- /dev/null
++++ b/gbp-test-native2.spec
+@@ -0,0 +1,35 @@
++Name: gbp-test-native2
++Summary: Test package for git-buildpackage
++Version: 2.0
++Release: 0
++Group: Development/Libraries
++License: GPLv2
++Source: foo.txt
++BuildRequires: unzip
++
++%description
++Package for testing the RPM functionality of git-buildpackage.
++Mimics a "native" package that doesn't have any source tarball.
++
++
++%prep
++# Just create build dir
++%setup -T -c
++cp %{SOURCE0} .
++
++
++%build
++# Nothing to do
++
++
++%install
++rm -rf %{buildroot}
++mkdir -p %{buildroot}/%{_datadir}/%{name}
++cp -R * %{buildroot}/%{_datadir}/%{name}
++
++
++
++%files
++%defattr(-,root,root,-)
++%dir %{_datadir}/%{name}
++%{_datadir}/%{name}
diff --git a/tests/component/rpm/data/gbp-test-native2.data/Repository-initialization.patch b/tests/component/rpm/data/gbp-test-native2.data/Repository-initialization.patch
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native2.data/Repository-initialization.patch
diff --git a/tests/component/rpm/data/gbp-test-native2.data/manifest.json b/tests/component/rpm/data/gbp-test-native2.data/manifest.json
new file mode 100644
index 0000000..526b195
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test-native2.data/manifest.json
@@ -0,0 +1,48 @@
+{
+ "HEAD": "ref: refs/heads/master\n",
+ "commits": {
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "message": "Repository initialization\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "patchfile": "Repository-initialization.patch",
+ "sha1": "0579e0753b78476ad822c54b6ea07e2e80c0cdd2",
+ "tree": "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+ },
+ "8a0c3b923beea5ccfb2c29db2f56a5f924fa02eb": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352378031 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448556624 +0200",
+ "message": "Add packaging files\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "8c635fc95942701989b9e777f57a16b36c7689a2"
+ ],
+ "patchfile": "Add-packaging-files.patch",
+ "sha1": "8a0c3b923beea5ccfb2c29db2f56a5f924fa02eb",
+ "tree": "c39480f420ac998a5654d2f636993c12f0d3625c"
+ },
+ "8c635fc95942701989b9e777f57a16b36c7689a2": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352396991 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448556624 +0200",
+ "message": "Add gbp.conf\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2"
+ ],
+ "patchfile": "Add-gbp.conf.patch",
+ "sha1": "8c635fc95942701989b9e777f57a16b36c7689a2",
+ "tree": "cc8ea141c352ebe5357ff49f53efb7bdf03fee22"
+ }
+ },
+ "refs": {
+ "refs/heads/master": "8a0c3b923beea5ccfb2c29db2f56a5f924fa02eb",
+ "refs/tags/release/2.0-0": "6b7594bd635ce9e841c9fa0bf8872ae811e4d3f9"
+ },
+ "tags": {
+ "6b7594bd635ce9e841c9fa0bf8872ae811e4d3f9": {
+ "message": "Test-Distro release 2.0-0",
+ "object": "8a0c3b923beea5ccfb2c29db2f56a5f924fa02eb",
+ "tag": "release/2.0-0",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448556639 +0200",
+ "type": "commit"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/component/rpm/data/gbp-test.data/Add-.gitignore.patch b/tests/component/rpm/data/gbp-test.data/Add-.gitignore.patch
new file mode 100644
index 0000000..f0259d2
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Add-.gitignore.patch
@@ -0,0 +1,16 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 2 Jul 2014 12:49:50 +0300
+Subject: [PATCH] Add .gitignore
+
+Ignore '*.tmp' and '*.swp'.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gitignore b/.gitignore
+new file mode 100644
+index 0000000..c9b0245
+--- /dev/null
++++ b/.gitignore
+@@ -0,0 +1,2 @@
++*.tmp
++*.swp
diff --git a/tests/component/rpm/data/gbp-test.data/Add-changelog.patch b/tests/component/rpm/data/gbp-test.data/Add-changelog.patch
new file mode 100644
index 0000000..59d342d
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Add-changelog.patch
@@ -0,0 +1,21 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 25 Feb 2014 11:02:26 +0200
+Subject: [PATCH] Add changelog
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/gbp-test.spec b/gbp-test.spec
+index 0a8fecc..9f0e198 100644
+--- a/gbp-test.spec
++++ b/gbp-test.spec
+@@ -44,3 +44,10 @@ install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
+ %defattr(-,root,root,-)
+ %dir %{_datadir}/%{name}
+ %{_datadir}/%{name}
++
++
++
++%changelog
++* Tue Feb 25 2014 Markus Lehtonen <markus.lehtonen@linux.intel.com> 1.1-2
++- Version 1.1
++
diff --git a/tests/component/rpm/data/gbp-test.data/Add-compressed-patches.patch b/tests/component/rpm/data/gbp-test.data/Add-compressed-patches.patch
new file mode 100644
index 0000000..67060dc
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Add-compressed-patches.patch
@@ -0,0 +1,64 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Mon, 20 May 2013 10:06:38 +0300
+Subject: [PATCH] Add compressed patches
+
+New release. Adds patches compressed with gzip and bzip2.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/gbp-test.spec b/gbp-test.spec
+index 0898d72..0a8fecc 100644
+--- a/gbp-test.spec
++++ b/gbp-test.spec
+@@ -1,7 +1,7 @@
+ Name: gbp-test
+ Summary: Test package for git-buildpackage
+ Version: 1.1
+-Release: 1
++Release: 2
+ Group: Development/Libraries
+ License: GPLv2
+ Source: %{name}-%{version}.tar.bz2
+@@ -9,6 +9,8 @@ Source1: foo.txt
+ Source20: bar.tar.gz
+ # Gbp-Ignore-Patches: 0
+ Patch0: my.patch
++Patch1: my-gz.patch.gz
++Patch2: my-bzip2.patch.bz2
+ Patch10: my2.patch
+ Patch20: my3.patch
+
+@@ -21,6 +23,8 @@ Package for testing the RPM functionality of git-buildpackage.
+ %setup -n %{name} -a 20
+
+ %patch0
++%patch1 -p1
++%patch2 -p1
+ %patch10 -p1
+
+
+diff --git a/my-bzip2.patch.bz2 b/my-bzip2.patch.bz2
+new file mode 100644
+index 0000000000000000000000000000000000000000..cd2bc996dbd27dd6ac3008ed346353d6d82f7cda
+GIT binary patch
+literal 137
+zcmV;40CxXET4*^jL0KkKSx_w20{{Ri-+%}ZKo9i*LIDVXH>Z!lAOLC_qtJ$$WZHny
+zp`+9*Q`Gd-^hSU(4LwZ@xPcw;C2q_TQKl_MQDP@s+zCLv$mS<vHG4HdhFie6#zNDJ
+r4=#g{e8OO0kw09kAtqF`e#A*fS@H`>#+0%01MznxQ-uiy0?lABiw!uO
+
+literal 0
+HcmV?d00001
+
+diff --git a/my-gz.patch.gz b/my-gz.patch.gz
+new file mode 100644
+index 0000000000000000000000000000000000000000..6bd6015f4cbcdd4c88b090e895289ef71d0af0fc
+GIT binary patch
+literal 166
+zcmV;X09pSZiwFo*%b8IC18sRNXL>GhVRU0?08Na`3c@fH1^4-ivyVv=tfEjP?p!E>
+zj}NHLjRw<O&{X^Rf)8+3!z{+f5TMjKX4J*<cvx+Bdz^kIn4SnR5!%iZtTEHF6p=j*
+zFdwv@H{&p|Goh41;ptIupOTPL{(YQ-G9v?N;hd10`gZN_SkonUOpL012c5pbrO5rD
+UBWFsw>Dq;O0eO1I8K?jN069iY*Z=?k
+
+literal 0
+HcmV?d00001
+
diff --git a/tests/component/rpm/data/gbp-test.data/Add-dummy-Makefile.patch b/tests/component/rpm/data/gbp-test.data/Add-dummy-Makefile.patch
new file mode 100644
index 0000000..9d7f2b1
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Add-dummy-Makefile.patch
@@ -0,0 +1,14 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 7 Nov 2012 09:13:25 +0200
+Subject: [PATCH] Add dummy Makefile
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/Makefile b/Makefile
+new file mode 100644
+index 0000000..ecbc49e
+--- /dev/null
++++ b/Makefile
+@@ -0,0 +1,2 @@
++all:
++ @echo "Ready!"
diff --git a/tests/component/rpm/data/gbp-test.data/Add-dummy-files.patch b/tests/component/rpm/data/gbp-test.data/Add-dummy-files.patch
new file mode 100644
index 0000000..2d2b3a8
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Add-dummy-files.patch
@@ -0,0 +1,22 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 7 Nov 2012 09:13:14 +0200
+Subject: [PATCH] Add dummy files
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/README b/README
+new file mode 100644
+index 0000000..a1311cb
+--- /dev/null
++++ b/README
+@@ -0,0 +1 @@
++Just for testing git-buildpackage.
+diff --git a/dummy.sh b/dummy.sh
+new file mode 100755
+index 0000000..8c33db6
+--- /dev/null
++++ b/dummy.sh
+@@ -0,0 +1,3 @@
++#!/bin/sh
++
++echo "Hello world"
diff --git a/tests/component/rpm/data/gbp-test.data/Add-gbp.conf.patch b/tests/component/rpm/data/gbp-test.data/Add-gbp.conf.patch
new file mode 100644
index 0000000..280371b
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Add-gbp.conf.patch
@@ -0,0 +1,19 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 19:20:17 +0200
+Subject: [PATCH] Add gbp.conf
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+new file mode 100644
+index 0000000..598b658
+--- /dev/null
++++ b/.gbp.conf
+@@ -0,0 +1,7 @@
++[DEFAULT]
++vendor = Test-Distro
++packaging-branch = master
++packaging-tag = release/%(version)s
++upstream-branch = upstream
++upstream-tag = upstream/%(upstreamversion)s
++packaging-tag-msg = %(vendor)s release %(version)s
diff --git a/tests/component/rpm/data/gbp-test.data/Add-initial-packaging-files.patch b/tests/component/rpm/data/gbp-test.data/Add-initial-packaging-files.patch
new file mode 100644
index 0000000..566fdc4
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Add-initial-packaging-files.patch
@@ -0,0 +1,117 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 19:12:21 +0200
+Subject: [PATCH] Add initial packaging files
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/bar.tar.gz b/bar.tar.gz
+new file mode 100644
+index 0000000000000000000000000000000000000000..f5dae8035202d6206a72803f35b756e8fbcb1d6c
+GIT binary patch
+literal 177
+zcmb2|=3oe$-XFlg{Pvt9Uz34I%foe+M|U?Y3Ej|XZQEqNf@_H&tG8#*a`B^EtqUIh
+zSJl^_d?txcZ^=iwt5d!t%uQXNpR?}%&7R7Xtw!E+*RF}oOukZJw%zN?lGS#<^mZvM
+z($l+B_5a5uZ71{N3o74k`|Uev%09g%IZa0vb^nX<%+SB|HTQOlhOg$-{%+^@uY+Ir
+arH6}VetLbE0U3OFKX1)<rOyl+3=9C`8d6&T
+
+literal 0
+HcmV?d00001
+
+diff --git a/foo.txt b/foo.txt
+new file mode 100644
+index 0000000..25ed442
+--- /dev/null
++++ b/foo.txt
+@@ -0,0 +1,3 @@
++FOO:
++
++file for testing rpm support of git-buildpackage.
+diff --git a/gbp-test.spec b/gbp-test.spec
+new file mode 100644
+index 0000000..c46a734
+--- /dev/null
++++ b/gbp-test.spec
+@@ -0,0 +1,42 @@
++Name: gbp-test
++Summary: Test package for git-buildpackage
++Version: 1.0
++Release: 1
++Group: Development/Libraries
++License: GPLv2
++Source: %{name}-%{version}.tar.bz2
++Source1: foo.txt
++Source20: bar.tar.gz
++# Gbp-Ignore-Patches: 0
++Patch0: my.patch
++Patch10: my2.patch
++Patch20: my3.patch
++
++
++%description
++Package for testing the RPM functionality of git-buildpackage.
++
++
++%prep
++%setup -n %{name} -a 20
++
++%patch0
++%patch10 -p1
++
++
++%build
++make
++
++
++%install
++rm -rf %{buildroot}
++mkdir -p %{buildroot}/%{_datadir}/%{name}
++cp -R * %{buildroot}/%{_datadir}/%{name}
++install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
++
++
++
++%files
++%defattr(-,root,root,-)
++%dir %{_datadir}/%{name}
++%{_datadir}/%{name}
+diff --git a/my.patch b/my.patch
+new file mode 100644
+index 0000000..50870df
+--- /dev/null
++++ b/my.patch
+@@ -0,0 +1,9 @@
++diff --git a/dummy.sh b/dummy.sh
++index 8c33db6..6f04268 100755
++--- dummy.sh
+++++ dummy.sh
++@@ -1,3 +1,3 @@
++ #!/bin/sh
++
++-echo "Hello world"
+++echo "Hello GBP"
+diff --git a/my2.patch b/my2.patch
+new file mode 100644
+index 0000000..ad5ca2d
+--- /dev/null
++++ b/my2.patch
+@@ -0,0 +1,7 @@
++diff --git a/mydir/myfile.txt b/mydir/myfile.txt
++new file mode 100644
++index 0000000..2cdad29
++--- /dev/null
+++++ b/mydir/myfile.txt
++@@ -0,0 +1 @@
+++Dummy
+diff --git a/my3.patch b/my3.patch
+new file mode 100644
+index 0000000..9fee859
+--- /dev/null
++++ b/my3.patch
+@@ -0,0 +1,7 @@
++diff --git a/README b/README
++index a1311cb..a59f1b9 100644
++--- a/README
+++++ b/README
++@@ -1 +1 @@
++-Just for testing git-buildpackage.
+++Just for testing GBP.
diff --git a/tests/component/rpm/data/gbp-test.data/Auto-import-file-s-from-branch-srcdata-gbp-test-master.patch b/tests/component/rpm/data/gbp-test.data/Auto-import-file-s-from-branch-srcdata-gbp-test-master.patch
new file mode 100644
index 0000000..c7134bf
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Auto-import-file-s-from-branch-srcdata-gbp-test-master.patch
@@ -0,0 +1,21 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 17 Jul 2014 11:27:27 +0300
+Subject: [PATCH] Auto-import file(s) from branch 'srcdata/gbp-test/master':
+ .gbp.conf
+
+Gbp: Ignore
+Gbp-Rpm: Ignore
+
+diff --git a/.gbp.conf b/.gbp.conf
+new file mode 100644
+index 0000000..598b658
+--- /dev/null
++++ b/.gbp.conf
+@@ -0,0 +1,7 @@
++[DEFAULT]
++vendor = Test-Distro
++packaging-branch = master
++packaging-tag = release/%(version)s
++upstream-branch = upstream
++upstream-tag = upstream/%(upstreamversion)s
++packaging-tag-msg = %(vendor)s release %(version)s
diff --git a/tests/component/rpm/data/gbp-test.data/Create-a-forked-version.patch b/tests/component/rpm/data/gbp-test.data/Create-a-forked-version.patch
new file mode 100644
index 0000000..6b8bc9f
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Create-a-forked-version.patch
@@ -0,0 +1,50 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 19:37:36 +0200
+Subject: [PATCH] Create a forked version.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+index 598b658..1f3671f 100644
+--- a/.gbp.conf
++++ b/.gbp.conf
+@@ -1,7 +1,7 @@
+ [DEFAULT]
+ vendor = Test-Distro
+-packaging-branch = master
+-packaging-tag = release/%(version)s
++packaging-branch = fork
++packaging-tag = fork/release/%(version)s
+ upstream-branch = upstream
+ upstream-tag = upstream/%(upstreamversion)s
+ packaging-tag-msg = %(vendor)s release %(version)s
+diff --git a/gbp-test.spec b/gbp-test.spec
+index c46a734..4759934 100644
+--- a/gbp-test.spec
++++ b/gbp-test.spec
+@@ -10,11 +10,11 @@ Source20: bar.tar.gz
+ # Gbp-Ignore-Patches: 0
+ Patch0: my.patch
+ Patch10: my2.patch
+-Patch20: my3.patch
+
+
+ %description
+ Package for testing the RPM functionality of git-buildpackage.
++Other version.
+
+
+ %prep
+diff --git a/my3.patch b/my3.patch
+deleted file mode 100644
+index 9fee859..0000000
+--- a/my3.patch
++++ /dev/null
+@@ -1,7 +0,0 @@
+-diff --git a/README b/README
+-index a1311cb..a59f1b9 100644
+---- a/README
+-+++ b/README
+-@@ -1 +1 @@
+--Just for testing git-buildpackage.
+-+Just for testing GBP.
diff --git a/tests/component/rpm/data/gbp-test.data/Initial-commit.patch b/tests/component/rpm/data/gbp-test.data/Initial-commit.patch
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Initial-commit.patch
diff --git a/tests/component/rpm/data/gbp-test.data/Modify-README.patch b/tests/component/rpm/data/gbp-test.data/Modify-README.patch
new file mode 100644
index 0000000..bde0d5e
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Modify-README.patch
@@ -0,0 +1,14 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 7 Nov 2012 09:15:41 +0200
+Subject: [PATCH] Modify README
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/README b/README
+index a1311cb..17b5e09 100644
+--- a/README
++++ b/README
+@@ -1 +1,3 @@
+ Just for testing git-buildpackage.
++
++Version 1.1
diff --git a/tests/component/rpm/data/gbp-test.data/Repository-initialization.patch b/tests/component/rpm/data/gbp-test.data/Repository-initialization.patch
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Repository-initialization.patch
diff --git a/tests/component/rpm/data/gbp-test.data/Update-gbp.conf-to-rename-the-generated-source-package.patch b/tests/component/rpm/data/gbp-test.data/Update-gbp.conf-to-rename-the-generated-source-package.patch
new file mode 100644
index 0000000..d238dae
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Update-gbp.conf-to-rename-the-generated-source-package.patch
@@ -0,0 +1,15 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 27 Aug 2013 16:57:44 +0300
+Subject: [PATCH] Update gbp.conf to rename the generated source package
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+index 1f3671f..d2302db 100644
+--- a/.gbp.conf
++++ b/.gbp.conf
+@@ -5,3 +5,4 @@ packaging-tag = fork/release/%(version)s
+ upstream-branch = upstream
+ upstream-tag = upstream/%(upstreamversion)s
+ packaging-tag-msg = %(vendor)s release %(version)s
++postbuild = for _r in $GBP_BUILD_DIR/SRPMS/*rpm; do _b=`basename $_r`; _n=`echo $_b | sed s"/src.rpm/other.src.rpm/"`; mv $_r `dirname $_r`/$_n; done
diff --git a/tests/component/rpm/data/gbp-test.data/Version-bump-to-1.1.patch b/tests/component/rpm/data/gbp-test.data/Version-bump-to-1.1.patch
new file mode 100644
index 0000000..4a81d7e
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/Version-bump-to-1.1.patch
@@ -0,0 +1,18 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 8 Nov 2012 19:37:36 +0200
+Subject: [PATCH] Version bump to 1.1
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/gbp-test.spec b/gbp-test.spec
+index c46a734..0898d72 100644
+--- a/gbp-test.spec
++++ b/gbp-test.spec
+@@ -1,6 +1,6 @@
+ Name: gbp-test
+ Summary: Test package for git-buildpackage
+-Version: 1.0
++Version: 1.1
+ Release: 1
+ Group: Development/Libraries
+ License: GPLv2
diff --git a/tests/component/rpm/data/gbp-test.data/manifest.json b/tests/component/rpm/data/gbp-test.data/manifest.json
new file mode 100644
index 0000000..08d2968
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/manifest.json
@@ -0,0 +1,262 @@
+{
+ "HEAD": "ref: refs/heads/master\n",
+ "commits": {
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "message": "Repository initialization\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "patchfile": "Repository-initialization.patch",
+ "sha1": "0579e0753b78476ad822c54b6ea07e2e80c0cdd2",
+ "tree": "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+ },
+ "0bbf100d2386abe5e13a3fdcaf4878285fe3d752": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1405585647 +0300",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448553313 +0200",
+ "message": "Auto-import file(s) from branch 'srcdata/gbp-test/master':\n .gbp.conf\n\nGbp: Ignore\nGbp-Rpm: Ignore",
+ "parents": [
+ "6450890aa002b0868537ee50cc1aea177fdcc941"
+ ],
+ "patchfile": "Auto-import-file-s-from-branch-srcdata-gbp-test-master.patch",
+ "sha1": "0bbf100d2386abe5e13a3fdcaf4878285fe3d752",
+ "tree": "3affc5c03684dd084deda7d117278924cfae691d"
+ },
+ "26073221b1b357f076e18446ff18904538757e40": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1405585647 +0300",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448553313 +0200",
+ "message": "my-gz",
+ "parents": [
+ "0bbf100d2386abe5e13a3fdcaf4878285fe3d752"
+ ],
+ "patchfile": "my-gz.patch",
+ "sha1": "26073221b1b357f076e18446ff18904538757e40",
+ "tree": "e1d995715363081b0816b3d9c2cbd15cf58d6ee9"
+ },
+ "2daf4a20c32d63966f55ffff05643388b58b7a4d": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1405585647 +0300",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448553314 +0200",
+ "message": "my2",
+ "parents": [
+ "4140ac36c9511620734b1692d175ff8bb7435b79"
+ ],
+ "patchfile": "my2.patch",
+ "sha1": "2daf4a20c32d63966f55ffff05643388b58b7a4d",
+ "tree": "cc443e8fdcc30fc4d818336d52a6031341111448"
+ },
+ "3da491d0deb6f39fec98b3ca2e946126052e5134": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272394 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272394 +0200",
+ "message": "Add dummy files\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2"
+ ],
+ "patchfile": "Add-dummy-files.patch",
+ "sha1": "3da491d0deb6f39fec98b3ca2e946126052e5134",
+ "tree": "ad6c786edb64f4bf1be4c38db1bf4dde485fd5c6"
+ },
+ "4140ac36c9511620734b1692d175ff8bb7435b79": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1405585647 +0300",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448553313 +0200",
+ "message": "my-bzip2",
+ "parents": [
+ "26073221b1b357f076e18446ff18904538757e40"
+ ],
+ "patchfile": "my-bzip2.patch",
+ "sha1": "4140ac36c9511620734b1692d175ff8bb7435b79",
+ "tree": "ea3ca259d5a73df217bbcc0b5e099faa3ba1dbbd"
+ },
+ "6450890aa002b0868537ee50cc1aea177fdcc941": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272541 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272541 +0200",
+ "message": "Modify README\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "cb5596d72659212d947f0cb882d758edb70a69b8"
+ ],
+ "patchfile": "Modify-README.patch",
+ "sha1": "6450890aa002b0868537ee50cc1aea177fdcc941",
+ "tree": "faacd47155bcaf94dbfbcb85d807512291b54dc5"
+ },
+ "760f5514aad64a31e26d08792984faaea59b740d": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352396256 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549248 +0200",
+ "message": "Create a forked version.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "f6c85dd3acf7811fd8a12aa304c3fd7a583bcdb1"
+ ],
+ "patchfile": "Create-a-forked-version.patch",
+ "sha1": "760f5514aad64a31e26d08792984faaea59b740d",
+ "tree": "05c80eaa14bc071479b9510a7eaadf9ab5ff9650"
+ },
+ "7ce872128f5d0f4970102bdd2fdfc85b77244c7a": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352394455 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352394455 +0200",
+ "message": "Initial commit\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "patchfile": "Initial-commit.patch",
+ "sha1": "7ce872128f5d0f4970102bdd2fdfc85b77244c7a",
+ "tree": "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+ },
+ "808906d7e5d1fa4ad6f79c863b56d12593e4e6ce": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1393318946 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549157 +0200",
+ "message": "Add changelog\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "b3cd99dae46fcec1ad7e4b2adf41b1ec6cfa590f"
+ ],
+ "patchfile": "Add-changelog.patch",
+ "sha1": "808906d7e5d1fa4ad6f79c863b56d12593e4e6ce",
+ "tree": "2aa6fb238d5fa3f5a73078ac77e8fa8bbdefc9a3"
+ },
+ "834a39e0889dfe9779336730bc68b7e8ef2cf793": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352395217 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549153 +0200",
+ "message": "Add gbp.conf\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "7ce872128f5d0f4970102bdd2fdfc85b77244c7a"
+ ],
+ "patchfile": "Add-gbp.conf.patch",
+ "sha1": "834a39e0889dfe9779336730bc68b7e8ef2cf793",
+ "tree": "dac3c42871a1dc32b65adac4eef1722e0a6831fb"
+ },
+ "925e9580ae2a76331dff7c09a27080c695d88cc3": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1404294590 +0300",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549157 +0200",
+ "message": "Add .gitignore\n\nIgnore '*.tmp' and '*.swp'.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "808906d7e5d1fa4ad6f79c863b56d12593e4e6ce"
+ ],
+ "patchfile": "Add-.gitignore.patch",
+ "sha1": "925e9580ae2a76331dff7c09a27080c695d88cc3",
+ "tree": "19149d2bb381d1d4e7d52d9e3c96df18d69b3eab"
+ },
+ "b03064b98c05d4778de76df5afa30bbf7b6c5631": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1449595707 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1449595707 +0200",
+ "message": "pristine-tar data for gbp-test-1.1.tar.bz2",
+ "parents": [
+ "bf7525b3afe67f7416246c7fa7a9c6c6dc6f2887"
+ ],
+ "patchfile": "pristine-tar-data-for-gbp-test-1.1.tar.bz2.patch",
+ "sha1": "b03064b98c05d4778de76df5afa30bbf7b6c5631",
+ "tree": "6f140200031de57c51de29fd2e57043c26d3174e"
+ },
+ "b3cd99dae46fcec1ad7e4b2adf41b1ec6cfa590f": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1369033598 +0300",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549157 +0200",
+ "message": "Add compressed patches\n\nNew release. Adds patches compressed with gzip and bzip2.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "f234683df1682cc81a631856b2bf284340234975"
+ ],
+ "patchfile": "Add-compressed-patches.patch",
+ "sha1": "b3cd99dae46fcec1ad7e4b2adf41b1ec6cfa590f",
+ "tree": "fc16065ed8b66d42d060670d611ecf6bad1746b9"
+ },
+ "bf7525b3afe67f7416246c7fa7a9c6c6dc6f2887": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1449595698 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1449595698 +0200",
+ "message": "pristine-tar data for gbp-test-1.0.tar.bz2",
+ "patchfile": "pristine-tar-data-for-gbp-test-1.0.tar.bz2.patch",
+ "sha1": "bf7525b3afe67f7416246c7fa7a9c6c6dc6f2887",
+ "tree": "810ea4b61a223e8ed1aca6e980fa51db7af65582"
+ },
+ "cb5596d72659212d947f0cb882d758edb70a69b8": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272405 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272405 +0200",
+ "message": "Add dummy Makefile\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "3da491d0deb6f39fec98b3ca2e946126052e5134"
+ ],
+ "patchfile": "Add-dummy-Makefile.patch",
+ "sha1": "cb5596d72659212d947f0cb882d758edb70a69b8",
+ "tree": "4d95f43faa3efb7b914f2c384853f0776649d567"
+ },
+ "cbf747086e12cc390cdae71ce297b0e1a55a420a": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1377611864 +0300",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549264 +0200",
+ "message": "Update gbp.conf to rename the generated source package\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "760f5514aad64a31e26d08792984faaea59b740d"
+ ],
+ "patchfile": "Update-gbp.conf-to-rename-the-generated-source-package.patch",
+ "sha1": "cbf747086e12cc390cdae71ce297b0e1a55a420a",
+ "tree": "22658ce6155376fc6348769bc1756725acca705b"
+ },
+ "f234683df1682cc81a631856b2bf284340234975": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352396256 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549157 +0200",
+ "message": "Version bump to 1.1\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "f6c85dd3acf7811fd8a12aa304c3fd7a583bcdb1"
+ ],
+ "patchfile": "Version-bump-to-1.1.patch",
+ "sha1": "f234683df1682cc81a631856b2bf284340234975",
+ "tree": "78054d45c9bb174e41b27a7eaa3c1b096a60fe5c"
+ },
+ "f6c85dd3acf7811fd8a12aa304c3fd7a583bcdb1": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352394741 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549157 +0200",
+ "message": "Add initial packaging files\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "834a39e0889dfe9779336730bc68b7e8ef2cf793"
+ ],
+ "patchfile": "Add-initial-packaging-files.patch",
+ "sha1": "f6c85dd3acf7811fd8a12aa304c3fd7a583bcdb1",
+ "tree": "4b34035c4e1b14c570946fab454901499eb8b717"
+ }
+ },
+ "refs": {
+ "refs/heads/fork": "cbf747086e12cc390cdae71ce297b0e1a55a420a",
+ "refs/heads/master": "925e9580ae2a76331dff7c09a27080c695d88cc3",
+ "refs/heads/pq/master": "2daf4a20c32d63966f55ffff05643388b58b7a4d",
+ "refs/heads/pristine-tar": "b03064b98c05d4778de76df5afa30bbf7b6c5631",
+ "refs/heads/upstream": "6450890aa002b0868537ee50cc1aea177fdcc941",
+ "refs/tags/fork/release/1.0-1": "e7d565fd0795315ef64c28aedabcfc4c0d7c579a",
+ "refs/tags/release/1.0-1": "b96b77fa4c4016f788aa5a36fa53241305ba86b2",
+ "refs/tags/release/1.1-1": "a64150c6e2feb9efa5c9cf150107a3b06a1657e7",
+ "refs/tags/release/1.1-2": "2d24c99cca2a563ca0e498ffc972d54049345f66",
+ "refs/tags/upstream/1.0": "3788dc189de65222f9740c48af30e2d4c8175726",
+ "refs/tags/upstream/1.1": "d55cd55506641d7dd6f968bba37451a599f26017"
+ },
+ "tags": {
+ "2d24c99cca2a563ca0e498ffc972d54049345f66": {
+ "message": "Test-Distro release 1.1-2",
+ "object": "b3cd99dae46fcec1ad7e4b2adf41b1ec6cfa590f",
+ "tag": "release/1.1-2",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549368 +0200",
+ "type": "commit"
+ },
+ "3788dc189de65222f9740c48af30e2d4c8175726": {
+ "message": "Version 1.0",
+ "object": "cb5596d72659212d947f0cb882d758edb70a69b8",
+ "tag": "upstream/1.0",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1377529152 +0300",
+ "type": "commit"
+ },
+ "a64150c6e2feb9efa5c9cf150107a3b06a1657e7": {
+ "message": "Test-Distro release 1.1-1",
+ "object": "f234683df1682cc81a631856b2bf284340234975",
+ "tag": "release/1.1-1",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549366 +0200",
+ "type": "commit"
+ },
+ "b96b77fa4c4016f788aa5a36fa53241305ba86b2": {
+ "message": "Test-Distro release 1.0-1",
+ "object": "f6c85dd3acf7811fd8a12aa304c3fd7a583bcdb1",
+ "tag": "release/1.0-1",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549318 +0200",
+ "type": "commit"
+ },
+ "d55cd55506641d7dd6f968bba37451a599f26017": {
+ "message": "Version 1.1",
+ "object": "6450890aa002b0868537ee50cc1aea177fdcc941",
+ "tag": "upstream/1.1",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1377529755 +0300",
+ "type": "commit"
+ },
+ "e7d565fd0795315ef64c28aedabcfc4c0d7c579a": {
+ "message": "Test-Distro release 1.0-1",
+ "object": "cbf747086e12cc390cdae71ce297b0e1a55a420a",
+ "tag": "fork/release/1.0-1",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448549401 +0200",
+ "type": "commit"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/component/rpm/data/gbp-test.data/my-bzip2.patch b/tests/component/rpm/data/gbp-test.data/my-bzip2.patch
new file mode 100644
index 0000000..b5588c7
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/my-bzip2.patch
@@ -0,0 +1,12 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 17 Jul 2014 11:27:27 +0300
+Subject: [PATCH] my-bzip2
+
+
+diff --git a/NEWS b/NEWS
+new file mode 100644
+index 0000000..e2620b0
+--- /dev/null
++++ b/NEWS
+@@ -0,0 +1 @@
++No news.
diff --git a/tests/component/rpm/data/gbp-test.data/my-gz.patch b/tests/component/rpm/data/gbp-test.data/my-gz.patch
new file mode 100644
index 0000000..a86beab
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/my-gz.patch
@@ -0,0 +1,12 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 17 Jul 2014 11:27:27 +0300
+Subject: [PATCH] my-gz
+
+
+diff --git a/AUTHORS b/AUTHORS
+new file mode 100644
+index 0000000..9c3f518
+--- /dev/null
++++ b/AUTHORS
+@@ -0,0 +1 @@
++Markus Lehtonen <markus.lehtonen@linux.intel.com>
diff --git a/tests/component/rpm/data/gbp-test.data/my2.patch b/tests/component/rpm/data/gbp-test.data/my2.patch
new file mode 100644
index 0000000..b0fedb8
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/my2.patch
@@ -0,0 +1,12 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 17 Jul 2014 11:27:27 +0300
+Subject: [PATCH] my2
+
+
+diff --git a/mydir/myfile.txt b/mydir/myfile.txt
+new file mode 100644
+index 0000000..2cdad29
+--- /dev/null
++++ b/mydir/myfile.txt
+@@ -0,0 +1 @@
++Dummy
diff --git a/tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.0.tar.bz2.patch b/tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.0.tar.bz2.patch
new file mode 100644
index 0000000..5c9826a
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.0.tar.bz2.patch
@@ -0,0 +1,38 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 8 Dec 2015 19:28:18 +0200
+Subject: [PATCH] pristine-tar data for gbp-test-1.0.tar.bz2
+
+
+diff --git a/gbp-test-1.0.tar.bz2.delta b/gbp-test-1.0.tar.bz2.delta
+new file mode 100644
+index 0000000000000000000000000000000000000000..e5368ef01667d60f2823c6a12bfbd493e987dfab
+GIT binary patch
+literal 850
+zcmV-Y1FifYiwFQR6K7Tc1MQZ1NEA^V$A6=Sp@!Q@bh%W9N#&b2GjG<w$Z8`JyVP2O
+zYG-%G-OXKhn;km|7b5y2DhP_OgAPlmsGx)HKO&RpkEo<9=?{;hqLhdP$)j=2tc{u=
+z*2vz6Io|c-`<wTBzc<e(6?+PmpaxL{=@6dpdb6T{zI}Hi5@0dmSi-VA!GKsS2n5V1
+znojjl64WHsYzCjCt|{?s{OLHO{(lsgBQRCd<DZf6wUjFrDTN-d(%Z7cnms4i+FO@P
+ze7+6Sg6@RB?LFmBkrl5dMOnij|5=W(k@=4}k>-D2kZPN=93eh6TuFw+$bMCEs*0p3
+z`Xo6duh*}7TwjU-Y5gjf*RK~>xfMxP)Nq(}cs-biCp;1m7W5#qyuc!Gg(q6Cs(!!L
+zb$^L|&nghG+3ENBlwvK*=@5iWB8!*^nV7|LCNCBur^8~wvM5*-*&!l{WI8Mfq9{a2
+zVS-o?45WxYHqu4R5kN+EJQr!l@q)3P?j}cmR1~qHB|s<^V1R8%%FMUilw~hR#2;@%
+z*()A}7BMkNX#q)dy3MkrNt4GtJL#^QKBT_vL2PsNpm9oL48tTx#IAk+tQ|XF9<o(5
+zw`Ls4sd*Dx1TY_9Fk{LCh-ZpM0gPl!`HaZ~ki?kA0K|o#$>GL)0KU8M&6UQ08z7b`
+z8VfM|7q(0&&I7<Ot4IG@g9#0M9rt?!O|Rm@$_8FDp^+hd8ks}iEp$o`YZW!<@fSx`
+z!|?ovaOD2aV@&t|z5vrR+<(nFRSE<YHL4m$`OhKY$NcAL{`Unb+Xn(3>RJl1@1A5Q
+z+(M?Ub*Hne8HeQ)Y-K}!+C!@^_mq9%*`tYz%P-H{-qK0V&$AbPm{fju_R5Qs(l@<0
+zc&*^><CZHk%I8*B)LRcMb6N{3Cz2-Hx~fa}X7ApYp1jTZwDh{DbsXEfzp{FQeOATz
+z3(AdM&zzRdCZTh|*w&OSXQm|D?_3S$-EMlnxg#|%r=#lJyFG39ZA0HY{}^w&SQY4K
+zul<zKSURlsrnjco^Uj8t9Cw)=(*EA$^#imG0VUcR#{YjlBL7(tBbxtxf&TII1VEus
+cC=?2XLZMJ76bgkxq4ZNe19KVy001Ze0MvG#PXGV_
+
+literal 0
+HcmV?d00001
+
+diff --git a/gbp-test-1.0.tar.bz2.id b/gbp-test-1.0.tar.bz2.id
+new file mode 100644
+index 0000000..0221a07
+--- /dev/null
++++ b/gbp-test-1.0.tar.bz2.id
+@@ -0,0 +1 @@
++4d95f43faa3efb7b914f2c384853f0776649d567
diff --git a/tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.1.tar.bz2.patch b/tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.1.tar.bz2.patch
new file mode 100644
index 0000000..5792c55
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test.data/pristine-tar-data-for-gbp-test-1.1.tar.bz2.patch
@@ -0,0 +1,38 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 8 Dec 2015 19:28:27 +0200
+Subject: [PATCH] pristine-tar data for gbp-test-1.1.tar.bz2
+
+
+diff --git a/gbp-test-1.1.tar.bz2.delta b/gbp-test-1.1.tar.bz2.delta
+new file mode 100644
+index 0000000000000000000000000000000000000000..3e4f0bf6ec4c81895ae8dbdcd0dbe8684b0778be
+GIT binary patch
+literal 851
+zcmV-Z1FZZXiwFQa6K7Tc1MQbvY)nBI$G_<%SY=m4TvlT}phVl5b6ue>>5{k<acSJ5
+zneLvww7cDP_SEe`PXwVV^&*i72@z2bBqHbw4@81g%Y#a#RZ&S1ks_`eoNil}Z8#Fy
+zHke<|WoFKN`G04=`OeG<(XxP7iE5BUuz@59op&OO`1^YovIK;acp?!Z$UI=ei4q0}
+zPNq{mlt(qHIvfz9>W1>b)}Qt>d;XW=D~@DqM*MRvUrPnFRPhFa%D0wfbLY4U=6<Vt
+z%0r<oIZ?l*zrAPtDIO)L(Ijh_<Uiphl9>NkAT0m;f(ff!YcLij#4A=9ocLT-+^Rw~
+z#TcYw8VrWjfUmn4p__|*!LU&*@+;J%sPQlx2+$cnNx&k<i$;)%AQB#Y@qsp~YB;QQ
+zp54*!nFS)}aEC)7rA*6{1Q9!@VM;OPim_92ioC2SB6hn8RVa~VugC44CJ>7(iwRPk
+zgar^yq$E!4<sxTHIBaHTEJ_J>ykNAM*%@x~Qg20BG_?fKQvuRfO<Y^>IkWv`$;jGy
+zk5Ay)hxDZ=B_k_BHMig4p&Ffhpz_6_%+(JZzQq&m_9DxWwiJY{39-w)<vdBGDl4DS
+z3bh~W{rXaX1pvd4Z8^XIR5}J=6tb;AHXA?&vW*2ui(jqr#uWfUSK+%O3xOXX6_sWJ
+zjQE8u($fL}2$k4>t%3A`Ptty`qpdwHu598p(nsmWFrp(rOms#M8x=Jg2$v;Q!*u_L
+zxy0|kk&o>A-xqLf5BFcQNu`m9q9#?tEdP1j>;F&SB$oeu!MMExVft`iA*4QUoHKMg
+zw(Wd)Ixl@>!@2zI4(EQ`m6V$Fg0|Pi8Wnb3%yPZITsO7J<9&6arapiDg(*|=B6nL(
+zT|fV!cINRLyBF@`uX$_k%yTU}(0<X~e7GscI{(<y8Dp2M{M^xU?|t=}7lqMkee~g*
+z4TW{%OR76!_ZwqtH?&n)&fcD?r$5WNRMq_GW9-nP9kC@x+jqut4_fQCy+f|&<VeNa
+zH=pb;XYDz5W`_N%=dBg%(A7zcD<P{NhwraP+Y(Wdtzo|Y3kms8WR7F`-xnA!&vpO|
+d27|$1Fc=I5gTY`h7!1Zg$`@fSxsL!S000iQobdnv
+
+literal 0
+HcmV?d00001
+
+diff --git a/gbp-test-1.1.tar.bz2.id b/gbp-test-1.1.tar.bz2.id
+new file mode 100644
+index 0000000..ea1b4e4
+--- /dev/null
++++ b/gbp-test-1.1.tar.bz2.id
+@@ -0,0 +1 @@
++faacd47155bcaf94dbfbcb85d807512291b54dc5
diff --git a/tests/component/rpm/data/gbp-test2-2.0-0.src.rpm b/tests/component/rpm/data/gbp-test2-2.0-0.src.rpm
new file mode 100644
index 0000000..776f639
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2-2.0-0.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test2-2.0-1.src.rpm b/tests/component/rpm/data/gbp-test2-2.0-1.src.rpm
new file mode 100644
index 0000000..ab3f076
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2-2.0-1.src.rpm
Binary files differ
diff --git a/tests/component/rpm/data/gbp-test2.data/Add-dummy-Makefile.patch b/tests/component/rpm/data/gbp-test2.data/Add-dummy-Makefile.patch
new file mode 100644
index 0000000..19f3da4
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/Add-dummy-Makefile.patch
@@ -0,0 +1,14 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 7 Nov 2012 09:13:25 +0200
+Subject: [PATCH] Add dummy Makefile
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/Makefile b/Makefile
+new file mode 100644
+index 0000000..de5341d
+--- /dev/null
++++ b/Makefile
+@@ -0,0 +1,2 @@
++all:
++ @echo "Ready (2.0)!"
diff --git a/tests/component/rpm/data/gbp-test2.data/Add-dummy-files.patch b/tests/component/rpm/data/gbp-test2.data/Add-dummy-files.patch
new file mode 100644
index 0000000..fdebc88
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/Add-dummy-files.patch
@@ -0,0 +1,23 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 7 Nov 2012 09:13:14 +0200
+Subject: [PATCH] Add dummy files
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/README b/README
+new file mode 100644
+index 0000000..1947ce8
+--- /dev/null
++++ b/README
+@@ -0,0 +1,2 @@
++Just for testing git-buildpackage.
++V2.0
+diff --git a/dummy.sh b/dummy.sh
+new file mode 100755
+index 0000000..7eeb298
+--- /dev/null
++++ b/dummy.sh
+@@ -0,0 +1,3 @@
++#!/bin/sh
++
++echo "Hello world 2.0"
diff --git a/tests/component/rpm/data/gbp-test2.data/Add-gbp.conf.patch b/tests/component/rpm/data/gbp-test2.data/Add-gbp.conf.patch
new file mode 100644
index 0000000..c6c4923
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/Add-gbp.conf.patch
@@ -0,0 +1,22 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 13 Nov 2012 13:37:10 +0200
+Subject: [PATCH] Add gbp.conf
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+new file mode 100644
+index 0000000..3d5f92d
+--- /dev/null
++++ b/.gbp.conf
+@@ -0,0 +1,10 @@
++[DEFAULT]
++vendor = Test-Distro
++packaging-branch = master
++packaging-tag = release/%(version)s
++upstream-branch = upstream
++upstream-tag = upstream/%(upstreamversion)s
++packaging-tag-msg = %(vendor)s release %(version)s
++packaging-dir = packaging
++patch-ignore-path = ^(packaging/.*|\.gbp.conf)
++
diff --git a/tests/component/rpm/data/gbp-test2.data/Add-packaging-files.patch b/tests/component/rpm/data/gbp-test2.data/Add-packaging-files.patch
new file mode 100644
index 0000000..5d13ffe
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/Add-packaging-files.patch
@@ -0,0 +1,123 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 13 Nov 2012 13:35:52 +0200
+Subject: [PATCH] Add packaging files
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/packaging/bar.tar.gz b/packaging/bar.tar.gz
+new file mode 100644
+index 0000000000000000000000000000000000000000..f5dae8035202d6206a72803f35b756e8fbcb1d6c
+GIT binary patch
+literal 177
+zcmb2|=3oe$-XFlg{Pvt9Uz34I%foe+M|U?Y3Ej|XZQEqNf@_H&tG8#*a`B^EtqUIh
+zSJl^_d?txcZ^=iwt5d!t%uQXNpR?}%&7R7Xtw!E+*RF}oOukZJw%zN?lGS#<^mZvM
+z($l+B_5a5uZ71{N3o74k`|Uev%09g%IZa0vb^nX<%+SB|HTQOlhOg$-{%+^@uY+Ir
+arH6}VetLbE0U3OFKX1)<rOyl+3=9C`8d6&T
+
+literal 0
+HcmV?d00001
+
+diff --git a/packaging/foo.txt b/packaging/foo.txt
+new file mode 100644
+index 0000000..25ed442
+--- /dev/null
++++ b/packaging/foo.txt
+@@ -0,0 +1,3 @@
++FOO:
++
++file for testing rpm support of git-buildpackage.
+diff --git a/packaging/gbp-test2.spec b/packaging/gbp-test2.spec
+new file mode 100644
+index 0000000..61aae33
+--- /dev/null
++++ b/packaging/gbp-test2.spec
+@@ -0,0 +1,48 @@
++Name: gbp-test2
++Summary: Test package 2 for git-buildpackage
++Epoch: 1
++Version: 2.0
++Release: 0
++Group: Development/Libraries
++License: GPLv2
++Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
++Source: foo.txt
++Source20: bar.tar.gz
++# Gbp-Ignore-Patches: 0
++Patch: my.patch
++Patch10: my2.patch
++Patch20: my3.patch
++Packager: Markus Lehtonen <markus.lehtonen@linux.intel.com>
++
++%description
++Package for testing the RPM functionality of git-buildpackage.
++Version 2 which has packaging and development in the same
++git branch.
++
++
++%prep
++%setup -T -n %{name}-%{version} -c -a 10
++
++%patch
++%patch -P 10 -p1
++
++echo "Do things"
++
++# Gbp-Patch-Macros
++
++%build
++make
++
++
++%install
++rm -rf %{buildroot}
++mkdir -p %{buildroot}/%{_datadir}/%{name}
++cp -R * %{buildroot}/%{_datadir}/%{name}
++install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
++
++
++
++%files
++%defattr(-,root,root,-)
++%dir %{_datadir}/%{name}
++%{_datadir}/%{name}
+diff --git a/packaging/my.patch b/packaging/my.patch
+new file mode 100644
+index 0000000..aa4eb5c
+--- /dev/null
++++ b/packaging/my.patch
+@@ -0,0 +1,9 @@
++diff --git a/dummy.sh b/dummy.sh
++index 8c33db6..6f04268 100755
++--- dummy.sh
+++++ dummy.sh
++@@ -1,3 +1,3 @@
++ #!/bin/sh
++
++-echo "Hello world 2.0"
+++echo "Hello GBP 2.0"
+diff --git a/packaging/my2.patch b/packaging/my2.patch
+new file mode 100644
+index 0000000..ad5ca2d
+--- /dev/null
++++ b/packaging/my2.patch
+@@ -0,0 +1,7 @@
++diff --git a/mydir/myfile.txt b/mydir/myfile.txt
++new file mode 100644
++index 0000000..2cdad29
++--- /dev/null
+++++ b/mydir/myfile.txt
++@@ -0,0 +1 @@
+++Dummy
+diff --git a/packaging/my3.patch b/packaging/my3.patch
+new file mode 100644
+index 0000000..9fee859
+--- /dev/null
++++ b/packaging/my3.patch
+@@ -0,0 +1,7 @@
++diff --git a/README b/README
++index a1311cb..a59f1b9 100644
++--- a/README
+++++ b/README
++@@ -1 +1 @@
++-Just for testing git-buildpackage.
+++Just for testing GBP.
diff --git a/tests/component/rpm/data/gbp-test2.data/My-addition.patch b/tests/component/rpm/data/gbp-test2.data/My-addition.patch
new file mode 100644
index 0000000..7e52b9f
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/My-addition.patch
@@ -0,0 +1,15 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 13 Nov 2012 13:55:46 +0200
+Subject: [PATCH] My addition
+
+Import my2.patch to the git tree.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/mydir/myfile.txt b/mydir/myfile.txt
+new file mode 100644
+index 0000000..2cdad29
+--- /dev/null
++++ b/mydir/myfile.txt
+@@ -0,0 +1 @@
++Dummy
diff --git a/tests/component/rpm/data/gbp-test2.data/Remove-imported-patches.patch b/tests/component/rpm/data/gbp-test2.data/Remove-imported-patches.patch
new file mode 100644
index 0000000..020a8a1
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/Remove-imported-patches.patch
@@ -0,0 +1,85 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 13 Nov 2012 13:57:35 +0200
+Subject: [PATCH] Remove imported patches
+
+And enable automatic patch-generation.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+index 6c8e847..52a1267 100644
+--- a/.gbp.conf
++++ b/.gbp.conf
+@@ -6,6 +6,7 @@ upstream-branch = upstream
+ upstream-tag = upstream/%(upstreamversion)s
+ packaging-tag-msg = %(vendor)s release %(version)s
+ packaging-dir = packaging
++patch-export = True
+ patch-ignore-path = ^(packaging/.*|\.gbp.conf)
+ spec-file = packaging/gbp-test2.spec
+
+diff --git a/packaging/gbp-test2.spec b/packaging/gbp-test2.spec
+index 5b41aae..409a102 100644
+--- a/packaging/gbp-test2.spec
++++ b/packaging/gbp-test2.spec
+@@ -2,7 +2,7 @@ Name: gbp-test2
+ Summary: Test package 2 for git-buildpackage
+ Epoch: 1
+ Version: 2.0
+-Release: 0
++Release: 1
+ Group: Development/Libraries
+ License: GPLv2
+ Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
+@@ -11,8 +11,6 @@ Source20: bar.tar.gz
+ Source9999: gbp-test2-alt.spec
+ # Gbp-Ignore-Patches: -1
+ Patch: my.patch
+-Patch10: http://example.com/patches/my2.patch
+-Patch20: my3.patch
+ Packager: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+ %description
+@@ -25,7 +23,6 @@ git branch.
+ %setup -T -n %{name}-%{version} -c -a 10
+
+ %patch
+-%patch -P 10 -p1
+
+ echo "Do things"
+
+diff --git a/packaging/my2.patch b/packaging/my2.patch
+deleted file mode 100644
+index 5554d9c..0000000
+--- a/packaging/my2.patch
++++ /dev/null
+@@ -1,16 +0,0 @@
+-From fb613432f15bc199a724741883b23fff9946b567 Mon Sep 17 00:00:00 2001
+-From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+-Date: Tue, 13 Nov 2012 13:55:46 +0200
+-Subject: [PATCH] My modification
+-
+-Add new file 'mydir/myfile.txt'.
+-
+-Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+-
+-diff --git a/mydir/myfile.txt b/mydir/myfile.txt
+-new file mode 100644
+-index 0000000..2cdad29
+---- /dev/null
+-+++ b/mydir/myfile.txt
+-@@ -0,0 +1 @@
+-+Dummy
+diff --git a/packaging/my3.patch b/packaging/my3.patch
+deleted file mode 100644
+index 9fee859..0000000
+--- a/packaging/my3.patch
++++ /dev/null
+@@ -1,7 +0,0 @@
+-diff --git a/README b/README
+-index a1311cb..a59f1b9 100644
+---- a/README
+-+++ b/README
+-@@ -1 +1 @@
+--Just for testing git-buildpackage.
+-+Just for testing GBP.
diff --git a/tests/component/rpm/data/gbp-test2.data/Repository-initialization.patch b/tests/component/rpm/data/gbp-test2.data/Repository-initialization.patch
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/Repository-initialization.patch
diff --git a/tests/component/rpm/data/gbp-test2.data/Update-.gbp.conf.patch b/tests/component/rpm/data/gbp-test2.data/Update-.gbp.conf.patch
new file mode 100644
index 0000000..704c65e
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/Update-.gbp.conf.patch
@@ -0,0 +1,20 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Thu, 12 Dec 2013 12:15:13 +0200
+Subject: [PATCH] Update .gbp.conf
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+index 6c8e847..e76aa24 100644
+--- a/.gbp.conf
++++ b/.gbp.conf
+@@ -1,7 +1,7 @@
+ [DEFAULT]
+ vendor = Test-Distro
+-packaging-branch = master
+-packaging-tag = release/%(version)s
++packaging-branch = master-orphan
++packaging-tag = release-orphan/%(version)s
+ upstream-branch = upstream
+ upstream-tag = upstream/%(upstreamversion)s
+ packaging-tag-msg = %(vendor)s release %(version)s
diff --git a/tests/component/rpm/data/gbp-test2.data/manifest.json b/tests/component/rpm/data/gbp-test2.data/manifest.json
new file mode 100644
index 0000000..bb04e16
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/manifest.json
@@ -0,0 +1,163 @@
+{
+ "HEAD": "ref: refs/heads/master\n",
+ "commits": {
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272349 +0200",
+ "message": "Repository initialization\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "patchfile": "Repository-initialization.patch",
+ "sha1": "0579e0753b78476ad822c54b6ea07e2e80c0cdd2",
+ "tree": "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+ },
+ "189b0a152a6f1405e021c2988755ce2f5d89c998": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1358253459 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551036 +0200",
+ "message": "packaging: change my2.patch to git email format\n\nChange my2.patch from plain diff to git email format to test author and\ndate parsing of gbp.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "db72a4efae2b72b56f7723df844d7a506e549ce1"
+ ],
+ "patchfile": "packaging-change-my2.patch-to-git-email-format.patch",
+ "sha1": "189b0a152a6f1405e021c2988755ce2f5d89c998",
+ "tree": "8bec5b20caa318012584b1e90942949704248a13"
+ },
+ "2a17b0d682aa73f10a6e242e0f60408b61812047": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352807855 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551076 +0200",
+ "message": "Remove imported patches\n\nAnd enable automatic patch-generation.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "9ff4f5f6853622a95489d2d575c63eceedc203f8"
+ ],
+ "patchfile": "Remove-imported-patches.patch",
+ "sha1": "2a17b0d682aa73f10a6e242e0f60408b61812047",
+ "tree": "771f74c3339e47cf7ad767bd39a27eec4aba4df0"
+ },
+ "6dccee0ab7689e5ae446d39535138ea39a88e70c": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272405 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352806279 +0200",
+ "message": "Add dummy Makefile\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "dc4481d39005968f1f2673d760af69b411eccc33"
+ ],
+ "patchfile": "Add-dummy-Makefile.patch",
+ "sha1": "6dccee0ab7689e5ae446d39535138ea39a88e70c",
+ "tree": "afdd4586454b4648d29a606ebf6be9c52708332f"
+ },
+ "80d0f75635f6ac35f080736236836aac1e596845": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1357733987 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551035 +0200",
+ "message": "packaging: add alternative spec file and change patch path\n\nAdd a second spec file for testing import-srpm spec guessing.\n\nChange one patch filename to be full url for testing import-srpm patch\nimport functionality.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "fb1bf5f16bfa1a4e3ffd29fcc0820bd60fbd1a35"
+ ],
+ "patchfile": "packaging-add-alternative-spec-file-and-change-patch-p.patch",
+ "sha1": "80d0f75635f6ac35f080736236836aac1e596845",
+ "tree": "41d5ec4dfb4b4be0d0552d50f34f5045d4512a46"
+ },
+ "9ff4f5f6853622a95489d2d575c63eceedc203f8": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352807746 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551036 +0200",
+ "message": "My addition\n\nImport my2.patch to the git tree.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "189b0a152a6f1405e021c2988755ce2f5d89c998"
+ ],
+ "patchfile": "My-addition.patch",
+ "sha1": "9ff4f5f6853622a95489d2d575c63eceedc203f8",
+ "tree": "472db0daae817c14f1cb74608e1d234758301da0"
+ },
+ "a93779ad111470b1619f2ceb7a31dd10d6ad6a4f": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1386843313 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551387 +0200",
+ "message": "Update .gbp.conf\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "189b0a152a6f1405e021c2988755ce2f5d89c998"
+ ],
+ "patchfile": "Update-.gbp.conf.patch",
+ "sha1": "a93779ad111470b1619f2ceb7a31dd10d6ad6a4f",
+ "tree": "e92ab121d622ee866dffd560ce2dcd561223719b"
+ },
+ "b7b009c4f87828ad51e26c5c62b91b7c5f83cfff": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352806630 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551002 +0200",
+ "message": "Add gbp.conf\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "6dccee0ab7689e5ae446d39535138ea39a88e70c"
+ ],
+ "patchfile": "Add-gbp.conf.patch",
+ "sha1": "b7b009c4f87828ad51e26c5c62b91b7c5f83cfff",
+ "tree": "7a64093756c1668d9d2888473baf9fd4d44ba11b"
+ },
+ "db72a4efae2b72b56f7723df844d7a506e549ce1": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1357897260 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551036 +0200",
+ "message": "packaging: reflect the corrected patch number handling\n\nFix spec files as GBP now treats 'Patch:' as patch number -1, instead of\n0.\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "80d0f75635f6ac35f080736236836aac1e596845"
+ ],
+ "patchfile": "packaging-reflect-the-corrected-patch-number-handling.patch",
+ "sha1": "db72a4efae2b72b56f7723df844d7a506e549ce1",
+ "tree": "cc668ba4e00ae7308e6a9f1b2225a820e50f618f"
+ },
+ "dc4481d39005968f1f2673d760af69b411eccc33": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352272394 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352806277 +0200",
+ "message": "Add dummy files\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "0579e0753b78476ad822c54b6ea07e2e80c0cdd2"
+ ],
+ "patchfile": "Add-dummy-files.patch",
+ "sha1": "dc4481d39005968f1f2673d760af69b411eccc33",
+ "tree": "af52702a859e84653bfab0a1e0f9253177772f28"
+ },
+ "f89f9c7f4662c16559e6b76771a6f6689d48e2ad": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1449597175 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1449597175 +0200",
+ "message": "pristine-tar data for gbp-test2-2.0.tar.gz",
+ "patchfile": "pristine-tar-data-for-gbp-test2-2.0.tar.gz.patch",
+ "sha1": "f89f9c7f4662c16559e6b76771a6f6689d48e2ad",
+ "tree": "29de04114bb7e206a81405bd95bb989fba0bce61"
+ },
+ "fb1bf5f16bfa1a4e3ffd29fcc0820bd60fbd1a35": {
+ "author": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1352806552 +0200",
+ "committer": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551003 +0200",
+ "message": "Add packaging files\n\nSigned-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>",
+ "parents": [
+ "b7b009c4f87828ad51e26c5c62b91b7c5f83cfff"
+ ],
+ "patchfile": "Add-packaging-files.patch",
+ "sha1": "fb1bf5f16bfa1a4e3ffd29fcc0820bd60fbd1a35",
+ "tree": "321aba18b5f072ecab528e2b004cdd4396b8c780"
+ }
+ },
+ "refs": {
+ "refs/heads/master": "2a17b0d682aa73f10a6e242e0f60408b61812047",
+ "refs/heads/master-orphan": "a93779ad111470b1619f2ceb7a31dd10d6ad6a4f",
+ "refs/heads/pristine-tar": "f89f9c7f4662c16559e6b76771a6f6689d48e2ad",
+ "refs/heads/upstream": "6dccee0ab7689e5ae446d39535138ea39a88e70c",
+ "refs/tags/release/1%2.0-0": "3ee597fee679b6138467ac62e4f857ef5b59b240",
+ "refs/tags/release/1%2.0-1": "d586d2c9384b2536bc1db0a6940578533ff9d122",
+ "refs/tags/upstream/2.0": "c5da8dd1e15c6845f9f7e176a1c5208b5157da14"
+ },
+ "tags": {
+ "3ee597fee679b6138467ac62e4f857ef5b59b240": {
+ "message": "Test-Distro release 1:2.0-0",
+ "object": "189b0a152a6f1405e021c2988755ce2f5d89c998",
+ "tag": "release/1%2.0-0",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551734 +0200",
+ "type": "commit"
+ },
+ "c5da8dd1e15c6845f9f7e176a1c5208b5157da14": {
+ "message": "Version 2.0",
+ "object": "6dccee0ab7689e5ae446d39535138ea39a88e70c",
+ "tag": "upstream/2.0",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1377529719 +0300",
+ "type": "commit"
+ },
+ "d586d2c9384b2536bc1db0a6940578533ff9d122": {
+ "message": "Test-Distro release 1:2.0-1",
+ "object": "2a17b0d682aa73f10a6e242e0f60408b61812047",
+ "tag": "release/1%2.0-1",
+ "tagger": "Markus Lehtonen <markus.lehtonen@linux.intel.com> 1448551738 +0200",
+ "type": "commit"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/component/rpm/data/gbp-test2.data/packaging-add-alternative-spec-file-and-change-patch-p.patch b/tests/component/rpm/data/gbp-test2.data/packaging-add-alternative-spec-file-and-change-patch-p.patch
new file mode 100644
index 0000000..1295be9
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/packaging-add-alternative-spec-file-and-change-patch-p.patch
@@ -0,0 +1,91 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Wed, 9 Jan 2013 14:19:47 +0200
+Subject: [PATCH] packaging: add alternative spec file and change patch path
+
+Add a second spec file for testing import-srpm spec guessing.
+
+Change one patch filename to be full url for testing import-srpm patch
+import functionality.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/.gbp.conf b/.gbp.conf
+index 3d5f92d..6c8e847 100644
+--- a/.gbp.conf
++++ b/.gbp.conf
+@@ -7,4 +7,5 @@ upstream-tag = upstream/%(upstreamversion)s
+ packaging-tag-msg = %(vendor)s release %(version)s
+ packaging-dir = packaging
+ patch-ignore-path = ^(packaging/.*|\.gbp.conf)
++spec-file = packaging/gbp-test2.spec
+
+diff --git a/packaging/gbp-test2-alt.spec b/packaging/gbp-test2-alt.spec
+new file mode 100644
+index 0000000..155ea43
+--- /dev/null
++++ b/packaging/gbp-test2-alt.spec
+@@ -0,0 +1,48 @@
++Name: gbp-test2-alt
++Summary: Test package 2 alternative for git-buildpackage
++Epoch: 1
++Version: 2.0
++Release: 0
++Group: Development/Libraries
++License: GPLv2
++Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
++Source: foo.txt
++Source20: bar.tar.gz
++# Gbp-Ignore-Patches: 0
++Patch: my.patch
++Patch10: http://example.com/patches/my2.patch
++Patch20: my3.patch
++Packager: Markus Lehtonen <markus.lehtonen@linux.intel.com>
++
++%description
++Package for testing the RPM functionality of git-buildpackage.
++Version 2 which has packaging and development in the same
++git branch. Alternative spec file used.
++
++
++%prep
++%setup -T -n %{name}-%{version} -c -a 10
++
++%patch
++%patch -P 20 -p1
++
++echo "Do things"
++
++# Gbp-Patch-Macros
++
++%build
++make
++
++
++%install
++rm -rf %{buildroot}
++mkdir -p %{buildroot}/%{_datadir}/%{name}
++cp -R * %{buildroot}/%{_datadir}/%{name}
++install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
++
++
++
++%files
++%defattr(-,root,root,-)
++%dir %{_datadir}/%{name}
++%{_datadir}/%{name}
+diff --git a/packaging/gbp-test2.spec b/packaging/gbp-test2.spec
+index 61aae33..eec5d26 100644
+--- a/packaging/gbp-test2.spec
++++ b/packaging/gbp-test2.spec
+@@ -8,9 +8,10 @@ License: GPLv2
+ Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
+ Source: foo.txt
+ Source20: bar.tar.gz
++Source9999: gbp-test2-alt.spec
+ # Gbp-Ignore-Patches: 0
+ Patch: my.patch
+-Patch10: my2.patch
++Patch10: http://example.com/patches/my2.patch
+ Patch20: my3.patch
+ Packager: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
diff --git a/tests/component/rpm/data/gbp-test2.data/packaging-change-my2.patch-to-git-email-format.patch b/tests/component/rpm/data/gbp-test2.data/packaging-change-my2.patch-to-git-email-format.patch
new file mode 100644
index 0000000..9aa20cc
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/packaging-change-my2.patch-to-git-email-format.patch
@@ -0,0 +1,26 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 15 Jan 2013 14:37:39 +0200
+Subject: [PATCH] packaging: change my2.patch to git email format
+
+Change my2.patch from plain diff to git email format to test author and
+date parsing of gbp.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/packaging/my2.patch b/packaging/my2.patch
+index ad5ca2d..5554d9c 100644
+--- a/packaging/my2.patch
++++ b/packaging/my2.patch
+@@ -1,3 +1,12 @@
++From fb613432f15bc199a724741883b23fff9946b567 Mon Sep 17 00:00:00 2001
++From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
++Date: Tue, 13 Nov 2012 13:55:46 +0200
++Subject: [PATCH] My modification
++
++Add new file 'mydir/myfile.txt'.
++
++Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
++
+ diff --git a/mydir/myfile.txt b/mydir/myfile.txt
+ new file mode 100644
+ index 0000000..2cdad29
diff --git a/tests/component/rpm/data/gbp-test2.data/packaging-reflect-the-corrected-patch-number-handling.patch b/tests/component/rpm/data/gbp-test2.data/packaging-reflect-the-corrected-patch-number-handling.patch
new file mode 100644
index 0000000..a0a2264
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/packaging-reflect-the-corrected-patch-number-handling.patch
@@ -0,0 +1,35 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Fri, 11 Jan 2013 11:41:00 +0200
+Subject: [PATCH] packaging: reflect the corrected patch number handling
+
+Fix spec files as GBP now treats 'Patch:' as patch number -1, instead of
+0.
+
+Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+diff --git a/packaging/gbp-test2-alt.spec b/packaging/gbp-test2-alt.spec
+index 155ea43..be5d8bc 100644
+--- a/packaging/gbp-test2-alt.spec
++++ b/packaging/gbp-test2-alt.spec
+@@ -8,7 +8,7 @@ License: GPLv2
+ Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
+ Source: foo.txt
+ Source20: bar.tar.gz
+-# Gbp-Ignore-Patches: 0
++# Gbp-Ignore-Patches: -1
+ Patch: my.patch
+ Patch10: http://example.com/patches/my2.patch
+ Patch20: my3.patch
+diff --git a/packaging/gbp-test2.spec b/packaging/gbp-test2.spec
+index eec5d26..5b41aae 100644
+--- a/packaging/gbp-test2.spec
++++ b/packaging/gbp-test2.spec
+@@ -9,7 +9,7 @@ Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
+ Source: foo.txt
+ Source20: bar.tar.gz
+ Source9999: gbp-test2-alt.spec
+-# Gbp-Ignore-Patches: 0
++# Gbp-Ignore-Patches: -1
+ Patch: my.patch
+ Patch10: http://example.com/patches/my2.patch
+ Patch20: my3.patch
diff --git a/tests/component/rpm/data/gbp-test2.data/pristine-tar-data-for-gbp-test2-2.0.tar.gz.patch b/tests/component/rpm/data/gbp-test2.data/pristine-tar-data-for-gbp-test2-2.0.tar.gz.patch
new file mode 100644
index 0000000..81504f6
--- /dev/null
+++ b/tests/component/rpm/data/gbp-test2.data/pristine-tar-data-for-gbp-test2-2.0.tar.gz.patch
@@ -0,0 +1,39 @@
+From: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+Date: Tue, 8 Dec 2015 19:52:55 +0200
+Subject: [PATCH] pristine-tar data for gbp-test2-2.0.tar.gz
+
+
+diff --git a/gbp-test2-2.0.tar.gz.delta b/gbp-test2-2.0.tar.gz.delta
+new file mode 100644
+index 0000000000000000000000000000000000000000..b57b2b423cadf9d96d26f78b097d3ebb713fcf6b
+GIT binary patch
+literal 879
+zcmV-#1Cab5iwFSs7-v=h1MQYuNEA^Rfd7+r!6diRkX&ke5bQ$E<;;wWMHDMVS1E}w
+zN{_S0-PK)p9mmWfobE-JhYWg(iZ+PI3n5g9Bo#gslm%%}nGZz|6+Ki&R&L{JQ7g5+
+z>_&Ay=5l7v|Ih!Q^Pm6B?;{nSQd*@$4B<><j0f+$B5^<7hY)$-1zF@}9*eR994|-`
+z1{)Vkr@m2Lr4!9&10T^Ut3BI(DHpB(FJ%=er==b*O<J3`*s&@v$yx35)y}VSC;e&1
+z7;(W#y*i1pjVSrgV`)VGb695i9}hA&I0`uqXNDhAU}9vyMpcaxotl$WLcCtT=5Y--
+z17vHF%j-9rMQ%!*R11gM1e?L+Bn}IT%$dOq$5#SZc%scm&F|L-&*A8QTLqF}Q~f?4
+zt<V?BPF1CpBPEKQW2X|K7|Tx4E{PH^=1?NqNlp$`I5jCd!r8&^DCWxIB0b=F%z+fe
+zgY2OJM5qdqVy@iLsY)XZ3mh+wPTTYw3nSEJL5`{pU?c#Hy)v)(aQWGWrt^EPDV>Qs
+zT3U^KG$uJKKy=k@a}u4*p8DdhaeqPX`~DN}dk@XF(}M{Jr9=d{;oZ*cS!*u2T8dU5
+z#RY8xMm~UfOh%S<0OL{lbbxeZDL@trKr*se0TRR4l<>;}06f(A;m86&;jU?;P)7(<
+zfN`j720+?xB$1J5Bo2xj6Ncn7e*a};BoE75)X@wC8L5Ujv*^Z83G(l;g=$qEe??4f
+zM6LfgA#(r6W=Y8Q|2QD9Lhrw(Mk9d$)naNRTK<c8ME!?Ff#rWZn0aUdd`Ne#hlJN%
+zt&^_X()W?lrd=85wK+F$llFa%=Dy;RrXG3I$|D|YyZf4pxa&_nA6xe5Vdt^n!)0@&
+zhR4TW)t;=(yV7~AyfG;6shkzG9Y6P^>!LsRQU3B>zQ(qhJp-5fzAR14OWR)PD74l*
+z?V0$j`(tm%m?b9z9c7`F^FArJPKT`N7oN7%FKTt~USU_;>aM=JmYUgrm(;bRW~y|z
+z3{0uNv*AtX`In}kBV<3Ya5HS|dh#+@6MFXc)2WOz_v$7+Fg~O2qZ+uEus^rg3t4|~
+zX8#WK+5nBUjp+Y>9FhOLA}B2X<AFJNRsk>=3<iV2U@#aA27|$1Fc|+RUjaKgTR;FP
+F008M9rcVF>
+
+literal 0
+HcmV?d00001
+
+diff --git a/gbp-test2-2.0.tar.gz.id b/gbp-test2-2.0.tar.gz.id
+new file mode 100644
+index 0000000..340feea
+--- /dev/null
++++ b/gbp-test2-2.0.tar.gz.id
+@@ -0,0 +1 @@
++afdd4586454b4648d29a606ebf6be9c52708332f
diff --git a/tests/component/rpm/data/manage.py b/tests/component/rpm/data/manage.py
new file mode 100755
index 0000000..3eb8c39
--- /dev/null
+++ b/tests/component/rpm/data/manage.py
@@ -0,0 +1,516 @@
+#!/usr/bin/python
+# vim:fileencoding=utf-8:et:ts=4:sw=4:sts=4
+#
+# Copyright (C) 2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+#
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Script for managing test package repositories and unittest data"""
+
+import argparse
+import ConfigParser
+import json
+import logging
+import os
+import shutil
+import subprocess
+import tempfile
+from collections import defaultdict
+from fnmatch import fnmatch
+from glob import glob
+
+
+logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
+LOG = logging.getLogger()
+
+
+class GitError(Exception):
+ """Exception for git errors"""
+ pass
+
+
+def run_cmd(cmd, opts=None, capture_stdout=False, capture_stderr=False,
+ input_data=None, extra_env=None):
+ """Run command"""
+ args = [cmd] + opts if opts else [cmd]
+ stdin = subprocess.PIPE if input_data else None
+ stdout = subprocess.PIPE if capture_stdout else None
+ stderr = subprocess.PIPE if capture_stderr else None
+ env = None
+ if extra_env:
+ env = dict(os.environ)
+ env.update(extra_env)
+ LOG.debug("Running command: '%s'", ' '.join(args))
+ popen = subprocess.Popen(args, stdin=stdin, stdout=stdout, stderr=stderr,
+ env=env)
+ stdout, stderr = popen.communicate(input_data)
+ ret_out = stdout.splitlines() if stdout else stdout
+ ret_err = stderr.splitlines() if stderr else stderr
+ return (popen.returncode, ret_out, ret_err)
+
+
+def git_cmd(cmd, opts=None, capture_stdout=False, input_data=None,
+ extra_env=None):
+ """Run git command"""
+ git_opts = [cmd] + opts if opts else [cmd]
+ ret, stdout, stderr = run_cmd('git', git_opts, capture_stdout, True,
+ input_data, extra_env)
+ if ret:
+ raise GitError("Git cmd ('%s') failed: %s" %
+ ('git ' + ' '.join(git_opts), '\n'.join(stderr)))
+ return stdout
+
+
+def git_cat_file(treeish):
+ """Get object content"""
+ info = {}
+ output = git_cmd('cat-file', ['-p', treeish], True)
+ for num, line in enumerate(output):
+ if not line:
+ break
+ key, val = line.split(' ', 1)
+ if key == 'parent':
+ if 'parents' in info:
+ info['parents'].append(val)
+ else:
+ info['parents'] = [val]
+ else:
+ info[key] = val
+ info['message'] = output[num + 1:]
+ return info
+
+
+def git_write_patch(treeish, outfile):
+ """Write patch with user-defined filename"""
+ cmd = ['git', 'format-patch', '-1', '--stdout', '--no-stat',
+ '--no-signature', treeish]
+ LOG.debug("Running command: '%s'", ' '.join(cmd))
+ with open(outfile, 'w') as fobj:
+ # Skip the first line of the patch that contains the commit sha1
+ popen = subprocess.Popen(['tail', '-n', '+2'], stdout=fobj,
+ stdin=subprocess.PIPE)
+ popen2 = subprocess.Popen(cmd, stdout=popen.stdin,
+ stderr=subprocess.PIPE)
+ _, stderr = popen2.communicate()
+ popen.communicate()
+ if popen.returncode:
+ raise GitError("Git format-patch failed: %s" % stderr)
+
+
+def parse_args(argv=None):
+ """Argument parser"""
+ main_parser = argparse.ArgumentParser()
+ main_parser.add_argument('--verbose', '-v', action='store_true',
+ help="Verbose output")
+
+ subparsers = main_parser.add_subparsers()
+ # Build command
+ parser = subparsers.add_parser('build', help='Build binary files')
+ parser.set_defaults(func=cmd_build)
+ parser.add_argument('--overwrite', '-O', action='store_true',
+ help="Overwrite existing files")
+ parser.add_argument('--output-dir', '-o', default='.',
+ help="Target directory for built artefacts")
+ parser.add_argument('--silent-build', '-s', action='store_true',
+ help="Silent build, i.e. no rpmbuild output shown")
+ parser.add_argument('reponame', nargs='*',
+ help="Name of package repository to build")
+ # Import command
+ parser = subparsers.add_parser('import-repo',
+ help="Create test package repositories")
+ parser.set_defaults(func=cmd_import_repos)
+ parser.add_argument('--force', '-f', action='store_true',
+ help="Overwrite existing repositories")
+ parser.add_argument('--output-dir', '-o', default='.',
+ help="Target directory for the imported repo(s)")
+ parser.add_argument('reponame', nargs='?',
+ help="Name of package repository to import")
+ parser.add_argument('repodir', nargs='?',
+ help="Directory name (under output directory) where "
+ "new repository is created")
+ # Export command
+ parser = subparsers.add_parser('export-repo',
+ help='Serialize test package repositories')
+ parser.set_defaults(func=cmd_export_repos)
+ parser.add_argument('--output-dir', '-o', default='.',
+ help="Target directory for the exported repo(s)")
+ parser.add_argument('reponame', nargs='?',
+ help="Name of package repository to export")
+ parser.add_argument('datadir', nargs='?',
+ help="Directory name (under output directory) where "
+ "data is exported")
+ return main_parser.parse_args(argv)
+
+
+def cond_copy(src, dst, overwrite=False):
+ """Copy if file does not exists, unless overwrite is enabled"""
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ src = os.path.abspath(src)
+ dst = os.path.abspath(dst)
+ if not os.path.exists(dst) or overwrite:
+ LOG.debug('Copying %s -> %s', src, dst)
+ shutil.copy(src, dst)
+ else:
+ LOG.debug('Skipping %s', src)
+
+
+def do_build(tag, builddir, silent_build=False):
+ """Run git-buildpackage-rpm"""
+ gbp_opts = ['buildpackage-rpm', '--git-ignore-new',
+ '--git-export=%s' % tag, '--git-export-dir=%s' % builddir,
+ '--git-ignore-branch']
+ rpmbuild_opts = ['-ba', '--target=noarch']
+ ret, out, _ = run_cmd('gbp', gbp_opts + rpmbuild_opts,
+ True, silent_build)
+ if ret:
+ for line in out:
+ print(line)
+ raise Exception('Building %s failed! Builddata can be found '
+ 'in %s' % (tag, builddir))
+
+
+def build_repo(repodir, outdir, silent_build=False, overwrite=False):
+ """Build the test package and extract unit test data"""
+ repodir = os.path.abspath(repodir)
+ outdir = os.path.abspath(outdir)
+ if not os.path.isdir(repodir):
+ raise Exception("Repodir %s does not exist" % repodir)
+ if not os.path.isdir(outdir):
+ os.mkdir(outdir)
+
+ tag_pattern = '*/release/*'
+ orig_cwd = os.getcwd()
+ os.chdir(repodir)
+ try:
+ tags = git_cmd('tag', ['-l', tag_pattern], True)
+ for ind, tag in enumerate(tags):
+ prefix = 'build-%s-%s_' % (os.path.basename(repodir), ind)
+ builddir = tempfile.mkdtemp(dir=orig_cwd, prefix=prefix)
+ LOG.info("Building tag '%s'", tag)
+ do_build(tag, builddir, silent_build)
+
+ # Create subdirs
+ orig_dir = '%s/%s' % (outdir, 'orig')
+ rpm_dir = '%s/%s' % (outdir, 'rpm')
+ for path in (orig_dir, rpm_dir):
+ if not os.path.isdir(path):
+ os.mkdir(path)
+
+ for fname in glob('%s/SRPMS/*rpm' % builddir):
+ cond_copy(fname, outdir, overwrite)
+ for fname in glob('%s/RPMS/*/*rpm' % builddir):
+ cond_copy(fname, rpm_dir, overwrite)
+ for fname in os.listdir('%s/SOURCES' % builddir):
+ if (fnmatch(fname, 'gbp*tar.gz') or
+ fnmatch(fname, 'gbp*tar.bz2') or
+ fnmatch(fname, 'gbp*zip')):
+
+ cond_copy('%s/SOURCES/%s' % (builddir, fname), orig_dir,
+ overwrite)
+ shutil.rmtree(builddir)
+ finally:
+ os.chdir(orig_cwd)
+
+
+def cmd_build(args):
+ """Subcommand building binary test data"""
+ if args.reponame:
+ repos = []
+ for repo in args.reponame:
+ if os.path.exists(repo):
+ repos.append(repo)
+ else:
+ repos.append(repo + '.repo')
+ else:
+ repos = glob('*.repo')
+ if not repos:
+ raise Exception("No repositories found, run 'import' in order to "
+ "initialize test package repositories for building")
+ # Read build config
+ config = ConfigParser.RawConfigParser()
+ config.read('build.conf')
+
+ for repodir in repos:
+ LOG.info("Building repository '%s'", repodir)
+ build_repo(repodir, args.output_dir, args.silent_build, args.overwrite)
+
+
+def write_repo_data(outfile, **kwargs):
+ """Write repository metadata into JSON file"""
+ #data = {'refs': refs, 'tags': tags, 'commits': commits}
+ data = kwargs
+ with open(outfile, 'w') as fobj:
+ json.dump(data, fobj, indent=4, sort_keys=True)
+
+
+def split_git_author(author):
+ """Split author/committer string into separate fields"""
+ name_email, date = author.rsplit('>', 1)
+ name, email = name_email.split('<', 1)
+ return name, email, date
+
+
+def commit_tree(commit):
+ """Create a tag object"""
+ name, email, date = split_git_author(commit['committer'])
+ env = {'GIT_COMMITTER_NAME': name,
+ 'GIT_COMMITTER_EMAIL': email,
+ 'GIT_COMMITTER_DATE': date}
+ name, email, date = split_git_author(commit['author'])
+ env.update({'GIT_AUTHOR_NAME': name,
+ 'GIT_AUTHOR_EMAIL': email,
+ 'GIT_AUTHOR_DATE': date})
+ git_opts = []
+ git_opts.append(commit['tree'])
+ if 'parents' in commit:
+ for parent in commit['parents']:
+ git_opts += ['-p', parent]
+ return git_cmd('commit-tree', git_opts, True, commit['message'] + '\n',
+ env)[0]
+
+
+def commit_patch(commit, patchfile):
+ """Apply and commit one patch"""
+ name, email, date = split_git_author(commit['committer'])
+ env = {'GIT_COMMITTER_NAME': name,
+ 'GIT_COMMITTER_EMAIL': email,
+ 'GIT_COMMITTER_DATE': date}
+ name, email, date = split_git_author(commit['author'])
+ env.update({'GIT_AUTHOR_NAME': name,
+ 'GIT_AUTHOR_EMAIL': email,
+ 'GIT_AUTHOR_DATE': date})
+ # Empty patch for empty commits -> would not apply
+ if os.stat(patchfile).st_size:
+ git_cmd('apply', ['--index', patchfile], True, None, env)
+ tree = git_cmd('write-tree', None, True, None, env)[0]
+ assert tree == commit['tree']
+ sha1 = commit_tree(commit)
+ git_cmd('checkout', [sha1], True)
+ return sha1
+
+
+def import_commit(commit, patchdir):
+ """Import one commit"""
+ patchfile = os.path.join(patchdir, commit['patchfile'])
+ # Repository state sanity check
+ if git_cmd('status', ['--porcelain'], True):
+ raise Exception("Refusing to import, git repository not clean at %s" %
+ os.getcwd())
+ if 'parents' not in commit:
+ # Start new history
+ git_cmd('checkout', ['--orphan', '__tmp__'], True)
+ if git_cmd('status', ['--porcelain'], True):
+ # Clean working tree and index
+ git_cmd('rm', ['-rf', '.'], True)
+ sha1 = commit_patch(commit, patchfile)
+ elif len(commit['parents']) == 1:
+ git_cmd('checkout', [commit['parents'][0]], True)
+ sha1 = commit_patch(commit, patchfile)
+ else:
+ raise Exception("Merge commits (%s) not supported!" % commit['sha1'])
+ # Sanity check for commit
+ assert sha1 == commit['sha1'], (
+ "SHA-1 of the created commit is wrong (%s != %s)" %
+ (sha1, commit['sha1']))
+
+
+def import_repo(datadir, repodir, force):
+ """De-serialize test package repodata into a Git repository"""
+ datadir = os.path.abspath(datadir)
+ repodir = os.path.abspath(repodir)
+ if not os.path.isdir(datadir):
+ raise Exception("Datadir %s does not exist" % datadir)
+ if os.path.isdir(repodir):
+ if not force:
+ raise Exception("Repository %s already exists! "
+ "Use --force to replace." % repodir)
+ else:
+ LOG.info('Removing existing repodir %s', repodir)
+ shutil.rmtree(repodir)
+ os.makedirs(repodir)
+ with open(os.path.join(datadir, 'manifest.json')) as fobj:
+ manifest = json.load(fobj)
+
+ orig_cwd = os.getcwd()
+ os.chdir(repodir)
+ try:
+ git_cmd('init', None, True)
+
+ # Create child mapping of commit history
+ commits = defaultdict(list)
+ for sha1, info in manifest['commits'].iteritems():
+ if 'parents' not in info:
+ commits['root'].append(sha1)
+ else:
+ for parent in info['parents']:
+ commits[parent].append(sha1)
+
+ # Re-create all commits
+ def import_commit_history(start):
+ """Import chain of commits"""
+ for sha1 in commits[start]:
+ import_commit(manifest['commits'][sha1], datadir)
+ import_commit_history(sha1)
+ import_commit_history('root')
+
+ # Re-create tags
+ for sha1, tag in manifest['tags'].iteritems():
+ signature_data = "object %s\ntype %s\ntag %s\ntagger %s\n\n%s\n" % (
+ tag['object'], tag['type'], tag['tag'], tag['tagger'],
+ tag['message'])
+ new_sha1 = git_cmd('mktag', None, True, signature_data)[0]
+ assert new_sha1 == sha1, \
+ "SHA-1 of the re-created tag is wrong (%s != %s)" % \
+ (new_sha1, sha1)
+
+ # Re-create refs
+ for ref, sha1 in manifest['refs'].iteritems():
+ git_cmd('update-ref', [ref, sha1], True)
+
+ # Forcefully set HEAD
+ with open(os.path.join('.git', 'HEAD'), 'w') as fobj:
+ fobj.write(manifest['HEAD'])
+ git_cmd('reset', ['--hard'], True)
+ finally:
+ os.chdir(orig_cwd)
+
+
+def cmd_import_repos(args):
+ """Subcommand for creating test pkg Git repositories"""
+ if args.reponame:
+ repos = [args.reponame] if os.path.exists(args.reponame) else \
+ [args.reponame + '.data']
+ else:
+ repos = glob('*.data')
+
+ for datadir in repos:
+ basename = os.path.basename(os.path.abspath(datadir))
+ base, ext = os.path.splitext(basename)
+ if args.repodir:
+ repodir = args.repodir
+ else:
+ repodir = base + '.repo' if ext == '.data' else basename + '.repo'
+ repodir = os.path.join(args.output_dir, repodir)
+ LOG.info("Importing repodata from '%s' into '%s'", datadir, repodir)
+ import_repo(datadir, repodir, args.force)
+
+
+def export_repo(repodir, datadir):
+ """Serialize one repository"""
+ repodir = os.path.abspath(repodir)
+ datadir = os.path.abspath(datadir)
+ if not os.path.isdir(repodir):
+ raise Exception("Repository %s does not exist" % repodir)
+ if os.path.isdir(datadir):
+ LOG.debug('Removing existing datadir %s', datadir)
+ shutil.rmtree(datadir)
+ os.makedirs(datadir)
+
+ ref_metadata = {}
+ tag_metadata = {}
+ commits_metadata = {}
+ orig_cwd = os.getcwd()
+ os.chdir(repodir)
+ try:
+ # Get refs
+ refs = [line.split() for line in
+ git_cmd('show-ref', ['--tags', '--heads'], True)]
+ for sha1, ref in refs:
+ ref_metadata[ref] = sha1
+ # Serialize tag objects
+ tags = git_cmd('tag', None, True)
+ for tag in tags:
+ obj_type = git_cmd('cat-file', ['-t', tag], True)[0]
+ if obj_type != 'tag':
+ continue
+ sha1 = git_cmd('rev-parse', [tag], True)[0]
+ tag_info = git_cat_file(tag)
+ tag_metadata[sha1] = {'type': tag_info['type'],
+ 'tag': tag_info['tag'],
+ 'object': tag_info['object'],
+ 'tagger': tag_info['tagger'],
+ 'message': '\n'.join(tag_info['message'])}
+ # Serialize commits objects
+ refs = [ref for _, ref in refs]
+ revisions = git_cmd('rev-list', ['--reverse'] + refs + ['--'],
+ True)
+ series = defaultdict(int)
+ for sha1 in revisions:
+ fn_base = git_cmd('show', ['--format=format:%f', '-s', sha1],
+ True)[0][:54]
+ # In case of overlapping filenames, add a numerical suffix
+ series[fn_base] += 1
+ if series[fn_base] > 1:
+ fn_base += '-%d' % series[fn_base]
+ patch_fn = fn_base + '.patch'
+ # Create patch file
+ git_write_patch(sha1, os.path.join(datadir, patch_fn))
+ commit_info = git_cat_file(sha1)
+ meta = {'sha1': sha1,
+ 'tree': commit_info['tree'],
+ 'author': commit_info['author'],
+ 'committer': commit_info['committer'],
+ 'message': '\n'.join(commit_info['message']),
+ 'patchfile': patch_fn}
+
+ if 'parents' in commit_info:
+ meta['parents'] = commit_info['parents']
+ commits_metadata[sha1] = meta
+
+ # Special handling for HEAD
+ with open(os.path.join('.git', 'HEAD')) as fobj:
+ head = fobj.read()
+
+ # Write all metadata into file
+ write_repo_data(os.path.join(datadir, 'manifest.json'),
+ refs=ref_metadata, tags=tag_metadata,
+ commits=commits_metadata, HEAD=head)
+ finally:
+ os.chdir(orig_cwd)
+
+
+def cmd_export_repos(args):
+ """Subcommand for updating test pkg repo data"""
+ if args.reponame:
+ repos = [args.reponame] if os.path.exists(args.reponame) else \
+ [args.reponame + '.repo']
+ else:
+ repos = glob('*.repo')
+
+ for repodir in repos:
+ basename = os.path.basename(os.path.abspath(repodir))
+ base, ext = os.path.splitext(basename)
+ if args.datadir:
+ datadir = args.datadir
+ else:
+ datadir = base + '.data' if ext == '.repo' else basename + '.data'
+ datadir = os.path.join(args.output_dir, datadir)
+ LOG.info("Exporting repodata from '%s' into '%s'", repodir, datadir)
+ export_repo(repodir, datadir)
+
+
+def main(argv=None):
+ """The main routine"""
+ args = parse_args(argv)
+ if args.verbose:
+ LOG.setLevel(logging.DEBUG)
+
+ args.func(args)
+ return 0
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2 b/tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2
new file mode 100644
index 0000000..a4c6b90
--- /dev/null
+++ b/tests/component/rpm/data/orig/gbp-test-1.0.tar.bz2
Binary files differ
diff --git a/tests/component/rpm/data/orig/gbp-test-1.1.tar.bz2 b/tests/component/rpm/data/orig/gbp-test-1.1.tar.bz2
new file mode 100644
index 0000000..398a0d0
--- /dev/null
+++ b/tests/component/rpm/data/orig/gbp-test-1.1.tar.bz2
Binary files differ
diff --git a/tests/component/rpm/data/orig/gbp-test-1.1.with_dotgit.tar.bz2 b/tests/component/rpm/data/orig/gbp-test-1.1.with_dotgit.tar.bz2
new file mode 100644
index 0000000..d7fe842
--- /dev/null
+++ b/tests/component/rpm/data/orig/gbp-test-1.1.with_dotgit.tar.bz2
Binary files differ
diff --git a/tests/component/rpm/data/orig/gbp-test-native-1.0.zip b/tests/component/rpm/data/orig/gbp-test-native-1.0.zip
new file mode 100644
index 0000000..592f633
--- /dev/null
+++ b/tests/component/rpm/data/orig/gbp-test-native-1.0.zip
Binary files differ
diff --git a/tests/component/rpm/data/orig/gbp-test2-2.0.tar.gz b/tests/component/rpm/data/orig/gbp-test2-2.0.tar.gz
new file mode 100644
index 0000000..e01abf5
--- /dev/null
+++ b/tests/component/rpm/data/orig/gbp-test2-2.0.tar.gz
Binary files differ
diff --git a/tests/component/rpm/data/rpm/gbp-test-1.0-1.noarch.rpm b/tests/component/rpm/data/rpm/gbp-test-1.0-1.noarch.rpm
new file mode 100644
index 0000000..45b2a3d
--- /dev/null
+++ b/tests/component/rpm/data/rpm/gbp-test-1.0-1.noarch.rpm
Binary files differ
diff --git a/tests/component/rpm/data/rpm/gbp-test-1.1-1.noarch.rpm b/tests/component/rpm/data/rpm/gbp-test-1.1-1.noarch.rpm
new file mode 100644
index 0000000..435c1c3
--- /dev/null
+++ b/tests/component/rpm/data/rpm/gbp-test-1.1-1.noarch.rpm
Binary files differ
diff --git a/tests/component/rpm/data/rpm/gbp-test-1.1-2.noarch.rpm b/tests/component/rpm/data/rpm/gbp-test-1.1-2.noarch.rpm
new file mode 100644
index 0000000..e19dc04
--- /dev/null
+++ b/tests/component/rpm/data/rpm/gbp-test-1.1-2.noarch.rpm
Binary files differ
diff --git a/tests/component/rpm/data/rpm/gbp-test-native-1.0-1.noarch.rpm b/tests/component/rpm/data/rpm/gbp-test-native-1.0-1.noarch.rpm
new file mode 100644
index 0000000..14ebba6
--- /dev/null
+++ b/tests/component/rpm/data/rpm/gbp-test-native-1.0-1.noarch.rpm
Binary files differ
diff --git a/tests/component/rpm/data/rpm/gbp-test-native2-2.0-0.noarch.rpm b/tests/component/rpm/data/rpm/gbp-test-native2-2.0-0.noarch.rpm
new file mode 100644
index 0000000..752c94a
--- /dev/null
+++ b/tests/component/rpm/data/rpm/gbp-test-native2-2.0-0.noarch.rpm
Binary files differ
diff --git a/tests/component/rpm/data/rpm/gbp-test2-2.0-0.noarch.rpm b/tests/component/rpm/data/rpm/gbp-test2-2.0-0.noarch.rpm
new file mode 100644
index 0000000..97cf265
--- /dev/null
+++ b/tests/component/rpm/data/rpm/gbp-test2-2.0-0.noarch.rpm
Binary files differ
diff --git a/tests/component/rpm/data/rpm/gbp-test2-2.0-1.noarch.rpm b/tests/component/rpm/data/rpm/gbp-test2-2.0-1.noarch.rpm
new file mode 100644
index 0000000..4b1a3cc
--- /dev/null
+++ b/tests/component/rpm/data/rpm/gbp-test2-2.0-1.noarch.rpm
Binary files differ
diff --git a/tests/component/rpm/test_buildpackage_rpm.py b/tests/component/rpm/test_buildpackage_rpm.py
new file mode 100644
index 0000000..78e9ff3
--- /dev/null
+++ b/tests/component/rpm/test_buildpackage_rpm.py
@@ -0,0 +1,625 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+#
+"""Unit tests for the gbp-buildpackage-rpm tool"""
+
+import glob
+import mock
+import os
+import re
+import shutil
+import stat
+import subprocess
+
+from nose import SkipTest
+from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
+
+from gbp.git import GitRepository
+from gbp.scripts.buildpackage_rpm import main as gbp_rpm
+from tests.component.rpm import RpmRepoTestBase, RPM_TEST_DATA_DIR
+from tests.testutils import ls_tar, ls_zip, capture
+
+# Disable "Method could be a function warning"
+# pylint: disable=R0201
+# Disable "Too many public methods"
+# pylint: disable=R0904
+
+
+DATA_DIR = os.path.join(RPM_TEST_DATA_DIR, 'rpm')
+ORIG_DATA_DIR = os.path.join(RPM_TEST_DATA_DIR, 'orig')
+
+MOCK_NOTIFICATIONS = []
+
+
+def mock_gbp(args):
+ """Wrapper for gbp-buildpackage-rpm"""
+ with capture.capture_stderr():
+ return gbp_rpm(['arg0', '--git-notify=off'] + args +
+ ['-ba', '--clean', '--target=noarch', '--nodeps'])
+
+
+def mock_notify(summary, message, notify_opt):
+ """Mock notification system"""
+ if notify_opt.is_off():
+ return True
+ # Auto will succeed
+ elif notify_opt.is_auto():
+ MOCK_NOTIFICATIONS.append((summary, message))
+ return True
+ # Otherwise fail
+ else:
+ return False
+
+
+class TestGbpRpm(RpmRepoTestBase):
+ """Basic tests for gbp buildpackage-rpm"""
+
+ @staticmethod
+ def ls_rpm(rpm):
+ """List the contents of an rpm package"""
+ args = ['rpm', '-q', '--qf',
+ '[%{FILEDIGESTS %{FILEMODES} %{FILENAMES}\n]', '-p']
+ popen = subprocess.Popen(args + [rpm], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = popen.communicate()
+ if popen.returncode:
+ raise Exception("Failed to get file metadata for %s: %s" %
+ (rpm, stderr))
+ return sorted([(nam, mod, dig) for dig, mod, nam in
+ [lin.split(None, 2) for lin in stdout.splitlines()]])
+
+ @staticmethod
+ def check_rpms(directory):
+ """Check build results"""
+ # Only check files, at least for now
+ files = glob.glob(directory + '/*rpm')
+ assert files, "No rpms (%s)found in %s" % (files, directory)
+ for path in files:
+ ref_file = os.path.join(DATA_DIR, os.path.basename(path))
+ eq_(TestGbpRpm.ls_rpm(path), TestGbpRpm.ls_rpm(ref_file))
+
+ @staticmethod
+ def check_and_rm_file(filepath, content):
+ """Check file content and remove it"""
+ with open(filepath) as fobj:
+ eq_(fobj.read(), content)
+ os.unlink(filepath)
+
+ def test_invalid_args(self):
+ """Check graceful exit when called with invalid args"""
+ GitRepository.create('.')
+ with assert_raises(SystemExit):
+ mock_gbp(['--git-invalid-arg'])
+
+ def test_outside_repo(self):
+ """Run outside a git repository"""
+ eq_(mock_gbp([]), 1)
+ self._check_log(0, 'gbp:error: %s is not a git repository' %
+ os.path.abspath('.'))
+
+ def test_native_build(self):
+ """Basic test of native pkg"""
+ self.init_test_repo('gbp-test-native')
+ eq_(mock_gbp([]), 0)
+ self.check_rpms('../rpmbuild/RPMS/*')
+ shutil.rmtree('../rpmbuild')
+
+ eq_(mock_gbp(['--git-native=off']), 2)
+ self._check_log(0, 'gbp:error: Invalid upstream treeish upstream/')
+
+ def test_native_build2(self):
+ """Basic test of another native pkg"""
+ self.init_test_repo('gbp-test-native2')
+ eq_(mock_gbp([]), 0)
+ self.check_rpms('../rpmbuild/RPMS/*')
+
+ def test_non_native_build(self):
+ """Basic test of non-native pkg"""
+ repo = self.init_test_repo('gbp-test')
+ eq_(mock_gbp([]), 0)
+ self.check_rpms('../rpmbuild/RPMS/*')
+
+ # Test nativity guessing from remote branches by creating a dummy
+ # remote branch
+ repo.update_ref('refs/remotes/fooremote/foobranch',
+ 'upstream')
+ eq_(mock_gbp(['--git-upstream-branch=foobranch']), 0)
+
+ def test_option_native(self):
+ """Test the --git-native option"""
+ self.init_test_repo('gbp-test2')
+ eq_(mock_gbp([]), 0)
+ self.check_rpms('../rpmbuild/RPMS/*')
+ shutil.rmtree('../rpmbuild')
+
+ # Building this pkg should succeed, but no patches generated,
+ # only one "manually maintained" patch
+ eq_(mock_gbp(['--git-native=on']), 0)
+ self.check_rpms('../rpmbuild/RPMS/*')
+ eq_(len(glob.glob('../rpmbuild/SOURCES/*patch')), 1)
+
+ def test_options_ignore(self):
+ """Test the --git-ignore-[new|untracked] options"""
+ self.init_test_repo('gbp-test-native')
+
+ # Create an untracked file
+ with open('untracked-file', 'w') as fobj:
+ fobj.write('this file is not tracked\n')
+
+ # Modify tracked file
+ with open('README', 'a') as fobj:
+ fobj.write('new stuff\n')
+
+ eq_(mock_gbp([]), 1)
+ eq_(mock_gbp(['--git-ignore-new']), 0)
+
+ @mock.patch('gbp.notifications.notify', mock_notify)
+ def test_option_notify(self):
+ """Test the --git-notify option"""
+ self.init_test_repo('gbp-test-native')
+
+ eq_(mock_gbp(['--git-notify=auto']), 0)
+ summary, message = MOCK_NOTIFICATIONS.pop()
+ ok_(re.match(r'Gbp-rpm successful', summary), summary)
+ ok_(re.match(r'Build of \S+ \S+ succeeded', message), message)
+
+ # Mock-notification will fail with "on" setting
+ eq_(mock_gbp(['--git-notify=on']), 1)
+ self._check_log(-1, "gbp:error: Failed to send notification")
+
+ # No notification when "off"
+ eq_(mock_gbp(['--git-notify=off']), 0)
+ eq_(len(MOCK_NOTIFICATIONS), 0)
+
+ def test_option_tmp_dir(self):
+ """Test the --git-tmp-dir option"""
+ if os.getenv("FAKED_MODE"):
+ raise SkipTest("Skipping we're running under fakeroot")
+
+ self.init_test_repo('gbp-test-native')
+
+ eq_(mock_gbp(['--git-tmp-dir=../gbptmp', '--git-builder=true']), 0)
+ ok_(os.path.isdir('../gbptmp'))
+
+ # Check tmpdir access/creation error
+ os.chmod('../gbptmp', 0)
+ try:
+ eq_(mock_gbp(['--git-tmp-dir=../gbptmp/foo', '--git-builder=true']), 1)
+ finally:
+ os.chmod('../gbptmp', stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
+
+ def test_tagging(self):
+ """Test tagging options"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ # Build and tag
+ eq_(mock_gbp(['--git-tag', '--git-packaging-tag=rel-tag']), 0)
+ self.check_rpms('../rpmbuild/RPMS/*')
+ ok_(repo.has_tag('rel-tag'))
+ sha = repo.rev_parse('HEAD')
+ eq_(sha, repo.rev_parse('rel-tag^0'))
+ self.check_rpms('../rpmbuild/RPMS/*')
+
+ # Should fail if the tag already exists
+ eq_(mock_gbp(['--git-tag', '--git-packaging-tag=rel-tag']), 1)
+
+ # Re-tag
+ eq_(mock_gbp(['--git-retag', '--git-packaging-tag=rel-tag']), 3)
+ self._check_log(-1, "gbp:error: '--git-retag' needs either '--git-tag'")
+
+ eq_(mock_gbp(['--git-tag', '--git-packaging-tag=rel-tag',
+ '--git-retag', '--git-export=HEAD^']), 0)
+ ok_(repo.has_tag('rel-tag'))
+ sha2 = repo.rev_parse('HEAD^')
+ ok_(sha2 != sha)
+ eq_(sha2, repo.rev_parse('rel-tag^0'))
+
+ # Tag-only
+ shutil.rmtree('../rpmbuild')
+ eq_(mock_gbp(['--git-tag-only', '--git-packaging-tag=rel-tag2']), 0)
+ ok_(not os.path.exists('../rpmbuild'))
+ ok_(repo.has_tag('rel-tag2'))
+
+ # Valid tag format string keys
+ tag_keys = ['upstreamversion', 'release', 'version', 'vendor']
+ # Should fail if the fag format has invalid keys (foo here)
+ tag_fmt = '_'.join(['%(' + key + ')s' for key in tag_keys + ['foo']])
+ eq_(mock_gbp(['--git-tag', '--git-packaging-tag=%(foo)s']), 1)
+ # Remove 'foo' and should succeed
+ tag_fmt = '_'.join(['%(' + key + ')s' for key in tag_keys])
+ eq_(mock_gbp(['--git-tag-only', '--git-packaging-tag=%s' % tag_fmt]), 0)
+
+ def test_option_upstream_tree(self):
+ """Test the --git-upstream-tree option"""
+ repo = self.init_test_repo('gbp-test')
+
+ # Dummy update to upstream branch
+ pkg_branch = repo.get_branch()
+ upstr_branch = 'upstream'
+ orig_files = ['gbp-test/' + path.decode() for
+ path in repo.ls_tree(upstr_branch)] + ['gbp-test']
+ repo.set_branch(upstr_branch)
+ for fname in ['new-file', 'new-file2']:
+ self.add_file(repo, fname, 'new content %s' % fname)
+ repo.set_branch(pkg_branch)
+
+ # TAG (default) does not contain the new files
+ eq_(mock_gbp([]), 0)
+ tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2')
+ self.check_files(orig_files, tar_files)
+ shutil.rmtree('../rpmbuild')
+
+ # Branch contains them both
+ eq_(mock_gbp(['--git-upstream-tree=BRANCH']), 0)
+ tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2')
+ self.check_files(orig_files +
+ ['gbp-test/new-file', 'gbp-test/new-file2'], tar_files)
+ shutil.rmtree('../rpmbuild')
+
+ # The first "extra-commit" in upstream contains only one new file
+ eq_(mock_gbp(['--git-upstream-tree=%s^' % upstr_branch]), 0)
+ tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2')
+ self.check_files(orig_files + ['gbp-test/new-file'], tar_files)
+ shutil.rmtree('../rpmbuild')
+
+ # Test invalid upstream treeish
+ eq_(mock_gbp(['--git-upstream-tree=TAG',
+ '--git-upstream-tag=invalid-tag']), 2)
+ self._check_log(-1, ".*Invalid upstream treeish invalid-tag")
+ eq_(mock_gbp(['--git-upstream-tree=BRANCH', '--git-native=no',
+ '--git-upstream-branch=invalid-branch']), 2)
+ self._check_log(-1, ".*invalid-branch is not a valid branch")
+ eq_(mock_gbp(['--git-upstream-tree=invalid-tree']), 2)
+ self._check_log(-1, ".*Invalid treeish object")
+
+ def test_pristine_tar(self):
+ """Test pristine-tar"""
+ repo = self.init_test_repo('gbp-test')
+
+ # Build succeeds when pristine-tar branch is present
+ eq_(mock_gbp(['--git-pristine-tar',
+ '--git-export=release/1.1-2']), 0)
+ self.check_rpms('../rpmbuild/RPMS/*')
+ shutil.rmtree('../rpmbuild')
+
+ # Pristine-tar checkout fails with no pristine-tar branch
+ repo.rename_branch('pristine-tar', 'pristine_tar')
+ eq_(mock_gbp(['--git-pristine-tar',
+ '--git-export=release/1.1-2']), 1)
+ self._check_log(-1, ".*Pristine-tar couldn't checkout")
+
+ def test_pristine_tar_commit(self):
+ """Test committing upstream tarball to pristine-tar"""
+ repo = self.init_test_repo('gbp-test')
+ repo.delete_branch('pristine-tar')
+
+ eq_(repo.has_branch('pristine-tar'), False)
+ eq_(mock_gbp(['--git-pristine-tar-commit',
+ '--git-export=release/1.0-1']), 0)
+ eq_(len(repo.get_commits(until='pristine-tar')), 1)
+ shutil.rmtree('../rpmbuild')
+
+ # Using --git-pristine-tar and --git-pristine-tar-commit should be ok
+ eq_(mock_gbp(['--git-pristine-tar', '--git-pristine-tar-commit']), 0)
+ eq_(len(repo.get_commits(until='pristine-tar')), 2)
+ shutil.rmtree('../rpmbuild')
+
+ # Second time no pristine-tar should not be committed
+ eq_(mock_gbp(['--git-pristine-tar-commit']), 0)
+ eq_(len(repo.get_commits(until='pristine-tar')), 2)
+
+ def test_tarball_dir(self):
+ """Test a separate tarball cache"""
+ self.init_test_repo('gbp-test')
+
+ # Create and populate tarball cache
+ tarball_dir = '../tarballs'
+ os.mkdir(tarball_dir)
+ shutil.copy2(os.path.join(ORIG_DATA_DIR, 'gbp-test-1.0.tar.bz2'),
+ tarball_dir)
+
+ # Test build when tarball is found from cache
+ eq_(mock_gbp(['--git-export=release/1.0-1',
+ '--git-tarball-dir=%s' % tarball_dir]), 0)
+ ok_(os.path.islink(os.path.join('..', 'rpmbuild', 'SOURCES',
+ 'gbp-test-1.0.tar.bz2')))
+
+ # Test build when tarball is not found from cache
+ eq_(mock_gbp(['--git-export=release/1.1-2',
+ '--git-tarball-dir=%s' % tarball_dir]), 0)
+ ok_(os.path.isfile(os.path.join('..', 'rpmbuild', 'SOURCES',
+ 'gbp-test-1.1.tar.bz2')))
+
+ def test_packaging_branch_options(self):
+ """Test the --packaging-branch and --ignore-branch cmdline options"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ eq_(mock_gbp(['--git-packaging-branch=foo']), 1)
+ self._check_log(-2, "gbp:error: You are not on branch 'foo'")
+
+ eq_(mock_gbp(['--git-packaging-branch=foo', '--git-ignore-branch']), 0)
+
+ # Test building when not on any branch
+ repo.set_branch(repo.rev_parse('HEAD'))
+ eq_(mock_gbp(['--git-builder=true']), 1)
+ eq_(mock_gbp(['--git-ignore-branch', '--git-builder=true']), 0)
+
+ def test_option_submodules(self):
+ """Test the --git-submodules option"""
+ repo = self.init_test_repo('gbp-test')
+
+ # Create submodule to upstream branch
+ sub_repo = self.orig_repos['gbp-test-native']
+ pkg_branch = repo.get_branch()
+ upstr_branch = 'upstream'
+ repo.set_branch(upstr_branch)
+ repo.add_submodule(sub_repo.path)
+ repo.commit_all('Add submodule')
+ repo.set_branch(pkg_branch)
+
+ sub_files = sub_repo.ls_tree('HEAD')
+ upstr_files = ['gbp-test/' + path.decode() for
+ path in repo.ls_tree(upstr_branch)]
+
+ # Test the "no" option
+ eq_(mock_gbp(['--git-no-submodules', '--git-upstream-tree=%s' %
+ upstr_branch, '--git-ignore-new']), 0)
+ tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2', False)
+ self.check_files(upstr_files, tar_files)
+ shutil.rmtree('../rpmbuild')
+
+ # Test the "yes" option
+ eq_(mock_gbp(['--git-submodules', '--git-upstream-tree=%s' %
+ upstr_branch, '--git-ignore-new']), 0)
+ tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2', False)
+ ref_files = upstr_files + ['gbp-test/gbp-test-native.repo/' + path.decode() for
+ path in sub_files]
+ self.check_files(ref_files, tar_files)
+ shutil.rmtree('../rpmbuild')
+
+ # Test submodule failure
+ shutil.rmtree('gbp-test-native.repo')
+ repo.create('gbp-test-native.repo')
+ eq_(mock_gbp(['--git-submodules', '--git-upstream-tree=%s' %
+ upstr_branch, '--git-ignore-new']), 2)
+ self._check_log(-2,
+ ".*Error generating submodules' archives: "
+ "Failed to list submodules of [0-9a-f]{40}: fatal: "
+ "not a tree object")
+
+ def test_option_submodules_native(self):
+ """Test the --git-submodules option for native packages"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ # Create submodule
+ sub_repo = self.orig_repos['gbp-test-native2']
+ repo.add_submodule(sub_repo.path)
+ repo.commit_all('Add submodule')
+
+ sub_files = sub_repo.ls_tree('HEAD')
+ master_files = ['gbp-test-native-1.0/' + path.decode() for
+ path in repo.ls_tree('HEAD')]
+
+ # Test
+ eq_(mock_gbp(['--git-submodules']), 0)
+ zip_files = ls_zip('../rpmbuild/SOURCES/gbp-test-native-1.0.zip', False)
+ ref_files = master_files + ['gbp-test-native-1.0/gbp-test-native2.repo/' + path.decode() for
+ path in sub_files]
+ self.check_files(ref_files, zip_files)
+
+ # Test submodule failure
+ shutil.rmtree('gbp-test-native2.repo')
+ repo.create('gbp-test-native2.repo')
+ eq_(mock_gbp(['--git-submodules', '--git-ignore-new']), 1)
+
+ def test_option_builder(self):
+ """Test --git-builder option and it's args"""
+ self.init_test_repo('gbp-test-native')
+ base_args = ['arg0', '--git-notify=off']
+
+ # Try rpmbuild with default args
+ eq_(gbp_rpm(base_args + ['--git-builder=rpmbuild', '--nodeps']), 0)
+
+ # Build without builder args
+ builder_script = 'echo -n $* > builder_args.txt'
+ eq_(gbp_rpm(base_args + ['--git-builder=%s' % builder_script]), 0)
+ with open('../rpmbuild/builder_args.txt') as fobj:
+ args = fobj.read()
+ eq_(args, 'gbp-test-native.spec')
+
+ # Build with builder args
+ eq_(gbp_rpm(base_args + ['--git-builder=%s' % builder_script,
+ '--arg1', '--arg2']), 0)
+ with open('../rpmbuild/builder_args.txt') as fobj:
+ args = fobj.read()
+ eq_(args, '--arg1 --arg2 gbp-test-native.spec')
+
+ def test_option_cleaner(self):
+ """Test --git-cleaner option"""
+ self.init_test_repo('gbp-test-native')
+
+ # Make repo dirty
+ with open('untracked-file', 'w') as fobj:
+ fobj.write('this file is not tracked\n')
+
+ # Build on dirty repo should fail
+ eq_(mock_gbp([]), 1)
+
+ # Build should succeed with cleaner
+ eq_(mock_gbp(['--git-cleaner=rm untracked-file']), 0)
+
+ def test_hook_options(self):
+ """Test different hook options"""
+ self.init_test_repo('gbp-test-native')
+
+ cleaner = 'echo -n cleaner >> ../hooks'
+ postexport = 'echo -n postexport >> $GBP_TMP_DIR/../hooks'
+ prebuild = 'echo -n prebuild >> $GBP_BUILD_DIR/../hooks'
+ postbuild = 'echo -n postbuild >> $GBP_BUILD_DIR/../hooks'
+ posttag = 'echo -n posttag >> ../hooks'
+ args = ['--git-cleaner=%s' % cleaner,
+ '--git-postexport=%s' % postexport,
+ '--git-prebuild=%s' % prebuild,
+ '--git-postbuild=%s' % postbuild,
+ '--git-posttag=%s' % posttag]
+
+ # Only cleaner and posttag is run when tagging
+ eq_(mock_gbp(args + ['--git-tag-only', '--git-packaging-tag=tag1']), 0)
+ self.check_and_rm_file('../hooks', 'cleanerposttag')
+
+ # Export and build scripts are run when not tagging
+ eq_(mock_gbp(args), 0)
+ self.check_and_rm_file('../hooks', 'cleanerpostexportprebuildpostbuild')
+ shutil.rmtree('../rpmbuild')
+
+ # All hooks are run when building
+ eq_(mock_gbp(args + ['--git-tag', '--git-packaging-tag=tag2']), 0)
+ self.check_and_rm_file('../hooks',
+ 'cleanerpostexportprebuildpostbuildposttag')
+ shutil.rmtree('../rpmbuild')
+
+ # Run with hooks disabled
+ eq_(mock_gbp(args + ['--git-no-hooks']), 0)
+ ok_(not os.path.exists('../hooks'))
+
+ def test_builddir_options(self):
+ """Test the options related to different build directories"""
+ self.init_test_repo('gbp-test-native')
+
+ eq_(mock_gbp(['--git-export-dir=../foo',
+ '--git-export-sourcedir=source',
+ '--git-export-specdir=spec']), 0)
+
+ # Check all directories
+ eq_(set(os.listdir('../foo')),
+ set(['BUILD', 'BUILDROOT', 'RPMS', 'source', 'spec', 'SRPMS']))
+
+ def test_export_failure(self):
+ """Test export dir permission problems"""
+
+ if os.getenv("FAKED_MODE"):
+ raise SkipTest("Skipping we're running under fakeroot")
+
+ self.init_test_repo('gbp-test-native')
+ s_rwx = stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC
+
+ # Pre-create all files
+ eq_(mock_gbp(['--git-builder=true']), 0)
+
+ # Error in exporting packaging files
+ os.chmod('../rpmbuild/SOURCES', 0)
+ try:
+ eq_(mock_gbp(['--git-builder=true']), 1)
+ finally:
+ os.chmod('../rpmbuild/SOURCES', s_rwx)
+ self._check_log(-1, ".*Error exporting packaging files")
+
+ # Error in creating archive
+ os.chmod('../rpmbuild/SOURCES/gbp-test-native-1.0.zip', 0)
+ try:
+ eq_(mock_gbp(['--git-builder=true']), 1)
+ finally:
+ os.chmod('../rpmbuild/SOURCES/gbp-test-native-1.0.zip', s_rwx)
+ self._check_log(-1, ".*Cannot create source tarball at '../rpmbuild/SOURCES'")
+
+ def test_option_export(self):
+ """Test the --git-export-option"""
+ repo = self.init_test_repo('gbp-test')
+
+ # Test exporting of some other commit than HEAD
+ eq_(mock_gbp(['--git-export=release/1.0-1']), 0)
+ eq_(os.listdir('../rpmbuild/RPMS/noarch'),
+ ['gbp-test-1.0-1.noarch.rpm'])
+ self.check_rpms('../rpmbuild/RPMS/*')
+
+ # Modify one tracked file, create one untracked and one ignored file
+ with open('foo.txt', 'a') as fobj:
+ fobj.write('staged')
+ fobj.flush()
+ repo.add_files('foo.txt')
+ fobj.write('unstaged')
+ with open('untracked', 'w') as fobj:
+ fobj.write('untracked')
+ with open('ignored.tmp', 'w') as fobj:
+ fobj.write('ignored')
+
+ base_args = ['--git-ignore-new', '--git-builder=true']
+ # Test exporting of git index
+ foo_txt_index = repo.show('HEAD:foo.txt').decode() + 'staged'
+ eq_(mock_gbp(base_args + ['--git-export=INDEX']), 0)
+ self.check_and_rm_file('../rpmbuild/SOURCES/foo.txt', foo_txt_index)
+ ok_(not os.path.exists('../rpmbuild/SOURCES/untracked'))
+ ok_(not os.path.exists('../rpmbuild/SOURCES/ignored.tmp'))
+ shutil.rmtree('../rpmbuild')
+
+ # Test exporting of working copy (include all files)
+ eq_(mock_gbp(base_args + ['--git-export=WC']), 0)
+ foo_txt_wc = repo.show('HEAD:foo.txt').decode() + 'staged' + 'unstaged'
+ self.check_and_rm_file('../rpmbuild/SOURCES/foo.txt', foo_txt_wc)
+ self.check_and_rm_file('../rpmbuild/SOURCES/untracked', 'untracked')
+ self.check_and_rm_file('../rpmbuild/SOURCES/ignored.tmp', 'ignored')
+ shutil.rmtree('../rpmbuild')
+
+ # Test exporting an invalid treeish
+ eq_(mock_gbp(base_args + ['--git-export=invalid-treeish']), 1)
+ self._check_log(-1, "gbp:error: Failed to determine export treeish")
+
+ def test_option_spec_file(self):
+ """Test the --git-spec-file cmdline option"""
+ repo = self.init_test_repo('gbp-test2')
+
+ eq_(mock_gbp(['--git-spec-file=foo.spec']), 1)
+ self._check_log(-1, "gbp:error: Can't parse spec: Git error")
+
+ eq_(mock_gbp(['--git-spec-file=']), 1)
+ self._check_log(-1, "gbp:error: Can't parse spec: Multiple spec files")
+
+ eq_(mock_gbp(['--git-spec-file=packaging/gbp-test2.spec']), 0)
+
+ # No spec file found error
+ repo.set_branch('upstream')
+ eq_(mock_gbp([]), 1)
+ self._check_log(-1, ".*Can't parse spec: No spec file found")
+
+ def test_option_packaging_dir(self):
+ """Test the --git-packaging-dir cmdline option"""
+ self.init_test_repo('gbp-test-native')
+
+ eq_(mock_gbp(['--git-packaging-dir=foo']), 1)
+ self._check_log(-1, "gbp:error: Can't parse spec: No spec file found")
+
+ # Packaging dir should be taken from spec file if it is defined
+ eq_(mock_gbp(['--git-packaging-dir=foo',
+ '--git-spec-file=packaging/gbp-test-native.spec']), 0)
+
+ def test_option_spec_vcs_tag(self):
+ """Test the --git-spec-vcs-tag cmdline option"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ eq_(mock_gbp(['--git-spec-vcs-tag=foobar-%(commit)s']), 0)
+ sha1 = repo.rev_parse('HEAD')
+ num_tags = 0
+ with open('../rpmbuild/SPECS/gbp-test-native.spec') as fobj:
+ for line in fobj.readlines():
+ if line.startswith('VCS: '):
+ ok_(re.match(r'VCS:\s+foobar-%s\n$' % sha1, line))
+ num_tags += 1
+ eq_(num_tags, 1)
+
+ # Test invalid key
+ eq_(mock_gbp(['--git-spec-vcs-tag=%(invalid-key)s']), 1)
+ self._check_log(-1, r".*Failed to format %\(invalid-key\)s")
diff --git a/tests/component/rpm/test_import_orig_rpm.py b/tests/component/rpm/test_import_orig_rpm.py
new file mode 100644
index 0000000..c27601d
--- /dev/null
+++ b/tests/component/rpm/test_import_orig_rpm.py
@@ -0,0 +1,311 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Test importing RPMs with git-import-orig"""
+
+import os
+import shutil
+import sys
+import subprocess
+from nose.tools import eq_
+from io import StringIO
+
+from gbp.scripts.import_orig import main as import_orig
+
+from tests.component import ComponentTestBase, ComponentTestGitRepository
+from tests.component.rpm import RPM_TEST_DATA_DIR
+
+# Disable "Method could be a function warning"
+# pylint: disable=R0201
+
+DATA_DIR = os.path.join(RPM_TEST_DATA_DIR, 'orig')
+
+
+def mock_import(args, stdin_data="\n\n", cwd=None):
+ """Wrapper for import-orig for feeding mock stdin data to it"""
+ old_cwd = os.path.abspath(os.path.curdir)
+ if cwd:
+ os.chdir(cwd)
+
+ # Create stub file with mock data
+ mock_stdin = StringIO()
+ mock_stdin.write(stdin_data)
+ mock_stdin.seek(0)
+
+ # Call import-orig-rpm with mock data
+ sys.stdin = mock_stdin
+ ret = import_orig(['arg0'] + args)
+ sys.stdin = sys.__stdin__
+ mock_stdin.close()
+
+ # Return to original working directory
+ if cwd:
+ os.chdir(old_cwd)
+ return ret
+
+
+class ImportOrigTestBase(ComponentTestBase):
+ """Base class for all import-orig-rpm unit tests"""
+
+ @classmethod
+ def setUpClass(cls):
+ """Class setup, common for all test cases"""
+ super(ImportOrigTestBase, cls).setUpClass()
+
+ def __init__(self, *args, **kwargs):
+ super(ImportOrigTestBase, self).__init__(*args, **kwargs)
+
+ def setUp(self):
+ """Test case setup"""
+ super(ImportOrigTestBase, self).setUp()
+
+ @classmethod
+ def check_tree(cls, repo, treeish, filelist):
+ """Check the contents (list of files) in a git treeish"""
+ treeish_files = repo.ls_tree(treeish)
+ ImportOrigTestBase.check_files(treeish_files, filelist)
+
+
+class TestImportOrig(ImportOrigTestBase):
+ """Basic tests for git-import-orig-rpm"""
+
+ def test_invalid_args(self):
+ """
+ See that import-orig-rpm fails gracefully when called with invalid args
+ """
+ repo = ComponentTestGitRepository.create('.')
+ origs = [os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2'),
+ os.path.join(DATA_DIR, 'gbp-test-1.1.tar.bz2')]
+ # Test empty args
+ eq_(mock_import([]), 1)
+ self._clear_log()
+
+ # Test multiple archives
+ eq_(mock_import([] + origs), 1)
+ self._check_log(0, 'gbp:error: More than one archive specified')
+ self._clear_log()
+
+ # Check that nothing is in the repo
+ self._check_repo_state(repo, None, [])
+
+ def test_import_outside_repo(self):
+ """Test importing when not in a git repository"""
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ # Import should fail
+ eq_(mock_import([orig]), 1)
+ self._check_log(0, 'gbp:error: %s is not a git repository' %
+ os.path.abspath(os.getcwd()))
+
+ def test_invalid_config_file(self):
+ """Test invalid config file"""
+ # Create dummy invalid config file and try to import (should fail)
+ ComponentTestGitRepository.create('.')
+ with open('.gbp.conf', 'w') as conffd:
+ conffd.write('foobar\n')
+ eq_(mock_import(['foo']), 3)
+ self._check_log(0, 'gbp:error: File contains no section headers.')
+
+ def test_import_tars(self):
+ """Test importing of tarballs, with and without merging"""
+ repo = ComponentTestGitRepository.create('.')
+ # Import first version, with --merge
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ eq_(mock_import(['--merge', orig]), 0)
+ files = ['Makefile', 'README', 'dummy.sh']
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+ eq_(len(repo.get_commits(until='master')), 1)
+ eq_(len(repo.get_commits(until='upstream')), 1)
+ eq_(repo.get_tags(), ['upstream/1.0'])
+
+ # Import second version, don't merge to master branch
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.1.tar.bz2')
+ eq_(mock_import(['--no-merge', orig]), 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+ eq_(len(repo.get_commits(until='master')), 1)
+ eq_(len(repo.get_commits(until='upstream')), 2)
+ eq_(repo.get_tags(), ['upstream/1.0', 'upstream/1.1'])
+ # Check that master is based on v1.0
+ sha1 = repo.rev_parse("%s^0" % 'upstream/1.0')
+ eq_(repo.get_merge_base('master', 'upstream'), sha1)
+
+ def test_import_zip(self):
+ """Test importing of zip archive"""
+ repo = ComponentTestGitRepository.create('.')
+ # Import zip with, no master branch should be present
+ orig = os.path.join(DATA_DIR, 'gbp-test-native-1.0.zip')
+ files = ['.gbp.conf', 'packaging/gbp-test-native.spec',
+ 'dummy.sh', 'README', 'Makefile']
+ eq_(mock_import([orig]), 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+ eq_(repo.get_tags(), ['upstream/1.0'])
+
+ def test_branch_update(self):
+ """Check that the working copy is kept in sync with branch HEAD"""
+ repo = ComponentTestGitRepository.create('.')
+ orig1 = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ orig2 = os.path.join(DATA_DIR, 'gbp-test-1.1.tar.bz2')
+ eq_(mock_import(['--merge', orig1]), 0)
+ repo.set_branch('upstream')
+ eq_(mock_import([orig2]), 0)
+ files = ['Makefile', 'README', 'dummy.sh']
+ self._check_repo_state(repo, 'upstream', ['master', 'upstream'], files)
+ eq_(len(repo.get_commits(until='upstream')), 2)
+
+ def test_import_dir(self):
+ """Test importing of unpacked sources"""
+ # Unpack sources and create repo
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ subprocess.Popen(['tar', 'xf', orig])
+ repo = ComponentTestGitRepository.create('myrepo')
+ os.chdir('myrepo')
+
+ # Import dir first, fool the version to be 0.9
+ eq_(mock_import(['../gbp-test'], 'gbp-test\n0.9\n'), 0)
+ files = ['Makefile', 'README', 'dummy.sh']
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+
+ # Import from unpacked and check that the contents is the same
+ eq_(mock_import([orig]), 0)
+ eq_(len(repo.diff('upstream/0.9', 'upstream/1.0')), 0)
+
+ def test_basic_filtering(self):
+ """Basic test for import filter"""
+ repo = ComponentTestGitRepository.create('.')
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.1.with_dotgit.tar.bz2')
+ # Try importing a tarball with git metadata
+ eq_(mock_import([orig], 'gbp-test\n1.0\n'), 1)
+ self._check_log(0, 'gbp:error: The orig tarball contains .git')
+
+ # Try filtering out .git directory and shell scripts
+ eq_(mock_import(['--filter=.git', '--filter=*.sh', '--merge', orig],
+ 'gbp-test\n1.0\n'), 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ eq_(len(repo.get_commits(until='master')), 1)
+ eq_(len(repo.get_commits(until='upstream')), 1)
+ eq_(repo.get_tags(), ['upstream/1.0'])
+ added_files = repo.get_commit_info('upstream')['files']['A']
+ eq_(set(added_files), set([b'Makefile', b'README']))
+
+ def test_noninteractive(self):
+ """Test non-interactive mode"""
+ repo = ComponentTestGitRepository.create('testrepo')
+ orig = os.path.join(DATA_DIR, 'gbp-test-native-1.0.zip')
+ orig_renamed = os.path.join(os.path.abspath('.'), 'foo.zip')
+ shutil.copy(orig, orig_renamed)
+ os.chdir('testrepo')
+
+ # Guessing name and version should fail
+ eq_(mock_import(['--no-interactive', orig_renamed]), 1)
+ self._check_log(-1, "gbp:error: Couldn't determine upstream package")
+
+ # Guessing from the original archive should succeed
+ eq_(mock_import(['--no-interactive', '--merge', orig],
+ stdin_data=''), 0)
+ files = ['.gbp.conf', 'Makefile', 'README', 'dummy.sh',
+ 'packaging/gbp-test-native.spec']
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+ eq_(len(repo.get_commits(until='master')), 1)
+
+ def test_misc_options(self):
+ """Test various options of git-import-orig-rpm"""
+ repo = ComponentTestGitRepository.create('.')
+ # Import one orig with default options to get upstream and
+ # packaging branch
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ eq_(mock_import(['--debian-branch=pack',
+ '--upstream-branch=orig', '-u0.8', orig]), 0)
+ self._check_repo_state(repo, 'pack', ['pack', 'orig'])
+
+ # Import the "native" zip to get packaging files
+ orig = os.path.join(DATA_DIR, 'gbp-test-native-1.0.zip')
+ base_args = ['--debian-branch=pack', '--upstream-branch=orig',
+ '--upstream-tag=orig/%(version)s', '--merge']
+ # Fake version to be 0.9
+ extra_args = ['-u0.9', '--upstream-vcs-tag=upstream/0.8', orig]
+ eq_(mock_import(base_args + extra_args), 0)
+ # Check repository state
+ files = ['dummy.sh', 'packaging/gbp-test-native.spec',
+ '.gbp.conf', 'Makefile', 'README']
+ self._check_repo_state(repo, 'pack', ['pack', 'orig'], files)
+ eq_(len(repo.get_commits(until='pack')), 3)
+ # Check tags
+ tags = repo.get_tags()
+ eq_(set(tags), set(['upstream/0.8', 'orig/0.9']))
+
+ # Change to packaging branch and create new commit
+ repo.set_branch('pack')
+ shutil.copy2('.git/HEAD', 'my_new_file')
+ repo.add_files('.')
+ repo.commit_all('My new commit')
+ # Import a new version, name should be taken from spec
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.1.tar.bz2')
+ extra_args = ['--no-interactive', '-u1.1', orig]
+ eq_(mock_import(base_args + extra_args, ''), 0)
+ # Threeupstream versions, "my new" commit and one merge commit
+ eq_(len(repo.get_commits(until='pack')), 6)
+ tags = repo.get_tags()
+ eq_(set(tags), set(['upstream/0.8', 'orig/0.9', 'orig/1.1']))
+
+ def test_import_hooks(self):
+ """Basic test for postimport hook"""
+ repo = ComponentTestGitRepository.create('.')
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+
+ script = ("echo -n branch: $GBP_BRANCH > ../hook.txt")
+ eq_(mock_import(['--postimport', script, '--merge', orig]), 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ eq_(repo.get_tags(), ['upstream/1.0'])
+ with open('../hook.txt', 'r') as hookout:
+ data = hookout.read()
+ eq_(data, 'branch: master')
+
+ def test_hook_error(self):
+ """Test postimport hook failure"""
+ repo = ComponentTestGitRepository.create('.')
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ eq_(mock_import(['--postimport=/bin/false', '--merge', '--no-rollback', orig]), 1)
+ self._check_log(-2, "gbp:error: Postimport-hook '/bin/false' failed:")
+ self._check_log(-1, 'gbp:error: Import of %s failed' % orig)
+ # Other parts of the import should've succeeded
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+
+
+class TestBareRepo(ImportOrigTestBase):
+ """Test importing to a bare repository"""
+
+ def test_basic_import_to_bare_repo(self):
+ """Test importing inside bare git repository"""
+ repo = ComponentTestGitRepository.create('.', bare=True)
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ eq_(mock_import([orig]), 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ eq_(len(repo.get_commits(until='upstream')), 1)
+ eq_(repo.get_tags(), ['upstream/1.0'])
+
+ # Import another version
+ repo.set_branch('upstream')
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.1.tar.bz2')
+ eq_(mock_import([orig]), 0)
+ self._check_repo_state(repo, 'upstream', ['master', 'upstream'])
+ eq_(len(repo.get_commits(until='upstream')), 2)
+
+ def test_pristine_import_to_bare(self):
+ """Test importing inside bare git repository"""
+ repo = ComponentTestGitRepository.create('.', bare=True)
+ orig = os.path.join(DATA_DIR, 'gbp-test-1.0.tar.bz2')
+ eq_(mock_import([orig]), 0)
+ # No pristine-tar branch should be present
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
diff --git a/tests/component/rpm/test_import_srpm.py b/tests/component/rpm/test_import_srpm.py
new file mode 100644
index 0000000..17344b2
--- /dev/null
+++ b/tests/component/rpm/test_import_srpm.py
@@ -0,0 +1,430 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Basic tests for the git-import-srpm tool"""
+
+import os
+import shutil
+import urllib
+from nose.plugins.skip import SkipTest
+from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
+from mock import Mock
+
+import gbp.scripts.import_srpm as import_srpm
+from gbp.git import GitRepository
+from gbp.rpm import SrcRpmFile
+
+from tests.component import ComponentTestBase
+from tests.component.rpm import RPM_TEST_DATA_DIR as DATA_DIR
+from tests.testutils import capture_stderr
+
+# Disable "Method could be a function warning"
+# pylint: disable=R0201
+
+
+def mock_import(args):
+ """Wrapper for import-srpm"""
+ # Call import-orig-rpm with added arg0
+ with capture_stderr():
+ return import_srpm.main(['arg0'] + args)
+
+
+class TestImportPacked(ComponentTestBase):
+ """Test importing of src.rpm files"""
+
+ def test_invalid_args(self):
+ """See that import-srpm fails gracefully if called with invalid args"""
+ eq_(mock_import([]), 1)
+ with assert_raises(SystemExit):
+ mock_import(['--invalid-arg=123'])
+
+ def test_basic_import(self):
+ """Test importing of non-native src.rpm"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+ eq_(mock_import(['--no-pristine-tar', srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test')
+ files = {'Makefile', 'README', 'bar.tar.gz', 'dummy.sh', 'foo.txt',
+ 'gbp-test.spec', 'my.patch', 'my2.patch', 'my3.patch'}
+ self._check_repo_state(repo, 'master', ['master', 'upstream'],
+ files=files,
+ tags=['packaging/1.0-1', 'upstream/1.0'])
+ # Two commits: upstream and packaging files
+ eq_(len(repo.get_commits()), 2)
+
+ def test_basic_import2(self):
+ """Import package with multiple spec files and full url patch"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test2-2.0-0.src.rpm')
+ eq_(mock_import(['--no-pristine-tar', srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test2')
+ files = {'Makefile', 'README', 'bar.tar.gz', 'dummy.sh', 'foo.txt',
+ 'gbp-test2.spec', 'gbp-test2-alt.spec', 'my.patch',
+ 'my2.patch', 'my3.patch'}
+ self._check_repo_state(repo, 'master', ['master', 'upstream'],
+ files=files,
+ tags=['packaging/1%2.0-0', 'upstream/2.0'])
+ # Two commits: upstream and packaging files
+ eq_(len(repo.get_commits()), 2)
+
+ def test_target_dir(self):
+ """Test importing to target dir"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+ eq_(mock_import(['--no-pristine-tar', srpm, 'targetdir']), 0)
+ # Check repository state
+ assert os.path.exists('targetdir')
+ repo = GitRepository('targetdir')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+
+ def test_basic_import_orphan(self):
+ """
+ Test importing of non-native src.rpm to separate packaging and
+ development branches
+ """
+ srpm = os.path.join(DATA_DIR, 'gbp-test2-2.0-0.src.rpm')
+ eq_(mock_import(['--no-pristine-tar', '--orphan-packaging', srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test2')
+ files = {'bar.tar.gz', 'foo.txt', 'gbp-test2.spec',
+ 'gbp-test2-alt.spec', 'my.patch', 'my2.patch', 'my3.patch'}
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+ # Only one commit: the packaging files
+ eq_(len(repo.get_commits()), 1)
+
+ def test_basic_native_import(self):
+ """Test importing of native src.rpm"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-native-1.0-1.src.rpm')
+ eq_(mock_import(['--native', srpm]), 0)
+ # Check repository state
+ files = {'.gbp.conf', 'Makefile', 'README', 'dummy.sh',
+ 'packaging/gbp-test-native.spec'}
+ repo = GitRepository('gbp-test-native')
+ self._check_repo_state(repo, 'master', ['master'],
+ files=files,
+ tags=['packaging/1.0-1'])
+ # Only one commit: the imported source tarball
+ eq_(len(repo.get_commits()), 1)
+
+ def test_import_no_orig_src(self):
+ """Test importing of (native) srpm without orig tarball"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-native2-2.0-0.src.rpm')
+ eq_(mock_import([srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test-native2')
+ self._check_repo_state(repo, 'master', ['master'])
+ # Only one commit: packaging files
+ eq_(len(repo.get_commits()), 1)
+
+ def test_multiple_versions(self):
+ """Test importing of multiple versions"""
+ srpms = [os.path.join(DATA_DIR, x) for x in ['gbp-test-1.0-1.src.rpm',
+ 'gbp-test-1.0-1.other.src.rpm',
+ 'gbp-test-1.1-1.src.rpm']]
+ eq_(mock_import(['--no-pristine-tar', srpms[0]]), 0)
+ repo = GitRepository('gbp-test')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ eq_(len(repo.get_commits()), 2)
+ # Try to import same version again
+ eq_(mock_import([srpms[1]]), 0)
+ eq_(len(repo.get_commits()), 2)
+ eq_(len(repo.get_commits(until='upstream')), 1)
+ eq_(mock_import(['--no-pristine-tar', '--allow-same-version', srpms[1]]), 0)
+ # Added new version of packaging
+ eq_(len(repo.get_commits()), 3)
+ eq_(len(repo.get_commits(until='upstream')), 1)
+ # Import new version
+ eq_(mock_import(['--no-pristine-tar', srpms[2]]), 0)
+ files = {'Makefile', 'README', 'bar.tar.gz', 'dummy.sh', 'foo.txt',
+ 'gbp-test.spec', 'my.patch', 'my2.patch', 'my3.patch'}
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+ eq_(len(repo.get_commits()), 5)
+ eq_(len(repo.get_commits(until='upstream')), 2)
+ # Check number of tags
+ eq_(len(repo.get_tags('upstream/*')), 2)
+ eq_(len(repo.get_tags('packaging/*')), 3)
+
+ def test_import_to_existing(self):
+ """Test importing to an existing repo"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+
+ # Create new repo
+ repo = GitRepository.create('myrepo')
+ os.chdir('myrepo')
+ shutil.copy2('.git/HEAD', 'foobar')
+ repo.add_files('.')
+ repo.commit_all('First commit')
+
+ # Test importing to non-clean repo
+ shutil.copy2('.git/HEAD', 'foobaz')
+ eq_(mock_import(['--create-missing', srpm]), 1)
+ self._check_log(0, 'gbp:error: Repository has uncommitted changes')
+ self._clear_log()
+ os.unlink('foobaz')
+
+ # The first import should fail because upstream branch is missing
+ eq_(mock_import([srpm]), 1)
+ self._check_log(-1, 'Also check the --create-missing-branches')
+ eq_(mock_import(['--no-pristine-tar', '--create-missing', srpm]), 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ # Four commits: our initial, upstream and packaging files
+ eq_(len(repo.get_commits()), 3)
+
+ # The import should fail because missing packaging-branch
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.1-1.src.rpm')
+ eq_(mock_import(['--packaging-branch=foo', srpm]), 1)
+ self._check_log(-1, 'Also check the --create-missing-branches')
+
+ def test_filter(self):
+ """Test filter option"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+ eq_(mock_import(['--no-pristine-tar', '--filter=README', '--filter=mydir', srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test')
+ files = set(['Makefile', 'dummy.sh', 'bar.tar.gz', 'foo.txt',
+ 'gbp-test.spec', 'my.patch', 'my2.patch', 'my3.patch'])
+ self._check_repo_state(repo, 'master', ['master', 'upstream'], files)
+
+ def test_tagging(self):
+ """Test tag options of import-srpm"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+
+ # Invalid packaging tag keywords
+ eq_(mock_import(['--no-pristine-tar', '--packaging-tag=%(foo)s', srpm]),
+ 1)
+ self._check_log(-1, ".*Missing value 'foo' in *.*")
+ # Remove upstream tag
+ repo = GitRepository('gbp-test')
+ repo.delete_tag('upstream/1.0')
+
+ # Invalid upstream tag keywords
+ eq_(mock_import(['--no-pristine-tar', '--upstream-tag=%(foo)s', srpm]),
+ 1)
+ self._check_log(-1, ".*Missing value 'foo' in.*")
+
+ # Try with good keywords, with --skip-packaging-tag
+ eq_(mock_import(['--no-pristine-tar', '--vendor=foo',
+ '--skip-packaging-tag',
+ '--packaging-tag=%(vendor)s/%(version)s',
+ '--upstream-tag=upst/%(version)s', srpm]), 0)
+ eq_(repo.describe('upstream'), 'upst/1.0')
+ eq_(len(repo.get_tags()), 1)
+
+ # Re-import, creating packaging tag
+ eq_(mock_import(['--no-pristine-tar', '--vendor=foo',
+ '--packaging-tag=%(vendor)s/%(version)s',
+ '--upstream-tag=upst/%(version)s', srpm]), 0)
+ eq_(repo.describe('HEAD'), 'foo/1.0-1')
+ eq_(len(repo.get_tags()), 2)
+
+ def test_tagging_native(self):
+ """Test tagging of native packages with import-srpm"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-native-1.0-1.src.rpm')
+
+ # Invalid packaging tag keywords
+ eq_(mock_import(['--no-pristine-tar', '--packaging-tag=%(foo)s',
+ srpm, '--native']), 1)
+ self._check_log(-1, ".*Missing value 'foo' in {.*")
+
+ # Try with good keywords, with --skip-packaging-tag.
+ # Upstream tag format should not matter
+ eq_(mock_import(['--no-pristine-tar', '--vendor=foo', '--native',
+ '--skip-packaging-tag',
+ '--packaging-tag=%(vendor)s/%(version)s',
+ '--upstream-tag=%(foo)s', srpm]), 0)
+ repo = GitRepository('gbp-test-native')
+ eq_(len(repo.get_tags()), 0)
+
+ # Run again, now creating packaging tag
+ eq_(mock_import(['--no-pristine-tar', '--vendor=foo', '--native',
+ '--packaging-tag=%(vendor)s/%(version)s',
+ '--upstream-tag=%(foo)s', srpm]), 0)
+ eq_(repo.describe('HEAD'), 'foo/1.0-1')
+
+ def test_misc_options(self):
+ """Test various options of git-import-srpm"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test2-2.0-0.src.rpm')
+
+ eq_(mock_import(['--no-pristine-tar',
+ '--packaging-branch=pack',
+ '--upstream-branch=orig',
+ '--packaging-dir=packaging',
+ '--packaging-tag=ver_%(upstreamversion)s-rel_%(release)s',
+ '--upstream-tag=orig/%(upstreamversion)s',
+ '--author-is-committer',
+ srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test2')
+ files = {'Makefile', 'README', 'dummy.sh', 'packaging/bar.tar.gz',
+ 'packaging/foo.txt', 'packaging/gbp-test2.spec',
+ 'packaging/gbp-test2-alt.spec', 'packaging/my.patch',
+ 'packaging/my2.patch', 'packaging/my3.patch'}
+ self._check_repo_state(repo, 'pack', ['pack', 'orig'], files)
+ eq_(len(repo.get_commits()), 2)
+ # Check packaging dir
+ eq_(len(repo.get_commits(paths='packaging')), 1)
+ # Check tags
+ tags = repo.get_tags()
+ eq_(set(tags), set(['orig/2.0', 'ver_2.0-rel_0']))
+ # Check git committer/author
+ info = repo.get_commit_info('pack')
+ eq_(info['author'].name, 'Markus Lehtonen')
+ eq_(info['author'].email, 'markus.lehtonen@linux.intel.com')
+ eq_(info['author'].name, info['committer'].name)
+ eq_(info['author'].email, info['committer'].email)
+
+ # Create a new commit by committing an empty tree
+ commit = repo.commit_tree('4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+ msg="Empty commit", parents=[])
+ repo.create_tag('foo/1.0', msg="New tag", commit=commit)
+ # Just blindly import another package on top of this to test more options
+ os.chdir('gbp-test2')
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+ eq_(mock_import(['--upstream-vcs-tag=foo/%(version)s',
+ '--upstream-branch=orig',
+ '--packaging-branch=pack',
+ srpm]), 0)
+ parents = repo.get_commits(until='orig', num=1, options='--format=%P')[0].split()
+ eq_(len(parents), 2)
+ ok_(commit in parents)
+ ok_(repo.rev_parse('orig/2.0^{}') in parents)
+
+
+class TestImportUnPacked(ComponentTestBase):
+ """Test importing of unpacked source rpms"""
+
+ def setUp(self):
+ super(TestImportUnPacked, self).setUp()
+ # Unpack some source rpms
+ os.mkdir('multi-unpack')
+ for pkg in ['gbp-test-1.0-1.src.rpm', 'gbp-test2-2.0-0.src.rpm']:
+ unpack_dir = pkg.replace('.src.rpm', '-unpack')
+ os.mkdir(unpack_dir)
+ pkg_path = os.path.join(DATA_DIR, pkg)
+ SrcRpmFile(pkg_path).unpack(unpack_dir)
+ SrcRpmFile(pkg_path).unpack('multi-unpack')
+
+ def test_import_dir(self):
+ """Test importing of directories"""
+ eq_(mock_import(['--no-pristine-tar', 'gbp-test-1.0-1-unpack']), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+
+ # Check that importing dir with multiple spec files fails
+ eq_(mock_import(['multi-unpack']), 1)
+ self._check_log(-1, 'gbp:error: Failed determine spec file: '
+ 'Multiple spec files found')
+
+ def test_import_spec(self):
+ """Test importing of spec file"""
+ specfile = 'gbp-test2-2.0-0-unpack/gbp-test2.spec'
+ eq_(mock_import([specfile]), 0)
+ # Check repository state
+ ok_(GitRepository('gbp-test2').is_clean())
+
+ def test_missing_files(self):
+ """Test importing of directory with missing packaging files"""
+ specfile = 'gbp-test2-2.0-0-unpack/gbp-test2.spec'
+ os.unlink('gbp-test2-2.0-0-unpack/my.patch')
+ eq_(mock_import([specfile]), 1)
+ self._check_log(-1, "gbp:error: File 'my.patch' listed in spec "
+ "not found")
+
+
+class TestDownloadImport(ComponentTestBase):
+ """Test download functionality"""
+
+ def test_urldownload(self):
+ """Test downloading and importing src.rpm from remote url"""
+ srpm = 'http://raw.github.com/marquiz/git-buildpackage-rpm-testdata/'\
+ 'master/gbp-test-1.0-1.src.rpm'
+ # Mock to use local files instead of really downloading
+ local_fn = os.path.join(DATA_DIR, os.path.basename(srpm))
+ import_srpm.urlopen = Mock()
+ import_srpm.urlopen.return_value = open(local_fn, 'rb')
+
+ eq_(mock_import(['--no-pristine-tar', srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+
+ def test_nonexistent_url(self):
+ """Test graceful failure when trying download from non-existent url"""
+ srpm = 'http://honk.sigxcpu.org/does/not/exist'
+ # Do not connect to remote, mock failure
+ import_srpm.urlopen = Mock()
+ import_srpm.urlopen.side_effect = urllib.error.HTTPError(srpm, 404, "Not found",
+ None, None)
+
+ eq_(mock_import([srpm]), 1)
+ self._check_log(-1, "gbp:error: Download failed: HTTP Error 404")
+ self._clear_log()
+
+ def test_invalid_url(self):
+ """Test graceful failure when trying download from invalid url"""
+ srpm = 'foob://url.does.not.exist.com/foo.src.rpm'
+ eq_(mock_import([srpm]), 1)
+ self._check_log(-1, ".*No such file or directory: 'foob://url.does.not.exist.com/foo.src.rpm")
+ self._clear_log()
+
+
+class TestPristineTar(ComponentTestBase):
+ """Test importing with pristine-tar"""
+
+ @classmethod
+ def setUpClass(cls):
+ if not os.path.exists('/usr/bin/pristine-tar'):
+ raise SkipTest('Skipping %s:%s as pristine-tar tool is not '
+ 'available' % (__name__, cls.__name__))
+ super(TestPristineTar, cls).setUpClass()
+
+ def test_basic_import_pristine_tar(self):
+ """Test importing of non-native src.rpm, with pristine-tar"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+ eq_(mock_import(['--pristine-tar', srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test')
+ self._check_repo_state(repo, 'master', ['master', 'upstream',
+ 'pristine-tar'])
+ # Two commits: upstream and packaging files
+ eq_(len(repo.get_commits()), 2)
+
+ def test_unsupported_archive(self):
+ """Test importing of src.rpm with a zip source archive"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-native-1.0-1.src.rpm')
+ eq_(mock_import(['--pristine-tar', srpm]), 0)
+ # Check repository state
+ repo = GitRepository('gbp-test-native')
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ # Check that a warning is printed
+ self._check_log(-1, "gbp:warning: Ignoring pristine-tar")
+
+
+class TestBareRepo(ComponentTestBase):
+ """Test importing to a bare repository"""
+
+ def test_basic_import_to_bare_repo(self):
+ """Test importing of srpm to a bare git repository"""
+ srpm = os.path.join(DATA_DIR, 'gbp-test-1.0-1.src.rpm')
+ # Create new repo
+ repo = GitRepository.create('myrepo', bare=True)
+ os.chdir('myrepo')
+ eq_(mock_import([srpm]), 0)
+ self._check_repo_state(repo, 'master', ['master', 'upstream'])
+ # Patch import to bare repos not supported -> only 2 commits
+ eq_(len(repo.get_commits(until='master')), 2)
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/component/rpm/test_pq_rpm.py b/tests/component/rpm/test_pq_rpm.py
new file mode 100644
index 0000000..16044fd
--- /dev/null
+++ b/tests/component/rpm/test_pq_rpm.py
@@ -0,0 +1,367 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Tests for the gbp pq-rpm tool"""
+
+import os
+import tempfile
+from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
+
+from gbp.scripts.pq_rpm import main as pq
+from gbp.git import GitRepository
+from gbp.command_wrappers import GitCommand
+from gbp.rpm import SpecFile
+
+from tests.component.rpm import RpmRepoTestBase
+from tests.testutils import capture_stderr
+
+# Disable "Method could be a function warning"
+# pylint: disable=R0201
+
+
+def mock_pq(args):
+ """Wrapper for pq"""
+ # Call pq-rpm with added arg0
+ return pq(['arg0'] + args)
+
+
+class TestPqRpm(RpmRepoTestBase):
+ """Basic tests for gbp-pq-rpm"""
+
+ def _has_patches(self, specfile, patches):
+ spec = SpecFile(specfile)
+ eq_(sorted([p['linevalue'] for p in spec._patches().values()]),
+ sorted(patches))
+
+ def test_invalid_args(self):
+ """See that pq-rpm fails gracefully when called with invalid args"""
+ GitRepository.create('.')
+ # Test empty args
+ eq_(mock_pq([]), 1)
+ self._check_log(0, 'gbp:error: No action given.')
+ self._clear_log()
+
+ # Test invalid command
+ eq_(mock_pq(['mycommand']), 1)
+ self._check_log(0, "gbp:error: Unknown action 'mycommand'")
+ self._clear_log()
+
+ # Test invalid cmdline options
+ with assert_raises(SystemExit):
+ with capture_stderr():
+ mock_pq(['--invalid-arg=123'])
+
+ def test_import_outside_repo(self):
+ """Run pq-rpm when not in a git repository"""
+ eq_(mock_pq(['export']), 1)
+ self._check_log(0, 'gbp:error: %s is not a git repository' %
+ os.path.abspath(os.getcwd()))
+
+ def test_import_export(self):
+ """Basic test for patch import and export"""
+ repo = self.init_test_repo('gbp-test')
+ branches = repo.get_local_branches() + ['patch-queue/master']
+ # Test import
+ eq_(mock_pq(['import']), 0)
+ files = ['AUTHORS', 'dummy.sh', 'Makefile', 'NEWS', 'README',
+ 'mydir/myfile.txt']
+ patches = ['my.patch', '0001-my-gz.patch', '0002-my-bzip2.patch', '0003-my2.patch']
+
+ branches.append('patch-queue/master')
+ self._check_repo_state(repo, 'patch-queue/master', branches, files)
+ eq_(repo.get_merge_base('upstream', 'patch-queue/master'),
+ repo.rev_parse('upstream'))
+ ok_(len(repo.get_commits('', 'upstream')) <
+ len(repo.get_commits('', 'patch-queue/master')))
+
+ # Test export
+ eq_(mock_pq(['export', '--upstream-tag', 'upstream/%(version)s']), 0)
+ files = ['.gbp.conf', '.gitignore', 'bar.tar.gz', 'foo.txt',
+ 'gbp-test.spec'] + patches
+ self._check_repo_state(repo, 'master', branches, files, clean=False)
+ eq_(repo.status()[' M'], [b'gbp-test.spec'])
+ self._has_patches('gbp-test.spec', patches)
+
+ # Another export after removing some patches
+ os.unlink('0001-my-gz.patch')
+ eq_(mock_pq(['export']), 0)
+ self._check_repo_state(repo, 'master', branches, files, clean=False)
+ self._has_patches('gbp-test.spec', patches)
+
+ def test_import_export2(self):
+ """Another test for import and export"""
+ repo = self.init_test_repo('gbp-test2')
+ branches = repo.get_local_branches() + ['patch-queue/master-orphan']
+ repo.set_branch('master-orphan')
+ # Import
+ eq_(mock_pq(['import']), 0)
+ files = ['dummy.sh', 'Makefile', 'README', 'mydir/myfile.txt']
+ patches = ['packaging/0001-PATCH-My-modification.patch', 'my.patch']
+ self._check_repo_state(repo, 'patch-queue/master-orphan', branches,
+ files)
+
+ # Test export with --drop
+ branches.remove('patch-queue/master-orphan')
+ eq_(mock_pq(['export', '--drop', '--upstream-tag',
+ 'upstream/%(version)s', '--spec-file',
+ 'packaging/gbp-test2.spec']), 0)
+ self._check_repo_state(repo, 'master-orphan', branches, clean=False)
+ eq_(repo.status()[' M'], [b'packaging/gbp-test2.spec'])
+ self._has_patches('packaging/gbp-test2.spec', patches)
+
+ def test_rebase(self):
+ """Basic test for rebase action"""
+ repo = self.init_test_repo('gbp-test')
+ repo.rename_branch('pq/master', 'patch-queue/master')
+ repo.set_branch('patch-queue/master')
+ branches = repo.get_local_branches()
+ # Make development branch out-of-sync
+ GitCommand("rebase")(['--onto', 'upstream^', 'upstream'])
+ # Sanity check for our git rebase...
+ ok_(repo.get_merge_base('patch-queue/master', 'upstream') !=
+ repo.rev_parse('upstream'))
+
+ # Do rebase
+ eq_(mock_pq(['rebase']), 0)
+ self._check_repo_state(repo, 'patch-queue/master', branches)
+ ok_(repo.get_merge_base('patch-queue/master', 'upstream') ==
+ repo.rev_parse('upstream'))
+
+ # Get to out-of-sync, again, and try rebase from master branch
+ GitCommand("rebase")(['--onto', 'upstream^', 'upstream'])
+ eq_(mock_pq(['switch']), 0)
+ eq_(mock_pq(['rebase']), 0)
+ self._check_repo_state(repo, 'patch-queue/master', branches)
+ ok_(repo.get_merge_base('patch-queue/master', 'upstream') ==
+ repo.rev_parse('upstream'))
+
+ def test_switch(self):
+ """Basic test for switch action"""
+ repo = self.init_test_repo('gbp-test')
+ branches = repo.get_local_branches() + ['patch-queue/master']
+ repo.create_branch('patch-queue/master')
+
+ # Switch to base branch and back to pq
+ eq_(mock_pq(['switch']), 0)
+ self._check_repo_state(repo, 'patch-queue/master', branches)
+ eq_(mock_pq(['switch']), 0)
+ self._check_repo_state(repo, 'master', branches)
+
+ def test_switch_drop(self):
+ """Basic test for drop action"""
+ repo = self.init_test_repo('gbp-test')
+ repo.rename_branch('pq/master', 'patch-queue/master')
+ repo.set_branch('patch-queue/master')
+ branches = repo.get_local_branches()
+
+ # Switch to master
+ eq_(mock_pq(['switch']), 0)
+ self._check_repo_state(repo, 'master', branches)
+
+ # Drop should succeed when on master branch
+ eq_(mock_pq(['drop']), 0)
+ branches.remove('patch-queue/master')
+ self._check_repo_state(repo, 'master', branches)
+
+ def test_drop_pq(self):
+ """drop action should work on pq branch"""
+ repo = self.init_test_repo('gbp-test')
+ repo.rename_branch('pq/master', 'patch-queue/master')
+ repo.set_branch('patch-queue/master')
+ branches = repo.get_local_branches()
+
+ # Switch to master
+ eq_(mock_pq(['switch']), 0)
+ self._check_repo_state(repo, 'master', branches)
+
+ # Drop should succeed when on master branch
+ eq_(mock_pq(['drop']), 0)
+ branches.remove('patch-queue/master')
+ self._check_repo_state(repo, 'master', branches)
+
+ def test_force_import(self):
+ """Test force import"""
+ repo = self.init_test_repo('gbp-test')
+ pkg_files = [f.decode() for f in repo.list_files()]
+ repo.rename_branch('pq/master', 'patch-queue/master')
+ repo.set_branch('patch-queue/master')
+ branches = repo.get_local_branches()
+ pq_files = [f.decode() for f in repo.list_files()]
+
+ # Re-import should fail
+ eq_(mock_pq(['import']), 1)
+ self._check_log(0, "gbp:error: Already on a patch-queue branch")
+ self._check_repo_state(repo, 'patch-queue/master', branches, pq_files)
+
+ # Mangle pq branch and try force import on top of that
+ repo.force_head('master', hard=True)
+ self._check_repo_state(repo, 'patch-queue/master', branches, pkg_files)
+ eq_(mock_pq(['import', '--force']), 0)
+ # .gbp.conf won't get imported by pq
+ pq_files.remove('.gbp.conf')
+ self._check_repo_state(repo, 'patch-queue/master', branches, pq_files)
+
+ # Switch back to master
+ eq_(mock_pq(['switch']), 0)
+ self._check_repo_state(repo, 'master', branches, pkg_files)
+
+ # Import should fail
+ eq_(mock_pq(['import']), 1)
+ self._check_log(-1, "gbp:error: Patch-queue branch .* already exists")
+ self._check_repo_state(repo, 'master', branches, pkg_files)
+
+ # Force import should succeed
+ eq_(mock_pq(['import', '--force']), 0)
+ self._check_repo_state(repo, 'patch-queue/master', branches, pq_files)
+
+ def test_apply(self):
+ """Basic test for apply action"""
+ repo = self.init_test_repo('gbp-test')
+ upstr_files = ['dummy.sh', 'Makefile', 'README']
+ branches = repo.get_local_branches() + ['patch-queue/master']
+
+ # No patch given
+ eq_(mock_pq(['apply']), 1)
+ self._check_log(-1, "gbp:error: No patch name given.")
+
+ # Create a pristine pq-branch
+ repo.create_branch('patch-queue/master', 'upstream')
+
+ # Apply patch
+ with tempfile.NamedTemporaryFile() as tmp_patch:
+ tmp_patch.write(repo.show('master:%s' % 'my.patch'))
+ tmp_patch.file.flush()
+ eq_(mock_pq(['apply', tmp_patch.name]), 0)
+ self._check_repo_state(repo, 'patch-queue/master', branches,
+ upstr_files)
+
+ # Apply another patch, now when already on pq branch
+ with tempfile.NamedTemporaryFile() as tmp_patch:
+ tmp_patch.write(repo.show('master:%s' % 'my2.patch'))
+ tmp_patch.file.flush()
+ eq_(mock_pq(['apply', tmp_patch.name]), 0)
+ self._check_repo_state(repo, 'patch-queue/master', branches,
+ upstr_files + ['mydir/myfile.txt'])
+
+ def test_option_patch_numbers(self):
+ """Test the --patch-numbers cmdline option"""
+ repo = self.init_test_repo('gbp-test')
+ repo.rename_branch('pq/master', 'patch-queue/master')
+ branches = repo.get_local_branches()
+ # Export
+ eq_(mock_pq(['export', '--no-patch-numbers']), 0)
+ patches = ['my-gz.patch', 'my-bzip2.patch', 'my2.patch', 'my.patch']
+ files = ['.gbp.conf', '.gitignore', 'bar.tar.gz', 'foo.txt',
+ 'gbp-test.spec'] + patches
+ self._check_repo_state(repo, 'master', branches, files, clean=False)
+ self._has_patches('gbp-test.spec', patches)
+
+ def test_option_tmp_dir(self):
+ """Test the --tmp-dir cmdline option"""
+ self.init_test_repo('gbp-test')
+ eq_(mock_pq(['import', '--tmp-dir=foo/bar']), 0)
+ # Check that the tmp dir basedir was created
+ ok_(os.path.isdir('foo/bar'))
+
+ def test_option_upstream_tag(self):
+ """Test the --upstream-tag cmdline option"""
+ repo = self.init_test_repo('gbp-test')
+
+ # Non-existent upstream-tag -> failure
+ eq_(mock_pq(['import', '--upstream-tag=foobar/%(upstreamversion)s']), 1)
+ self._check_log(-1, "gbp:error: Couldn't find upstream version")
+
+ # Create tag -> import should succeed
+ repo.create_tag('foobar/1.1', msg="test tag", commit='upstream')
+ eq_(mock_pq(['import', '--upstream-tag=foobar/%(upstreamversion)s']), 0)
+
+ def test_option_spec_file(self):
+ """Test --spec-file commandline option"""
+ self.init_test_repo('gbp-test')
+
+ # Non-existent spec file should lead to failure
+ eq_(mock_pq(['import', '--spec-file=foo.spec']), 1)
+ self._check_log(-1, "gbp:error: Can't parse spec: Unable to read spec")
+ # Correct spec file
+ eq_(mock_pq(['import', '--spec-file=gbp-test.spec']), 0)
+
+ # Force import on top to test parsing spec from another branch
+ eq_(mock_pq(['import', '--spec-file=gbp-test.spec', '--force',
+ '--upstream-tag', 'upstream/%(version)s']), 0)
+
+ # Test with export, too
+ eq_(mock_pq(['export', '--spec-file=foo.spec']), 1)
+ self._check_log(-1, "gbp:error: Can't parse spec: Unable to read spec")
+ eq_(mock_pq(['export', '--spec-file=gbp-test.spec']), 0)
+
+ def test_option_packaging_dir(self):
+ """Test --packaging-dir command line option"""
+ self.init_test_repo('gbp-test')
+
+ # Wrong packaging dir should lead to failure
+ eq_(mock_pq(['import', '--packaging-dir=foo']), 1)
+ self._check_log(-1, "gbp:error: Can't parse spec: No spec file found")
+ # Use correct packaging dir
+ eq_(mock_pq(['import', '--packaging-dir=.']), 0)
+
+ # Test with export, --spec-file option should override packaging dir
+ eq_(mock_pq(['export', '--packaging-dir=foo', '--upstream-tag',
+ 'upstream/%(version)s',
+ '--spec-file=gbp-test.spec']), 0)
+
+ def test_export_with_merges(self):
+ """Test exporting pq-branch with merge commits"""
+ repo = self.init_test_repo('gbp-test')
+ repo.rename_branch('pq/master', 'patch-queue/master')
+ repo.set_branch('patch-queue/master')
+ branches = repo.get_local_branches()
+
+ # Create a merge commit in pq-branch
+ patches = repo.format_patches('HEAD^', 'HEAD', '.')
+ repo.force_head('HEAD^', hard=True)
+ repo.commit_dir('.', 'Merge with master', 'patch-queue/master',
+ ['master'])
+ merge_rev = repo.rev_parse('HEAD', short=7)
+ eq_(mock_pq(['apply', patches[0].decode()]), 0)
+ upstr_rev = repo.rev_parse('upstream', short=7)
+ os.unlink(patches[0])
+
+ # Export should create diff up to the merge point and one "normal" patch
+ eq_(mock_pq(['export']), 0)
+ patches = ['my.patch',
+ '%s-to-%s.diff' % (upstr_rev, merge_rev), '0002-my2.patch']
+ files = ['.gbp.conf', '.gitignore', 'bar.tar.gz', 'foo.txt',
+ 'gbp-test.spec'] + patches
+ self._check_repo_state(repo, 'master', branches, files, clean=False)
+ self._has_patches('gbp-test.spec', patches)
+
+ def test_import_unapplicable_patch(self):
+ """Test import when a patch does not apply"""
+ repo = self.init_test_repo('gbp-test')
+ branches = repo.get_local_branches()
+ # Mangle patch
+ with open('my2.patch', 'w') as patch_file:
+ patch_file.write('-this-does\n+not-apply\n')
+ eq_(mock_pq(['import']), 1)
+ self._check_log(-2, "Please commit your changes or stash them")
+ self._check_repo_state(repo, 'master', branches, clean=False)
+
+ # Now commit the changes to the patch and try again
+ repo.add_files(['my2.patch'], force=True)
+ repo.commit_files(['my2.patch'], msg="Mangle patch")
+ eq_(mock_pq(['import']), 1)
+ self._check_log(-1, "gbp:error: Import failed: Error running git apply")
+ self._check_repo_state(repo, 'master', branches, clean=False)
diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py
new file mode 100644
index 0000000..97f5d7e
--- /dev/null
+++ b/tests/component/rpm/test_rpm_ch.py
@@ -0,0 +1,334 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2013-2015 Intel Corporation <markus.lehtonen@linux.intel.com>
+# 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
+# <http://www.gnu.org/licenses/>
+"""Tests for the gbp-rpm-ch tool"""
+
+import os
+import re
+from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
+
+from gbp.scripts.rpm_ch import main as rpm_ch
+from gbp.git import GitRepository
+from tests.testutils import capture_stderr
+
+from tests.component.rpm import RpmRepoTestBase
+
+# Disable "Method could be a function warning"
+# pylint: disable=R0201
+
+
+def mock_ch(args):
+ """Wrapper for gbp-rpm-ch"""
+ with capture_stderr():
+ return rpm_ch(['arg0', '--packaging-branch=master',
+ '--spawn-editor=never'] + args)
+
+
+class TestRpmCh(RpmRepoTestBase):
+ """Basic tests for gbp-rpm-ch"""
+
+ def setUp(self):
+ """Test case setup"""
+ super(TestRpmCh, self).setUp()
+ # Set environment so that commits succeed without git config
+ os.environ['GIT_AUTHOR_NAME'] = 'My Name'
+ os.environ['GIT_COMMITTER_NAME'] = 'My Name'
+ os.environ['EMAIL'] = 'me@example.com'
+
+ @staticmethod
+ def read_file(filename):
+ """Read file to a list"""
+ with open(filename) as fobj:
+ return fobj.readlines()
+
+ def test_invalid_args(self):
+ """See that gbp-rpm-ch fails gracefully when called with invalid args"""
+ GitRepository.create('.')
+
+ with assert_raises(SystemExit):
+ mock_ch(['--invalid-opt'])
+
+ def test_import_outside_repo(self):
+ """Run gbp-rpm-ch when not in a git repository"""
+ eq_(mock_ch([]), 1)
+ self._check_log(0, 'gbp:error: No Git repository at ')
+
+ def test_update_spec_changelog(self):
+ """Test updating changelog in spec"""
+ repo = self.init_test_repo('gbp-test')
+ eq_(mock_ch([]), 0)
+ eq_(repo.status(), {' M': [b'gbp-test.spec']})
+
+ def test_update_changes_file(self):
+ """Test updating a separate changes file"""
+ repo = self.init_test_repo('gbp-test-native')
+ eq_(mock_ch([]), 0)
+ eq_(repo.status(), {' M': [b'packaging/gbp-test-native.changes']})
+
+ def test_create_spec_changelog(self):
+ """Test creating changelog in spec file"""
+ repo = self.init_test_repo('gbp-test2')
+ orig_content = self.read_file('packaging/gbp-test2.spec')
+
+ # Fails if no starting point is given
+ eq_(mock_ch([]), 1)
+ self._check_log(-1, "gbp:error: Couldn't determine starting point")
+
+ # Give starting point
+ eq_(mock_ch(['--since=HEAD^']), 0)
+ eq_(repo.status(), {' M': [b'packaging/gbp-test2.spec']})
+ content = self.read_file('packaging/gbp-test2.spec')
+ # Should contain 4 lines (%changelog, header, 1 entry and an empty line)
+ eq_(len(content), len(orig_content) + 4)
+
+ def test_create_changes_file(self):
+ """Test creating a separate changes file"""
+ repo = self.init_test_repo('gbp-test2')
+
+ # Fails if no starting point is given
+ eq_(mock_ch(['--changelog-file=CHANGES']), 1)
+ self._check_log(-1, "gbp:error: Couldn't determine starting point")
+
+ # Give starting point
+ eq_(mock_ch(['--since=HEAD^', '--changelog-file=CHANGES']), 0)
+ eq_(repo.status(), {'??': [b'packaging/gbp-test2.changes']})
+ content = self.read_file('packaging/gbp-test2.changes')
+ # Should contain 3 lines (header, 1 entry and an empty line)
+ eq_(len(content), 3)
+
+ def test_option_changelog_file(self):
+ """Test the --changelog-file cmdline option"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ # Guess changelog file
+ eq_(mock_ch(['--changelog-file=CHANGES']), 0)
+ eq_(repo.status(), {' M': [b'packaging/gbp-test-native.changes']})
+
+ # Use spec file as changelog
+ eq_(mock_ch(['--changelog-file=SPEC', '--since=HEAD^']), 0)
+ eq_(repo.status(), {' M': [b'packaging/gbp-test-native.changes',
+ b'packaging/gbp-test-native.spec']})
+
+ # Arbitrary name
+ eq_(mock_ch(['--changelog-file=foo.changes', '--since=HEAD^']), 0)
+ eq_(repo.status(), {' M': [b'packaging/gbp-test-native.changes',
+ b'packaging/gbp-test-native.spec'],
+ '??': [b'foo.changes']})
+
+ def test_option_spec_file(self):
+ """Test the --spec-file cmdline option"""
+ repo = self.init_test_repo('gbp-test2')
+
+ eq_(mock_ch(['--spec-file=foo.spec']), 1)
+ self._check_log(-1, "gbp:error: Unable to read spec file")
+
+ eq_(mock_ch(['--spec-file=']), 1)
+ self._check_log(-1, "gbp:error: Multiple spec files found")
+
+ eq_(mock_ch(['--spec-file=packaging/gbp-test2.spec', '--since=HEAD^']),
+ 0)
+ eq_(repo.status(), {' M': [b'packaging/gbp-test2.spec']})
+
+ def test_option_packaging_dir(self):
+ """Test the --packaging-dir cmdline option"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ eq_(mock_ch(['--packaging-dir=foo']), 1)
+ self._check_log(-1, "gbp:error: No spec file found")
+
+ # Packaging dir should be taken from spec file if it is defined
+ eq_(mock_ch(['--packaging-dir', 'foo', '--spec-file',
+ 'packaging/gbp-test-native.spec']), 0)
+ eq_(repo.status(), {' M': [b'packaging/gbp-test-native.changes']})
+
+ def test_branch_options(self):
+ """Test the --packaging-branch and --ignore-branch cmdline options"""
+ self.init_test_repo('gbp-test-native')
+
+ eq_(mock_ch(['--packaging-branch=foo']), 1)
+ self._check_log(-2, "gbp:error: You are not on branch 'foo'")
+
+ eq_(mock_ch(['--packaging-branch=foo', '--ignore-branch']), 0)
+
+ def test_option_no_release(self):
+ """Test the --no-release cmdline option"""
+ self.init_test_repo('gbp-test-native')
+ orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+ eq_(mock_ch(['--no-release']), 0)
+ content = self.read_file('packaging/gbp-test-native.changes')
+ # Only one line (entry) added
+ eq_(len(content), len(orig_content) + 1)
+
+ def test_author(self):
+ """Test determining the author name/email"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ # Test taking email address from env
+ os.environ['EMAIL'] = 'user@host.com'
+ eq_(mock_ch([]), 0)
+ header = self.read_file('packaging/gbp-test-native.changes')[0]
+ ok_(re.match(r'.+ <user@host\.com> .+', header))
+
+ # Missing git config setting should not cause a failure
+ del os.environ['EMAIL']
+ del os.environ['GIT_AUTHOR_NAME']
+ os.environ['GIT_CONFIG_NOSYSTEM'] = '1'
+ os.environ['HOME'] = os.path.abspath('.')
+ eq_(mock_ch(['--git-author', '--since=HEAD^1']), 0)
+
+ # Test the --git-author option
+ saved_author = os.environ.get('GIT_AUTHOR_NAME')
+ saved_email = os.environ.get('GIT_AUTHOR_EMAIL')
+ os.environ['GIT_AUTHOR_NAME'] = 'John Doe'
+ os.environ['GIT_AUTHOR_EMAIL'] = 'jd@host.com'
+ with open(os.path.join(repo.git_dir, 'config'), 'a') as fobj:
+ fobj.write('[user]\n name=John Doe\n email=jd@host.com\n')
+ eq_(mock_ch(['--git-author', '--since=HEAD^']), 0)
+ header = self.read_file('packaging/gbp-test-native.changes')[0]
+ ok_(re.match(r'.+ John Doe <jd@host\.com> .+', header), header)
+ if saved_author:
+ os.environ['GIT_AUTHOR_NAME'] = saved_author
+ if saved_email:
+ os.environ['GIT_AUTHOR_EMAIL'] = saved_email
+
+ def test_option_full(self):
+ """Test the --full cmdline option"""
+ repo = self.init_test_repo('gbp-test-native')
+ orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+ eq_(mock_ch(['--full', '--since=HEAD^']), 0)
+ commit_msg_body = repo.get_commit_info('HEAD')['body']
+ full_msg = [line for line in commit_msg_body.splitlines() if line]
+ content = self.read_file('packaging/gbp-test-native.changes')
+ # New lines: header, 1 entry "header", entry "body" from commit message
+ # and one empty line
+ eq_(len(content), len(orig_content) + 3 + len(full_msg))
+
+ def test_option_ignore_regex(self):
+ """Test the --ignore-regex cmdline option"""
+ repo = self.init_test_repo('gbp-test-native')
+ orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+ eq_(mock_ch(['--full', '--since', 'HEAD^', '--ignore-regex',
+ 'Signed-off-by:.*']), 0)
+ commit_msg_body = repo.get_commit_info('HEAD')['body']
+ full_msg = [line for line in commit_msg_body.splitlines() if
+ (line and not line.startswith('Signed-off-by:'))]
+ content = self.read_file('packaging/gbp-test-native.changes')
+ # New lines: header, 1 entry "header", filtered entry "body" from
+ # commit message and one empty line
+ eq_(len(content), len(orig_content) + 3 + len(full_msg))
+
+ def test_option_id_len(self):
+ """Test the --id-len cmdline option"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ eq_(mock_ch(['--id-len=10']), 0)
+ commit_id = repo.rev_parse('HEAD', 10)
+ content = self.read_file('packaging/gbp-test-native.changes')
+ ok_(content[1].startswith('- [%s] ' % commit_id))
+
+ def test_option_changelog_revision(self):
+ """Test the --id-len cmdline option"""
+ self.init_test_repo('gbp-test-native')
+
+ # Test invalid format (unknown field)
+ eq_(mock_ch(['--changelog-revision=%(unknown_field)s']), 1)
+ self._check_log(-1, 'gbp:error: Unable to construct revision field')
+
+ # Test acceptable format
+ eq_(mock_ch(['--changelog-revision=foobar']), 0)
+ header = self.read_file('packaging/gbp-test-native.changes')[0]
+ ok_(re.match(r'.+ foobar$', header))
+
+ def test_option_editor_cmd(self):
+ """Test the --editor-cmd and --spawn-editor cmdline options"""
+ repo = self.init_test_repo('gbp-test-native')
+ eq_(mock_ch(['--spawn-editor=release', '--editor-cmd=rm']), 0)
+ eq_(repo.status(), {' D': [b'packaging/gbp-test-native.changes']})
+
+ repo.force_head('HEAD', hard=True)
+ ok_(repo.is_clean())
+
+ os.environ['EDITOR'] = 'rm'
+ eq_(mock_ch(['--spawn-editor=always', '--editor-cmd=']),
+ 0)
+
+ def test_user_customizations(self):
+ """Test the user customizations"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ # Non-existent customization file
+ eq_(mock_ch(['--customizations=customizations.py']), 1)
+
+ # Create user customizations file
+ with open('customizations.py', 'w') as fobj:
+ fobj.write("class ChangelogEntryFormatter(object):\n")
+ fobj.write(" @classmethod\n")
+ fobj.write(" def compose(cls, commit_info, **kwargs):\n")
+ fobj.write(" return ['- %s' % commit_info['id']]\n")
+
+ eq_(mock_ch(['--customizations=customizations.py']), 0)
+ entry = self.read_file('packaging/gbp-test-native.changes')[1]
+ sha = repo.rev_parse('HEAD')
+ eq_(entry, '- %s\n' % sha)
+
+ def test_paths(self):
+ """Test tracking of certain paths only"""
+ repo = self.init_test_repo('gbp-test-native')
+ orig_content = self.read_file('packaging/gbp-test-native.changes')
+
+ # Add new commit with known content
+ self.add_file(repo, 'new-file.txt', 'this is new content\n')
+
+ # Only track a non-existent file
+ eq_(mock_ch(['--since=HEAD^', 'non-existent-path']), 0)
+ content = self.read_file('packaging/gbp-test-native.changes')
+ # New lines: header and one empty line, no entries
+ eq_(len(content), len(orig_content) + 2)
+
+ # Track existing file
+ repo.force_head('HEAD', hard=True)
+ eq_(mock_ch(['--since=HEAD^', 'new-file.txt']), 0)
+ content = self.read_file('packaging/gbp-test-native.changes')
+ # New lines: header, one entry line and one empty line
+ eq_(len(content), len(orig_content) + 3)
+
+ def test_commit_guessing(self):
+ """Basic tests for guessing the starting point"""
+ repo = self.init_test_repo('gbp-test-native')
+
+ # Check 'tagname' that is not found
+ eq_(mock_ch(['--changelog-revision=%(tagname)s']), 0)
+ self._check_log(0, 'gbp:warning: Changelog points to tagname')
+
+ # Check 'upstreamversion' and 'release' fields
+ repo.force_head('HEAD', hard=True)
+ eq_(mock_ch(['--changelog-revision=%(upstreamversion)s-%(release)s']),
+ 0)
+
+ def test_commit_guessing_fail(self):
+ """Test for failure of start commit guessing"""
+ self.init_test_repo('gbp-test-native')
+
+ # Add "very old" header to changelog
+ with open('packaging/gbp-test-native.changes', 'w') as ch_fp:
+ ch_fp.write('* Sat Jan 01 2000 User <user@host.com> 123\n- foo\n')
+ # rpm-ch should fail by not being able to find any commits before the
+ # last changelog section
+ eq_(mock_ch([]), 1)
+ self._check_log(-1, "gbp:error: Couldn't determine starting point")
diff --git a/tests/context.py b/tests/context.py
new file mode 100644
index 0000000..413227c
--- /dev/null
+++ b/tests/context.py
@@ -0,0 +1,61 @@
+# this context.py should be included by all tests
+# idea from http://kennethreitz.com/repository-structure-and-python.html
+
+import os
+import shutil
+import sys
+import tempfile
+
+sys.path.insert(0, os.path.abspath('..'))
+
+import gbp # noqa: E402
+import gbp.log # noqa: E402
+
+gbp.log.setup(False, False)
+
+
+# the top or root dir of the git-buildpackage source tree to be used by tests
+projectdir = os.path.dirname(os.path.dirname(os.path.abspath(gbp.__file__)))
+
+_chdir_backup = None
+_tmpdirs = []
+
+
+def chdir(dir):
+ global _chdir_backup
+ if not _chdir_backup:
+ _chdir_backup = os.path.abspath(os.curdir)
+ os.chdir(str(dir))
+
+
+def new_tmpdir(name):
+ global _tmpdirs
+ prefix = 'gbp_%s_' % name
+ tmpdir = TmpDir(prefix)
+ _tmpdirs.append(tmpdir)
+ return tmpdir
+
+
+def teardown():
+ if _chdir_backup:
+ os.chdir(_chdir_backup)
+ for tmpdir in _tmpdirs:
+ tmpdir.rmdir()
+ del _tmpdirs[:]
+
+
+class TmpDir(object):
+
+ def __init__(self, suffix='', prefix='tmp'):
+ self.path = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
+
+ def rmdir(self):
+ if self.path and not os.getenv("GBP_TESTS_NOCLEAN"):
+ shutil.rmtree(self.path)
+ self.path = None
+
+ def __repr__(self):
+ return self.path
+
+ def join(self, *args):
+ return os.path.join(self.path, *args)
diff --git a/tests/data/brackets-in-subject.patch b/tests/data/brackets-in-subject.patch
new file mode 100644
index 0000000..055a603
--- /dev/null
+++ b/tests/data/brackets-in-subject.patch
@@ -0,0 +1,12 @@
+From: Guido Günther <agx@sigxcpu.org>
+Subject: [text] foobar
+
+ foo
+
+diff --git a/foo b/foo
+new file mode 100644
+index 0000000..257cc56
+--- /dev/null
++++ b/foo
+@@ -0,0 +1 @@
++foo
diff --git a/tests/data/foo.patch b/tests/data/foo.patch
new file mode 100644
index 0000000..c4d0b60
--- /dev/null
+++ b/tests/data/foo.patch
@@ -0,0 +1,12 @@
+From: Guido Günther <agx@sigxcpu.org>
+Subject: foobar
+
+ foo
+
+diff --git a/foo b/foo
+new file mode 100644
+index 0000000..257cc56
+--- /dev/null
++++ b/foo
+@@ -0,0 +1 @@
++foo
diff --git a/tests/data/gbp_config.conf b/tests/data/gbp_config.conf
new file mode 100644
index 0000000..cdd12d7
--- /dev/null
+++ b/tests/data/gbp_config.conf
@@ -0,0 +1,4 @@
+# Data for TestGbpConfig
+
+[config]
+color-scheme = checkcheck
diff --git a/tests/data/gbp_create_remote_repo.conf b/tests/data/gbp_create_remote_repo.conf
new file mode 100644
index 0000000..15c440d
--- /dev/null
+++ b/tests/data/gbp_create_remote_repo.conf
@@ -0,0 +1,8 @@
+# Data for TestGbpCreateRemoteRepo
+
+[remote-config config1]]
+remote-url-pattern = ssh://git.debian.org/git/pkg-libvirt/%(pkg)s.git
+template-dir = /srv/alioth.debian.org/chroot/home/groups/pkg-libvirt/git-template
+
+[remote-config config2]
+remote-url-pattern = ssh://git.debian.org/git/calendarserver/%(pkg)s.git
diff --git a/tests/data/pristine_tar/testfile1 b/tests/data/pristine_tar/testfile1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/data/pristine_tar/testfile1
diff --git a/tests/data/pristine_tar/testfile2 b/tests/data/pristine_tar/testfile2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/data/pristine_tar/testfile2
diff --git a/tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz b/tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz
new file mode 100644
index 0000000..f5dae80
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/bar.tar.gz
Binary files differ
diff --git a/tests/data/rpm/rpmbuild/SOURCES/foo.txt b/tests/data/rpm/rpmbuild/SOURCES/foo.txt
new file mode 100644
index 0000000..25ed442
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/foo.txt
@@ -0,0 +1,3 @@
+FOO:
+
+file for testing rpm support of git-buildpackage.
diff --git a/tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2 b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2
new file mode 100644
index 0000000..7d0759f
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-1.0.tar.bz2
Binary files differ
diff --git a/tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip
new file mode 100644
index 0000000..22a273d
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/gbp-test-native-1.0.zip
Binary files differ
diff --git a/tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz b/tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz
new file mode 100644
index 0000000..7b3eaf3
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/gbp-test2-3.0.tar.gz
Binary files differ
diff --git a/tests/data/rpm/rpmbuild/SOURCES/my.patch b/tests/data/rpm/rpmbuild/SOURCES/my.patch
new file mode 100644
index 0000000..50870df
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/my.patch
@@ -0,0 +1,9 @@
+diff --git a/dummy.sh b/dummy.sh
+index 8c33db6..6f04268 100755
+--- dummy.sh
++++ dummy.sh
+@@ -1,3 +1,3 @@
+ #!/bin/sh
+
+-echo "Hello world"
++echo "Hello GBP"
diff --git a/tests/data/rpm/rpmbuild/SOURCES/my2.patch b/tests/data/rpm/rpmbuild/SOURCES/my2.patch
new file mode 100644
index 0000000..ad5ca2d
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/my2.patch
@@ -0,0 +1,7 @@
+diff --git a/mydir/myfile.txt b/mydir/myfile.txt
+new file mode 100644
+index 0000000..2cdad29
+--- /dev/null
++++ b/mydir/myfile.txt
+@@ -0,0 +1 @@
++Dummy
diff --git a/tests/data/rpm/rpmbuild/SOURCES/my3.patch b/tests/data/rpm/rpmbuild/SOURCES/my3.patch
new file mode 100644
index 0000000..9fee859
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SOURCES/my3.patch
@@ -0,0 +1,7 @@
+diff --git a/README b/README
+index a1311cb..a59f1b9 100644
+--- a/README
++++ b/README
+@@ -1 +1 @@
+-Just for testing git-buildpackage.
++Just for testing GBP.
diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec
new file mode 100644
index 0000000..38b07e4
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native.spec
@@ -0,0 +1,34 @@
+Name: gbp-test-native
+Summary: Test package for git-buildpackage
+Version: 1.0
+Release: 1
+Group: Development/Libraries
+License: GPLv2
+Source1: %{name}-%{version}.zip
+BuildRequires: unzip
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+Mimics a "native" package
+
+
+%prep
+unzip %{SOURCE1}
+%setup -T -D
+
+
+%build
+make
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec
new file mode 100644
index 0000000..34fd33d
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test-native2.spec
@@ -0,0 +1,35 @@
+Name: gbp-test-native2
+Summary: Test package for git-buildpackage
+Version: 2.0
+Release: 0
+Group: Development/Libraries
+License: GPLv2
+Source: foo.txt
+BuildRequires: unzip
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+Mimics a "native" package that doesn't have any source tarball.
+
+
+%prep
+# Just create build dir
+%setup -T -c
+cp %{SOURCE0} .
+
+
+%build
+# Nothing to do
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test.spec
new file mode 100644
index 0000000..c46a734
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test.spec
@@ -0,0 +1,42 @@
+Name: gbp-test
+Summary: Test package for git-buildpackage
+Version: 1.0
+Release: 1
+Group: Development/Libraries
+License: GPLv2
+Source: %{name}-%{version}.tar.bz2
+Source1: foo.txt
+Source20: bar.tar.gz
+# Gbp-Ignore-Patches: 0
+Patch0: my.patch
+Patch10: my2.patch
+Patch20: my3.patch
+
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+
+
+%prep
+%setup -n %{name} -a 20
+
+%patch0
+%patch10 -p1
+
+
+%build
+make
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
+
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
diff --git a/tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec b/tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec
new file mode 100644
index 0000000..8a92725
--- /dev/null
+++ b/tests/data/rpm/rpmbuild/SPECS/gbp-test2.spec
@@ -0,0 +1,60 @@
+Name: gbp-test2
+Summary: Test package 2 for git-buildpackage
+Epoch: 2
+Version: 3.0
+Release: 0
+Group: Development/Libraries
+License: GPLv2
+Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
+Source: foo.txt
+Source20: bar.tar.gz
+# Gbp-Ignore-Patches: -1
+Patch: my.patch
+Patch10: my2.patch
+Patch20: my3.patch
+Packager: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+VCS: myoldvcstag
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+
+%package empty
+Summary: Empty subpackage
+
+%description empty
+Empty subpackage for the %{name} test package.
+
+
+%prep
+%setup -T -n %{name}-%{version} -c -a 10
+
+%patch
+%patch -P 10 -p1
+
+echo "Do things"
+
+# Gbp-Patch-Macros
+
+%build
+make
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
+
+
+%changelog
+* Tue Feb 04 2014 Name <email> 1
+- My change
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
+
+%files empty
+%defattr(-,root,root,-)
diff --git a/tests/data/rpm/specs/gbp-test-native.spec b/tests/data/rpm/specs/gbp-test-native.spec
new file mode 120000
index 0000000..60de36f
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-native.spec
@@ -0,0 +1 @@
+../rpmbuild/SPECS/gbp-test-native.spec \ No newline at end of file
diff --git a/tests/data/rpm/specs/gbp-test-native2.spec b/tests/data/rpm/specs/gbp-test-native2.spec
new file mode 120000
index 0000000..ad13ad6
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-native2.spec
@@ -0,0 +1 @@
+../rpmbuild/SPECS/gbp-test-native2.spec \ No newline at end of file
diff --git a/tests/data/rpm/specs/gbp-test-quirks.spec b/tests/data/rpm/specs/gbp-test-quirks.spec
new file mode 100644
index 0000000..0bb1564
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-quirks.spec
@@ -0,0 +1,30 @@
+#
+# Spec for testing some quirks of spec parsing
+#
+
+Name: pkg_name
+Summary: Spec for testing some quirks of spec parsing
+Version: 0.1
+Release: 1.2
+License: GPLv2
+Source1: foobar.tar.gz
+# Gbp-Ignore-Patches: 2 4 888
+Patch1: 01.patch
+Patch2: 02.patch
+Patch3: 03.patch
+Patch4: 04.patch
+Patch5: 05.patch
+
+%description
+Spec for testing some quirks of spec parsing. No intended for building an RPM.
+
+%prep
+# We don't have Source0 so rpmbuild would fail, but gbp shouldn't crash
+%setup -q
+
+# Patches are applied out-of-order wrt. numbering
+%patch5
+%patch2
+%patch1 -F2
+# Patch 999 does not exist, rpmbuild would fail but GBP should not
+%patch999
diff --git a/tests/data/rpm/specs/gbp-test-reference.spec b/tests/data/rpm/specs/gbp-test-reference.spec
new file mode 100644
index 0000000..050d139
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-reference.spec
@@ -0,0 +1,43 @@
+Name: gbp-test
+Summary: Test package for git-buildpackage
+Version: 1.0
+Release: 1
+Group: Development/Libraries
+License: GPLv2
+Source: %{name}-%{version}.tar.bz2
+Source1: foo.txt
+Source20: bar.tar.gz
+# Gbp-Ignore-Patches: 0
+Patch0: my.patch
+# Patches auto-generated by git-buildpackage:
+Patch1: new.patch
+
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+
+
+%prep
+%setup -n %{name} -a 20
+
+%patch0
+# new.patch
+%patch1 -p1
+
+
+%build
+make
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
+
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
diff --git a/tests/data/rpm/specs/gbp-test-reference2.spec b/tests/data/rpm/specs/gbp-test-reference2.spec
new file mode 100644
index 0000000..0fbe026
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-reference2.spec
@@ -0,0 +1,47 @@
+Name: gbp-test
+VCS: myvcstag
+Summary: Test package for git-buildpackage
+Version: 1.0
+Release: 1
+Group: Development/Libraries
+License: GPLv2
+Source: %{name}-%{version}.tar.bz2
+Source1: foo.txt
+Source20: bar.tar.gz
+# Gbp-Ignore-Patches: 0
+Patch0: my.patch
+# Patches auto-generated by git-buildpackage:
+Patch1: new.patch
+
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+
+
+%prep
+%setup -n %{name} -a 20
+
+%patch0
+# new.patch
+%patch1 -p1
+
+
+%build
+make
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
+
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
+%changelog
+* Wed Feb 05 2014 Name <email> 1
+- New entry
diff --git a/tests/data/rpm/specs/gbp-test-tags.spec b/tests/data/rpm/specs/gbp-test-tags.spec
new file mode 100644
index 0000000..d0f2e9e
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-tags.spec
@@ -0,0 +1,78 @@
+#
+# Spec file for testing all RPM tags (that we know of
+#
+
+%define suse_release %(test -e /etc/SuSE-release && head -n1 /etc/SuSE-release | cut -d ' ' -f2 | cut --output-delimiter=0 -d. -f1,2 || echo 0)
+%if "%{suse_release}" >= "1201"
+%define test_weak_dep_tags 1
+%if "%{suse_release}" < "1302"
+%define test_weak_dep_tags_2 1
+%endif
+%endif
+
+%define test_arch_os_tags %(test -n "$GBP_SKIP_ARCH_OS_TAGS" && echo 0 || echo 1)
+
+%define source_fn_base source
+%define patch_fn_base patch
+
+# Gbp-Undefined-Tag: foobar
+
+# Test that we accept different cases
+NAME: my_name
+version: 0
+ReLeasE: 0
+
+# Rest of the tags
+Epoch: 0
+Summary: my_summary
+License: my_license
+Distribution: my_distribution
+Vendor: my_vendor
+Group: my_group
+Packager: my_packager
+Url: my_url
+Vcs: my_vcs
+Source: my_source
+Patch: my_%patch_fn_base
+Patch0: my_%{patch_fn_base}0
+Nosource: 0
+Nopatch: 0
+#Icon: my_icon
+BuildRoot: my_buildroot
+Provides: my_provides
+Requires: my_requires
+Conflicts: my_conflicts
+Obsoletes: my_obsoletes
+BuildConflicts: my_buildconflicts
+BuildRequires: my_buildrequires
+AutoReqProv: No
+AutoReq: No
+AutoProv: No
+DistTag: my_disttag
+BugUrl: my_bugurl
+
+%if 0%{?test_weak_dep_tags}
+Recommends: my_recommends
+Suggests: my_suggests
+Supplements: my_supplements
+Enhances: my_enhances
+%if 0%{?test_weak_dep_tags_2}
+BuildRecommends:my_buildrecommends
+BuildSuggests: my_buildsuggests
+BuildSupplements:my_buildsupplements
+BuildEnhances: my_buildenhances
+%endif
+%endif
+
+# These should be filtered out by GBP
+%if "%{test_arch_os_tags}" != "0"
+BuildArch: my_buildarch
+ExcludeArch: my_excludearch
+ExclusiveArch: my_exclusivearch
+ExcludeOs: my_excludeos
+ExclusiveOs: my_exclusiveos
+%endif
+
+%description
+Package for testing GBP.
+
diff --git a/tests/data/rpm/specs/gbp-test-updates-reference.spec b/tests/data/rpm/specs/gbp-test-updates-reference.spec
new file mode 100644
index 0000000..5ab8fe6
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-updates-reference.spec
@@ -0,0 +1,43 @@
+#
+# Spec file for testing deleting/adding/updating tags and macros
+#
+
+# Gbp-Undefined-Tag: foobar
+
+# Test that we accept different cases
+Name: my_name
+Version: 0
+Release: 1
+Summary: my_summary
+License: new license
+Distribution: my_distribution
+Group: my_group
+Packager: my_packager
+Url: my_url
+Vcs: my_vcs
+Nosource: 0
+Nopatch: 0
+BuildRoot: my_buildroot
+Provides: my_provides
+Requires: my_requires
+Conflicts: my_conflicts
+Obsoletes: my_obsoletes
+BuildConflicts: my_buildconflicts
+BuildRequires: my_buildrequires
+AutoReqProv: No
+AutoReq: No
+AutoProv: No
+DistTag: my_disttag
+BugUrl: my_bugurl
+
+%description
+Package for testing GBP.
+
+%prep
+%setup -n my_prefix
+
+%patch0 my new args
+
+%build
+
+%install
diff --git a/tests/data/rpm/specs/gbp-test-updates.spec b/tests/data/rpm/specs/gbp-test-updates.spec
new file mode 100644
index 0000000..e68f4b2
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test-updates.spec
@@ -0,0 +1,48 @@
+#
+# Spec file for testing deleting/adding/updating tags and macros
+#
+
+# Gbp-Undefined-Tag: foobar
+
+# Test that we accept different cases
+Name: my_name
+Version: 0
+Release: 1
+Summary: my_summary
+License: my_license
+Distribution: my_distribution
+Vendor: my_vendor
+Group: my_group
+Packager: my_packager
+Url: my_url
+Vcs: my_vcs
+Source: my_source
+Patch: my_%patch_fn_base
+Patch0: my_%{patch_fn_base}0
+Nosource: 0
+Nopatch: 0
+BuildRoot: my_buildroot
+Provides: my_provides
+Requires: my_requires
+Conflicts: my_conflicts
+Obsoletes: my_obsoletes
+BuildConflicts: my_buildconflicts
+BuildRequires: my_buildrequires
+AutoReqProv: No
+AutoReq: No
+AutoProv: No
+DistTag: my_disttag
+BugUrl: my_bugurl
+
+%description
+Package for testing GBP.
+
+%prep
+%setup -n my_prefix
+
+%patch -b my_patch
+%patch -P0 -b my_patch0
+
+%build
+
+%install
diff --git a/tests/data/rpm/specs/gbp-test.spec b/tests/data/rpm/specs/gbp-test.spec
new file mode 120000
index 0000000..30ae284
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test.spec
@@ -0,0 +1 @@
+../rpmbuild/SPECS/gbp-test.spec \ No newline at end of file
diff --git a/tests/data/rpm/specs/gbp-test2-reference.spec b/tests/data/rpm/specs/gbp-test2-reference.spec
new file mode 100644
index 0000000..1882131
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test2-reference.spec
@@ -0,0 +1,61 @@
+Name: gbp-test2
+Summary: Test package 2 for git-buildpackage
+Epoch: 2
+Version: 3.0
+Release: 0
+Group: Development/Libraries
+License: GPLv2
+Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
+Source: foo.txt
+Source20: bar.tar.gz
+# Gbp-Ignore-Patches: -1
+Patch: my.patch
+# Patches auto-generated by git-buildpackage:
+Patch0: new.patch
+Packager: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+
+%package empty
+Summary: Empty subpackage
+
+%description empty
+Empty subpackage for the %{name} test package.
+
+
+%prep
+%setup -T -n %{name}-%{version} -c -a 10
+
+%patch
+
+echo "Do things"
+
+# Gbp-Patch-Macros
+# new.patch
+%if 1
+%patch0 -p1
+%endif
+
+%build
+make
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
+
+
+%changelog
+* Wed Feb 05 2014 Name <email> 2
+- New entry
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
+
+%files empty
+%defattr(-,root,root,-)
diff --git a/tests/data/rpm/specs/gbp-test2-reference2.spec b/tests/data/rpm/specs/gbp-test2-reference2.spec
new file mode 100644
index 0000000..d41f450
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test2-reference2.spec
@@ -0,0 +1,68 @@
+Name: gbp-test2
+Summary: Test package 2 for git-buildpackage
+Epoch: 2
+Version: 3.0
+Release: 0
+Group: Development/Libraries
+License: GPLv2
+Source10: ftp://ftp.host.com/%{name}-%{version}.tar.gz
+Source: foo.txt
+Source20: bar.tar.gz
+# Gbp-Ignore-Patches: -1
+Patch: my.patch
+# Patches auto-generated by git-buildpackage:
+Patch0: 1.patch
+Patch1: 2.patch
+Packager: Markus Lehtonen <markus.lehtonen@linux.intel.com>
+VCS: myvcstag
+
+%description
+Package for testing the RPM functionality of git-buildpackage.
+
+%package empty
+Summary: Empty subpackage
+
+%description empty
+Empty subpackage for the %{name} test package.
+
+
+%prep
+%setup -T -n %{name}-%{version} -c -a 10
+
+%patch
+
+echo "Do things"
+
+# Gbp-Patch-Macros
+# 1.patch
+%if true
+%patch0 -p1
+%endif
+# 2.patch
+%ifarch %ix86
+%patch1 -p1
+%endif
+
+%build
+make
+
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+cp -R * %{buildroot}/%{_datadir}/%{name}
+install %{SOURCE0} %{buildroot}/%{_datadir}/%{name}
+
+
+%changelog
+* Tue Feb 04 2014 Name <email> 1
+- My change
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
+
+%files empty
+%defattr(-,root,root,-)
diff --git a/tests/data/rpm/specs/gbp-test2.spec b/tests/data/rpm/specs/gbp-test2.spec
new file mode 120000
index 0000000..af4080c
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test2.spec
@@ -0,0 +1 @@
+../rpmbuild/SPECS/gbp-test2.spec \ No newline at end of file
diff --git a/tests/data/rpm/specs/gbp-test3.spec b/tests/data/rpm/specs/gbp-test3.spec
new file mode 100644
index 0000000..c0c20c8
--- /dev/null
+++ b/tests/data/rpm/specs/gbp-test3.spec
@@ -0,0 +1,32 @@
+Name: gbp-test3
+Summary: Test package 3 for git-buildpackage
+Version: 1.0
+Release: 0
+Group: Development/Libraries
+License: GPLv2
+Source: %{name}-%{version}.tar.gz
+# Gbp-Ignore-Patches: 10
+Patch: my.patch
+Patch10: my2.patch
+Patch20: my3.patch
+
+%description
+Another test package for git-buildpackage.
+
+
+%prep
+%autosetup -n %{name}-%{version}
+
+
+%build
+make
+
+
+%install
+mkdir -p %{buildroot}/%{_datadir}/%{name}
+
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/%{name}
+%{_datadir}/%{name}
diff --git a/tests/data/rpm/srpms/gbp-test-1.0-1.src.rpm b/tests/data/rpm/srpms/gbp-test-1.0-1.src.rpm
new file mode 100644
index 0000000..74afbd6
--- /dev/null
+++ b/tests/data/rpm/srpms/gbp-test-1.0-1.src.rpm
Binary files differ
diff --git a/tests/data/rpm/srpms/gbp-test2-3.0-0.src.rpm b/tests/data/rpm/srpms/gbp-test2-3.0-0.src.rpm
new file mode 100644
index 0000000..1cf12c7
--- /dev/null
+++ b/tests/data/rpm/srpms/gbp-test2-3.0-0.src.rpm
Binary files differ
diff --git a/tests/data/test1.conf b/tests/data/test1.conf
new file mode 100644
index 0000000..e7ffeb4
--- /dev/null
+++ b/tests/data/test1.conf
@@ -0,0 +1,35 @@
+# Data for TestConfigParser
+
+[DEFAULT]
+default_option = default_default1
+single_override_option1 = single_override_default1
+single_git_override_option1 = single_git_override_default1
+single_gbp_override_option1 = single_gbp_override_default1
+new_overrides_git_option1 = new_overrides_git_default1
+
+# These commands only have a single section overriding defaults.
+# There are no alterntive old or new names
+[cmd1]
+single_override_option1 = single_override_value1
+
+[git-cmd2]
+single_git_override_option1 = single_git_override_value1
+
+[gbp-cmd3]
+single_gbp_override_option1 = single_gbp_override_value1
+
+# This commands have a new name overriding the old git- section
+# The order of the sections differs though
+[git-cmd4]
+new_overrides_git_option1 = new_overrides_git_overridden1
+
+[cmd4]
+new_overrides_git_option1 = new_overrides_git_value1
+
+[cmd5]
+new_overrides_git_option1 = new_overrides_git_value1
+
+[git-cmd5]
+new_overrides_git_option1 = new_overrides_git_overridden1
+
+
diff --git a/tests/doctests/__init__.py b/tests/doctests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/doctests/__init__.py
diff --git a/tests/doctests/test_Changelog.py b/tests/doctests/test_Changelog.py
new file mode 100644
index 0000000..1fb8a30
--- /dev/null
+++ b/tests/doctests/test_Changelog.py
@@ -0,0 +1,344 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{gbp.deb.changelog.ChangeLog}
+"""
+from .. import context # noqa: 401
+from .. testutils import have_cmd
+
+import nose
+
+cl_debian = """git-buildpackage (0.5.32) unstable; urgency=low
+
+ * [efe9220] Use known_compressions in guess_upstream_version too
+ (Closes: #645477)
+ * [e984baf] git-import-orig: fix --filter
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 17 Oct 2011 10:15:22 +0200
+
+git-buildpackage (0.5.31) unstable; urgency=low
+
+ [ Guido Günther ]
+ * [3588d88] Fix pristine-tar error message
+ * [8da98da] gbp-pq: don't fail on missing series file but create an empty
+ branch instead
+
+ [ Salvatore Bonaccorso ]
+ * [b33cf74] Fix URL to cl2vcs service.
+ Refer to https://honk.sigxcpu.org/cl2vcs instead of
+ https://honk.sigxcpu.org/cl2vcs for the cl2vcs service. (Closes: #640141)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 28 Sep 2011 20:21:34 +0200
+"""
+
+cl_upstream = """python-dateutil (1.0-1) unstable; urgency=low
+
+ * Initial release (Closes: #386256)
+
+ -- Guido Günther <agx@sigxcpu.org> Wed, 6 Sep 2006 10:33:06 +0200
+"""
+
+cl_epoch = """xserver-xorg-video-nv (1:1.2.0-3) unstable; urgency=low
+
+ [ Steve Langasek ]
+ * Upload to unstable
+
+ -- David Nusinow <dnusinow@debian.org> Mon, 18 Sep 2006 19:57:45 -0400
+"""
+
+
+def setup():
+ """Setup test module"""
+ if not have_cmd('debchange'):
+ raise nose.SkipTest('debchange tool not present')
+
+
+def test_parse_debian_only():
+ """
+ Parse a the changelog of debian only package
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+
+ Properties tested:
+ - L{gbp.deb.changelog.ChangeLog.version}
+ - L{gbp.deb.changelog.ChangeLog.debian_version}
+ - L{gbp.deb.changelog.ChangeLog.upstream_version}
+ - L{gbp.deb.changelog.ChangeLog.epoch}
+ - L{gbp.deb.changelog.ChangeLog.noepoch}
+
+ >>> import gbp.deb.changelog
+ >>> cl = gbp.deb.changelog.ChangeLog(cl_debian)
+ >>> cl.version
+ '0.5.32'
+ >>> cl.version == cl['Version']
+ True
+ >>> cl.debian_version
+ '0.5.32'
+ >>> cl.debian_version == cl['Debian-Version']
+ True
+ >>> cl.noepoch
+ '0.5.32'
+ >>> cl.noepoch == cl['NoEpoch-Version']
+ True
+ >>> cl.epoch
+ >>> cl.upstream_version
+ """
+
+
+def test_parse_no_eopch():
+ """
+ Parse a the changelog of a package without eopch
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+ - L{gbp.deb.changelog.ChangeLog.has_epoch}
+
+ Properties tested:
+ - L{gbp.deb.changelog.ChangeLog.version}
+ - L{gbp.deb.changelog.ChangeLog.debian_version}
+ - L{gbp.deb.changelog.ChangeLog.upstream_version}
+ - L{gbp.deb.changelog.ChangeLog.epoch}
+ - L{gbp.deb.changelog.ChangeLog.noepoch}
+
+ >>> import gbp.deb.changelog
+ >>> cl = gbp.deb.changelog.ChangeLog(cl_upstream)
+ >>> cl.version
+ '1.0-1'
+ >>> cl.version == cl['Version']
+ True
+ >>> cl.debian_version
+ '1'
+ >>> cl.debian_version == cl['Debian-Version']
+ True
+ >>> cl.noepoch
+ '1.0-1'
+ >>> cl.noepoch == cl['NoEpoch-Version']
+ True
+ >>> cl.epoch
+ >>> cl.upstream_version
+ '1.0'
+ >>> cl.has_epoch()
+ False
+ >>> cl.distribution
+ 'unstable'
+ """
+
+
+def test_parse_eopch():
+ """
+ Parse a the changelog of a package without epoch
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+ - L{gbp.deb.changelog.ChangeLog.has_epoch}
+
+ Properties tested:
+ - L{gbp.deb.changelog.ChangeLog.version}
+ - L{gbp.deb.changelog.ChangeLog.debian_version}
+ - L{gbp.deb.changelog.ChangeLog.upstream_version}
+ - L{gbp.deb.changelog.ChangeLog.epoch}
+ - L{gbp.deb.changelog.ChangeLog.noepoch}
+
+ >>> import gbp.deb.changelog
+ >>> cl = gbp.deb.changelog.ChangeLog(cl_epoch)
+ >>> cl.version
+ '1:1.2.0-3'
+ >>> cl.version == cl['Version']
+ True
+ >>> cl.debian_version
+ '3'
+ >>> cl.debian_version == cl['Debian-Version']
+ True
+ >>> cl.noepoch
+ '1.2.0-3'
+ >>> cl.noepoch == cl['NoEpoch-Version']
+ True
+ >>> cl.epoch
+ '1'
+ >>> cl.upstream_version
+ '1.2.0'
+ >>> cl.has_epoch()
+ True
+ """
+
+
+def test_parse_name():
+ """
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+
+ Properties tested:
+ - L{gbp.deb.changelog.ChangeLog.name}
+
+ >>> import gbp.deb.changelog
+ >>> cl = gbp.deb.changelog.ChangeLog(cl_debian)
+ >>> cl.name
+ 'git-buildpackage'
+ """
+
+
+def test_parse_last_mod():
+ """
+ Test author, email and date of last modification
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+
+ Properties tested:
+ - L{gbp.deb.changelog.ChangeLog.name}
+ - L{gbp.deb.changelog.ChangeLog.email}
+ - L{gbp.deb.changelog.ChangeLog.date}
+
+ >>> import gbp.deb.changelog
+ >>> cl = gbp.deb.changelog.ChangeLog(cl_debian)
+ >>> cl.author.startswith('Guido')
+ True
+ >>> cl.email
+ 'agx@sigxcpu.org'
+ >>> cl.date
+ 'Mon, 17 Oct 2011 10:15:22 +0200'
+ """
+
+
+def test_parse_sections():
+ """
+ Test if we can parse sections out of the changelog
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+ - L{gbp.deb.changelog.ChangeLogSection.__init__}
+ - L{gbp.deb.changelog.ChangeLogSection.parse}
+
+ Properties tested:
+ - L{gbp.deb.changelog.ChangeLog.sections}
+
+ >>> import gbp.deb.changelog
+ >>> cl = gbp.deb.changelog.ChangeLog(cl_debian)
+ >>> cl.sections[0].package
+ 'git-buildpackage'
+ >>> cl.sections[0].version
+ '0.5.32'
+ >>> cl.sections[1].package
+ 'git-buildpackage'
+ >>> cl.sections[1].version
+ '0.5.31'
+ """
+
+
+def test_get_changes():
+ """
+ Test if we can get changes
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+ - L{gbp.deb.changelog.ChangeLog.get_changes}
+
+ >>> import gbp.deb.changelog
+ >>> cl = gbp.deb.changelog.ChangeLog(cl_debian)
+ >>> len(cl.get_changes().split('\\n'))
+ 19
+ >>> len(cl.get_changes('0.5.31').split('\\n'))
+ 7
+ >>> cl['Changes'].split('\\n')[0]
+ ' git-buildpackage (0.5.32) unstable; urgency=low'
+ >>> len(cl['Changes'])
+ 187
+ """
+
+
+def test_add_section():
+ """
+ Test if we can add a section to an existing changelog
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+ - L{gbp.deb.changelog.ChangeLog._parse}
+ - L{gbp.deb.changelog.ChangeLog.add_section}
+ - L{gbp.deb.changelog.ChangeLog.spawn_dch}
+
+ >>> import os
+ >>> import tempfile
+ >>> import shutil
+ >>> import gbp.deb.changelog
+ >>> from ..testutils import OsReleaseFile
+ >>> os_release = OsReleaseFile('/etc/lsb-release')
+ >>> olddir = os.path.abspath(os.path.curdir)
+ >>> testdir = tempfile.mkdtemp(prefix='gbp-test-changelog-')
+ >>> testdebdir = os.path.join(testdir, 'debian')
+ >>> testclname = os.path.join(testdebdir, "changelog")
+ >>> os.mkdir(testdebdir)
+ >>> clh = open(os.path.join(testdebdir, "changelog"), "w", encoding='utf-8')
+ >>> ret = clh.write(cl_debian)
+ >>> clh.close()
+ >>> os.chdir(testdir)
+ >>> os.path.abspath(os.path.curdir) == testdir
+ True
+ >>> cl = gbp.deb.changelog.ChangeLog(filename=testclname)
+ >>> cl.add_section(msg=["Test add section"], distribution=None, author="Debian Maintainer", email="maint@debian.org")
+ >>> cl = gbp.deb.changelog.ChangeLog(filename=testclname)
+ >>> version = '0.5.32ubuntu1' if os_release['DISTRIB_ID'] == 'Ubuntu' else '0.5.33'
+ >>> cl.version == version
+ True
+ >>> cl.debian_version == version
+ True
+ >>> distributions = ['UNRELEASED', os_release['DISTRIB_CODENAME'] or 'unstable']
+ >>> cl['Distribution'] in distributions
+ True
+ >>> 'Test add section' in cl['Changes']
+ True
+ >>> os.chdir(olddir)
+ >>> os.path.abspath(os.path.curdir) == olddir
+ True
+ >>> shutil.rmtree(testdir, ignore_errors=True)
+ """
+
+
+def test_add_entry():
+ """
+ Test if we can add an entry to an existing changelog
+
+ Methods tested:
+ - L{gbp.deb.changelog.ChangeLog.__init__}
+ - L{gbp.deb.changelog.ChangeLog._parse}
+ - L{gbp.deb.changelog.ChangeLog.add_entry}
+ - L{gbp.deb.changelog.ChangeLog.spawn_dch}
+
+ >>> import os
+ >>> import tempfile
+ >>> import shutil
+ >>> import gbp.deb.changelog
+ >>> from ..testutils import OsReleaseFile
+ >>> os_release = OsReleaseFile('/etc/lsb-release')
+ >>> olddir = os.path.abspath(os.path.curdir)
+ >>> testdir = tempfile.mkdtemp(prefix='gbp-test-changelog-')
+ >>> testdebdir = os.path.join(testdir, 'debian')
+ >>> testclname = os.path.join(testdebdir, "changelog")
+ >>> os.mkdir(testdebdir)
+ >>> clh = open(os.path.join(testdebdir, "changelog"), "w", encoding='utf-8')
+ >>> ret = clh.write(cl_debian)
+ >>> clh.close()
+ >>> os.chdir(testdir)
+ >>> os.path.abspath(os.path.curdir) == testdir
+ True
+ >>> cl = gbp.deb.changelog.ChangeLog(filename=testclname)
+ >>> cl.add_section(msg=["Test add section"], distribution=None, author="Debian Maintainer", email="maint@debian.org")
+ >>> cl.add_entry(msg=["Test add entry"], author="Debian Maintainer", email="maint@debian.org")
+ >>> cl = gbp.deb.changelog.ChangeLog(filename=testclname)
+ >>> version = '0.5.32ubuntu1' if os_release['DISTRIB_ID'] == 'Ubuntu' else '0.5.33'
+ >>> cl.version == version
+ True
+ >>> cl.debian_version == version
+ True
+ >>> distributions = ['UNRELEASED', os_release['DISTRIB_CODENAME'] or 'unstable']
+ >>> cl['Distribution'] in distributions
+ True
+ >>> 'Test add entry' in cl['Changes']
+ True
+ >>> cl['Changes'].split('*',1)[1]
+ ' Test add section\\n * Test add entry'
+ >>> os.chdir(olddir)
+ >>> os.path.abspath(os.path.curdir) == olddir
+ True
+ >>> shutil.rmtree(testdir, ignore_errors=True)
+ """
diff --git a/tests/doctests/test_Config.py b/tests/doctests/test_Config.py
new file mode 100644
index 0000000..8d6eadb
--- /dev/null
+++ b/tests/doctests/test_Config.py
@@ -0,0 +1,112 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{gbp.config.GbpOptionParser}
+Test L{gbp.config.GbpOptionParserDebian}
+"""
+
+from .. import context # noqa: F401
+
+
+def test_option_parser():
+ """
+ Methods tested:
+ - L{gbp.config.GbpOptionParser.add_config_file_option}
+ - L{gbp.config.GbpOptionParser.add_boolean_config_file_option}
+
+ >>> import gbp.config
+ >>> c = gbp.config.GbpOptionParser('common', prefix='test')
+ >>> c.add_config_file_option(option_name='upstream-branch', dest='upstream')
+ >>> c.add_boolean_config_file_option(option_name='overlay', dest='overlay')
+ >>> c.add_boolean_config_file_option(option_name='track', dest='track')
+ """
+
+
+def test_option_parser_debian():
+ """
+ Methods tested:
+ - L{gbp.config.GbpOptionParserDebian.add_config_file_option}
+
+ >>> import gbp.config
+ >>> c = gbp.config.GbpOptionParserDebian('debian')
+ >>> c.add_config_file_option(option_name='builder', dest='builder')
+ Traceback (most recent call last):
+ ...
+ KeyError: 'builder'
+ >>> c.add_config_file_option(option_name='builder', dest='builder', help='foo')
+ """
+
+
+def test_option_group():
+ """
+ Methods tested:
+ - L{gbp.config.GbpOptionGroup.add_config_file_option}
+ - L{gbp.config.GbpOptionGroup.add_boolean_config_file_option}
+
+ >>> import gbp.config
+ >>> c = gbp.config.GbpOptionParser('debian')
+ >>> g = gbp.config.GbpOptionGroup(c, 'wheezy')
+ >>> g.add_config_file_option(option_name='debian-branch', dest='branch')
+ >>> g.add_boolean_config_file_option(option_name='track', dest='track')
+ """
+
+
+def test_tristate():
+ """
+ Methods tested:
+ - L{gbp.config.GbpOptionParser.add_config_file_option}
+
+ >>> import gbp.config
+ >>> c = gbp.config.GbpOptionParser('tristate')
+ >>> c.add_config_file_option(option_name="color", dest="color", type='tristate')
+ >>> options, args= c.parse_args(['--color=auto'])
+ >>> options.color
+ auto
+ >>> options.color.is_off()
+ False
+ >>> c = gbp.config.GbpOptionParser('tristate')
+ >>> c.add_config_file_option(option_name="color", dest="color", type='tristate')
+ >>> options, args= c.parse_args(['--color=off'])
+ >>> options.color
+ off
+ >>> options.color.is_off()
+ True
+ """
+
+
+def test_filter():
+ """
+ The filter option should always parse as a list
+ >>> import os
+ >>> from gbp.config import GbpOptionParser
+ >>> tmpdir = str(context.new_tmpdir('bar'))
+ >>> confname = os.path.join(tmpdir, 'gbp.conf')
+ >>> GbpOptionParser._set_config_file_value('bar', 'filter', 'asdf', filename=confname)
+ >>> os.environ['GBP_CONF_FILES'] = confname
+ >>> parser = GbpOptionParser('bar')
+ >>> parser.config['filter']
+ ['asdf']
+ >>> f = open(confname, 'w')
+ >>> ret = f.write("[bar]\\nfilter = ['this', 'is', 'a', 'list']\\n")
+ >>> f.close()
+ >>> parser = GbpOptionParser('bar')
+ >>> parser.config['filter']
+ ['this', 'is', 'a', 'list']
+ >>> del os.environ['GBP_CONF_FILES']
+ """
+
+
+def test_filters():
+ """
+ The filter can be given in plural form
+ >>> import os
+ >>> from gbp.config import GbpOptionParser
+ >>> tmpdir = str(context.new_tmpdir('bar'))
+ >>> confname = os.path.join(tmpdir, 'gbp.conf')
+ >>> GbpOptionParser._set_config_file_value('bar', 'filters', '["abc", "def"]\\n', filename=confname)
+ >>> os.environ['GBP_CONF_FILES'] = confname
+ >>> parser = GbpOptionParser('bar')
+ >>> parser.config['filter']
+ ['abc', 'def']
+ >>> del os.environ['GBP_CONF_FILES']
+ """
diff --git a/tests/doctests/test_Control.py b/tests/doctests/test_Control.py
new file mode 100644
index 0000000..36d08a8
--- /dev/null
+++ b/tests/doctests/test_Control.py
@@ -0,0 +1,93 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{gbp.deb.control.Control}
+"""
+
+from .. import context # noqa: 401
+
+cl_debian = """Source: git-buildpackage
+Section: vcs
+Priority: optional
+Maintainer: Guido Günther <agx@sigxcpu.org>
+Build-Depends: debhelper (>= 7.0.50~), python (>> 2.6.6-3~),
+ pychecker, gtk-doc-tools, sgml2x, docbook-utils, jade, python-dateutil, python-nose,
+ bash-completion, perl, python-epydoc, python-coverage, python-setuptools,
+ # For the testsuite
+ git (>= 1:1.7.9.1-1~), bzip2, unzip, pristine-tar
+Standards-Version: 3.9.3
+Vcs-Git: git://honk.sigxcpu.org/git/git-buildpackage.git
+Vcs-Browser: http://git.debian.org/?p=users/agx/git-buildpackage.git
+Homepage: https://honk.sigxcpu.org/piki/projects/git-buildpackage/
+X-Python-Version: >= 2.6
+
+Package: git-buildpackage
+Architecture: all
+Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}, devscripts (>= 2.10.66~),
+ git (>= 1:1.7.9.1-1~), python-dateutil
+Recommends: pristine-tar (>= 0.5), cowbuilder
+Suggests: python-notify, unzip
+Description: Suite to help with Debian packages in Git repositories
+ This package contains the following tools:
+ * git-import-{dsc,dscs}: import existing Debian source packages into a git
+ repository
+ * git-import-orig: import a new upstream version into the git repository
+ * git-buildpackage: build a package out of a git repository, check for local
+ modifications and tag appropriately
+ * git-dch: generate Debian changelog entries from Git commit messages
+ * gbp-{pull,clone}: clone and pull from remote repos
+ * gbp-pq: manage debian/patches easily
+ * gbp-create-remote-repo: create remote repositories
+"""
+
+
+def test_parse_control():
+ """
+ Parse a the control of debian package
+
+ Methods tested:
+ - L{gbp.deb.control.Control.__init__}
+
+ Properties tested:
+ - L{gbp.deb.control.Control.name}
+ - L{gbp.deb.control.Control.section}
+ - L{gbp.deb.control.Control.priority}
+
+ >>> import gbp.deb.control
+ >>> cl = gbp.deb.control.Control(cl_debian)
+ >>> cl.name
+ 'git-buildpackage'
+ >>> cl.name == cl['Source']
+ True
+ >>> cl.section
+ 'vcs'
+ >>> cl.section == cl['Section']
+ True
+ >>> cl.priority
+ 'optional'
+ >>> cl.priority == cl['Priority']
+ True
+ >>> cl['Standards-Version']
+ '3.9.3'
+ >>> cl['Package']
+
+ """
+
+
+def test_no_control_error():
+ """
+ Raise an error if no control file exist or is empty
+
+ Methods tested:
+ - L{gbp.deb.control.Control.__init__}
+
+ >>> import gbp.deb.control
+ >>> cl = gbp.deb.control.Control(filename="doesnotexist")
+ Traceback (most recent call last):
+ ...
+ gbp.deb.control.NoControlError: Control file doesnotexist does not exist
+ >>> cl = gbp.deb.control.Control("notparsable")
+ Traceback (most recent call last):
+ ...
+ gbp.deb.control.ParseControlError: Empty or invalid control file or contents
+ """
diff --git a/tests/doctests/test_GitModifier.py b/tests/doctests/test_GitModifier.py
new file mode 100644
index 0000000..4ac3c7f
--- /dev/null
+++ b/tests/doctests/test_GitModifier.py
@@ -0,0 +1,87 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{gbp.git.GitModifier}
+"""
+
+from .. import context # noqa: F401
+
+
+def test_author():
+ """
+ Methods tested:
+ - L{gbp.git.GitModifier.get_author_env}
+ - L{gbp.git.GitModifier.get_committer_env}
+ - L{gbp.git.GitModifier.keys}
+
+ >>> import gbp.git
+ >>> modifier = gbp.git.GitModifier('foo', 'bar')
+ >>> modifier.name
+ 'foo'
+ >>> modifier.email
+ 'bar'
+ >>> modifier.get_author_env()['GIT_AUTHOR_EMAIL']
+ 'bar'
+ >>> modifier.get_author_env()['GIT_AUTHOR_NAME']
+ 'foo'
+ >>> modifier.get_committer_env()['GIT_COMMITTER_NAME']
+ 'foo'
+ >>> modifier.get_committer_env()['GIT_COMMITTER_EMAIL']
+ 'bar'
+ >>> modifier._get_env('foo')
+ Traceback (most recent call last):
+ ...
+ gbp.git.modifier.GitModifierError: Neither committer nor author
+ >>> modifier['name']
+ 'foo'
+ >>> modifier['email']
+ 'bar'
+ >>> modifier['date']
+ """
+
+
+def test_date():
+ """
+ Methods tested:
+ - L{gbp.git.GitModifier.__init__}
+
+ Properties tested:
+ - L{gbp.git.GitModifier.date}
+ - L{gbp.git.GitModifier.datetime}
+ - L{gbp.git.GitModifier.tz_offset}
+
+ >>> import gbp.git
+ >>> import datetime
+ >>> modifier = gbp.git.GitModifier('foo', 'bar', 1)
+ >>> modifier.date
+ '1 +0000'
+ >>> modifier.date = '1 +0400'
+ >>> modifier.date
+ '1 +0400'
+ >>> modifier['date']
+ '1 +0400'
+ >>> modifier.datetime # doctest: +ELLIPSIS
+ datetime.datetime(1970, 1, 1, 4, 0, 1, tzinfo=<gbp.git.modifier.GitTz...>)
+ >>> modifier.date = datetime.datetime(1970, 1, 1, 0, 0, 1)
+ >>> modifier.date
+ '1 +0000'
+ >>> modifier.datetime # doctest: +ELLIPSIS
+ datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=<gbp.git.modifier.GitTz...>)
+ >>> modifier.tz_offset
+ '+0000'
+ """
+
+
+def test_dict():
+ """
+ Test C{dict} interface
+ >>> import gbp.git
+ >>> modifier = gbp.git.GitModifier('foo', 'bar', 1)
+ >>> sorted(modifier.keys())
+ ['date', 'email', 'name']
+ >>> sorted(modifier.items())
+ [('date', '1 +0000'), ('email', 'bar'), ('name', 'foo')]
+ >>> modifier.get('name')
+ 'foo'
+ >>> modifier.get('doesnotexist')
+ """
diff --git a/tests/doctests/test_GitRepository.py b/tests/doctests/test_GitRepository.py
new file mode 100644
index 0000000..ca32394
--- /dev/null
+++ b/tests/doctests/test_GitRepository.py
@@ -0,0 +1,1087 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{gbp.git.GitRepository}
+
+This testcase creates several repositores:
+
+ - A repository at I{dirs['repo']} called I{repo}
+ - A bare repository at I{dirs['bare']} called I{bare}
+ - A clone of I{repo} below I{dirs['clone']} called I{clone}
+ - A mirror of I{repo} below I{mirror_dirs['clone']} called I{mirror}
+"""
+
+from .. import context
+
+import gbp.log
+
+gbp.log.setup(color=False, verbose=True)
+
+dirs = {}
+subdirs = ['repo', 'bare', 'clone', 'mirror_clone']
+
+
+def setup_module():
+ tmpdir = context.new_tmpdir(__name__)
+ for s in subdirs:
+ dirs[s] = tmpdir.join(s)
+
+
+def teardown_module():
+ for s in subdirs:
+ del dirs[s]
+ context.teardown()
+
+
+def test_create():
+ """
+ Create a repository
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create}
+
+ Properties tested:
+ - L{gbp.git.GitRepository.path}
+ - L{gbp.git.GitRepository.git_dir}
+
+ >>> import os, gbp.git
+ >>> repo = gbp.git.GitRepository.create(dirs['repo'])
+ >>> repo.path == dirs['repo']
+ True
+ >>> repo.git_dir == os.path.join(dirs['repo'], '.git')
+ True
+ >>> type(repo) == gbp.git.GitRepository
+ True
+ """
+
+
+def test_empty():
+ """
+ Empty repos have no branch
+
+ Methods tested:
+ - L{gbp.git.GitRepository.get_branch}
+ - L{gbp.git.GitRepository.is_empty}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.get_branch()
+ >>> repo.branch
+ >>> repo.is_empty()
+ True
+ """
+
+
+def test_subdir():
+ """
+ Make surewe can init repos from a subdir
+ >>> import gbp.git, os
+ >>> os.mkdir(os.path.join(dirs['repo'], 'subdir'))
+ >>> repo = gbp.git.GitRepository(os.path.join(dirs['repo'], 'subdir'), toplevel=False)
+ >>> repo.path == dirs['repo']
+ True
+ >>> repo = gbp.git.GitRepository(os.path.join(dirs['repo'], 'subdir'), toplevel=True) # doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Not the toplevel of a Git repository at ...
+ """
+
+
+def test_add_files():
+ """
+ Add some dummy data
+
+ Methods tested:
+ - L{gbp.git.GitRepository.add_files}
+ - L{gbp.git.GitRepository.commit_all}
+ - L{gbp.git.GitRepository.is_clean}
+
+ Properties tested:
+ - L{gbp.git.GitRepository.head}
+
+ >>> import gbp.git, shutil, os
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> ret = shutil.copy(os.path.join(repo.path, ".git/HEAD"),
+ ... os.path.join(repo.path, "testfile"))
+ >>> repo.is_clean()[0]
+ False
+ >>> repo.is_clean('doesnotexist')[0]
+ True
+ >>> repo.is_clean(paths='testfile')[0]
+ False
+ >>> repo.is_clean(paths=['doesnotexist', 'testfile'])[0]
+ False
+ >>> repo.is_clean(ignore_untracked=True)[0]
+ True
+ >>> repo.add_files(repo.path, force=True)
+ >>> repo.commit_all(msg="foo")
+ >>> repo.is_clean()[0]
+ True
+ >>> h = repo.head
+ >>> len(h)
+ 40
+ """
+
+
+def test_rename_file():
+ """
+ Methods tested:
+ - L{gbp.git.GitRepository.rename_file}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.rename_file("testfile", "testfile2")
+ >>> repo.rename_file("testfile2", "testfile")
+ >>> repo.rename_file("doesnotexit", "testfile2")
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: Failed to move 'doesnotexit' to 'testfile2': fatal: bad source, source=doesnotexit, destination=testfile2
+ """
+
+
+def test_branch_master():
+ """
+ First branch is called I{master}
+
+ Methods tested:
+ - L{gbp.git.GitRepository.get_branch}
+ >>> import gbp.git, shutil
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.get_branch()
+ 'master'
+ >>> repo.branch
+ 'master'
+ """
+
+
+def test_clean():
+ """
+ Remove untracked files from the working tree
+
+ Methods tested:
+ - L{gbp.git.GitRepository.clean}
+
+ >>> import gbp.git, shutil, os
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> ret = shutil.copy(os.path.join(repo.path, ".git/HEAD"),
+ ... os.path.join(repo.path, "testclean"))
+ >>> repo.clean(dry_run=True)
+ >>> repo.is_clean()[0]
+ False
+ >>> repo.clean(directories=True, force=True)
+ >>> repo.is_clean()[0]
+ True
+ """
+
+
+def test_create_branch():
+ """
+ Create a branch name I{foo}
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create_branch}
+ - L{gbp.git.GitRepository.branch_contains}
+
+ >>> import gbp.git, shutil
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.create_branch("foo")
+ >>> repo.branch_contains("foo", 'HEAD')
+ True
+ >>> repo.branch_contains("doesnotexist", 'HEAD', remote=True)
+ False
+ """
+
+
+def test_delete_branch():
+ """
+ Create a branch named I{foo2} and delete it
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create_branch}
+ - L{gbp.git.GitRepository.delete_branch}
+
+ >>> import gbp.git, shutil
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.create_branch("bar")
+ >>> repo.delete_branch("bar")
+ >>> repo.delete_branch("master")
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Can't delete the branch you're on
+ """
+
+
+def test_set_branch():
+ """
+ Switch to branch named I{foo}
+
+ Methods tested:
+ - L{gbp.git.GitRepository.set_branch}
+ - L{gbp.git.GitRepository.get_branch}
+ - L{gbp.git.GitRepository.branch}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.set_branch("foo")
+ >>> repo.get_branch() == "foo"
+ True
+ >>> repo.branch == "foo"
+ True
+ """
+
+
+def test_rename_branch():
+ """
+ Create branch named I{baz}, rename it to I{bax} and finally delete it
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create_branch}
+ - L{gbp.git.GitRepository.rename_branch}
+ - L{gbp.git.GitRepository.delete_branch}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.create_branch("baz")
+ >>> repo.rename_branch("baz", "bax")
+ >>> repo.delete_branch("bax")
+ """
+
+
+def test_set_upstream_branch():
+ """
+ Set upstream branch master -> origin/master
+
+ >>> import os, shutil
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> os.makedirs(os.path.join(repo.git_dir, 'refs/remotes/origin'))
+ >>> ret = shutil.copy(os.path.join(repo.git_dir, 'refs/heads/master'), \
+ os.path.join(repo.git_dir, 'refs/remotes/origin/'))
+ >>> repo.add_remote_repo('origin', 'git://git.example.com/git/origin')
+ >>> repo.set_upstream_branch('master', 'origin/master')
+ >>> repo.get_upstream_branch('master')
+ 'origin/master'
+ >>> repo.set_upstream_branch('bla', 'origin/master')
+ Traceback (most recent call last):
+ gbp.git.repository.GitRepositoryError: Branch bla doesn't exist!
+ >>> repo.set_upstream_branch('foo', 'origin/bla')
+ Traceback (most recent call last):
+ gbp.git.repository.GitRepositoryError: Branch origin/bla doesn't exist!
+ """
+
+
+def test_get_upstream_branch():
+ """
+ Get info about upstream branches set in test_set_upstream_branch
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.get_upstream_branch('master')
+ 'origin/master'
+ >>> repo.get_upstream_branch('foo')
+ ''
+ >>> repo.get_upstream_branch('bla')
+ Traceback (most recent call last):
+ gbp.git.repository.GitRepositoryError: Branch bla doesn't exist!
+ """
+
+
+def test_tag():
+ """
+ Create a tag named I{tag} and check its existance
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create_tag}
+ - L{gbp.git.GitRepository.verify_tag}
+ - L{gbp.git.GitRepository.has_tag}
+ - L{gbp.git.GitRepository.get_tags}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.create_tag("tag")
+ >>> repo.has_tag("tag")
+ True
+ >>> repo.has_tag("unknown")
+ False
+ >>> repo.create_tag("tag2", msg="foo")
+ >>> repo.has_tag("tag2")
+ True
+ >>> repo.verify_tag("tag2")
+ False
+ >>> repo.get_tags()
+ ['tag', 'tag2']
+ >>> repo.tags
+ ['tag', 'tag2']
+ """
+
+
+def test_describe():
+ """
+ Describe commit-ish
+
+ Methods tested:
+ - L{gbp.git.GitRepository.describe}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> sha = repo.rev_parse('HEAD')
+ >>> repo.describe('HEAD')
+ 'tag2'
+ >>> repo.describe('HEAD', longfmt=True) == 'tag2-0-g%s' % sha[:7]
+ True
+ >>> repo.describe('HEAD', pattern='foo*')
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Can't describe HEAD. Git error: fatal: No names found, cannot describe anything.
+ >>> repo.describe('HEAD', pattern='foo*', always=True) == sha[:7]
+ True
+ >>> repo.describe('HEAD', always=True, abbrev=16)
+ 'tag2'
+ >>> repo.describe('HEAD', pattern='foo*', always=True, abbrev=16) == sha[:16]
+ True
+ >>> tag = repo.describe('HEAD', longfmt=True, abbrev=16) == 'tag2-0-g%s' % sha[:16]
+ >>> repo.delete_tag('tag2')
+ >>> repo.describe('HEAD', tags=True)
+ 'tag'
+ >>> repo.describe('HEAD', tags=True, exact_match=True)
+ 'tag'
+ >>> repo.create_tag('tag2', msg='foo')
+ """
+
+
+def test_find_tag():
+ """
+ Find tags
+
+ Methods tested:
+ - L{gbp.git.GitRepository.find_tag}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.find_tag('HEAD')
+ 'tag2'
+ >>> repo.find_tag('HEAD', pattern='foo*')
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Can't describe HEAD. Git error: fatal: No names found, cannot describe anything.
+ """
+
+
+def test_find_branch_tag():
+ """
+ Find the closest tags on a certain branch to a given commit
+
+ Methods tested:
+ - L{gbp.git.GitRepository.find_branch_tag}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.find_branch_tag('HEAD', 'master', 'tag*')
+ 'tag2'
+ >>> repo.find_branch_tag('HEAD', 'master', 'v*') # doctest:+ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Can't describe .... Git error: fatal: No names found, cannot describe anything.
+ """
+
+
+def test_move_tag():
+ """
+ Move a tag
+
+ Methods tested:
+ - L{gbp.git.GitRepository.move_tag}
+ - L{gbp.git.GitRepository.has_tag}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.move_tag("tag", "moved")
+ >>> repo.has_tag("tag")
+ False
+ >>> repo.has_tag("moved")
+ True
+ """
+
+
+def test_delete_tag():
+ """
+ Delete tags
+
+ Methods tested:
+ - L{gbp.git.GitRepository.delete_tag}
+ - L{gbp.git.GitRepository.has_tag}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.has_tag("moved")
+ True
+ >>> repo.delete_tag("moved")
+ >>> repo.has_tag("moved")
+ False
+ """
+
+
+def test_get_obj_type():
+ """
+ Find commit SHA1 related to tags
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create_tag}
+ - L{gbp.git.GitRepository.get_obj_type}
+ - L{gbp.git.GitRepository.delete_tag}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.create_tag("tag3", "tag msg")
+ >>> repo.get_obj_type("tag3")
+ 'tag'
+ >>> repo.get_obj_type("HEAD")
+ 'commit'
+ >>> repo.get_obj_type("HEAD:testfile")
+ 'blob'
+ >>> repo.delete_tag("tag3")
+ """
+
+
+def test_list_files():
+ """
+ List files in the index
+
+ Methods tested:
+ - L{gbp.git.GitRepository.list_files}
+ - L{gbp.git.GitRepository.add_files}
+ - L{gbp.git.GitRepository.commit_staged}
+ - L{gbp.git.GitRepository.commit_files}
+ - L{gbp.git.GitRepository.force_head}
+
+ >>> import gbp.git, os, shutil
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> src = os.path.join(repo.path, ".git/HEAD")
+ >>> dst = os.path.join(repo.path, "testfile")
+ >>> repo.list_files()
+ [b'testfile']
+ >>> repo.list_files(['modified'])
+ []
+ >>> repo.list_files(['modified', 'deleted'])
+ []
+ >>> repo.list_files(['modified', 'deleted', 'cached'])
+ [b'testfile']
+ >>> ret = shutil.copy(src, dst)
+ >>> repo.list_files(['modified'])
+ [b'testfile']
+ >>> repo.add_files(dst)
+ >>> repo.commit_staged(msg="foo")
+ >>> repo.list_files(['modified'])
+ []
+ >>> repo.list_files(['foo'])
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Unknown type 'foo'
+ >>> repo.force_head('HEAD^', hard=True)
+ >>> repo.list_files(['modified'])
+ []
+ >>> ret = shutil.copy(src, dst)
+ >>> repo.list_files(['modified'])
+ [b'testfile']
+ >>> repo.commit_files(dst, msg="foo")
+ >>> repo.list_files(['modified'])
+ []
+ """
+
+
+def test_get_commits():
+ """
+ Test listing commits
+
+ Methods tested:
+ - L{gbp.git.GitRepository.get_commits}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> commits = repo.get_commits()
+ >>> type(commits) == list and len(commits) == 2
+ True
+ >>> len(repo.get_commits(num=1)) == 1
+ True
+ >>> commits2 = repo.get_commits(since='HEAD~1')
+ >>> len(commits2) == 1
+ True
+ >>> commits2[0] == commits[0]
+ True
+ >>> commits2 = repo.get_commits(until='HEAD~1')
+ >>> len(commits2) == 1
+ True
+ >>> commits2[0] == commits[-1]
+ True
+ >>> repo.get_commits(paths=['foo', 'bar'])
+ []
+ >>> repo.get_commits(paths=['testfile']) == commits
+ True
+ """
+
+
+def test_get_commit_info():
+ """
+ Test inspecting commits
+
+ Methods tested:
+ - L{gbp.git.GitRepository.get_commit_info}
+
+ >>> import gbp.git
+ >>> from datetime import datetime
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> info = repo.get_commit_info('HEAD')
+ >>> info['id']
+ 'HEAD'
+ >>> info['body']
+ ''
+ >>> info['subject']
+ 'foo'
+ >>> '@' in info['author'].email
+ True
+ >>> '@' in info['committer'].email
+ True
+ >>> now = datetime.now()
+ >>> (now - datetime.fromtimestamp(int(info['author'].date.split()[0]))).seconds < 10
+ True
+ >>> (now - datetime.fromtimestamp(int(info['committer'].date.split()[0]))).seconds < 10
+ True
+ >>> info['patchname']
+ 'foo'
+ >>> info['files'] # doctest:+ELLIPSIS
+ defaultdict(<class 'list'>, {'M': [b'testfile']})
+ >>> repo.get_subject('HEAD')
+ 'foo'
+ """
+
+
+def test_diff():
+ """
+ Test git-diff
+
+ Methods tested:
+ - L{gbp.git.GitRepository.diff}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> len(repo.diff('HEAD~1', 'HEAD')) > 3
+ True
+ >>> len(repo.diff('HEAD~1', 'HEAD', 'testfile')) > 3
+ True
+ >>> len(repo.diff('HEAD~1', 'HEAD', 'testfile', text=True)) > 3
+ True
+ >>> len(repo.diff('HEAD~1', 'HEAD', 'filenotexist')) == 0
+ True
+ >>> repo.diff('HEAD~1', 'HEAD') == repo.diff('HEAD~1')
+ True
+ """
+
+
+def test_diff_status():
+ """
+ Methods tested:
+ - L{gbp.git.GitRepository.diff_status}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.diff_status("HEAD", "HEAD")
+ defaultdict(<class 'list'>, {})
+ >>> repo.diff_status("HEAD~1", "HEAD")
+ defaultdict(<class 'list'>, {'M': [b'testfile']})
+ """
+
+
+def test_mirror_clone():
+ """
+ Mirror a repository
+
+ Methods tested:
+ - L{gbp.git.GitRepository.clone}
+ - L{gbp.git.GitRepository.is_empty}
+ - L{gbp.git.GitRepository.set_branch}
+ - L{gbp.git.GitRepository.has_branch}
+ - L{gbp.git.GitRepository.branch}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.set_branch('master')
+ >>> repo.branch
+ 'master'
+ >>> mirror = gbp.git.GitRepository.clone(dirs['mirror_clone'], repo.path, mirror=True)
+ >>> mirror.is_empty()
+ False
+ >>> mirror.branch
+ 'master'
+ >>> mirror.has_branch('foo')
+ True
+ >>> mirror.has_branch('bar')
+ False
+ >>> mirror.set_branch('foo')
+ >>> mirror.branch
+ 'foo'
+ >>> mirror.force_head('foo^')
+ """
+
+
+def test_clone():
+ """
+ Clone a repository
+
+ Methods tested:
+ - L{gbp.git.GitRepository.clone}
+ - L{gbp.git.GitRepository.is_empty}
+ - L{gbp.git.GitRepository.set_branch}
+ - L{gbp.git.GitRepository.branch}
+ - L{gbp.git.GitRepository.get_merge_branch}
+ - L{gbp.git.GitRepository.get_remote_branches}
+ - L{gbp.git.GitRepository.get_local_branches}
+ - L{gbp.git.GitRepository.get_remote_repos}
+ - L{gbp.git.GitRepository.has_remote_repo}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.set_branch('master')
+ >>> clone = gbp.git.GitRepository.clone(dirs['clone'], repo.path)
+ >>> clone.is_empty()
+ False
+ >>> clone.branch
+ 'master'
+ >>> clone.get_remote_branches()
+ ['origin/HEAD', 'origin/foo', 'origin/master']
+ >>> clone.get_local_branches()
+ ['master']
+ >>> clone.get_merge_branch('master')
+ 'origin/master'
+ >>> clone.create_branch('foo', 'origin/foo')
+ >>> clone.get_merge_branch('foo')
+ 'origin/foo'
+ >>> clone.create_branch('bar')
+ >>> clone.get_merge_branch('bar') # None if no merge branch exists
+ >>> clone.get_local_branches()
+ ['bar', 'foo', 'master']
+ >>> clone.get_remote_repos()
+ ['origin']
+ >>> clone.has_remote_repo('origin')
+ True
+ >>> clone.has_branch('origin/master', remote=True)
+ True
+ >>> clone.has_remote_repo('godiug')
+ False
+ """
+
+
+def test_get_remotes():
+ """
+ Check remotes
+
+ Methods tested:
+ - L{gbp.git.GitRepository.get_remotes}
+
+ >>> import os
+ >>> import gbp.git.repository
+ >>> repo = gbp.git.repository.GitRepository(os.path.join(dirs['clone'], 'repo'))
+ >>> remotes = repo.get_remotes()
+ >>> len(remotes)
+ 1
+ >>> origin = remotes['origin']
+ >>> origin.name
+ 'origin'
+ >>> origin.fetch_url == dirs['repo']
+ True
+ >>> origin.push_urls == [dirs['repo']]
+ True
+ """
+
+
+def test_merge():
+ """
+ Merge a branch
+
+ Methods tested:
+ - L{gbp.git.GitRepository.merge}
+ - L{gbp.git.GitRepository.set_branch}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.set_branch('master')
+ >>> repo.merge('foo')
+ >>> repo.is_in_merge()
+ False
+ """
+
+
+def test_pull():
+ """
+ Pull from a remote repository
+
+ Methods tested:
+ - L{gbp.git.GitRepository.set_branch}
+ - L{gbp.git.GitRepository.pull}
+
+ >>> import gbp.git, os
+ >>> d = os.path.join(dirs['clone'], 'repo')
+ >>> clone = gbp.git.GitRepository(d)
+ >>> clone.set_branch('master')
+ >>> clone.pull()
+ >>> clone.pull(all_remotes=True)
+ >>> clone.pull('origin', all_remotes=True)
+ """
+
+
+def test_fetch():
+ """
+ Fetch from a remote repository
+
+ Methods tested:
+ - L{gbp.git.GitRepository.fetch}
+ - L{gbp.git.GitRepository.push}
+ - L{gbp.git.GitRepository.push_tag}
+ - L{gbp.git.GitRepository.add_remote_repo}
+ - L{gbp.git.GitRepository.remove_remote_repo}
+
+ >>> import gbp.git, os
+ >>> d = os.path.join(dirs['clone'], 'repo')
+ >>> clone = gbp.git.GitRepository(d)
+ >>> clone.fetch()
+ >>> clone.push()
+ >>> clone.push('origin', dry_run=True)
+ >>> clone.push('origin')
+ >>> clone.push('origin', 'master')
+ >>> clone.push('origin', 'master', force=True)
+ >>> clone.create_tag('tag3')
+ >>> clone.push_tag('origin', 'tag3', True)
+ >>> clone.push_tag('origin', 'tag3')
+ >>> clone.create_tag('tag4')
+ >>> clone.push('origin', 'master', tags=True)
+ >>> clone.add_remote_repo('foo', dirs['repo'])
+ >>> clone.fetch('foo')
+ >>> clone.fetch('foo', tags=True)
+ >>> clone.fetch('foo', refspec='refs/heads/master')
+ >>> clone.fetch(all_remotes=True)
+ >>> clone.remove_remote_repo('foo')
+ """
+
+
+def test_create_bare():
+ """
+ Create a bare repository
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create}
+ - L{gbp.git.GitRepository.is_empty}
+
+ >>> import gbp.git
+ >>> bare = gbp.git.GitRepository.create(dirs['bare'], bare=True, description="msg")
+ >>> bare.path == dirs['bare']
+ True
+ >>> bare.git_dir == dirs['bare']
+ True
+ >>> type(bare) == gbp.git.GitRepository
+ True
+ >>> bare.is_empty()
+ True
+ >>> bare.is_clean()
+ (True, '')
+ """
+
+
+def test_nonexistent():
+ """
+ Check that accessing a non-existent repository fails.
+
+ Methods tested:
+ - L{gbp.git.GitRepository.__init__}
+
+ >>> import gbp.git
+ >>> bare = gbp.git.GitRepository("/does/not/exist")
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: No Git repository at '/does/not/exist'
+ """
+
+
+def test_create_noperm():
+ """
+ Check that creating a repository at a path that isn't writeable fails
+
+ Methods tested:
+ - L{gbp.git.GitRepository.create}
+
+ >>> import gbp.git
+ >>> gbp.git.GitRepository.create("/does/not/exist")
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Cannot create Git repository at '/does/not/exist': [Errno 13] Permission denied: '/does'
+ """
+
+
+def test_checkout():
+ """
+ Checkout treeishs
+
+ Methods tested:
+ - L{gbp.git.GitRepository.checkout}
+ - L{gbp.git.GitRepository.get_branch}
+ - L{gbp.git.GitRepository.set_branch}
+ - L{gbp.git.GitRepository.rev_parse}
+
+ Properties tested:
+ - L{gbp.git.GitRepository.branch}
+ - L{gbp.git.GitRepository.tags}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.checkout('master')
+ >>> repo.branch
+ 'master'
+ >>> repo.rev_parse('doesnotexist')
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: revision 'doesnotexist' not found
+ >>> sha1 = repo.rev_parse('master', short=10)
+ >>> len(sha1)
+ 10
+ >>> sha1 = repo.rev_parse('master')
+ >>> len(sha1)
+ 40
+ >>> repo.checkout(sha1)
+ >>> repo.branch
+ >>> repo.get_branch()
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Currently not on a branch
+ >>> tag = repo.tags[0]
+ >>> repo.checkout(tag)
+ >>> repo.branch
+ """
+
+
+def test_gc():
+ """
+ Test garbage collection
+
+ Methods tested:
+ - L{gbp.git.GitRepository.collect_garbage}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.collect_garbage()
+ >>> repo.collect_garbage(prune=True)
+ >>> repo.collect_garbage(prune='all', aggressive=True)
+ """
+
+
+def test_grep_log():
+ """
+ Test grepping through commit messages
+
+ Methods tested:
+ - L{gbp.git.GitRepository.grep_log}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.set_branch('master')
+ >>> len(repo.grep_log('foo')) == 2
+ True
+ >>> len(repo.grep_log('foo', 'master')) == 2
+ True
+ >>> repo.grep_log('blafasel')
+ []
+ >>> repo.grep_log('foo', 'doesnotexist')
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Error grepping log for foo: fatal: bad revision 'doesnotexist'
+ """
+
+
+def test_is_ff():
+ """
+ Test if branch is fast forwardable
+
+ Methods tested:
+ - L{gbp.git.GitRepository.is_fast_forward}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.is_fast_forward('master', 'foo')
+ (True, True)
+ >>> repo.create_branch('ff', 'HEAD^')
+ >>> repo.is_fast_forward('ff', 'master')
+ (True, False)
+ >>> repo.is_fast_forward('master', 'ff')
+ (False, True)
+ """
+
+
+def test_update_ref():
+ """
+ Test updating a reference
+
+ Methods tested:
+ - L{gbp.git.GitRepository.update_ref}
+
+ >>> import gbp.git, os
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.update_ref('new_ref', 'master', msg='update')
+ >>> os.path.exists(os.path.join(repo.git_dir, 'new_ref'))
+ True
+ """
+
+
+def test_make_tree():
+ """
+ Test git-mk-tree
+
+ Methods tested:
+ - L{gbp.git.GitRepository.write_file}
+ - L{gbp.git.GitRepository.list_tree}
+ - L{gbp.git.GitRepository.make_tree}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> sha1 = repo.write_file('testfile')
+ >>> sha1
+ '19af7398c894bc5e86e17259317e4db519e9241f'
+ >>> head = repo.list_tree('HEAD')
+ >>> head
+ [['100644', 'blob', '19af7398c894bc5e86e17259317e4db519e9241f', b'testfile']]
+ >>> head.append(['100644', 'blob', '19af7398c894bc5e86e17259317e4db519e9241f', 'testfile2'])
+ >>> newtree = repo.make_tree(head)
+ >>> newtree
+ '745951810c9e22fcc6de9b23f05efd6ab5512123'
+ >>> repo.list_tree(newtree, recurse=False, paths='testfile')
+ [['100644', 'blob', '19af7398c894bc5e86e17259317e4db519e9241f', b'testfile']]
+ >>> repo.make_tree([])
+ '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
+ """
+
+
+def test_update_submodules():
+ """
+ Updating submodules if we don't have any is a noop
+
+ Methods tested:
+ - L{gbp.git.GitRepository.has_submodules}
+ - L{gbp.git.GitRepository.update_submodules}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.has_submodules()
+ False
+ >>> repo.update_submodules()
+ """
+
+
+def test_get_merge_base():
+ """
+ Find the common ancestor of two objects
+
+ Methods tested:
+ - L{gbp.git.GitRepository.get_merge_base}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> sha1 = repo.get_merge_base('master', 'foo')
+ >>> len(sha1)
+ 40
+ >>> repo.get_merge_base('master', 'doesnotexist')
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Failed to get common ancestor: fatal: Not a valid object name doesnotexist
+ """
+
+
+def test_status():
+ r"""
+ Methods tested:
+ - L{gbp.git.GitRepository.status}
+
+ >>> import gbp.git, os, shutil
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> fname = os.path.join(repo.path, "test_status")
+ >>> ret = shutil.copy(os.path.join(repo.path, ".git/HEAD"), fname)
+ >>> list(repo.status().items())
+ [('??', [b'test_status'])]
+ >>> list(repo.status(['bla*']).items())
+ []
+ >>> list(repo.status(['te*']).items())
+ [('??', [b'test_status'])]
+ >>> repo.add_files(repo.path, force=True)
+ >>> repo.commit_all(msg='added %s' % fname)
+ >>> _ = repo._git_inout('mv', [fname, fname + 'new'])
+ >>> list(repo.status().items())
+ [('R ', [b'test_status\x00test_statusnew'])]
+ """
+
+
+def test_cmd_has_feature():
+ r"""
+ Methods tested:
+ - L{gbp.git.GitRepository._cmd_has_feature}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo._cmd_has_feature("commit", "a")
+ True
+ >>> repo._cmd_has_feature("commit", "reuse-message")
+ True
+ >>> repo._cmd_has_feature("merge", "n")
+ True
+ >>> repo._cmd_has_feature("merge", "stat")
+ True
+ >>> repo._cmd_has_feature("format-patch", "cc")
+ True
+ >>> repo._cmd_has_feature("merge", "foobaroption")
+ False
+ >>> repo._cmd_has_feature("foobarcmd", "foobaroption")
+ Traceback (most recent call last):
+ ...
+ gbp.git.repository.GitRepositoryError: Invalid git command 'foobarcmd': No manual entry for gitfoobarcmd
+ >>> repo._cmd_has_feature("show", "standard-notes")
+ True
+ >>> repo._cmd_has_feature("show", "no-standard-notes")
+ True
+ """
+
+
+def test_set_user_name_and_email():
+ r"""
+ Methods tested:
+ - L{gbp.git.GitRepository.set_user_name}
+ - L{gbp.git.GitRepository.set_user_email}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.set_user_name("Michael Stapelberg")
+ >>> repo.set_user_email("stapelberg@test.invalid")
+ """
+
+
+def test_set_config_and_get_config():
+ r"""
+ Methods tested:
+ - L{gbp.git.GitRepository.set_config}
+ - L{gbp.git.GitRepository.get_config}
+
+ >>> import gbp.git
+ >>> repo = gbp.git.GitRepository(dirs['repo'])
+ >>> repo.set_config("user.email", "foo@example.com")
+ >>> repo.get_config("user.email")
+ 'foo@example.com'
+ """
+
+
+def test_git_dir():
+ """
+ Properties tested:
+ - L{gbp.git.GitRepository.git_dir}
+ >>> import os, gbp.git
+ >>> git_dir = os.path.join(dirs['repo'], '.git')
+ >>> os.environ['GIT_DIR'] = git_dir
+ >>> somewhere = gbp.git.GitRepository(os.path.join(dirs['repo'], '..'))
+ >>> somewhere.git_dir == git_dir
+ True
+ >>> del os.environ['GIT_DIR']
+ """
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/doctests/test_GitVfs.py b/tests/doctests/test_GitVfs.py
new file mode 100644
index 0000000..16501e4
--- /dev/null
+++ b/tests/doctests/test_GitVfs.py
@@ -0,0 +1,116 @@
+# vim: set fileencoding=utf-8 :
+
+"""
+Test L{gbp.git.GitVfs}
+"""
+
+import os
+import gbp.log
+
+from .. import context # noqa: F401
+
+gbp.log.setup(color=False, verbose=True)
+
+
+def setup_repo():
+ repo_dir = context.new_tmpdir(__name__)
+ repo = gbp.git.GitRepository.create(str(repo_dir))
+ content = b'al pha\na\nb\nc'
+ with open(os.path.join(repo.path, 'foo.txt'), 'w') as f:
+ f.write(content.decode())
+ repo.add_files(repo.path, force=True)
+ repo.commit_all(msg="foo")
+ return (repo, content)
+
+
+def test_read():
+ """
+ Create a repository
+
+ Methods tested:
+ - L{gbp.git.GitVfs.open}
+ - L{gbp.git.GitVfs._File.readline}
+ - L{gbp.git.GitVfs._File.readlines}
+ - L{gbp.git.GitVfs._File.read}
+ - L{gbp.git.GitVfs._File.close}
+
+ >>> import gbp.git.vfs
+ >>> (repo, content) = setup_repo()
+ >>> vfs = gbp.git.vfs.GitVfs(repo, 'HEAD')
+ >>> gf = vfs.open('foo.txt')
+ >>> gf.readline()
+ 'al pha\\n'
+ >>> gf.readline()
+ 'a\\n'
+ >>> gf.readlines()
+ ['b\\n', 'c']
+ >>> gf.readlines()
+ []
+ >>> gf.readline()
+ ''
+ >>> gf.readline()
+ ''
+ >>> gf.close()
+ >>> gbp.git.vfs.GitVfs(repo, 'HEAD').open('foo.txt').read() == content.decode()
+ True
+ >>> gf = vfs.open('doesnotexist')
+ Traceback (most recent call last):
+ ...
+ OSError: can't get HEAD:doesnotexist: fatal: Path 'doesnotexist' does not exist in 'HEAD'
+ >>> context.teardown()
+ """
+
+
+def test_binary_read():
+ """
+ Create a repository
+
+ Methods tested:
+ - L{gbp.git.GitVfs.open}
+ - L{gbp.git.GitVfs._File.readline}
+ - L{gbp.git.GitVfs._File.readlines}
+ - L{gbp.git.GitVfs._File.read}
+ - L{gbp.git.GitVfs._File.close}
+
+ >>> import gbp.git.vfs
+ >>> (repo, content) = setup_repo()
+ >>> vfs = gbp.git.vfs.GitVfs(repo, 'HEAD')
+ >>> gf = vfs.open('foo.txt', 'rb')
+ >>> gf.readline()
+ b'al pha\\n'
+ >>> gf.readline()
+ b'a\\n'
+ >>> gf.readlines()
+ [b'b\\n', b'c']
+ >>> gf.readlines()
+ []
+ >>> gf.readline()
+ b''
+ >>> gf.readline()
+ b''
+ >>> gf.close()
+ >>> gbp.git.vfs.GitVfs(repo, 'HEAD').open('foo.txt', 'rb').read() == content
+ True
+ >>> gf = vfs.open('doesnotexist')
+ Traceback (most recent call last):
+ ...
+ OSError: can't get HEAD:doesnotexist: fatal: Path 'doesnotexist' does not exist in 'HEAD'
+ >>> context.teardown()
+ """
+
+
+def test_content_manager():
+ """
+ Create a repository
+
+ Methods tested:
+ - L{gbp.git.GitVfs.open}
+
+ >>> import gbp.git.vfs
+ >>> (repo, content) = setup_repo()
+ >>> vfs = gbp.git.vfs.GitVfs(repo, 'HEAD')
+ >>> with vfs.open('foo.txt') as gf:
+ ... data = gf.readlines()
+ >>> data
+ ['al pha\\n', 'a\\n', 'b\\n', 'c']
+ """
diff --git a/tests/doctests/test_PristineTar.py b/tests/doctests/test_PristineTar.py
new file mode 100644
index 0000000..3616319
--- /dev/null
+++ b/tests/doctests/test_PristineTar.py
@@ -0,0 +1,174 @@
+# vim: set fileencoding=utf-8 :
+"""
+Test pristine-tar related methods in
+
+ - L{gbp.deb.pristinetar.DebianPristineTar}
+
+and
+
+ - L{gbp.deb.git.DebianGitRepository}
+
+This testcase creates this reposity:
+
+ - A repository at I{dirs['repo']} called I{repo}
+
+"""
+
+import os
+from .. import context
+
+test_data = os.path.join(context.projectdir, "tests/data/pristine_tar")
+dirs = {}
+
+
+def setup_module():
+ dirs['repo'] = context.new_tmpdir(__name__).join('repo')
+
+
+def teardown_module():
+ del dirs['repo']
+ context.teardown()
+
+
+def test_create():
+ """
+ Create a repository
+
+ Methods tested:
+ - L{gbp.deb.git.DebianGitRepository.create}
+
+ >>> import os, gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository.create(dirs['repo'])
+ """
+
+
+def test_empty_repo():
+ """
+ Empty repos have no branch pristine-tar branch
+
+ Methods tested:
+ - L{gbp.deb.git.DebianGitRepository.has_pristine_tar_branch}
+ - L{gbp.deb.pristinetar.DebianPristineTar.has_commit}
+
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> repo.has_pristine_tar_branch()
+ False
+ >>> repo.pristine_tar.has_commit('upstream', '1.0', 'gzip')
+ False
+ """
+
+
+def test_commit_dir():
+ """
+ Empty repos have no branch pristine-tar branch
+
+ Methods tested:
+ - L{gbp.git.repository.GitRepository.commit_dir}
+ - L{gbp.git.repository.GitRepository.create_branch}
+
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> commit = repo.commit_dir(test_data, msg="initial commit", branch=None)
+ >>> repo.create_branch('upstream')
+ """
+
+
+def test_create_tarball():
+ """
+ Create a tarball from a git tree
+
+ Methods tested:
+ - L{gbp.deb.git.DebianGitRepository.archive}
+
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> repo.archive('tar', 'upstream/', '../upstream_1.0.orig.tar', 'upstream')
+ >>> gbp.command_wrappers.Command('gzip', [ '-n', '%s/../upstream_1.0.orig.tar' % dirs['repo']])()
+ """
+
+
+def test_pristine_tar_commit():
+ """
+ Commit the delta to the pristine-tar branch
+
+ Methods tested:
+ - L{gbp.deb.pristinetar.DebianPristineTar.commit}
+
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> repo.pristine_tar.commit('../upstream_1.0.orig.tar.gz', 'upstream')
+ """
+
+
+def test_pristine_has_commit():
+ """
+ Find delta on the pristine tar branch
+
+ Methods tested:
+ - L{gbp.deb.pristinetar.DebianPristineTar.has_commit}
+ - L{gbp.pkg.pristinetar.PristineTar.get_commit}
+
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> repo.pristine_tar.has_commit('upstream', '1.0', 'bzip2')
+ False
+ >>> repo.pristine_tar.has_commit('upstream', '1.0', 'gzip')
+ True
+ >>> repo.pristine_tar.has_commit('upstream', '1.0')
+ True
+ >>> branch = repo.rev_parse('pristine-tar')
+ >>> commit = repo.pristine_tar.get_commit('upstream_1.0.orig.tar.gz')
+ >>> branch == commit
+ True
+ """
+
+
+def test_pristine_tar_checkout():
+ """
+ Checkout a tarball using pristine-tar
+
+ Methods tested:
+ - L{gbp.deb.pristinetar.DebianPristineTar.checkout}
+
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> repo.pristine_tar.checkout('upstream', '1.0', 'gzip', '..')
+ """
+
+
+def test_pristine_tar_verify():
+ """
+ Verify a tarball using pristine-tar
+
+ Methods tested:
+ - L{gbp.deb.pristinetar.DebianPristineTar.verify}
+
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> if repo.pristine_tar.has_feature_verify():
+ ... repo.pristine_tar.verify('../upstream_1.0.orig.tar.gz')
+ """
+
+
+def test_pristine_tar_checkout_nonexistent():
+ """
+ Checkout a tarball that does not exist using pristine-tar
+
+ Methods tested:
+ - L{gbp.deb.pristinetar.DebianPristineTar.checkout}
+
+ # Silence error output
+ >>> import gbp.deb.git
+ >>> repo = gbp.deb.git.DebianGitRepository(dirs['repo'])
+ >>> _gbp_log_err_bak = gbp.log.err
+ >>> gbp.log.err = lambda x: None
+ >>> repo.pristine_tar.checkout('upstream', '1.1', 'gzip', '..')
+ Traceback (most recent call last):
+ ...
+ gbp.command_wrappers.CommandExecFailed: Pristine-tar couldn't checkout "upstream_1.1.orig.tar.gz": fatal: Path 'upstream_1.1.orig.tar.gz.delta' does not exist in 'refs/heads/pristine-tar'
+ pristine-tar: git show refs/heads/pristine-tar:upstream_1.1.orig.tar.gz.delta failed
+ >>> gbp.log.err = _gbp_log_err_bak
+ """
+
+# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
diff --git a/tests/doctests/test_create_remote_repo.py b/tests/doctests/test_create_remote_repo.py
new file mode 100644
index 0000000..fa82ef5
--- /dev/null
+++ b/tests/doctests/test_create_remote_repo.py
@@ -0,0 +1,122 @@
+# vim: set fileencoding=utf-8 :
+
+from gbp.scripts.create_remote_repo import (build_remote_script, # noqa: F401
+ parse_url) # noqa: F401
+
+
+def test_build_remote_script():
+ """
+ >>> build_remote_script({'base': 'base', 'dir': 'dir', 'pkg': 'pkg', 'template-dir': None, 'bare': True}, 'branch')
+ '\\nset -e\\numask 002\\nif [ -d base"dir" ]; then\\n echo "Repository at "basedir" already exists - giving up."\\n exit 1\\nfi\\nmkdir -p base"dir"\\ncd base"dir"\\ngit init --shared --bare\\necho "pkg packaging" > ./description\\necho "ref: refs/heads/branch" > ./HEAD\\n'
+ """
+
+
+def test_build_remote_script_template_dir():
+ """
+ >>> build_remote_script({'base': 'base', 'dir': 'dir', 'pkg': 'pkg', 'template-dir': '/doesnot/exist', 'bare': True}, 'branch')
+ '\\nset -e\\numask 002\\nif [ -d base"dir" ]; then\\n echo "Repository at "basedir" already exists - giving up."\\n exit 1\\nfi\\nmkdir -p base"dir"\\ncd base"dir"\\ngit init --shared --bare --template=/doesnot/exist\\necho "pkg packaging" > ./description\\necho "ref: refs/heads/branch" > ./HEAD\\n'
+ """
+
+
+def test_build_remote_script_bare():
+ """
+ >>> build_remote_script({'base': 'base', 'dir': 'dir', 'pkg': 'pkg', 'template-dir': None, 'bare': False}, 'branch')
+ '\\nset -e\\numask 002\\nif [ -d base"dir" ]; then\\n echo "Repository at "basedir" already exists - giving up."\\n exit 1\\nfi\\nmkdir -p base"dir"\\ncd base"dir"\\ngit init --shared\\necho "pkg packaging" > .git/description\\necho "ref: refs/heads/branch" > .git/HEAD\\n'
+ """
+
+
+def test_parse_url():
+ """
+ >>> url = parse_url("ssh://host/path/%(pkg)s", "origin", "package")
+ >>> url['base']
+ ''
+ >>> url['dir']
+ '/path/package'
+ >>> url['host']
+ 'host'
+ >>> url['name']
+ 'origin'
+ >>> url['pkg']
+ 'package'
+ >>> url['port']
+ >>> url['scheme']
+ 'ssh'
+ >>> url['template-dir']
+ >>> url['url']
+ 'ssh://host/path/package'
+
+ >>> url = parse_url("ssh://host:22/path/to/repo.git", "origin", "package")
+ >>> url['base']
+ ''
+ >>> url['dir']
+ '/path/to/repo.git'
+ >>> url['host']
+ 'host'
+ >>> url['name']
+ 'origin'
+ >>> url['pkg']
+ 'package'
+ >>> url['port']
+ '22'
+ >>> url['scheme']
+ 'ssh'
+ >>> url['template-dir']
+ >>> url['url']
+ 'ssh://host:22/path/to/repo.git'
+
+ >>> url = parse_url("ssh://host:22/~/path/%(pkg)s.git", "origin", "package")
+ >>> url['dir']
+ 'path/package.git'
+ >>> url['host']
+ 'host'
+ >>> url['name']
+ 'origin'
+ >>> url['pkg']
+ 'package'
+ >>> url['port']
+ '22'
+ >>> url['scheme']
+ 'ssh'
+ >>> url['template-dir']
+ >>> url['url']
+ 'ssh://host:22/~/path/package.git'
+ >>> url['bare']
+ True
+
+ >>> url = parse_url("ssh://host:22/~user/path/%(pkg)s.git", "origin", "package", "/doesnot/exist", bare=False)
+ >>> url['dir']
+ 'path/package.git'
+ >>> url['host']
+ 'host'
+ >>> url['name']
+ 'origin'
+ >>> url['pkg']
+ 'package'
+ >>> url['port']
+ '22'
+ >>> url['scheme']
+ 'ssh'
+ >>> url['template-dir']
+ '/doesnot/exist'
+ >>> url['url']
+ 'ssh://host:22/~user/path/package.git'
+ >>> url['bare']
+ False
+
+ >>> parse_url("git://host/repo.git", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: URL must use ssh protocol.
+ >>> parse_url("ssh://host/path/repo", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: URL needs to contain either a repository name or '%(pkg)s'
+ >>> parse_url("ssh://host:asdf/path/%(pkg)s.git", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: URL contains invalid port.
+ >>> parse_url("ssh://host/~us er/path/%(pkg)s.git", "origin", "package")
+ Traceback (most recent call last):
+ ...
+ gbp.errors.GbpError: URL contains invalid ~username expansion.
+ """
diff --git a/tests/test_RollbackDebianGitRepository.py b/tests/test_RollbackDebianGitRepository.py
new file mode 100644
index 0000000..a6ccc59
--- /dev/null
+++ b/tests/test_RollbackDebianGitRepository.py
@@ -0,0 +1,51 @@
+# vim: set fileencoding=utf-8 :
+"""Test L{gbp.deb.rollbackgit}"""
+
+import os
+
+from . testutils import DebianGitTestRepo
+from gbp.deb.rollbackgit import RollbackDebianGitRepository
+from gbp.git.repository import GitRepositoryError
+
+
+class TestRollbackGitRepository(DebianGitTestRepo):
+
+ def setUp(self):
+ DebianGitTestRepo.setUp(self, RollbackDebianGitRepository)
+
+ def test_empty_rollback(self):
+ self.repo.rollback()
+ self.assertEquals(self.repo.rollback_errors, [])
+
+ def test_rrr_tag(self):
+ self.repo.rrr_tag('doesnotexist')
+ self.assertEquals(self.repo.rollbacks, [('doesnotexist', 'tag', 'delete', None)])
+ self.repo.rollback()
+ self.assertEquals(self.repo.rollback_errors, [])
+
+ def test_rrr_branch(self):
+ self.repo.rrr_branch('doesnotexist', 'delete')
+ self.assertEquals(self.repo.rollbacks, [('doesnotexist', 'branch', 'delete', None)])
+ self.repo.rollback()
+ self.assertEquals(self.repo.rollback_errors, [])
+
+ def test_rrr_merge(self):
+ self.repo.rrr_merge('HEAD')
+ self.assertEquals(self.repo.rollbacks, [('HEAD', 'commit', 'abortmerge', None)])
+ self.repo.rollback()
+ self.assertEquals(self.repo.rollback_errors, [])
+
+ def test_rrr_merge_abort(self):
+ self.repo.rrr_merge('HEAD')
+ self.assertEquals(self.repo.rollbacks, [('HEAD', 'commit', 'abortmerge', None)])
+ # Test that we abort the merge in case MERGE_HEAD exists
+ with open(os.path.join(self.repo.git_dir, 'MERGE_HEAD'), 'w'):
+ pass
+ self.assertTrue(self.repo.is_in_merge())
+ self.repo.rollback()
+ self.assertFalse(self.repo.is_in_merge())
+ self.assertEquals(self.repo.rollback_errors, [])
+
+ def test_rrr_unknown_action(self):
+ with self.assertRaisesRegexp(GitRepositoryError, "Unknown action 'unknown' for tag 'doesnotmatter'"):
+ self.repo.rrr('doesnotmatter', 'unknown', 'tag')
diff --git a/tests/testutils/__init__.py b/tests/testutils/__init__.py
new file mode 100644
index 0000000..6ce1bc5
--- /dev/null
+++ b/tests/testutils/__init__.py
@@ -0,0 +1,143 @@
+# vim: set fileencoding=utf-8 :
+
+from .. import context # noqa: F401
+
+import os
+import shutil
+import subprocess
+import tarfile
+import tempfile
+import unittest
+import zipfile
+
+import gbp.log
+import gbp.errors
+from gbp.deb.changelog import ChangeLog
+
+from . gbplogtester import GbpLogTester
+from . debiangittestrepo import DebianGitTestRepo
+from . capture import capture_stdout, capture_stderr
+from . popen import patch_popen
+
+__all__ = ['GbpLogTester', 'DebianGitTestRepo', 'OsReleaseFile',
+ 'MockedChangeLog', 'get_dch_default_urgency',
+ 'capture_stderr', 'capture_stdout',
+ 'ls_dir', 'ls_tar', 'ls_zip',
+ 'patch_popen', 'have_cmd', 'skip_without_cmd']
+
+
+class OsReleaseFile(object):
+ """Repesents a simple file with key-value pairs"""
+
+ def __init__(self, filename="/etc/os-release"):
+ self._values = {}
+
+ try:
+ with open(filename, 'r') as filed:
+ for line in filed.readlines():
+ try:
+ key, value = line.split('=', 1)
+ except ValueError:
+ pass
+ else:
+ self._values[key] = value.strip()
+ except IOError as err:
+ gbp.log.info('Failed to read OS release file %s: %s' %
+ (filename, err))
+
+ def __getitem__(self, key):
+ if key in self._values:
+ return self._values[key]
+ return None
+
+ def __contains__(self, key):
+ return key in self._values
+
+ def __str__(self):
+ return str(self._values)
+
+ def __repr__(self):
+ return repr(self._values)
+
+
+class MockedChangeLog(ChangeLog):
+ contents = """foo (%s) experimental; urgency=low
+
+ %s
+
+ -- Debian Maintainer <maint@debian.org> Sat, 01 Jan 2012 00:00:00 +0100"""
+
+ def __init__(self, version, changes="a important change"):
+ ChangeLog.__init__(self,
+ contents=self.contents % (version, changes))
+
+
+def get_dch_default_urgency():
+ """Determine the default urgency level used by dch"""
+ urgency = 'medium'
+ tempdir = tempfile.mkdtemp()
+ tmp_dch_name = os.path.join(tempdir, 'changelog')
+ try:
+ dch_cmd = ['debchange', '--create', '--empty', '--changelog', tmp_dch_name,
+ '--package=foo', '--newversion=1',
+ '--distribution=UNRELEASED']
+ ret = subprocess.Popen(dch_cmd).wait()
+ except OSError:
+ pass
+ else:
+ if ret == 0:
+ with open(tmp_dch_name, encoding='utf-8') as dchfile:
+ header = dchfile.readline().strip()
+ urgency = header.split()[-1].replace('urgency=', '')
+ finally:
+ if os.path.isdir(tempdir):
+ shutil.rmtree(tempdir)
+ return urgency
+
+
+def ls_dir(directory, directories=True):
+ """List the contents of directory, recurse to subdirectories"""
+ contents = set()
+ for root, dirs, files in os.walk(directory):
+ prefix = ''
+ if root != directory:
+ prefix = os.path.relpath(root, directory) + '/'
+ contents.update(['%s%s' % (prefix, fname) for fname in files])
+ if directories:
+ contents.update(['%s%s' % (prefix, dname) for dname in dirs])
+ return contents
+
+
+def ls_tar(tarball, directories=True):
+ """List the contents of tar archive"""
+ tmpdir = tempfile.mkdtemp()
+ try:
+ tarobj = tarfile.open(tarball, 'r')
+ tarobj.extractall(tmpdir)
+ return ls_dir(tmpdir, directories)
+ finally:
+ shutil.rmtree(tmpdir)
+
+
+def ls_zip(archive, directories=True):
+ """List the contents of zip file"""
+ tmpdir = tempfile.mkdtemp()
+ try:
+ zipobj = zipfile.ZipFile(archive, 'r')
+ zipobj.extractall(tmpdir)
+ return ls_dir(tmpdir, directories)
+ finally:
+ shutil.rmtree(tmpdir)
+
+
+def have_cmd(cmd):
+ """Check if a command is available"""
+ return True if shutil.which(cmd) else False
+
+
+def skip_without_cmd(cmd):
+ """Skip if a command is not available"""
+ if have_cmd(cmd):
+ return lambda func: func
+ else:
+ return unittest.skip("Command '%s' not found" % cmd)
diff --git a/tests/testutils/capture.py b/tests/testutils/capture.py
new file mode 100644
index 0000000..cc53110
--- /dev/null
+++ b/tests/testutils/capture.py
@@ -0,0 +1,53 @@
+# vim: set fileencoding=utf-8 :
+
+import sys
+from contextlib import contextmanager
+from io import StringIO
+
+
+class _StderrCapture(StringIO):
+ def save(self):
+ self.safed = sys.stderr
+ sys.stderr = self
+
+ def restore(self):
+ if self.safed is not None:
+ sys.stderr = self.safed
+ self.safed = None
+
+ def output(self):
+ self.seek(0)
+ return self.read()
+
+
+class _StdoutCapture(StringIO):
+ def save(self):
+ self.safed = sys.stdout
+ sys.stdout = self
+
+ def restore(self):
+ if self.safed is not None:
+ sys.stdout = self.safed
+ self.safed = None
+
+ def output(self):
+ self.seek(0)
+ return self.read()
+
+
+@contextmanager
+def capture_stderr():
+ """Capture an output and return its content"""
+ c = _StderrCapture()
+ c.save()
+ yield c
+ c.restore()
+
+
+@contextmanager
+def capture_stdout():
+ """Capture an output and return its content"""
+ c = _StdoutCapture()
+ c.save()
+ yield c
+ c.restore()
diff --git a/tests/testutils/data.py b/tests/testutils/data.py
new file mode 100644
index 0000000..da2b28d
--- /dev/null
+++ b/tests/testutils/data.py
@@ -0,0 +1,35 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2016 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, please see
+# <http://www.gnu.org/licenses/>
+
+from functools import wraps
+
+import unittest
+
+
+class TestCaseWithData(unittest.TestCase):
+ @staticmethod
+ def feed(data):
+ def wrapper(fn):
+ @wraps(fn)
+ def feed_item(self, *args):
+ for d in data:
+ try:
+ fn(self, *((d,) + args))
+ except self.failureException as e:
+ raise self.failureException(e.message + " with data %s" % repr(d))
+ return feed_item
+ return wrapper
diff --git a/tests/testutils/debiangittestrepo.py b/tests/testutils/debiangittestrepo.py
new file mode 100644
index 0000000..2018452
--- /dev/null
+++ b/tests/testutils/debiangittestrepo.py
@@ -0,0 +1,47 @@
+# vim: set fileencoding=utf-8 :
+
+from .. import context
+
+import os
+import unittest
+
+import gbp.deb.git
+
+
+class DebianGitTestRepo(unittest.TestCase):
+ """Scratch repo for a single unit test"""
+
+ def setUp(self, repo_cls=None):
+ name = 'test_repo'
+ self.tmpdir = context.new_tmpdir(__name__)
+
+ if repo_cls is None:
+ repo_cls = gbp.deb.git.DebianGitRepository
+
+ repodir = self.tmpdir.join(name)
+ self.repodir = os.path.join(str(self.tmpdir), name)
+ self.repo = repo_cls.create(repodir)
+
+ def tearDown(self):
+ context.teardown()
+
+ def add_file(self, name, content=None, msg=None, mode=None):
+ """
+ Add a single file with name I{name} and content I{content}. If
+ I{content} is C{none} the content of the file is undefined.
+
+ @param name: the file's path relativ to the git repo
+ @type name: C{str}
+ @param content: the file's content
+ @type content: C{str}
+ """
+ path = os.path.join(self.repo.path, name)
+
+ d = os.path.dirname(path)
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+ with open(path, mode or 'w+') as f:
+ content is None or f.write(content)
+ self.repo.add_files(name, force=True)
+ self.repo.commit_files(path, msg or "added %s" % name)
diff --git a/tests/testutils/gbplogtester.py b/tests/testutils/gbplogtester.py
new file mode 100644
index 0000000..b42e090
--- /dev/null
+++ b/tests/testutils/gbplogtester.py
@@ -0,0 +1,108 @@
+# vim: set fileencoding=utf-8 :
+
+import re
+from io import StringIO
+from nose.tools import ok_, assert_less
+
+import gbp.log
+
+
+class GbpLogTester(object):
+ """
+ Helper class for tests that need to capture logging output
+ """
+ def __init__(self):
+ """Object initialization"""
+ # Warnings and Errors
+ self._log = None
+ self._loghandler = None
+ # Info and Debug messages
+ self._log_info = None
+ self._loghandler_info = None
+
+ def _capture_log(self, capture=True):
+ """ Capture log"""
+ if capture:
+ assert self._log is None, "Log capture already started"
+
+ handlers = list(gbp.log.LOGGER.handlers)
+ for hdl in handlers:
+ gbp.log.LOGGER.removeHandler(hdl)
+
+ self._log = StringIO()
+ self._loghandler = gbp.log.GbpStreamHandler(self._log, False)
+ self._loghandler.addFilter(gbp.log.GbpFilter([gbp.log.WARNING,
+ gbp.log.ERROR]))
+
+ self._log_info = StringIO()
+ self._loghandler_info = gbp.log.GbpStreamHandler(self._log_info, False)
+ self._loghandler_info.addFilter(gbp.log.GbpFilter([gbp.log.DEBUG,
+ gbp.log.INFO]))
+ gbp.log.LOGGER.addHandler(self._loghandler)
+ gbp.log.LOGGER.addHandler(self._loghandler_info)
+ else:
+ assert self._log is not None, "Log capture not started"
+ gbp.log.LOGGER.removeHandler(self._loghandler)
+ self._loghandler.close()
+ self._log.close()
+ self._loghandler = self._log = None
+
+ gbp.log.LOGGER.removeHandler(self._loghandler_info)
+ self._loghandler_info.close()
+ self._log_info.close()
+ self._loghandler_info = self._log_info = None
+
+ def _get_log(self):
+ """Get the captured log output"""
+ self._log.seek(0)
+ return self._log.readlines()
+
+ def _get_log_info(self):
+ self._log_info.seek(0)
+ return self._log_info.readlines()
+
+ def _check_log_empty(self):
+ """Check that nothig was logged"""
+ output = self._get_log()
+ ok_(output == [], "Log is not empty: %s" % output)
+
+ def _check_log(self, linenum, regex):
+ """Check that the specified line on log matches expectations"""
+ if self._log is None:
+ raise Exception("BUG in unittests: no log captured!")
+ log = self._get_log()
+ assert_less(linenum, len(log),
+ "Not enough log lines: %d" % len(log))
+ output = self._get_log()[linenum].strip()
+ ok_(re.match(regex, output),
+ "Log entry '%s' doesn't match '%s'" % (output, regex))
+
+ def _check_in_log(self, regex):
+ """Check that at least one line in log matches expectations"""
+ found = False
+ if self._log is None:
+ raise Exception("BUG in unittests: no log captured!")
+ log = self._get_log()
+ for line in log:
+ if re.match(regex, line):
+ found = True
+ break
+ ok_(found, "No line of %s matched '%s'" % (log, regex))
+
+ def _check_in_info_log(self, regex):
+ """Check that at least one line in info log matches expectations"""
+ found = False
+ if self._log_info is None:
+ raise Exception("BUG in unittests: no log captured!")
+ log = self._get_log_info()
+ for line in log:
+ if re.match(regex, line):
+ found = True
+ break
+ ok_(found, "No line of %s matched '%s'" % (log, regex))
+
+ def _clear_log(self):
+ """Clear the mock strerr"""
+ if self._log is not None:
+ self._log.seek(0)
+ self._log.truncate()
diff --git a/tests/testutils/popen.py b/tests/testutils/popen.py
new file mode 100644
index 0000000..725e1f7
--- /dev/null
+++ b/tests/testutils/popen.py
@@ -0,0 +1,18 @@
+# vim: set fileencoding=utf-8 :
+
+import functools
+import mock
+
+
+def patch_popen(stdout=b'', stderr=b'', returncode=1):
+ """Decorator to easily set the return value of popen.communicate()"""
+ def patch_popen_decorator(func):
+ @functools.wraps(func)
+ def wrap(self):
+ with mock.patch('subprocess.Popen') as create_mock:
+ popen_mock = mock.Mock(**{'returncode': returncode,
+ 'communicate.return_value': (stdout, stderr)})
+ create_mock.return_value = popen_mock
+ return func(self, create_mock)
+ return wrap
+ return patch_popen_decorator
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..2f98f90
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,23 @@
+[tox]
+envlist = pep8, py27
+skipsdist = True
+
+[testenv]
+whitelist_externals =
+ find
+install_command = pip install {opts} {packages}
+deps = -r{toxinidir}/requirements.txt
+sitepackages = True
+commands =
+ find . -type f -name "*.pyc" -delete
+
+[testenv:pep8]
+deps = hacking
+commands =
+ find . -type f -name "*.pyc" -delete
+ flake8 {posargs}
+
+[testenv:py27]
+commands =
+ find . -type f -name "*.pyc" -delete
+ python setup.py nosetests --verbosity=3 --with-xcoverage {posargs}