From ca40ba71ed6aa6e29464f882af1e9653e7091247 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Thu, 17 Oct 2019 12:22:45 +0200 Subject: [PATCH 1/3] Optionally specify the main git repository Follows git's convention for option naming --- README.rst | 1 + git_archive_all.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 16dbbfb..3dd7f6c 100644 --- a/README.rst +++ b/README.rst @@ -33,6 +33,7 @@ Archive repository with all its submodules. --prefix=PREFIX Prepend PREFIX to each filename in the archive. OUTPUT_FILE name is used by default to avoid tarbomb. You can set it to '' in order to explicitly request tarbomb. + -C BASE_REPO Use BASE_REPO as the main repository git working directory to archive. Defaults to current directory when empty --no-exclude Don't read .gitattributes files for patterns containing export-ignore attributes. --force-submodules Force a `git submodule init && git submodule update` at each level before iterating submodules diff --git a/git_archive_all.py b/git_archive_all.py index 8d454ec..5034f91 100755 --- a/git_archive_all.py +++ b/git_archive_all.py @@ -484,8 +484,8 @@ def main(): from optparse import OptionParser, SUPPRESS_HELP parser = OptionParser( - usage="usage: %prog [-v] [--prefix PREFIX] [--no-exclude] [--force-submodules]" - " [--extra EXTRA1 ...] [--dry-run] [-0 | ... | -9] OUTPUT_FILE", + usage="usage: %prog [-v] [-C BASE_REPO] [--prefix PREFIX] [--no-exclude]" + " [--force-submodules] [--extra EXTRA1 ...] [--dry-run] [-0 | ... | -9] OUTPUT_FILE", version="%prog {0}".format(__version__) ) @@ -497,6 +497,13 @@ def main(): OUTPUT_FILE name is used by default to avoid tarbomb. You can set it to '' in order to explicitly request tarbomb""") + parser.add_option('-C', + type='string', + dest='base_repo', + default=None, + help="""use BASE_REPO as the main repository git working directory to archive. + Defaults to current directory when empty""") + parser.add_option('-v', '--verbose', action='store_true', dest='verbose', @@ -561,7 +568,9 @@ def main(): archiver = GitArchiver(options.prefix, options.exclude, options.force_sub, - options.extra) + options.extra, + path.abspath(options.base_repo) if options.base_repo is not None else None + ) archiver.create(output_file_path, options.dry_run, compresslevel=options.compresslevel) except Exception as e: parser.exit(2, "{0}\n".format(e)) From df3a9a20a95925c3edf9d5b62d6cd98395640e99 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Thu, 31 Oct 2019 14:58:15 +0100 Subject: [PATCH 2/3] Make main() more testable --- git_archive_all.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/git_archive_all.py b/git_archive_all.py index 5034f91..e3e4922 100755 --- a/git_archive_all.py +++ b/git_archive_all.py @@ -480,7 +480,10 @@ def get_git_version(cls): return None -def main(): +def main(argv=None): + if argv is None: + argv = sys.argv + from optparse import OptionParser, SUPPRESS_HELP parser = OptionParser( @@ -538,7 +541,7 @@ def main(): dest='compresslevel', help=SUPPRESS_HELP) - options, args = parser.parse_args() + options, args = parser.parse_args(argv[1:]) if len(args) != 1: parser.error("You must specify exactly one output file") @@ -575,8 +578,8 @@ def main(): except Exception as e: parser.exit(2, "{0}\n".format(e)) - sys.exit(0) + return 0 if __name__ == '__main__': - main() + sys.exit(main()) From 53042d32529e3a104f1b9ebc2645bd80503b9edc Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Thu, 31 Oct 2019 14:58:31 +0100 Subject: [PATCH 3/3] Test for running tool with actual CLI parsing Also tests the `-C` option --- test_git_archive_all.py | 71 ++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/test_git_archive_all.py b/test_git_archive_all.py index 13bae77..9d3f09a 100644 --- a/test_git_archive_all.py +++ b/test_git_archive_all.py @@ -14,6 +14,7 @@ import pycodestyle import pytest +import git_archive_all from git_archive_all import GitArchiver @@ -140,6 +141,36 @@ def archive(self, path): a.create(path) +def make_expected_tree(contents): + e = {} + + for k, v in contents.items(): + if v.kind == 'file' and not v.excluded: + e[k] = v.contents + elif v.kind in ('dir', 'submodule') and not v.excluded: + for nested_k, nested_v in make_expected_tree(v.contents).items(): + e[as_posix(os.path.join(k, nested_k))] = nested_v + + return e + + +def make_actual_tree(tar_file): + a = {} + + for m in tar_file.getmembers(): + if m.isfile(): + name = m.name + + if sys.version_info < (3,): + name = m.name.decode('utf-8') + + a[name] = tar_file.extractfile(m).read().decode() + else: + raise NotImplementedError + + return a + + base = { 'app': DirRecord({ '__init__.py': FileRecord('#Beautiful is better than ugly.'), @@ -294,36 +325,30 @@ def test_ignore(contents, tmpdir, git_env, monkeypatch): repo.archive(repo_tar_path) repo_tar = TarFile(repo_tar_path, format=PAX_FORMAT, encoding='utf-8') - def make_expected(contents): - e = {} + expected = make_expected_tree(contents) + actual = make_actual_tree(repo_tar) - for k, v in contents.items(): - if v.kind == 'file' and not v.excluded: - e[k] = v.contents - elif v.kind in ('dir', 'submodule') and not v.excluded: - for nested_k, nested_v in make_expected(v.contents).items(): - e[as_posix(os.path.join(k, nested_k))] = nested_v - - return e + assert actual == expected - def make_actual(tar_file): - a = {} - for m in tar_file.getmembers(): - if m.isfile(): - name = m.name +def test_cli(tmpdir, git_env, monkeypatch): + contents = base - if sys.version_info < (3,): - name = m.name.decode('utf-8') + for name, value in git_env.items(): + monkeypatch.setenv(name, value) - a[name] = tar_file.extractfile(m).read().decode() - else: - raise NotImplementedError + repo_path = os.path.join(str(tmpdir), 'repo') + repo = Repo(repo_path) + repo.init() + repo.add_dir('.', contents) + repo.commit('init') - return a + repo_tar_path = os.path.join(str(tmpdir), 'repo.tar') + git_archive_all.main(['git_archive_all.py', '--prefix', '', '-C', repo_path, repo_tar_path]) + repo_tar = TarFile(repo_tar_path, format=PAX_FORMAT, encoding='utf-8') - expected = make_expected(contents) - actual = make_actual(repo_tar) + expected = make_expected_tree(contents) + actual = make_actual_tree(repo_tar) assert actual == expected