summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--git_superproject.py70
-rw-r--r--tests/test_git_superproject.py31
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
25import hashlib
25import os 26import os
26import sys 27import sys
27 28
28from error import BUG_REPORT_URL 29from error import BUG_REPORT_URL
29from git_command import GitCommand 30from git_command import GitCommand
30from git_refs import R_HEADS 31from git_refs import R_HEADS
31import 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'
37class Superproject(object): 37class 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',