Skip to content

Commit 5f77fa0

Browse files
authored
Merge pull request #3795 from Flamefire/git-tags
make sure git clone with a tag argument actually downloads a tag
2 parents bbd003f + 6a83152 commit 5f77fa0

File tree

2 files changed

+134
-78
lines changed

2 files changed

+134
-78
lines changed

easybuild/tools/filetools.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,32 +2503,65 @@ def get_source_tarball_from_git(filename, targetdir, git_config):
25032503
# compose 'git clone' command, and run it
25042504
clone_cmd = ['git', 'clone']
25052505

2506+
if not keep_git_dir:
2507+
# Speed up cloning by only fetching the most recent commit, not the whole history
2508+
# When we don't want to keep the .git folder there won't be a difference in the result
2509+
clone_cmd.extend(['--depth', '1'])
2510+
25062511
if tag:
25072512
clone_cmd.extend(['--branch', tag])
2508-
2509-
if recursive:
2510-
clone_cmd.append('--recursive')
2513+
if recursive:
2514+
clone_cmd.append('--recursive')
2515+
else:
2516+
# checkout is done separately below for specific commits
2517+
clone_cmd.append('--no-checkout')
25112518

25122519
clone_cmd.append('%s/%s.git' % (url, repo_name))
25132520

25142521
tmpdir = tempfile.mkdtemp()
25152522
cwd = change_dir(tmpdir)
2516-
run.run_cmd(' '.join(clone_cmd), log_all=True, log_ok=False, simple=False, regexp=False)
2523+
run.run_cmd(' '.join(clone_cmd), log_all=True, simple=True, regexp=False)
25172524

25182525
# if a specific commit is asked for, check it out
25192526
if commit:
25202527
checkout_cmd = ['git', 'checkout', commit]
25212528
if recursive:
25222529
checkout_cmd.extend(['&&', 'git', 'submodule', 'update', '--init', '--recursive'])
25232530

2524-
run.run_cmd(' '.join(checkout_cmd), log_all=True, log_ok=False, simple=False, regexp=False, path=repo_name)
2531+
run.run_cmd(' '.join(checkout_cmd), log_all=True, simple=True, regexp=False, path=repo_name)
2532+
2533+
elif not build_option('extended_dry_run'):
2534+
# If we wanted to get a tag make sure we actually got a tag and not a branch with the same name
2535+
# This doesn't make sense in dry-run mode as we don't have anything to check
2536+
cmd = 'git describe --exact-match --tags HEAD'
2537+
# Note: Disable logging to also disable the error handling in run_cmd
2538+
(out, ec) = run.run_cmd(cmd, log_ok=False, log_all=False, regexp=False, path=repo_name)
2539+
if ec != 0 or tag not in out.splitlines():
2540+
print_warning('Tag %s was not downloaded in the first try due to %s/%s containing a branch'
2541+
' with the same name. You might want to alert the maintainers of %s about that issue.',
2542+
tag, url, repo_name, repo_name)
2543+
cmds = []
2544+
2545+
if not keep_git_dir:
2546+
# make the repo unshallow first;
2547+
# this is equivalent with 'git fetch -unshallow' in Git 1.8.3+
2548+
# (first fetch seems to do nothing, unclear why)
2549+
cmds.append('git fetch --depth=2147483647 && git fetch --depth=2147483647')
2550+
2551+
cmds.append('git checkout refs/tags/' + tag)
2552+
# Clean all untracked files, e.g. from left-over submodules
2553+
cmds.append('git clean --force -d -x')
2554+
if recursive:
2555+
cmds.append('git submodule update --init --recursive')
2556+
for cmd in cmds:
2557+
run.run_cmd(cmd, log_all=True, simple=True, regexp=False, path=repo_name)
25252558

25262559
# create an archive and delete the git repo directory
25272560
if keep_git_dir:
25282561
tar_cmd = ['tar', 'cfvz', targetpath, repo_name]
25292562
else:
25302563
tar_cmd = ['tar', 'cfvz', targetpath, '--exclude', '.git', repo_name]
2531-
run.run_cmd(' '.join(tar_cmd), log_all=True, log_ok=False, simple=False, regexp=False)
2564+
run.run_cmd(' '.join(tar_cmd), log_all=True, simple=True, regexp=False)
25322565

25332566
# cleanup (repo_name dir does not exist in dry run mode)
25342567
change_dir(cwd)

test/framework/filetools.py

Lines changed: 95 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2537,62 +2537,8 @@ def test_diff_files(self):
25372537
def test_get_source_tarball_from_git(self):
25382538
"""Test get_source_tarball_from_git function."""
25392539

2540-
git_config = {
2541-
'repo_name': 'testrepository',
2542-
'url': 'https://github.com/easybuilders',
2543-
'tag': 'main',
2544-
}
25452540
target_dir = os.path.join(self.test_prefix, 'target')
25462541

2547-
try:
2548-
ft.get_source_tarball_from_git('test.tar.gz', target_dir, git_config)
2549-
# (only) tarball is created in specified target dir
2550-
self.assertTrue(os.path.isfile(os.path.join(target_dir, 'test.tar.gz')))
2551-
self.assertEqual(os.listdir(target_dir), ['test.tar.gz'])
2552-
2553-
del git_config['tag']
2554-
git_config['commit'] = '8456f86'
2555-
ft.get_source_tarball_from_git('test2.tar.gz', target_dir, git_config)
2556-
self.assertTrue(os.path.isfile(os.path.join(target_dir, 'test2.tar.gz')))
2557-
self.assertEqual(sorted(os.listdir(target_dir)), ['test.tar.gz', 'test2.tar.gz'])
2558-
2559-
except EasyBuildError as err:
2560-
if "Network is down" in str(err):
2561-
print("Ignoring download error in test_get_source_tarball_from_git, working offline?")
2562-
else:
2563-
raise err
2564-
2565-
git_config = {
2566-
'repo_name': 'testrepository',
2567-
'url': '[email protected]:easybuilders',
2568-
'tag': 'master',
2569-
}
2570-
args = ['test.tar.gz', self.test_prefix, git_config]
2571-
2572-
for key in ['repo_name', 'url', 'tag']:
2573-
orig_value = git_config.pop(key)
2574-
if key == 'tag':
2575-
error_pattern = "Neither tag nor commit found in git_config parameter"
2576-
else:
2577-
error_pattern = "%s not specified in git_config parameter" % key
2578-
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2579-
git_config[key] = orig_value
2580-
2581-
git_config['commit'] = '8456f86'
2582-
error_pattern = "Tag and commit are mutually exclusive in git_config parameter"
2583-
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2584-
del git_config['commit']
2585-
2586-
git_config['unknown'] = 'foobar'
2587-
error_pattern = "Found one or more unexpected keys in 'git_config' specification"
2588-
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2589-
del git_config['unknown']
2590-
2591-
args[0] = 'test.txt'
2592-
error_pattern = "git_config currently only supports filename ending in .tar.gz"
2593-
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2594-
args[0] = 'test.tar.gz'
2595-
25962542
# only test in dry run mode, i.e. check which commands would be executed without actually running them
25972543
build_options = {
25982544
'extended_dry_run': True,
@@ -2602,13 +2548,10 @@ def test_get_source_tarball_from_git(self):
26022548

26032549
def run_check():
26042550
"""Helper function to run get_source_tarball_from_git & check dry run output"""
2605-
self.mock_stdout(True)
2606-
self.mock_stderr(True)
2607-
res = ft.get_source_tarball_from_git('test.tar.gz', target_dir, git_config)
2608-
stdout = self.get_stdout()
2609-
stderr = self.get_stderr()
2610-
self.mock_stdout(False)
2611-
self.mock_stderr(False)
2551+
with self.mocked_stdout_stderr():
2552+
res = ft.get_source_tarball_from_git('test.tar.gz', target_dir, git_config)
2553+
stdout = self.get_stdout()
2554+
stderr = self.get_stderr()
26122555
self.assertEqual(stderr, '')
26132556
regex = re.compile(expected)
26142557
self.assertTrue(regex.search(stdout), "Pattern '%s' found in: %s" % (regex.pattern, stdout))
@@ -2619,58 +2562,138 @@ def run_check():
26192562
git_config = {
26202563
'repo_name': 'testrepository',
26212564
'url': '[email protected]:easybuilders',
2622-
'tag': 'master',
2565+
'tag': 'tag_for_tests',
26232566
}
2567+
git_repo = {'git_repo': '[email protected]:easybuilders/testrepository.git'} # Just to make the below shorter
26242568
expected = '\n'.join([
2625-
r' running command "git clone --branch master [email protected]:easybuilders/testrepository.git"',
2569+
r' running command "git clone --depth 1 --branch tag_for_tests %(git_repo)s"',
26262570
r" \(in .*/tmp.*\)",
26272571
r' running command "tar cfvz .*/target/test.tar.gz --exclude .git testrepository"',
26282572
r" \(in .*/tmp.*\)",
2629-
])
2573+
]) % git_repo
26302574
run_check()
26312575

26322576
git_config['recursive'] = True
26332577
expected = '\n'.join([
2634-
r' running command "git clone --branch master --recursive [email protected]:easybuilders/testrepository.git"',
2578+
r' running command "git clone --depth 1 --branch tag_for_tests --recursive %(git_repo)s"',
26352579
r" \(in .*/tmp.*\)",
26362580
r' running command "tar cfvz .*/target/test.tar.gz --exclude .git testrepository"',
26372581
r" \(in .*/tmp.*\)",
2638-
])
2582+
]) % git_repo
26392583
run_check()
26402584

26412585
git_config['keep_git_dir'] = True
26422586
expected = '\n'.join([
2643-
r' running command "git clone --branch master --recursive [email protected]:easybuilders/testrepository.git"',
2587+
r' running command "git clone --branch tag_for_tests --recursive %(git_repo)s"',
26442588
r" \(in .*/tmp.*\)",
26452589
r' running command "tar cfvz .*/target/test.tar.gz testrepository"',
26462590
r" \(in .*/tmp.*\)",
2647-
])
2591+
]) % git_repo
26482592
run_check()
26492593
del git_config['keep_git_dir']
26502594

26512595
del git_config['tag']
26522596
git_config['commit'] = '8456f86'
26532597
expected = '\n'.join([
2654-
r' running command "git clone --recursive [email protected]:easybuilders/testrepository.git"',
2598+
r' running command "git clone --depth 1 --no-checkout %(git_repo)s"',
26552599
r" \(in .*/tmp.*\)",
26562600
r' running command "git checkout 8456f86 && git submodule update --init --recursive"',
26572601
r" \(in testrepository\)",
26582602
r' running command "tar cfvz .*/target/test.tar.gz --exclude .git testrepository"',
26592603
r" \(in .*/tmp.*\)",
2660-
])
2604+
]) % git_repo
26612605
run_check()
26622606

26632607
del git_config['recursive']
26642608
expected = '\n'.join([
2665-
r' running command "git clone [email protected]:easybuilders/testrepository.git"',
2609+
r' running command "git clone --depth 1 --no-checkout %(git_repo)s"',
26662610
r" \(in .*/tmp.*\)",
26672611
r' running command "git checkout 8456f86"',
26682612
r" \(in testrepository\)",
26692613
r' running command "tar cfvz .*/target/test.tar.gz --exclude .git testrepository"',
26702614
r" \(in .*/tmp.*\)",
2671-
])
2615+
]) % git_repo
26722616
run_check()
26732617

2618+
# Test with real data.
2619+
init_config()
2620+
git_config = {
2621+
'repo_name': 'testrepository',
2622+
'url': 'https://github.com/easybuilders',
2623+
'tag': 'branch_tag_for_test',
2624+
}
2625+
2626+
try:
2627+
res = ft.get_source_tarball_from_git('test.tar.gz', target_dir, git_config)
2628+
# (only) tarball is created in specified target dir
2629+
test_file = os.path.join(target_dir, 'test.tar.gz')
2630+
self.assertEqual(res, test_file)
2631+
self.assertTrue(os.path.isfile(test_file))
2632+
self.assertEqual(os.listdir(target_dir), ['test.tar.gz'])
2633+
# Check that we indeed downloaded the right tag
2634+
extracted_dir = tempfile.mkdtemp(prefix='extracted_dir')
2635+
extracted_repo_dir = ft.extract_file(test_file, extracted_dir, change_into_dir=False)
2636+
self.assertTrue(os.path.isfile(os.path.join(extracted_repo_dir, 'this-is-a-branch.txt')))
2637+
os.remove(test_file)
2638+
2639+
# use a tag that clashes with a branch name and make sure this is handled correctly
2640+
git_config['tag'] = 'tag_for_tests'
2641+
with self.mocked_stdout_stderr():
2642+
res = ft.get_source_tarball_from_git('test.tar.gz', target_dir, git_config)
2643+
stderr = self.get_stderr()
2644+
self.assertIn('Tag tag_for_tests was not downloaded in the first try', stderr)
2645+
self.assertEqual(res, test_file)
2646+
self.assertTrue(os.path.isfile(test_file))
2647+
# Check that we indeed downloaded the tag and not the branch
2648+
extracted_dir = tempfile.mkdtemp(prefix='extracted_dir')
2649+
extracted_repo_dir = ft.extract_file(test_file, extracted_dir, change_into_dir=False)
2650+
self.assertTrue(os.path.isfile(os.path.join(extracted_repo_dir, 'this-is-a-tag.txt')))
2651+
2652+
del git_config['tag']
2653+
git_config['commit'] = '8456f86'
2654+
res = ft.get_source_tarball_from_git('test2.tar.gz', target_dir, git_config)
2655+
test_file = os.path.join(target_dir, 'test2.tar.gz')
2656+
self.assertEqual(res, test_file)
2657+
self.assertTrue(os.path.isfile(test_file))
2658+
self.assertEqual(sorted(os.listdir(target_dir)), ['test.tar.gz', 'test2.tar.gz'])
2659+
2660+
except EasyBuildError as err:
2661+
if "Network is down" in str(err):
2662+
print("Ignoring download error in test_get_source_tarball_from_git, working offline?")
2663+
else:
2664+
raise err
2665+
2666+
git_config = {
2667+
'repo_name': 'testrepository',
2668+
'url': '[email protected]:easybuilders',
2669+
'tag': 'tag_for_tests',
2670+
}
2671+
args = ['test.tar.gz', self.test_prefix, git_config]
2672+
2673+
for key in ['repo_name', 'url', 'tag']:
2674+
orig_value = git_config.pop(key)
2675+
if key == 'tag':
2676+
error_pattern = "Neither tag nor commit found in git_config parameter"
2677+
else:
2678+
error_pattern = "%s not specified in git_config parameter" % key
2679+
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2680+
git_config[key] = orig_value
2681+
2682+
git_config['commit'] = '8456f86'
2683+
error_pattern = "Tag and commit are mutually exclusive in git_config parameter"
2684+
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2685+
del git_config['commit']
2686+
2687+
git_config['unknown'] = 'foobar'
2688+
error_pattern = "Found one or more unexpected keys in 'git_config' specification"
2689+
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2690+
del git_config['unknown']
2691+
2692+
args[0] = 'test.txt'
2693+
error_pattern = "git_config currently only supports filename ending in .tar.gz"
2694+
self.assertErrorRegex(EasyBuildError, error_pattern, ft.get_source_tarball_from_git, *args)
2695+
args[0] = 'test.tar.gz'
2696+
26742697
def test_is_sha256_checksum(self):
26752698
"""Test for is_sha256_checksum function."""
26762699
a_sha256_checksum = '44332000aa33b99ad1e00cbd1a7da769220d74647060a10e807b916d73ea27bc'

0 commit comments

Comments
 (0)