summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/manifest-format.txt12
-rw-r--r--git_command.py4
-rw-r--r--git_config.py8
-rwxr-xr-xhooks/commit-msg10
-rw-r--r--manifest_xml.py6
-rw-r--r--project.py30
-rwxr-xr-xrepo2
-rw-r--r--subcmds/branches.py20
-rw-r--r--subcmds/forall.py290
9 files changed, 245 insertions, 137 deletions
diff --git a/docs/manifest-format.txt b/docs/manifest-format.txt
index 65cd70bc..d5c6a024 100644
--- a/docs/manifest-format.txt
+++ b/docs/manifest-format.txt
@@ -143,14 +143,14 @@ Project elements not setting their own `dest-branch` will inherit
143this value. If this value is not set, projects will use `revision` 143this value. If this value is not set, projects will use `revision`
144by default instead. 144by default instead.
145 145
146Attribute `sync_j`: Number of parallel jobs to use when synching. 146Attribute `sync-j`: Number of parallel jobs to use when synching.
147 147
148Attribute `sync_c`: Set to true to only sync the given Git 148Attribute `sync-c`: Set to true to only sync the given Git
149branch (specified in the `revision` attribute) rather than the 149branch (specified in the `revision` attribute) rather than the
150whole ref space. Project elements lacking a sync_c element of 150whole ref space. Project elements lacking a sync-c element of
151their own will use this value. 151their own will use this value.
152 152
153Attribute `sync_s`: Set to true to also sync sub-projects. 153Attribute `sync-s`: Set to true to also sync sub-projects.
154 154
155 155
156Element manifest-server 156Element manifest-server
@@ -238,11 +238,11 @@ group "notdefault", it will not be automatically downloaded by repo.
238If the project has a parent element, the `name` and `path` here 238If the project has a parent element, the `name` and `path` here
239are the prefixed ones. 239are the prefixed ones.
240 240
241Attribute `sync_c`: Set to true to only sync the given Git 241Attribute `sync-c`: Set to true to only sync the given Git
242branch (specified in the `revision` attribute) rather than the 242branch (specified in the `revision` attribute) rather than the
243whole ref space. 243whole ref space.
244 244
245Attribute `sync_s`: Set to true to also sync sub-projects. 245Attribute `sync-s`: Set to true to also sync sub-projects.
246 246
247Attribute `upstream`: Name of the Git branch in which a sha1 247Attribute `upstream`: Name of the Git branch in which a sha1
248can be found. Used when syncing a revision locked manifest in 248can be found. Used when syncing a revision locked manifest in
diff --git a/git_command.py b/git_command.py
index 354fc715..53b3e75c 100644
--- a/git_command.py
+++ b/git_command.py
@@ -80,13 +80,13 @@ class _GitCall(object):
80 def version(self): 80 def version(self):
81 p = GitCommand(None, ['--version'], capture_stdout=True) 81 p = GitCommand(None, ['--version'], capture_stdout=True)
82 if p.Wait() == 0: 82 if p.Wait() == 0:
83 return p.stdout 83 return p.stdout.decode('utf-8')
84 return None 84 return None
85 85
86 def version_tuple(self): 86 def version_tuple(self):
87 global _git_version 87 global _git_version
88 if _git_version is None: 88 if _git_version is None:
89 ver_str = git.version().decode('utf-8') 89 ver_str = git.version()
90 _git_version = Wrapper().ParseGitVersion(ver_str) 90 _git_version = Wrapper().ParseGitVersion(ver_str)
91 if _git_version is None: 91 if _git_version is None:
92 print('fatal: "%s" unsupported' % ver_str, file=sys.stderr) 92 print('fatal: "%s" unsupported' % ver_str, file=sys.stderr)
diff --git a/git_config.py b/git_config.py
index 380bdd24..aa07d1b7 100644
--- a/git_config.py
+++ b/git_config.py
@@ -216,9 +216,9 @@ class GitConfig(object):
216 """Resolve any url.*.insteadof references. 216 """Resolve any url.*.insteadof references.
217 """ 217 """
218 for new_url in self.GetSubSections('url'): 218 for new_url in self.GetSubSections('url'):
219 old_url = self.GetString('url.%s.insteadof' % new_url) 219 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
220 if old_url is not None and url.startswith(old_url): 220 if old_url is not None and url.startswith(old_url):
221 return new_url + url[len(old_url):] 221 return new_url + url[len(old_url):]
222 return url 222 return url
223 223
224 @property 224 @property
@@ -697,7 +697,7 @@ class Branch(object):
697 self._Set('merge', self.merge) 697 self._Set('merge', self.merge)
698 698
699 else: 699 else:
700 fd = open(self._config.file, 'ab') 700 fd = open(self._config.file, 'a')
701 try: 701 try:
702 fd.write('[branch "%s"]\n' % self.name) 702 fd.write('[branch "%s"]\n' % self.name)
703 if self.remote: 703 if self.remote:
diff --git a/hooks/commit-msg b/hooks/commit-msg
index 5ca2b112..d8f009b6 100755
--- a/hooks/commit-msg
+++ b/hooks/commit-msg
@@ -1,5 +1,4 @@
1#!/bin/sh 1#!/bin/sh
2# From Gerrit Code Review 2.6
3# 2#
4# Part of Gerrit Code Review (http://code.google.com/p/gerrit/) 3# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
5# 4#
@@ -27,7 +26,7 @@ MSG="$1"
27# 26#
28add_ChangeId() { 27add_ChangeId() {
29 clean_message=`sed -e ' 28 clean_message=`sed -e '
30 /^diff --git a\/.*/{ 29 /^diff --git .*/{
31 s/// 30 s///
32 q 31 q
33 } 32 }
@@ -39,6 +38,11 @@ add_ChangeId() {
39 return 38 return
40 fi 39 fi
41 40
41 if test "false" = "`git config --bool --get gerrit.createChangeId`"
42 then
43 return
44 fi
45
42 # Does Change-Id: already exist? if so, exit (no change). 46 # Does Change-Id: already exist? if so, exit (no change).
43 if grep -i '^Change-Id:' "$MSG" >/dev/null 47 if grep -i '^Change-Id:' "$MSG" >/dev/null
44 then 48 then
@@ -77,7 +81,7 @@ add_ChangeId() {
77 # Skip the line starting with the diff command and everything after it, 81 # Skip the line starting with the diff command and everything after it,
78 # up to the end of the file, assuming it is only patch data. 82 # up to the end of the file, assuming it is only patch data.
79 # If more than one line before the diff was empty, strip all but one. 83 # If more than one line before the diff was empty, strip all but one.
80 /^diff --git a/ { 84 /^diff --git / {
81 blankLines = 0 85 blankLines = 0
82 while (getline) { } 86 while (getline) { }
83 next 87 next
diff --git a/manifest_xml.py b/manifest_xml.py
index bd1ab69b..890c954d 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -86,16 +86,20 @@ class _XmlRemote(object):
86 # about here are: 86 # about here are:
87 # * no scheme in the base url, like <hostname:port> 87 # * no scheme in the base url, like <hostname:port>
88 # * persistent-https:// 88 # * persistent-https://
89 # * rpc://
89 # We handle this by replacing these with obscure protocols 90 # We handle this by replacing these with obscure protocols
90 # and then replacing them with the original when we are done. 91 # and then replacing them with the original when we are done.
91 # gopher -> <none> 92 # gopher -> <none>
92 # wais -> persistent-https 93 # wais -> persistent-https
94 # nntp -> rpc
93 if manifestUrl.find(':') != manifestUrl.find('/') - 1: 95 if manifestUrl.find(':') != manifestUrl.find('/') - 1:
94 manifestUrl = 'gopher://' + manifestUrl 96 manifestUrl = 'gopher://' + manifestUrl
95 manifestUrl = re.sub(r'^persistent-https://', 'wais://', manifestUrl) 97 manifestUrl = re.sub(r'^persistent-https://', 'wais://', manifestUrl)
98 manifestUrl = re.sub(r'^rpc://', 'nntp://', manifestUrl)
96 url = urllib.parse.urljoin(manifestUrl, url) 99 url = urllib.parse.urljoin(manifestUrl, url)
97 url = re.sub(r'^gopher://', '', url) 100 url = re.sub(r'^gopher://', '', url)
98 url = re.sub(r'^wais://', 'persistent-https://', url) 101 url = re.sub(r'^wais://', 'persistent-https://', url)
102 url = re.sub(r'^nntp://', 'rpc://', url)
99 return url 103 return url
100 104
101 def ToRemoteSpec(self, projectName): 105 def ToRemoteSpec(self, projectName):
@@ -264,6 +268,8 @@ class XmlManifest(object):
264 revision = self.remotes[remoteName].revision or d.revisionExpr 268 revision = self.remotes[remoteName].revision or d.revisionExpr
265 if not revision or revision != p.revisionExpr: 269 if not revision or revision != p.revisionExpr:
266 e.setAttribute('revision', p.revisionExpr) 270 e.setAttribute('revision', p.revisionExpr)
271 if p.upstream and p.upstream != p.revisionExpr:
272 e.setAttribute('upstream', p.upstream)
267 273
268 for c in p.copyfiles: 274 for c in p.copyfiles:
269 ce = doc.createElement('copyfile') 275 ce = doc.createElement('copyfile')
diff --git a/project.py b/project.py
index e0703519..95403ccb 100644
--- a/project.py
+++ b/project.py
@@ -46,7 +46,7 @@ if not is_python3():
46def _lwrite(path, content): 46def _lwrite(path, content):
47 lock = '%s.lock' % path 47 lock = '%s.lock' % path
48 48
49 fd = open(lock, 'wb') 49 fd = open(lock, 'w')
50 try: 50 try:
51 fd.write(content) 51 fd.write(content)
52 finally: 52 finally:
@@ -1706,6 +1706,7 @@ class Project(object):
1706 if command.Wait() != 0: 1706 if command.Wait() != 0:
1707 raise GitError('git archive %s: %s' % (self.name, command.stderr)) 1707 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1708 1708
1709
1709 def _RemoteFetch(self, name=None, 1710 def _RemoteFetch(self, name=None,
1710 current_branch_only=False, 1711 current_branch_only=False,
1711 initial=False, 1712 initial=False,
@@ -1808,19 +1809,30 @@ class Project(object):
1808 else: 1809 else:
1809 cmd.append('--tags') 1810 cmd.append('--tags')
1810 1811
1812 spec = []
1811 if not current_branch_only: 1813 if not current_branch_only:
1812 # Fetch whole repo 1814 # Fetch whole repo
1813 cmd.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*'))) 1815 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
1814 elif tag_name is not None: 1816 elif tag_name is not None:
1815 cmd.append('tag') 1817 spec.append('tag')
1816 cmd.append(tag_name) 1818 spec.append(tag_name)
1817 else: 1819 else:
1818 branch = self.revisionExpr 1820 branch = self.revisionExpr
1819 if is_sha1: 1821 if is_sha1:
1820 branch = self.upstream 1822 branch = self.upstream
1821 if branch.startswith(R_HEADS): 1823 if branch.startswith(R_HEADS):
1822 branch = branch[len(R_HEADS):] 1824 branch = branch[len(R_HEADS):]
1823 cmd.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch))) 1825 spec.append(str((u'+refs/heads/%s:' % branch) + remote.ToLocal('refs/heads/%s' % branch)))
1826 cmd.extend(spec)
1827
1828 shallowfetch = self.config.GetString('repo.shallowfetch')
1829 if shallowfetch and shallowfetch != ' '.join(spec):
1830 GitCommand(self, ['fetch', '--unshallow', name] + shallowfetch.split(),
1831 bare=True, ssh_proxy=ssh_proxy).Wait()
1832 if depth:
1833 self.config.SetString('repo.shallowfetch', ' '.join(spec))
1834 else:
1835 self.config.SetString('repo.shallowfetch', None)
1824 1836
1825 ok = False 1837 ok = False
1826 for _i in range(2): 1838 for _i in range(2):
@@ -2205,6 +2217,14 @@ class Project(object):
2205 if name in symlink_dirs and not os.path.lexists(src): 2217 if name in symlink_dirs and not os.path.lexists(src):
2206 os.makedirs(src) 2218 os.makedirs(src)
2207 2219
2220 # If the source file doesn't exist, ensure the destination
2221 # file doesn't either.
2222 if name in symlink_files and not os.path.lexists(src):
2223 try:
2224 os.remove(dst)
2225 except OSError:
2226 pass
2227
2208 if name in to_symlink: 2228 if name in to_symlink:
2209 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst) 2229 os.symlink(os.path.relpath(src, os.path.dirname(dst)), dst)
2210 elif copy_all and not os.path.islink(dst): 2230 elif copy_all and not os.path.islink(dst):
diff --git a/repo b/repo
index 3fd0166e..6338483b 100755
--- a/repo
+++ b/repo
@@ -738,7 +738,7 @@ def main(orig_args):
738 try: 738 try:
739 _Init(args) 739 _Init(args)
740 except CloneFailure: 740 except CloneFailure:
741 shutil.rmtree(repodir, ignore_errors=True) 741 shutil.rmtree(os.path.join(repodir, S_repo), ignore_errors=True)
742 sys.exit(1) 742 sys.exit(1)
743 repo_main, rel_repo_dir = _FindRepo() 743 repo_main, rel_repo_dir = _FindRepo()
744 else: 744 else:
diff --git a/subcmds/branches.py b/subcmds/branches.py
index f714c1e8..2902684a 100644
--- a/subcmds/branches.py
+++ b/subcmds/branches.py
@@ -47,6 +47,10 @@ class BranchInfo(object):
47 return self.current > 0 47 return self.current > 0
48 48
49 @property 49 @property
50 def IsSplitCurrent(self):
51 return self.current != 0 and self.current != len(self.projects)
52
53 @property
50 def IsPublished(self): 54 def IsPublished(self):
51 return self.published > 0 55 return self.published > 0
52 56
@@ -139,10 +143,14 @@ is shown, then the branch appears in all projects.
139 if in_cnt < project_cnt: 143 if in_cnt < project_cnt:
140 fmt = out.write 144 fmt = out.write
141 paths = [] 145 paths = []
142 if in_cnt < project_cnt - in_cnt: 146 non_cur_paths = []
147 if i.IsSplitCurrent or (in_cnt < project_cnt - in_cnt):
143 in_type = 'in' 148 in_type = 'in'
144 for b in i.projects: 149 for b in i.projects:
145 paths.append(b.project.relpath) 150 if not i.IsSplitCurrent or b.current:
151 paths.append(b.project.relpath)
152 else:
153 non_cur_paths.append(b.project.relpath)
146 else: 154 else:
147 fmt = out.notinproject 155 fmt = out.notinproject
148 in_type = 'not in' 156 in_type = 'not in'
@@ -154,13 +162,19 @@ is shown, then the branch appears in all projects.
154 paths.append(p.relpath) 162 paths.append(p.relpath)
155 163
156 s = ' %s %s' % (in_type, ', '.join(paths)) 164 s = ' %s %s' % (in_type, ', '.join(paths))
157 if width + 7 + len(s) < 80: 165 if not i.IsSplitCurrent and (width + 7 + len(s) < 80):
166 fmt = out.current if i.IsCurrent else fmt
158 fmt(s) 167 fmt(s)
159 else: 168 else:
160 fmt(' %s:' % in_type) 169 fmt(' %s:' % in_type)
170 fmt = out.current if i.IsCurrent else out.write
161 for p in paths: 171 for p in paths:
162 out.nl() 172 out.nl()
163 fmt(width*' ' + ' %s' % p) 173 fmt(width*' ' + ' %s' % p)
174 fmt = out.write
175 for p in non_cur_paths:
176 out.nl()
177 fmt(width*' ' + ' %s' % p)
164 else: 178 else:
165 out.write(' in all projects') 179 out.write(' in all projects')
166 out.nl() 180 out.nl()
diff --git a/subcmds/forall.py b/subcmds/forall.py
index 03ebcb21..7771ec16 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -14,7 +14,9 @@
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function 16from __future__ import print_function
17import errno
17import fcntl 18import fcntl
19import multiprocessing
18import re 20import re
19import os 21import os
20import select 22import select
@@ -31,6 +33,7 @@ _CAN_COLOR = [
31 'log', 33 'log',
32] 34]
33 35
36
34class ForallColoring(Coloring): 37class ForallColoring(Coloring):
35 def __init__(self, config): 38 def __init__(self, config):
36 Coloring.__init__(self, config, 'forall') 39 Coloring.__init__(self, config, 'forall')
@@ -132,9 +135,31 @@ without iterating through the remaining projects.
132 g.add_option('-v', '--verbose', 135 g.add_option('-v', '--verbose',
133 dest='verbose', action='store_true', 136 dest='verbose', action='store_true',
134 help='Show command error messages') 137 help='Show command error messages')
138 g.add_option('-j', '--jobs',
139 dest='jobs', action='store', type='int', default=1,
140 help='number of commands to execute simultaneously')
135 141
136 def WantPager(self, opt): 142 def WantPager(self, opt):
137 return opt.project_header 143 return opt.project_header and opt.jobs == 1
144
145 def _SerializeProject(self, project):
146 """ Serialize a project._GitGetByExec instance.
147
148 project._GitGetByExec is not pickle-able. Instead of trying to pass it
149 around between processes, make a dict ourselves containing only the
150 attributes that we need.
151
152 """
153 return {
154 'name': project.name,
155 'relpath': project.relpath,
156 'remote_name': project.remote.name,
157 'lrev': project.GetRevisionId(),
158 'rrev': project.revisionExpr,
159 'annotations': dict((a.name, a.value) for a in project.annotations),
160 'gitdir': project.gitdir,
161 'worktree': project.worktree,
162 }
138 163
139 def Execute(self, opt, args): 164 def Execute(self, opt, args):
140 if not opt.command: 165 if not opt.command:
@@ -173,11 +198,7 @@ without iterating through the remaining projects.
173 # pylint: enable=W0631 198 # pylint: enable=W0631
174 199
175 mirror = self.manifest.IsMirror 200 mirror = self.manifest.IsMirror
176 out = ForallColoring(self.manifest.manifestProject.config)
177 out.redirect(sys.stdout)
178
179 rc = 0 201 rc = 0
180 first = True
181 202
182 if not opt.regex: 203 if not opt.regex:
183 projects = self.GetProjects(args) 204 projects = self.GetProjects(args)
@@ -186,113 +207,156 @@ without iterating through the remaining projects.
186 207
187 os.environ['REPO_COUNT'] = str(len(projects)) 208 os.environ['REPO_COUNT'] = str(len(projects))
188 209
189 for (cnt, project) in enumerate(projects): 210 pool = multiprocessing.Pool(opt.jobs)
190 env = os.environ.copy() 211 try:
191 def setenv(name, val): 212 config = self.manifest.manifestProject.config
192 if val is None: 213 results_it = pool.imap(
193 val = '' 214 DoWorkWrapper,
194 env[name] = val.encode() 215 [[mirror, opt, cmd, shell, cnt, config, self._SerializeProject(p)]
195 216 for cnt, p in enumerate(projects)]
196 setenv('REPO_PROJECT', project.name) 217 )
197 setenv('REPO_PATH', project.relpath) 218 pool.close()
198 setenv('REPO_REMOTE', project.remote.name) 219 for r in results_it:
199 setenv('REPO_LREV', project.GetRevisionId()) 220 rc = rc or r
200 setenv('REPO_RREV', project.revisionExpr) 221 if r != 0 and opt.abort_on_errors:
201 setenv('REPO_I', str(cnt + 1)) 222 raise Exception('Aborting due to previous error')
202 for a in project.annotations: 223 except (KeyboardInterrupt, WorkerKeyboardInterrupt):
203 setenv("REPO__%s" % (a.name), a.value) 224 # Catch KeyboardInterrupt raised inside and outside of workers
204 225 print('Interrupted - terminating the pool')
205 if mirror: 226 pool.terminate()
206 setenv('GIT_DIR', project.gitdir) 227 rc = rc or errno.EINTR
207 cwd = project.gitdir 228 except Exception as e:
208 else: 229 # Catch any other exceptions raised
209 cwd = project.worktree 230 print('Got an error, terminating the pool: %r' % e,
210 231 file=sys.stderr)
211 if not os.path.exists(cwd): 232 pool.terminate()
212 if (opt.project_header and opt.verbose) \ 233 rc = rc or getattr(e, 'errno', 1)
213 or not opt.project_header: 234 finally:
214 print('skipping %s/' % project.relpath, file=sys.stderr) 235 pool.join()
215 continue
216
217 if opt.project_header:
218 stdin = subprocess.PIPE
219 stdout = subprocess.PIPE
220 stderr = subprocess.PIPE
221 else:
222 stdin = None
223 stdout = None
224 stderr = None
225
226 p = subprocess.Popen(cmd,
227 cwd = cwd,
228 shell = shell,
229 env = env,
230 stdin = stdin,
231 stdout = stdout,
232 stderr = stderr)
233
234 if opt.project_header:
235 class sfd(object):
236 def __init__(self, fd, dest):
237 self.fd = fd
238 self.dest = dest
239 def fileno(self):
240 return self.fd.fileno()
241
242 empty = True
243 errbuf = ''
244
245 p.stdin.close()
246 s_in = [sfd(p.stdout, sys.stdout),
247 sfd(p.stderr, sys.stderr)]
248
249 for s in s_in:
250 flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
251 fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
252
253 while s_in:
254 in_ready, _out_ready, _err_ready = select.select(s_in, [], [])
255 for s in in_ready:
256 buf = s.fd.read(4096)
257 if not buf:
258 s.fd.close()
259 s_in.remove(s)
260 continue
261
262 if not opt.verbose:
263 if s.fd != p.stdout:
264 errbuf += buf
265 continue
266
267 if empty:
268 if first:
269 first = False
270 else:
271 out.nl()
272
273 if mirror:
274 project_header_path = project.name
275 else:
276 project_header_path = project.relpath
277 out.project('project %s/', project_header_path)
278 out.nl()
279 out.flush()
280 if errbuf:
281 sys.stderr.write(errbuf)
282 sys.stderr.flush()
283 errbuf = ''
284 empty = False
285
286 s.dest.write(buf)
287 s.dest.flush()
288
289 r = p.wait()
290 if r != 0:
291 if r != rc:
292 rc = r
293 if opt.abort_on_errors:
294 print("error: %s: Aborting due to previous error" % project.relpath,
295 file=sys.stderr)
296 sys.exit(r)
297 if rc != 0: 236 if rc != 0:
298 sys.exit(rc) 237 sys.exit(rc)
238
239
240class WorkerKeyboardInterrupt(Exception):
241 """ Keyboard interrupt exception for worker processes. """
242 pass
243
244
245def DoWorkWrapper(args):
246 """ A wrapper around the DoWork() method.
247
248 Catch the KeyboardInterrupt exceptions here and re-raise them as a different,
249 ``Exception``-based exception to stop it flooding the console with stacktraces
250 and making the parent hang indefinitely.
251
252 """
253 project = args.pop()
254 try:
255 return DoWork(project, *args)
256 except KeyboardInterrupt:
257 print('%s: Worker interrupted' % project['name'])
258 raise WorkerKeyboardInterrupt()
259
260
261def DoWork(project, mirror, opt, cmd, shell, cnt, config):
262 env = os.environ.copy()
263 def setenv(name, val):
264 if val is None:
265 val = ''
266 env[name] = val.encode()
267
268 setenv('REPO_PROJECT', project['name'])
269 setenv('REPO_PATH', project['relpath'])
270 setenv('REPO_REMOTE', project['remote_name'])
271 setenv('REPO_LREV', project['lrev'])
272 setenv('REPO_RREV', project['rrev'])
273 setenv('REPO_I', str(cnt + 1))
274 for name in project['annotations']:
275 setenv("REPO__%s" % (name), project['annotations'][name])
276
277 if mirror:
278 setenv('GIT_DIR', project['gitdir'])
279 cwd = project['gitdir']
280 else:
281 cwd = project['worktree']
282
283 if not os.path.exists(cwd):
284 if (opt.project_header and opt.verbose) \
285 or not opt.project_header:
286 print('skipping %s/' % project['relpath'], file=sys.stderr)
287 return
288
289 if opt.project_header:
290 stdin = subprocess.PIPE
291 stdout = subprocess.PIPE
292 stderr = subprocess.PIPE
293 else:
294 stdin = None
295 stdout = None
296 stderr = None
297
298 p = subprocess.Popen(cmd,
299 cwd=cwd,
300 shell=shell,
301 env=env,
302 stdin=stdin,
303 stdout=stdout,
304 stderr=stderr)
305
306 if opt.project_header:
307 out = ForallColoring(config)
308 out.redirect(sys.stdout)
309 class sfd(object):
310 def __init__(self, fd, dest):
311 self.fd = fd
312 self.dest = dest
313 def fileno(self):
314 return self.fd.fileno()
315
316 empty = True
317 errbuf = ''
318
319 p.stdin.close()
320 s_in = [sfd(p.stdout, sys.stdout),
321 sfd(p.stderr, sys.stderr)]
322
323 for s in s_in:
324 flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
325 fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
326
327 while s_in:
328 in_ready, _out_ready, _err_ready = select.select(s_in, [], [])
329 for s in in_ready:
330 buf = s.fd.read(4096)
331 if not buf:
332 s.fd.close()
333 s_in.remove(s)
334 continue
335
336 if not opt.verbose:
337 if s.fd != p.stdout:
338 errbuf += buf
339 continue
340
341 if empty and out:
342 if not cnt == 0:
343 out.nl()
344
345 if mirror:
346 project_header_path = project['name']
347 else:
348 project_header_path = project['relpath']
349 out.project('project %s/', project_header_path)
350 out.nl()
351 out.flush()
352 if errbuf:
353 sys.stderr.write(errbuf)
354 sys.stderr.flush()
355 errbuf = ''
356 empty = False
357
358 s.dest.write(buf)
359 s.dest.flush()
360
361 r = p.wait()
362 return r