scm-wrapper: Refactor getting files from Git

Before this patch, there were two code paths: either getting the only
the wanted content by calling git-archive, or cloning the repository and
copying the files.

Both these approaches have the downside of not allowing retriving
content from a specific git commit.

The workaround is to create a new empty repo (in the location to which
we cloned previously), fetching the specific commit to there and then
checking it out.

This supports any commit and works identically for any protocol. The
downside is that all files in that commit will be downloaded. It should
be no worse than the git-clone path, but can result in bigger transfers
than git-archive.

Unfortunately this is only supported with git 2.5+. On older version
fetch will fail with no error message (tested with 1.8.3). This can be
used to fall back to full clone. This fallback is clearly suboptimal in
terms of data transfer, but it should work reliably.

Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
This commit is contained in:
Lubomír Sedlář 2019-02-25 14:07:58 +01:00
parent ef6bb20a0e
commit ff4e6d4782
2 changed files with 110 additions and 164 deletions

View file

@ -104,15 +104,23 @@ class FileSCMTestCase(SCMBaseTest):
class GitSCMTestCase(SCMBaseTest):
def assertCalls(self, mock_run, url, branch, command=None):
command = [command] if command else []
self.assertEqual(
[call[0][0] for call in mock_run.call_args_list],
[
["git", "init"],
["git", "fetch", "--depth=1", url, branch],
["git", "checkout", "FETCH_HEAD"],
] + command,
)
@mock.patch('pungi.wrappers.scm.run')
def test_get_file(self, run):
commands = []
def process(cmd, workdir=None, **kwargs):
if cmd.startswith('/usr/bin/git'):
fname = cmd.split('|')[0].strip().split(' ')[-1]
touch(os.path.join(workdir, fname))
commands.append(cmd)
touch(os.path.join(workdir, 'some_file.txt'))
touch(os.path.join(workdir, 'other_file.txt'))
run.side_effect = process
@ -121,19 +129,43 @@ class GitSCMTestCase(SCMBaseTest):
'file': 'some_file.txt'},
self.destdir)
self.assertStructure(retval, ['some_file.txt'])
self.assertEqual(
commands,
['/usr/bin/git archive --remote=git://example.com/git/repo.git master some_file.txt | tar xf -'])
self.assertCalls(run, "git://example.com/git/repo.git", "master")
@mock.patch('pungi.wrappers.scm.run')
def test_get_file_generated_by_command_via_git(self, run):
commands = []
def test_get_file_fetch_fails(self, run):
url = "git://example.com/git/repo.git"
def process(cmd, workdir=None, **kwargs):
if cmd.startswith('/usr/bin/git'):
checkout = cmd.split(' ')[-1]
touch(os.path.join(checkout, 'some_file.txt'))
commands.append(cmd)
if "fetch" in cmd:
exc = RuntimeError()
exc.output = ""
raise exc
touch(os.path.join(workdir, 'some_file.txt'))
touch(os.path.join(workdir, 'other_file.txt'))
run.side_effect = process
retval = scm.get_file_from_scm(
{"scm": "git", "repo": url, "file": "some_file.txt"}, self.destdir
)
self.assertStructure(retval, ['some_file.txt'])
self.assertEqual(
[call[0][0] for call in run.call_args_list],
[
["git", "init"],
["git", "fetch", "--depth=1", url, "master"],
["git", "remote", "add", "origin", url],
["git", "remote", "update", "origin"],
["git", "checkout", "master"],
],
)
@mock.patch('pungi.wrappers.scm.run')
def test_get_file_generated_by_command(self, run):
def process(cmd, workdir=None, **kwargs):
if cmd[0] == "git":
touch(os.path.join(workdir, 'some_file.txt'))
return 0, ''
run.side_effect = process
@ -144,21 +176,16 @@ class GitSCMTestCase(SCMBaseTest):
'command': 'make'},
self.destdir)
self.assertStructure(retval, ['some_file.txt'])
self.assertRegexpMatches(
commands[0],
r'/usr/bin/git clone --depth 1 --branch=master git://example.com/git/repo.git /tmp/.+')
self.assertEqual(commands[1:], ['make'])
self.assertCalls(run, "git://example.com/git/repo.git", "master", "make")
@mock.patch('pungi.wrappers.scm.run')
def test_get_file_and_fail_to_generate(self, run):
commands = []
def process(cmd, workdir=None, **kwargs):
if cmd.startswith('/usr/bin/git'):
checkout = cmd.split(' ')[-1]
touch(os.path.join(checkout, 'some_file.txt'))
commands.append(cmd)
return len(commands), 'output'
if cmd[0] == "git":
touch(os.path.join(workdir, 'some_file.txt'))
return 0, "output"
return 1, "output"
run.side_effect = process
@ -169,63 +196,14 @@ class GitSCMTestCase(SCMBaseTest):
'command': 'make'},
self.destdir)
self.assertEqual(str(ctx.exception), "'make' failed with exit code 2")
@mock.patch('pungi.wrappers.scm.run')
def test_get_file_generated_by_command_via_https(self, run):
commands = []
def process(cmd, workdir=None, **kwargs):
if cmd.startswith('/usr/bin/git'):
checkout = cmd.split(' ')[-1]
touch(os.path.join(checkout, 'some_file.txt'))
commands.append(cmd)
return 0, ''
run.side_effect = process
retval = scm.get_file_from_scm({'scm': 'git',
'repo': 'https://example.com/git/repo.git',
'file': 'some_file.txt',
'command': 'make'},
self.destdir)
self.assertStructure(retval, ['some_file.txt'])
self.assertRegexpMatches(
commands[0],
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
self.assertEqual(commands[1:], ['make'])
@mock.patch('pungi.wrappers.scm.run')
def test_get_file_via_https(self, run):
commands = []
def process(cmd, workdir=None, **kwargs):
checkout = cmd.split(' ')[-1]
touch(os.path.join(checkout, 'some_file.txt'))
touch(os.path.join(checkout, 'other_file.txt'))
commands.append(cmd)
run.side_effect = process
retval = scm.get_file_from_scm({'scm': 'git',
'repo': 'https://example.com/git/repo.git',
'file': 'some_file.txt'},
self.destdir)
self.assertStructure(retval, ['some_file.txt'])
self.assertEqual(1, len(commands))
self.assertRegexpMatches(
commands[0],
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
self.assertEqual(str(ctx.exception), "'make' failed with exit code 1")
@mock.patch('pungi.wrappers.scm.run')
def test_get_dir(self, run):
commands = []
def process(cmd, workdir=None, **kwargs):
fname = cmd.split('|')[0].strip().split(' ')[-1]
touch(os.path.join(workdir, fname, 'first'))
touch(os.path.join(workdir, fname, 'second'))
commands.append(cmd)
touch(os.path.join(workdir, "subdir", 'first'))
touch(os.path.join(workdir, "subdir", 'second'))
run.side_effect = process
@ -234,21 +212,15 @@ class GitSCMTestCase(SCMBaseTest):
'dir': 'subdir'},
self.destdir)
self.assertStructure(retval, ['first', 'second'])
self.assertEqual(
commands,
['/usr/bin/git archive --remote=git://example.com/git/repo.git master subdir | tar xf -'])
self.assertCalls(run, "git://example.com/git/repo.git", "master")
@mock.patch('pungi.wrappers.scm.run')
def test_get_dir_via_git_and_generate(self, run):
commands = []
def test_get_dir_and_generate(self, run):
def process(cmd, workdir=None, **kwargs):
if cmd.startswith('/usr/bin/git'):
checkout = cmd.split(' ')[-1]
touch(os.path.join(checkout, 'subdir', 'first'))
touch(os.path.join(checkout, 'subdir', 'second'))
commands.append(cmd)
if cmd[0] == "git":
touch(os.path.join(workdir, 'subdir', 'first'))
touch(os.path.join(workdir, 'subdir', 'second'))
return 0, ''
run.side_effect = process
@ -259,59 +231,7 @@ class GitSCMTestCase(SCMBaseTest):
'command': 'make'},
self.destdir)
self.assertStructure(retval, ['first', 'second'])
self.assertRegexpMatches(
commands[0],
r'/usr/bin/git clone --depth 1 --branch=master git://example.com/git/repo.git /tmp/.+')
self.assertEqual(commands[1:], ['make'])
@mock.patch('pungi.wrappers.scm.run')
def test_get_dir_via_https(self, run):
commands = []
def process(cmd, workdir=None, **kwargs):
checkout = cmd.split(' ')[-1]
touch(os.path.join(checkout, 'subdir', 'first'))
touch(os.path.join(checkout, 'subdir', 'second'))
commands.append(cmd)
run.side_effect = process
retval = scm.get_dir_from_scm({'scm': 'git',
'repo': 'https://example.com/git/repo.git',
'dir': 'subdir'},
self.destdir)
self.assertStructure(retval, ['first', 'second'])
self.assertRegexpMatches(
commands[0],
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
@mock.patch('pungi.wrappers.scm.run')
def test_get_dir_via_https_and_generate(self, run):
commands = []
def process(cmd, workdir=None, **kwargs):
if cmd.startswith('/usr/bin/git'):
checkout = cmd.split(' ')[-1]
touch(os.path.join(checkout, 'subdir', 'first'))
touch(os.path.join(checkout, 'subdir', 'second'))
commands.append(cmd)
return 0, ''
run.side_effect = process
retval = scm.get_dir_from_scm({'scm': 'git',
'repo': 'https://example.com/git/repo.git',
'dir': 'subdir',
'command': 'make'},
self.destdir)
self.assertStructure(retval, ['first', 'second'])
self.assertRegexpMatches(
commands[0],
r'/usr/bin/git clone --depth 1 --branch=master https://example.com/git/repo.git /tmp/.+')
self.assertEqual(commands[1:], ['make'])
self.assertCalls(run, "git://example.com/git/repo.git", "master", "make")
class RpmSCMTestCase(SCMBaseTest):