Skip to content

Commit 612293c

Browse files
Added export_repository support and corresponding tests for git client.
Signed-off-by: Leander Stephen D'Souza <[email protected]>
1 parent 989c470 commit 612293c

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

test/test_git.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Unit tests for GitClient checkout functionality"""
22

33
import os
4+
import tarfile
45
import tempfile
56
import unittest
67

@@ -59,5 +60,86 @@ def test_checkout_to_existing_directory(self):
5960
self.assertFalse(success, 'Checkout should fail for non-empty directory')
6061

6162

63+
class TestExportRepository(unittest.TestCase):
64+
"""Test cases for GitClient export_repository method"""
65+
66+
def setUp(self):
67+
# Create a temporary directory for testing
68+
self.test_dir = tempfile.mkdtemp()
69+
self.repo_path = os.path.join(self.test_dir, 'test_repo')
70+
self.export_dir = os.path.join(self.test_dir, 'exports')
71+
os.makedirs(self.export_dir, exist_ok=True)
72+
73+
self.test_repo_url = 'https://github.com/octocat/Hello-World.git'
74+
self.client = GitClient(self.repo_path)
75+
76+
def tearDown(self):
77+
if os.path.exists(self.test_dir):
78+
rmtree(self.test_dir)
79+
80+
def test_export_specific_branch(self):
81+
"""Test exporting the repository at a specific branch"""
82+
success = self.client.checkout(self.test_repo_url, version='test')
83+
84+
if not success:
85+
self.fail('Failed to clone test repository')
86+
87+
basepath = os.path.join(self.export_dir, 'repo_export')
88+
filepath = self.client.export_repository('test', basepath)
89+
90+
self.assertTrue(os.path.exists(filepath), 'Export file should exist')
91+
self.assertGreater(
92+
os.path.getsize(filepath), 0, 'Export file should not be empty'
93+
)
94+
# Verify it's a valid tar.gz file
95+
try:
96+
if filepath and isinstance(filepath, str):
97+
with tarfile.open(filepath, 'r:gz') as tar:
98+
members = tar.getnames()
99+
self.assertIn('README', members, 'Should contain README file')
100+
else:
101+
self.fail('Exported file path is invalid')
102+
except tarfile.ReadError:
103+
self.fail('Exported file should be a valid tar.gz archive')
104+
105+
def test_export_with_local_changes_uses_temp_dir(self):
106+
"""Test that export uses temp directory when there are local changes"""
107+
# First clone the repository
108+
success = self.client.checkout(self.test_repo_url)
109+
if not success:
110+
self.fail('Failed to clone test repository')
111+
112+
# Create a local change
113+
test_file = os.path.join(self.repo_path, 'test_change.txt')
114+
with open(test_file, 'w', encoding='utf-8') as f:
115+
f.write('test change')
116+
117+
basepath = os.path.join(self.export_dir, 'local_changes_test')
118+
filepath = self.client.export_repository(None, basepath)
119+
120+
self.assertIsNotNone(filepath, 'Export should succeed even with local changes')
121+
self.assertTrue(os.path.exists(filepath), 'Export file should exist')
122+
self.assertGreater(
123+
os.path.getsize(filepath), 0, 'Export file should not be empty'
124+
)
125+
126+
# Clean up
127+
os.remove(test_file)
128+
129+
def test_export_invalid_branch(self):
130+
"""Test exporting a non-existent branch should fail gracefully"""
131+
success = self.client.checkout(self.test_repo_url)
132+
133+
if not success:
134+
self.fail('Failed to clone test repository')
135+
136+
basepath = os.path.join(self.export_dir, 'nonexistent_export')
137+
result = self.client.export_repository('nonexistent-branch-12345', basepath)
138+
139+
self.assertEqual(
140+
result, False, 'Export should return False for non-existent ref'
141+
)
142+
143+
62144
if __name__ == '__main__':
63145
unittest.main()

vcs2l/clients/git.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import subprocess
3+
import tempfile
34
from shutil import which
45

56
from vcs2l.clients.vcs_base import VcsClientBase
@@ -782,6 +783,47 @@ def _check_color(self, cmd):
782783
if GitClient._config_color_is_auto:
783784
cmd[1:1] = '-c', 'color.ui=always'
784785

786+
def export_repository(self, version, basepath):
787+
"""Export repository to a tar.gz file at the specified version."""
788+
if not GitClient.is_repository(self.path):
789+
return False
790+
791+
self._check_executable()
792+
filepath = '{0}.tar.gz'.format(basepath)
793+
794+
# If current version matches export version and no local changes, export directly
795+
current_sha = self._get_current_version()
796+
export_sha = self._get_version_sha(version) if version else current_sha
797+
if current_sha == export_sha and self._has_no_local_changes():
798+
cmd = [
799+
GitClient._executable,
800+
'archive',
801+
'--format=tar.gz',
802+
'--output={}'.format(filepath),
803+
version or 'HEAD',
804+
]
805+
result = self._run_command(cmd)
806+
if result['returncode'] == 0:
807+
return filepath
808+
809+
# Otherwise use temp directory approach
810+
tmpd_path = tempfile.mkdtemp()
811+
try:
812+
tmpgit = GitClient(tmpd_path)
813+
if tmpgit.checkout(self.path, version=version, shallow=False):
814+
cmd = [
815+
GitClient._executable,
816+
'archive',
817+
'--format=tar.gz',
818+
'--output={}'.format(filepath),
819+
version or 'HEAD',
820+
]
821+
result = tmpgit._run_command(cmd)
822+
return filepath if result['returncode'] == 0 else False
823+
return False
824+
finally:
825+
rmtree(tmpd_path)
826+
785827
def checkout(self, url, version=None, verbose=False, shallow=False, timeout=None):
786828
"""Clone a git repository to the specified path and checkout a specific version."""
787829
if url is None or url.strip() == '':
@@ -867,6 +909,24 @@ def _get_hash_ref_tuples(self, ls_remote_output):
867909
tuples.append((hash_, ref))
868910
return tuples
869911

912+
def _get_current_version(self):
913+
"""Get current HEAD SHA."""
914+
cmd = [GitClient._executable, 'rev-parse', 'HEAD']
915+
result = self._run_command(cmd)
916+
return result['output'].strip() if result['returncode'] == 0 else None
917+
918+
def _get_version_sha(self, version):
919+
"""Get SHA for a given version."""
920+
cmd = [GitClient._executable, 'rev-parse', version]
921+
result = self._run_command(cmd)
922+
return result['output'].strip() if result['returncode'] == 0 else None
923+
924+
def _has_no_local_changes(self):
925+
"""Check if there are no local changes."""
926+
cmd = [GitClient._executable, 'diff', '--quiet']
927+
result = self._run_command(cmd)
928+
return result['returncode'] == 0
929+
870930

871931
if not GitClient._executable:
872932
GitClient._executable = which('git')

0 commit comments

Comments
 (0)