Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(560)

Side by Side Diff: git_cache.py

Issue 240203005: Implement git-drover. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """A git command for managing a local cache of git repositories.""" 6 """A git command for managing a local cache of git repositories."""
7 7
8 from __future__ import print_function 8 from __future__ import print_function
9 import errno 9 import errno
10 import logging 10 import logging
11 import optparse 11 import optparse
12 import os 12 import os
13 import tempfile 13 import tempfile
14 import subprocess 14 import subprocess
15 import sys 15 import sys
16 import urlparse 16 import urlparse
17 17
18 from download_from_google_storage import Gsutil 18 from download_from_google_storage import Gsutil
19 import gclient_utils 19 import gclient_utils
20 import subcommand 20 import subcommand
21 21
22 try: 22 try:
23 # pylint: disable=E0602 23 # pylint: disable=E0602
24 WinErr = WindowsError 24 WinErr = WindowsError
25 except NameError: 25 except NameError:
26 class WinErr(Exception): 26 class WinErr(Exception):
27 pass 27 pass
28 28
29
29 class LockError(Exception): 30 class LockError(Exception):
30 pass 31 pass
31 32
32 33
33 class Lockfile(object): 34 class Lockfile(object):
34 """Class to represent a cross-platform process-specific lockfile.""" 35 """Class to represent a cross-platform process-specific lockfile."""
35 36
36 def __init__(self, path): 37 def __init__(self, path):
37 self.path = os.path.abspath(path) 38 self.path = os.path.abspath(path)
38 self.lockfile = self.path + ".lock" 39 self.lockfile = self.path + ".lock"
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
128 'third_party', 'gsutil', 'gsutil') 129 'third_party', 'gsutil', 'gsutil')
129 bootstrap_bucket = 'chromium-git-cache' 130 bootstrap_bucket = 'chromium-git-cache'
130 131
131 def __init__(self, url, refs=None, print_func=None): 132 def __init__(self, url, refs=None, print_func=None):
132 self.url = url 133 self.url = url
133 self.refs = refs or [] 134 self.refs = refs or []
134 self.basedir = self.UrlToCacheDir(url) 135 self.basedir = self.UrlToCacheDir(url)
135 self.mirror_path = os.path.join(self.GetCachePath(), self.basedir) 136 self.mirror_path = os.path.join(self.GetCachePath(), self.basedir)
136 self.print = print_func or print 137 self.print = print_func or print
137 138
139 @classmethod
140 def from_repo(cls, path=None):
141 """Returns Mirror if path is in a cached repo, else None."""
142 args = ['-C', path] if path else []
143 args = [cls.git_exe] + args + ['rev-parse', '--git-dir']
144 git_path = subprocess.check_output(args).strip()
145 alt_path = os.path.join(git_path, 'objects', 'info', 'alternates')
146
147 if os.path.exists(alt_path):
148 with open(alt_path, 'rb') as alt:
149 mirror_path = alt.read().strip()
150 mirror_path = os.path.dirname(mirror_path)
151 cache_path = os.path.dirname(mirror_path)
152 if os.path.exists(mirror_path):
153 url = subprocess.check_output(
154 [cls.git_exe, '-C', mirror_path, 'config', 'remote.origin.url']
155 ).strip()
156
157 # TODO(iannucci): cache_path should NOT be a class attribute. Maybe
158 # a `default_cache_path`, but not the actual path.
159 cls.SetCachePath(cache_path)
160
161 return cls(url)
162
138 @staticmethod 163 @staticmethod
139 def UrlToCacheDir(url): 164 def UrlToCacheDir(url):
140 """Convert a git url to a normalized form for the cache dir path.""" 165 """Convert a git url to a normalized form for the cache dir path."""
141 parsed = urlparse.urlparse(url) 166 parsed = urlparse.urlparse(url)
142 norm_url = parsed.netloc + parsed.path 167 norm_url = parsed.netloc + parsed.path
143 if norm_url.endswith('.git'): 168 if norm_url.endswith('.git'):
144 norm_url = norm_url[:-len('.git')] 169 norm_url = norm_url[:-len('.git')]
145 return norm_url.replace('-', '--').replace('/', '-').lower() 170 return norm_url.replace('-', '--').replace('/', '-').lower()
146 171
147 @staticmethod 172 @staticmethod
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
199 self.RunGit(['config', '--replace-all', 'remote.origin.fetch', 224 self.RunGit(['config', '--replace-all', 'remote.origin.fetch',
200 '+refs/heads/*:refs/heads/*'], cwd=cwd) 225 '+refs/heads/*:refs/heads/*'], cwd=cwd)
201 for ref in self.refs: 226 for ref in self.refs:
202 ref = ref.lstrip('+').rstrip('/') 227 ref = ref.lstrip('+').rstrip('/')
203 if ref.startswith('refs/'): 228 if ref.startswith('refs/'):
204 refspec = '+%s:%s' % (ref, ref) 229 refspec = '+%s:%s' % (ref, ref)
205 else: 230 else:
206 refspec = '+refs/%s/*:refs/%s/*' % (ref, ref) 231 refspec = '+refs/%s/*:refs/%s/*' % (ref, ref)
207 self.RunGit(['config', '--add', 'remote.origin.fetch', refspec], cwd=cwd) 232 self.RunGit(['config', '--add', 'remote.origin.fetch', refspec], cwd=cwd)
208 233
209 def bootstrap_repo(self, directory): 234 def bootstrap_repo(self, directory, verbose):
210 """Bootstrap the repo from Google Stroage if possible. 235 """Bootstrap the repo from Google Stroage if possible.
211 236
212 Requires 7z on Windows and Unzip on Linux/Mac. 237 Requires 7z on Windows and Unzip on Linux/Mac.
213 """ 238 """
214 if sys.platform.startswith('win'): 239 if sys.platform.startswith('win'):
215 if not self.FindExecutable('7z'): 240 if not self.FindExecutable('7z'):
216 self.print(''' 241 self.print('''
217 Cannot find 7z in the path. If you want git cache to be able to bootstrap from 242 Cannot find 7z in the path. If you want git cache to be able to bootstrap from
218 Google Storage, please install 7z from: 243 Google Storage, please install 7z from:
219 244
(...skipping 15 matching lines...) Expand all
235 _, ls_out, _ = gsutil.check_call('ls', gs_folder) 260 _, ls_out, _ = gsutil.check_call('ls', gs_folder)
236 ls_out_sorted = sorted(ls_out.splitlines()) 261 ls_out_sorted = sorted(ls_out.splitlines())
237 if not ls_out_sorted: 262 if not ls_out_sorted:
238 # This repo is not on Google Storage. 263 # This repo is not on Google Storage.
239 return False 264 return False
240 latest_checkout = ls_out_sorted[-1] 265 latest_checkout = ls_out_sorted[-1]
241 266
242 # Download zip file to a temporary directory. 267 # Download zip file to a temporary directory.
243 try: 268 try:
244 tempdir = tempfile.mkdtemp() 269 tempdir = tempfile.mkdtemp()
245 self.print('Downloading %s' % latest_checkout) 270 if not verbose:
agable 2014/04/18 00:17:08 print if *not* verbose??
iannucci 2014/04/28 21:05:28 Yeah, otherwise this doubles up with the gsutil ou
246 code, out, err = gsutil.check_call('cp', latest_checkout, tempdir) 271 self.print('Downloading %s' % latest_checkout)
272 code, out, err = gsutil.check_call('cp', latest_checkout, tempdir,
273 verbose=verbose)
247 if code: 274 if code:
248 self.print('%s\n%s' % (out, err)) 275 self.print('%s\n%s' % (out, err))
249 return False 276 return False
250 filename = os.path.join(tempdir, latest_checkout.split('/')[-1]) 277 filename = os.path.join(tempdir, latest_checkout.split('/')[-1])
251 278
252 # Unpack the file with 7z on Windows, or unzip everywhere else. 279 # Unpack the file with 7z on Windows, or unzip everywhere else.
253 if sys.platform.startswith('win'): 280 if sys.platform.startswith('win'):
254 cmd = ['7z', 'x', '-o%s' % directory, '-tzip', filename] 281 cmd = ['7z', 'x', '-o%s' % directory, '-tzip', filename]
255 else: 282 else:
256 cmd = ['unzip', filename, '-d', directory] 283 cmd = ['unzip', filename, '-d', directory]
257 retcode = subprocess.call(cmd) 284 retcode = subprocess.call(cmd)
258 finally: 285 finally:
259 # Clean up the downloaded zipfile. 286 # Clean up the downloaded zipfile.
260 gclient_utils.rmtree(tempdir) 287 gclient_utils.rmtree(tempdir)
261 288
262 if retcode: 289 if retcode:
263 self.print( 290 self.print(
264 'Extracting bootstrap zipfile %s failed.\n' 291 'Extracting bootstrap zipfile %s failed.\n'
265 'Resuming normal operations.' % filename) 292 'Resuming normal operations.' % filename)
266 return False 293 return False
267 return True 294 return True
268 295
269 def exists(self): 296 def exists(self):
270 return os.path.isfile(os.path.join(self.mirror_path, 'config')) 297 return os.path.isfile(os.path.join(self.mirror_path, 'config'))
271 298
272 def populate(self, depth=None, shallow=False, bootstrap=False, 299 def populate(self, depth=None, shallow=False, bootstrap=False,
273 verbose=False): 300 verbose=False, fetch_specs=()):
agable 2014/04/18 00:17:08 None
iannucci 2014/04/28 21:05:28 Er... why? it's iterable and immutable... that's s
274 if shallow and not depth: 301 if shallow and not depth:
275 depth = 10000 302 depth = 10000
276 gclient_utils.safe_makedirs(self.GetCachePath()) 303 gclient_utils.safe_makedirs(self.GetCachePath())
277 304
278 v = [] 305 v = []
279 if verbose: 306 if verbose:
280 v = ['-v', '--progress'] 307 v = ['-v', '--progress']
281 308
282 d = [] 309 d = []
283 if depth: 310 if depth:
284 d = ['--depth', str(depth)] 311 d = ['--depth', str(depth)]
285 312
286 313
287 with Lockfile(self.mirror_path): 314 with Lockfile(self.mirror_path):
288 # Setup from scratch if the repo is new or is in a bad state. 315 # Setup from scratch if the repo is new or is in a bad state.
289 tempdir = None 316 tempdir = None
290 if not os.path.exists(os.path.join(self.mirror_path, 'config')): 317 if not os.path.exists(os.path.join(self.mirror_path, 'config')):
291 gclient_utils.rmtree(self.mirror_path) 318 gclient_utils.rmtree(self.mirror_path)
292 tempdir = tempfile.mkdtemp( 319 tempdir = tempfile.mkdtemp(
293 suffix=self.basedir, dir=self.GetCachePath()) 320 suffix=self.basedir, dir=self.GetCachePath())
294 bootstrapped = not depth and bootstrap and self.bootstrap_repo(tempdir) 321 bootstrapped = (not depth and bootstrap and
322 self.bootstrap_repo(tempdir, verbose))
295 if not bootstrapped: 323 if not bootstrapped:
296 self.RunGit(['init', '--bare'], cwd=tempdir) 324 self.RunGit(['init', '--bare'], cwd=tempdir)
297 else: 325 else:
298 if depth and os.path.exists(os.path.join(self.mirror_path, 'shallow')): 326 if depth and os.path.exists(os.path.join(self.mirror_path, 'shallow')):
299 logging.warn( 327 logging.warn(
300 'Shallow fetch requested, but repo cache already exists.') 328 'Shallow fetch requested, but repo cache already exists.')
301 d = [] 329 d = []
302 330
303 rundir = tempdir or self.mirror_path 331 rundir = tempdir or self.mirror_path
304 self.config(rundir) 332 self.config(rundir)
305 fetch_cmd = ['fetch'] + v + d + ['origin'] 333 fetch_cmd = ['fetch'] + v + d + ['origin']
306 fetch_specs = subprocess.check_output( 334 fetch_specs = fetch_specs or subprocess.check_output(
307 [self.git_exe, 'config', '--get-all', 'remote.origin.fetch'], 335 [self.git_exe, 'config', '--get-all', 'remote.origin.fetch'],
308 cwd=rundir).strip().splitlines() 336 cwd=rundir).strip().splitlines()
309 for spec in fetch_specs: 337 for spec in fetch_specs:
310 try: 338 try:
311 self.RunGit(fetch_cmd + [spec], cwd=rundir, retry=True) 339 self.RunGit(fetch_cmd + [spec], cwd=rundir, retry=True)
312 except subprocess.CalledProcessError: 340 except subprocess.CalledProcessError:
313 logging.warn('Fetch of %s failed' % spec) 341 logging.warn('Fetch of %s failed' % spec)
314 if tempdir: 342 if tempdir:
315 os.rename(tempdir, self.mirror_path) 343 os.rename(tempdir, self.mirror_path)
316 344
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
490 return options, args 518 return options, args
491 519
492 520
493 def main(argv): 521 def main(argv):
494 dispatcher = subcommand.CommandDispatcher(__name__) 522 dispatcher = subcommand.CommandDispatcher(__name__)
495 return dispatcher.execute(OptionParser(), argv) 523 return dispatcher.execute(OptionParser(), argv)
496 524
497 525
498 if __name__ == '__main__': 526 if __name__ == '__main__':
499 sys.exit(main(sys.argv[1:])) 527 sys.exit(main(sys.argv[1:]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698