summaryrefslogtreecommitdiffstats
path: root/git_superproject.py
diff options
context:
space:
mode:
Diffstat (limited to 'git_superproject.py')
-rw-r--r--git_superproject.py149
1 files changed, 149 insertions, 0 deletions
diff --git a/git_superproject.py b/git_superproject.py
new file mode 100644
index 00000000..3e87e929
--- /dev/null
+++ b/git_superproject.py
@@ -0,0 +1,149 @@
1# Copyright (C) 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Provide functionality to get all projects and their SHAs from Superproject.
16
17For more information on superproject, check out:
18https://en.wikibooks.org/wiki/Git/Submodules_and_Superprojects
19
20Examples:
21 superproject = Superproject()
22 project_shas = superproject.GetAllProjectsSHAs()
23"""
24
25import os
26import sys
27
28from error import GitError
29from git_command import GitCommand
30import platform_utils
31
32
33class Superproject(object):
34 """Get SHAs from superproject.
35
36 It does a 'git clone' of superproject and 'git ls-tree' to get list of SHAs for all projects.
37 It contains project_shas which is a dictionary with project/sha entries.
38 """
39 def __init__(self, repodir, superproject_dir='exp-superproject'):
40 """Initializes superproject.
41
42 Args:
43 repodir: Path to the .repo/ dir for holding all internal checkout state.
44 superproject_dir: Relative path under |repodir| to checkout superproject.
45 """
46 self._project_shas = None
47 self._repodir = os.path.abspath(repodir)
48 self._superproject_dir = superproject_dir
49 self._superproject_path = os.path.join(self._repodir, superproject_dir)
50
51 @property
52 def project_shas(self):
53 """Returns a dictionary of projects and their SHAs."""
54 return self._project_shas
55
56 def _Clone(self, url, branch=None):
57 """Do a 'git clone' for the given url and branch.
58
59 Args:
60 url: superproject's url to be passed to git clone.
61 branch: the branchname to be passed as argument to git clone.
62
63 Returns:
64 True if 'git clone <url> <branch>' is successful, or False.
65 """
66 cmd = ['clone', url, '--depth', '1']
67 if branch:
68 cmd += ['--branch', branch]
69 p = GitCommand(None,
70 cmd,
71 cwd=self._superproject_path,
72 capture_stdout=True,
73 capture_stderr=True)
74 retval = p.Wait()
75 if retval:
76 # `git clone` is documented to produce an exit status of `128` if
77 # the requested url or branch are not present in the configuration.
78 print('repo: error: git clone call failed with return code: %r, stderr: %r' %
79 (retval, p.stderr), file=sys.stderr)
80 return False
81 return True
82
83 def _LsTree(self):
84 """Returns the data from 'git ls-tree -r HEAD'.
85
86 Works only in git repositories.
87
88 Returns:
89 data: data returned from 'git ls-tree -r HEAD' instead of None.
90 """
91 git_dir = os.path.join(self._superproject_path, 'superproject')
92 if not os.path.exists(git_dir):
93 raise GitError('git ls-tree. Missing drectory: %s' % git_dir)
94 data = None
95 cmd = ['ls-tree', '-z', '-r', 'HEAD']
96 p = GitCommand(None,
97 cmd,
98 cwd=git_dir,
99 capture_stdout=True,
100 capture_stderr=True)
101 retval = p.Wait()
102 if retval == 0:
103 data = p.stdout
104 else:
105 # `git clone` is documented to produce an exit status of `128` if
106 # the requested url or branch are not present in the configuration.
107 print('repo: error: git ls-tree call failed with return code: %r, stderr: %r' % (
108 retval, p.stderr), file=sys.stderr)
109 return data
110
111 def GetAllProjectsSHAs(self, url, branch=None):
112 """Get SHAs for all projects from superproject and save them in _project_shas.
113
114 Args:
115 url: superproject's url to be passed to git clone.
116 branch: the branchname to be passed as argument to git clone.
117
118 Returns:
119 A dictionary with the projects/SHAs instead of None.
120 """
121 if not url:
122 raise ValueError('url argument is not supplied.')
123 if os.path.exists(self._superproject_path):
124 platform_utils.rmtree(self._superproject_path)
125 os.mkdir(self._superproject_path)
126
127 # TODO(rtenneti): we shouldn't be cloning the repo from scratch every time.
128 if not self._Clone(url, branch):
129 raise GitError('git clone failed for url: %s' % url)
130
131 data = self._LsTree()
132 if not data:
133 raise GitError('git ls-tree failed for url: %s' % url)
134
135 # Parse lines like the following to select lines starting with '160000' and
136 # build a dictionary with project path (last element) and its SHA (3rd element).
137 #
138 # 160000 commit 2c2724cb36cd5a9cec6c852c681efc3b7c6b86ea\tart\x00
139 # 120000 blob acc2cbdf438f9d2141f0ae424cec1d8fc4b5d97f\tbootstrap.bash\x00
140 shas = {}
141 for line in data.split('\x00'):
142 ls_data = line.split(None, 3)
143 if not ls_data:
144 break
145 if ls_data[0] == '160000':
146 shas[ls_data[3]] = ls_data[2]
147
148 self._project_shas = shas
149 return shas