diff options
-rw-r--r-- | git_superproject.py | 70 | ||||
-rw-r--r-- | tests/test_git_superproject.py | 31 |
2 files changed, 48 insertions, 53 deletions
diff --git a/git_superproject.py b/git_superproject.py index 471dadc4..a09edc15 100644 --- a/git_superproject.py +++ b/git_superproject.py | |||
@@ -22,13 +22,13 @@ Examples: | |||
22 | project_commit_ids = superproject.UpdateProjectsRevisionId(projects) | 22 | project_commit_ids = superproject.UpdateProjectsRevisionId(projects) |
23 | """ | 23 | """ |
24 | 24 | ||
25 | import hashlib | ||
25 | import os | 26 | import os |
26 | import sys | 27 | import sys |
27 | 28 | ||
28 | from error import BUG_REPORT_URL | 29 | from error import BUG_REPORT_URL |
29 | from git_command import GitCommand | 30 | from git_command import GitCommand |
30 | from git_refs import R_HEADS | 31 | from git_refs import R_HEADS |
31 | import platform_utils | ||
32 | 32 | ||
33 | _SUPERPROJECT_GIT_NAME = 'superproject.git' | 33 | _SUPERPROJECT_GIT_NAME = 'superproject.git' |
34 | _SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml' | 34 | _SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml' |
@@ -37,9 +37,9 @@ _SUPERPROJECT_MANIFEST_NAME = 'superproject_override.xml' | |||
37 | class Superproject(object): | 37 | class Superproject(object): |
38 | """Get commit ids from superproject. | 38 | """Get commit ids from superproject. |
39 | 39 | ||
40 | It does a 'git clone' of superproject and 'git ls-tree' to get list of commit ids | 40 | Initializes a local copy of a superproject for the manifest. This allows |
41 | for all projects. It contains project_commit_ids which is a dictionary with | 41 | lookup of commit ids for all projects. It contains _project_commit_ids which |
42 | project/commit id entries. | 42 | is a dictionary with project/commit id entries. |
43 | """ | 43 | """ |
44 | def __init__(self, manifest, repodir, superproject_dir='exp-superproject'): | 44 | def __init__(self, manifest, repodir, superproject_dir='exp-superproject'): |
45 | """Initializes superproject. | 45 | """Initializes superproject. |
@@ -58,8 +58,12 @@ class Superproject(object): | |||
58 | self._superproject_path = os.path.join(self._repodir, superproject_dir) | 58 | self._superproject_path = os.path.join(self._repodir, superproject_dir) |
59 | self._manifest_path = os.path.join(self._superproject_path, | 59 | self._manifest_path = os.path.join(self._superproject_path, |
60 | _SUPERPROJECT_MANIFEST_NAME) | 60 | _SUPERPROJECT_MANIFEST_NAME) |
61 | self._work_git = os.path.join(self._superproject_path, | 61 | git_name = '' |
62 | _SUPERPROJECT_GIT_NAME) | 62 | if self._manifest.superproject: |
63 | remote_name = self._manifest.superproject['remote'].name | ||
64 | git_name = hashlib.md5(remote_name.encode('utf8')).hexdigest() + '-' | ||
65 | self._work_git_name = git_name + _SUPERPROJECT_GIT_NAME | ||
66 | self._work_git = os.path.join(self._superproject_path, self._work_git_name) | ||
63 | 67 | ||
64 | @property | 68 | @property |
65 | def project_commit_ids(self): | 69 | def project_commit_ids(self): |
@@ -77,20 +81,15 @@ class Superproject(object): | |||
77 | branch = branch[len(R_HEADS):] | 81 | branch = branch[len(R_HEADS):] |
78 | return branch | 82 | return branch |
79 | 83 | ||
80 | def _Clone(self, url): | 84 | def _Init(self): |
81 | """Do a 'git clone' for the given url. | 85 | """Sets up a local Git repository to get a copy of a superproject. |
82 | |||
83 | Args: | ||
84 | url: superproject's url to be passed to git clone. | ||
85 | 86 | ||
86 | Returns: | 87 | Returns: |
87 | True if git clone is successful, or False. | 88 | True if initialization is successful, or False. |
88 | """ | 89 | """ |
89 | if not os.path.exists(self._superproject_path): | 90 | if not os.path.exists(self._superproject_path): |
90 | os.mkdir(self._superproject_path) | 91 | os.mkdir(self._superproject_path) |
91 | cmd = ['clone', url, '--filter', 'blob:none', '--bare'] | 92 | cmd = ['init', '--bare', self._work_git_name] |
92 | if self._branch: | ||
93 | cmd += ['--branch', self._branch] | ||
94 | p = GitCommand(None, | 93 | p = GitCommand(None, |
95 | cmd, | 94 | cmd, |
96 | cwd=self._superproject_path, | 95 | cwd=self._superproject_path, |
@@ -98,24 +97,27 @@ class Superproject(object): | |||
98 | capture_stderr=True) | 97 | capture_stderr=True) |
99 | retval = p.Wait() | 98 | retval = p.Wait() |
100 | if retval: | 99 | if retval: |
101 | # `git clone` is documented to produce an exit status of `128` if | 100 | print('repo: error: git init call failed with return code: %r, stderr: %r' % |
102 | # the requested url or branch are not present in the configuration. | ||
103 | print('repo: error: git clone call failed with return code: %r, stderr: %r' % | ||
104 | (retval, p.stderr), file=sys.stderr) | 101 | (retval, p.stderr), file=sys.stderr) |
105 | return False | 102 | return False |
106 | return True | 103 | return True |
107 | 104 | ||
108 | def _Fetch(self): | 105 | def _Fetch(self, url): |
109 | """Do a 'git fetch' to to fetch the latest content. | 106 | """Fetches a local copy of a superproject for the manifest based on url. |
107 | |||
108 | Args: | ||
109 | url: superproject's url. | ||
110 | 110 | ||
111 | Returns: | 111 | Returns: |
112 | True if 'git fetch' is successful, or False. | 112 | True if fetch is successful, or False. |
113 | """ | 113 | """ |
114 | if not os.path.exists(self._work_git): | 114 | if not os.path.exists(self._work_git): |
115 | print('git fetch missing drectory: %s' % self._work_git, | 115 | print('git fetch missing drectory: %s' % self._work_git, |
116 | file=sys.stderr) | 116 | file=sys.stderr) |
117 | return False | 117 | return False |
118 | cmd = ['fetch', 'origin', '+refs/heads/*:refs/heads/*', '--prune'] | 118 | cmd = ['fetch', url, '--force', '--no-tags', '--filter', 'blob:none'] |
119 | if self._branch: | ||
120 | cmd += [self._branch + ':' + self._branch] | ||
119 | p = GitCommand(None, | 121 | p = GitCommand(None, |
120 | cmd, | 122 | cmd, |
121 | cwd=self._work_git, | 123 | cwd=self._work_git, |
@@ -129,7 +131,7 @@ class Superproject(object): | |||
129 | return True | 131 | return True |
130 | 132 | ||
131 | def _LsTree(self): | 133 | def _LsTree(self): |
132 | """Returns the data from 'git ls-tree ...'. | 134 | """Gets the commit ids for all projects. |
133 | 135 | ||
134 | Works only in git repositories. | 136 | Works only in git repositories. |
135 | 137 | ||
@@ -153,14 +155,12 @@ class Superproject(object): | |||
153 | if retval == 0: | 155 | if retval == 0: |
154 | data = p.stdout | 156 | data = p.stdout |
155 | else: | 157 | else: |
156 | # `git clone` is documented to produce an exit status of `128` if | ||
157 | # the requested url or branch are not present in the configuration. | ||
158 | print('repo: error: git ls-tree call failed with return code: %r, stderr: %r' % ( | 158 | print('repo: error: git ls-tree call failed with return code: %r, stderr: %r' % ( |
159 | retval, p.stderr), file=sys.stderr) | 159 | retval, p.stderr), file=sys.stderr) |
160 | return data | 160 | return data |
161 | 161 | ||
162 | def Sync(self): | 162 | def Sync(self): |
163 | """Sync superproject either by git clone/fetch. | 163 | """Gets a local copy of a superproject for the manifest. |
164 | 164 | ||
165 | Returns: | 165 | Returns: |
166 | True if sync of superproject is successful, or False. | 166 | True if sync of superproject is successful, or False. |
@@ -179,17 +179,10 @@ class Superproject(object): | |||
179 | file=sys.stderr) | 179 | file=sys.stderr) |
180 | return False | 180 | return False |
181 | 181 | ||
182 | do_clone = True | 182 | if not self._Init(): |
183 | if os.path.exists(self._superproject_path): | 183 | return False |
184 | if not self._Fetch(): | 184 | if not self._Fetch(url): |
185 | # If fetch fails due to a corrupted git directory, then do a git clone. | 185 | return False |
186 | platform_utils.rmtree(self._superproject_path) | ||
187 | else: | ||
188 | do_clone = False | ||
189 | if do_clone: | ||
190 | if not self._Clone(url): | ||
191 | print('error: git clone failed for url: %s' % url, file=sys.stderr) | ||
192 | return False | ||
193 | return True | 186 | return True |
194 | 187 | ||
195 | def _GetAllProjectsCommitIds(self): | 188 | def _GetAllProjectsCommitIds(self): |
@@ -203,7 +196,8 @@ class Superproject(object): | |||
203 | 196 | ||
204 | data = self._LsTree() | 197 | data = self._LsTree() |
205 | if not data: | 198 | if not data: |
206 | print('error: git ls-tree failed for superproject', file=sys.stderr) | 199 | print('error: git ls-tree failed to return data for superproject', |
200 | file=sys.stderr) | ||
207 | return None | 201 | return None |
208 | 202 | ||
209 | # Parse lines like the following to select lines starting with '160000' and | 203 | # Parse lines like the following to select lines starting with '160000' and |
diff --git a/tests/test_git_superproject.py b/tests/test_git_superproject.py index d2ee9f4f..07b9a7db 100644 --- a/tests/test_git_superproject.py +++ b/tests/test_git_superproject.py | |||
@@ -97,17 +97,17 @@ class SuperprojectTestCase(unittest.TestCase): | |||
97 | with mock.patch.object(self._superproject, '_GetBranch', return_value='junk'): | 97 | with mock.patch.object(self._superproject, '_GetBranch', return_value='junk'): |
98 | self.assertFalse(superproject.Sync()) | 98 | self.assertFalse(superproject.Sync()) |
99 | 99 | ||
100 | def test_superproject_get_superproject_mock_clone(self): | 100 | def test_superproject_get_superproject_mock_init(self): |
101 | """Test with _Clone failing.""" | 101 | """Test with _Init failing.""" |
102 | with mock.patch.object(self._superproject, '_Clone', return_value=False): | 102 | with mock.patch.object(self._superproject, '_Init', return_value=False): |
103 | self.assertFalse(self._superproject.Sync()) | 103 | self.assertFalse(self._superproject.Sync()) |
104 | 104 | ||
105 | def test_superproject_get_superproject_mock_fetch(self): | 105 | def test_superproject_get_superproject_mock_fetch(self): |
106 | """Test with _Fetch failing and _clone being called.""" | 106 | """Test with _Fetch failing.""" |
107 | with mock.patch.object(self._superproject, '_Clone', return_value=True): | 107 | with mock.patch.object(self._superproject, '_Init', return_value=True): |
108 | os.mkdir(self._superproject._superproject_path) | 108 | os.mkdir(self._superproject._superproject_path) |
109 | with mock.patch.object(self._superproject, '_Fetch', return_value=False): | 109 | with mock.patch.object(self._superproject, '_Fetch', return_value=False): |
110 | self.assertTrue(self._superproject.Sync()) | 110 | self.assertFalse(self._superproject.Sync()) |
111 | 111 | ||
112 | def test_superproject_get_all_project_commit_ids_mock_ls_tree(self): | 112 | def test_superproject_get_all_project_commit_ids_mock_ls_tree(self): |
113 | """Test with LsTree being a mock.""" | 113 | """Test with LsTree being a mock.""" |
@@ -116,14 +116,15 @@ class SuperprojectTestCase(unittest.TestCase): | |||
116 | '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00' | 116 | '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00' |
117 | '120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00' | 117 | '120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00' |
118 | '160000 commit ade9b7a0d874e25fff4bf2552488825c6f111928\tbuild/bazel\x00') | 118 | '160000 commit ade9b7a0d874e25fff4bf2552488825c6f111928\tbuild/bazel\x00') |
119 | with mock.patch.object(self._superproject, '_Clone', return_value=True): | 119 | with mock.patch.object(self._superproject, '_Init', return_value=True): |
120 | with mock.patch.object(self._superproject, '_LsTree', return_value=data): | 120 | with mock.patch.object(self._superproject, '_Fetch', return_value=True): |
121 | commit_ids = self._superproject._GetAllProjectsCommitIds() | 121 | with mock.patch.object(self._superproject, '_LsTree', return_value=data): |
122 | self.assertEqual(commit_ids, { | 122 | commit_ids = self._superproject._GetAllProjectsCommitIds() |
123 | 'art': '2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea', | 123 | self.assertEqual(commit_ids, { |
124 | 'bootable/recovery': 'e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06', | 124 | 'art': '2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea', |
125 | 'build/bazel': 'ade9b7a0d874e25fff4bf2552488825c6f111928' | 125 | 'bootable/recovery': 'e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06', |
126 | }) | 126 | 'build/bazel': 'ade9b7a0d874e25fff4bf2552488825c6f111928' |
127 | }) | ||
127 | 128 | ||
128 | def test_superproject_write_manifest_file(self): | 129 | def test_superproject_write_manifest_file(self): |
129 | """Test with writing manifest to a file after setting revisionId.""" | 130 | """Test with writing manifest to a file after setting revisionId.""" |
@@ -151,7 +152,7 @@ class SuperprojectTestCase(unittest.TestCase): | |||
151 | projects = self._superproject._manifest.projects | 152 | projects = self._superproject._manifest.projects |
152 | data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00' | 153 | data = ('160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00' |
153 | '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00') | 154 | '160000 commit e9d25da64d8d365dbba7c8ee00fe8c4473fe9a06\tbootable/recovery\x00') |
154 | with mock.patch.object(self._superproject, '_Clone', return_value=True): | 155 | with mock.patch.object(self._superproject, '_Init', return_value=True): |
155 | with mock.patch.object(self._superproject, '_Fetch', return_value=True): | 156 | with mock.patch.object(self._superproject, '_Fetch', return_value=True): |
156 | with mock.patch.object(self._superproject, | 157 | with mock.patch.object(self._superproject, |
157 | '_LsTree', | 158 | '_LsTree', |