diff options
-rw-r--r-- | error.py | 9 | ||||
-rw-r--r-- | git_config.py | 6 | ||||
-rwxr-xr-x | main.py | 4 | ||||
-rw-r--r-- | project.py | 145 | ||||
-rwxr-xr-x | repo | 103 | ||||
-rw-r--r-- | subcmds/init.py | 7 |
6 files changed, 250 insertions, 24 deletions
@@ -57,6 +57,15 @@ class UploadError(Exception): | |||
57 | def __str__(self): | 57 | def __str__(self): |
58 | return self.reason | 58 | return self.reason |
59 | 59 | ||
60 | class DownloadError(Exception): | ||
61 | """Cannot download a repository. | ||
62 | """ | ||
63 | def __init__(self, reason): | ||
64 | self.reason = reason | ||
65 | |||
66 | def __str__(self): | ||
67 | return self.reason | ||
68 | |||
60 | class NoSuchProjectError(Exception): | 69 | class NoSuchProjectError(Exception): |
61 | """A specified project does not exist in the work tree. | 70 | """A specified project does not exist in the work tree. |
62 | """ | 71 | """ |
diff --git a/git_config.py b/git_config.py index e4f4a0ab..bcd6e8d6 100644 --- a/git_config.py +++ b/git_config.py | |||
@@ -491,6 +491,12 @@ def close_ssh(): | |||
491 | URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):') | 491 | URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):') |
492 | URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/') | 492 | URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/') |
493 | 493 | ||
494 | def GetSchemeFromUrl(url): | ||
495 | m = URI_ALL.match(url) | ||
496 | if m: | ||
497 | return m.group(1) | ||
498 | return None | ||
499 | |||
494 | def _preconnect(url): | 500 | def _preconnect(url): |
495 | m = URI_ALL.match(url) | 501 | m = URI_ALL.match(url) |
496 | if m: | 502 | if m: |
@@ -37,6 +37,7 @@ from command import InteractiveCommand | |||
37 | from command import MirrorSafeCommand | 37 | from command import MirrorSafeCommand |
38 | from command import PagedCommand | 38 | from command import PagedCommand |
39 | from editor import Editor | 39 | from editor import Editor |
40 | from error import DownloadError | ||
40 | from error import ManifestInvalidRevisionError | 41 | from error import ManifestInvalidRevisionError |
41 | from error import NoSuchProjectError | 42 | from error import NoSuchProjectError |
42 | from error import RepoChangedException | 43 | from error import RepoChangedException |
@@ -143,6 +144,9 @@ class _Repo(object): | |||
143 | else: | 144 | else: |
144 | print >>sys.stderr, 'real\t%dh%dm%.3fs' \ | 145 | print >>sys.stderr, 'real\t%dh%dm%.3fs' \ |
145 | % (hours, minutes, seconds) | 146 | % (hours, minutes, seconds) |
147 | except DownloadError, e: | ||
148 | print >>sys.stderr, 'error: %s' % str(e) | ||
149 | sys.exit(1) | ||
146 | except ManifestInvalidRevisionError, e: | 150 | except ManifestInvalidRevisionError, e: |
147 | print >>sys.stderr, 'error: %s' % str(e) | 151 | print >>sys.stderr, 'error: %s' % str(e) |
148 | sys.exit(1) | 152 | sys.exit(1) |
@@ -24,9 +24,11 @@ import urllib2 | |||
24 | 24 | ||
25 | from color import Coloring | 25 | from color import Coloring |
26 | from git_command import GitCommand | 26 | from git_command import GitCommand |
27 | from git_config import GitConfig, IsId | 27 | from git_config import GitConfig, IsId, GetSchemeFromUrl |
28 | from error import DownloadError | ||
28 | from error import GitError, HookError, ImportError, UploadError | 29 | from error import GitError, HookError, ImportError, UploadError |
29 | from error import ManifestInvalidRevisionError | 30 | from error import ManifestInvalidRevisionError |
31 | from progress import Progress | ||
30 | 32 | ||
31 | from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M | 33 | from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M |
32 | 34 | ||
@@ -884,15 +886,13 @@ class Project(object): | |||
884 | 886 | ||
885 | ## Sync ## | 887 | ## Sync ## |
886 | 888 | ||
887 | def Sync_NetworkHalf(self, quiet=False): | 889 | def Sync_NetworkHalf(self, quiet=False, is_new=None): |
888 | """Perform only the network IO portion of the sync process. | 890 | """Perform only the network IO portion of the sync process. |
889 | Local working directory/branch state is not affected. | 891 | Local working directory/branch state is not affected. |
890 | """ | 892 | """ |
891 | is_new = not self.Exists | 893 | if is_new is None: |
894 | is_new = not self.Exists | ||
892 | if is_new: | 895 | if is_new: |
893 | if not quiet: | ||
894 | print >>sys.stderr | ||
895 | print >>sys.stderr, 'Initializing project %s ...' % self.name | ||
896 | self._InitGitDir() | 896 | self._InitGitDir() |
897 | 897 | ||
898 | self._InitRemote() | 898 | self._InitRemote() |
@@ -1312,9 +1312,16 @@ class Project(object): | |||
1312 | name = self.remote.name | 1312 | name = self.remote.name |
1313 | 1313 | ||
1314 | ssh_proxy = False | 1314 | ssh_proxy = False |
1315 | if self.GetRemote(name).PreConnectFetch(): | 1315 | remote = self.GetRemote(name) |
1316 | if remote.PreConnectFetch(): | ||
1316 | ssh_proxy = True | 1317 | ssh_proxy = True |
1317 | 1318 | ||
1319 | bundle_dst = os.path.join(self.gitdir, 'clone.bundle') | ||
1320 | bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp') | ||
1321 | use_bundle = False | ||
1322 | if os.path.exists(bundle_dst) or os.path.exists(bundle_tmp): | ||
1323 | use_bundle = True | ||
1324 | |||
1318 | if initial: | 1325 | if initial: |
1319 | alt = os.path.join(self.gitdir, 'objects/info/alternates') | 1326 | alt = os.path.join(self.gitdir, 'objects/info/alternates') |
1320 | try: | 1327 | try: |
@@ -1329,6 +1336,8 @@ class Project(object): | |||
1329 | ref_dir = None | 1336 | ref_dir = None |
1330 | 1337 | ||
1331 | if ref_dir and 'objects' == os.path.basename(ref_dir): | 1338 | if ref_dir and 'objects' == os.path.basename(ref_dir): |
1339 | if use_bundle: | ||
1340 | use_bundle = False | ||
1332 | ref_dir = os.path.dirname(ref_dir) | 1341 | ref_dir = os.path.dirname(ref_dir) |
1333 | packed_refs = os.path.join(self.gitdir, 'packed-refs') | 1342 | packed_refs = os.path.join(self.gitdir, 'packed-refs') |
1334 | remote = self.GetRemote(name) | 1343 | remote = self.GetRemote(name) |
@@ -1368,6 +1377,7 @@ class Project(object): | |||
1368 | 1377 | ||
1369 | else: | 1378 | else: |
1370 | ref_dir = None | 1379 | ref_dir = None |
1380 | use_bundle = True | ||
1371 | 1381 | ||
1372 | cmd = ['fetch'] | 1382 | cmd = ['fetch'] |
1373 | 1383 | ||
@@ -1376,15 +1386,37 @@ class Project(object): | |||
1376 | depth = self.manifest.manifestProject.config.GetString('repo.depth') | 1386 | depth = self.manifest.manifestProject.config.GetString('repo.depth') |
1377 | if depth and initial: | 1387 | if depth and initial: |
1378 | cmd.append('--depth=%s' % depth) | 1388 | cmd.append('--depth=%s' % depth) |
1389 | use_bundle = False | ||
1379 | 1390 | ||
1380 | if quiet: | 1391 | if quiet: |
1381 | cmd.append('--quiet') | 1392 | cmd.append('--quiet') |
1382 | if not self.worktree: | 1393 | if not self.worktree: |
1383 | cmd.append('--update-head-ok') | 1394 | cmd.append('--update-head-ok') |
1384 | cmd.append(name) | 1395 | |
1385 | if tag is not None: | 1396 | if use_bundle and not os.path.exists(bundle_dst): |
1386 | cmd.append('tag') | 1397 | bundle_url = remote.url + '/clone.bundle' |
1387 | cmd.append(tag) | 1398 | bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url) |
1399 | if GetSchemeFromUrl(bundle_url) in ('http', 'https'): | ||
1400 | use_bundle = self._FetchBundle( | ||
1401 | bundle_url, | ||
1402 | bundle_tmp, | ||
1403 | bundle_dst, | ||
1404 | quiet=quiet) | ||
1405 | else: | ||
1406 | use_bundle = False | ||
1407 | |||
1408 | if use_bundle: | ||
1409 | if not quiet: | ||
1410 | cmd.append('--quiet') | ||
1411 | cmd.append(bundle_dst) | ||
1412 | for f in remote.fetch: | ||
1413 | cmd.append(str(f)) | ||
1414 | cmd.append('refs/tags/*:refs/tags/*') | ||
1415 | else: | ||
1416 | cmd.append(name) | ||
1417 | if tag is not None: | ||
1418 | cmd.append('tag') | ||
1419 | cmd.append(tag) | ||
1388 | 1420 | ||
1389 | ok = GitCommand(self, | 1421 | ok = GitCommand(self, |
1390 | cmd, | 1422 | cmd, |
@@ -1399,8 +1431,99 @@ class Project(object): | |||
1399 | os.remove(packed_refs) | 1431 | os.remove(packed_refs) |
1400 | self.bare_git.pack_refs('--all', '--prune') | 1432 | self.bare_git.pack_refs('--all', '--prune') |
1401 | 1433 | ||
1434 | if os.path.exists(bundle_dst): | ||
1435 | os.remove(bundle_dst) | ||
1436 | if os.path.exists(bundle_tmp): | ||
1437 | os.remove(bundle_tmp) | ||
1438 | |||
1402 | return ok | 1439 | return ok |
1403 | 1440 | ||
1441 | def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet=False): | ||
1442 | keep = True | ||
1443 | done = False | ||
1444 | dest = open(tmpPath, 'a+b') | ||
1445 | try: | ||
1446 | dest.seek(0, os.SEEK_END) | ||
1447 | pos = dest.tell() | ||
1448 | |||
1449 | req = urllib2.Request(srcUrl) | ||
1450 | if pos > 0: | ||
1451 | req.add_header('Range', 'bytes=%d-' % pos) | ||
1452 | |||
1453 | try: | ||
1454 | r = urllib2.urlopen(req) | ||
1455 | except urllib2.HTTPError, e: | ||
1456 | if e.code == 404: | ||
1457 | keep = False | ||
1458 | return False | ||
1459 | elif e.info()['content-type'] == 'text/plain': | ||
1460 | try: | ||
1461 | msg = e.read() | ||
1462 | if len(msg) > 0 and msg[-1] == '\n': | ||
1463 | msg = msg[0:-1] | ||
1464 | msg = ' (%s)' % msg | ||
1465 | except: | ||
1466 | msg = '' | ||
1467 | else: | ||
1468 | try: | ||
1469 | from BaseHTTPServer import BaseHTTPRequestHandler | ||
1470 | res = BaseHTTPRequestHandler.responses[e.code] | ||
1471 | msg = ' (%s: %s)' % (res[0], res[1]) | ||
1472 | except: | ||
1473 | msg = '' | ||
1474 | raise DownloadError('HTTP %s%s' % (e.code, msg)) | ||
1475 | except urllib2.URLError, e: | ||
1476 | raise DownloadError('%s (%s)' % (e.reason, req.get_host())) | ||
1477 | |||
1478 | p = None | ||
1479 | try: | ||
1480 | size = r.headers['content-length'] | ||
1481 | unit = 1 << 10 | ||
1482 | |||
1483 | if size and not quiet: | ||
1484 | if size > 1024 * 1.3: | ||
1485 | unit = 1 << 20 | ||
1486 | desc = 'MB' | ||
1487 | else: | ||
1488 | desc = 'KB' | ||
1489 | p = Progress( | ||
1490 | 'Downloading %s' % self.relpath, | ||
1491 | int(size) / unit, | ||
1492 | units=desc) | ||
1493 | if pos > 0: | ||
1494 | p.update(pos / unit) | ||
1495 | |||
1496 | s = 0 | ||
1497 | while True: | ||
1498 | d = r.read(8192) | ||
1499 | if d == '': | ||
1500 | done = True | ||
1501 | return True | ||
1502 | dest.write(d) | ||
1503 | if p: | ||
1504 | s += len(d) | ||
1505 | if s >= unit: | ||
1506 | p.update(s / unit) | ||
1507 | s = s % unit | ||
1508 | if p: | ||
1509 | if s >= unit: | ||
1510 | p.update(s / unit) | ||
1511 | else: | ||
1512 | p.update(1) | ||
1513 | finally: | ||
1514 | r.close() | ||
1515 | if p: | ||
1516 | p.end() | ||
1517 | finally: | ||
1518 | dest.close() | ||
1519 | |||
1520 | if os.path.exists(dstPath): | ||
1521 | os.remove(dstPath) | ||
1522 | if done: | ||
1523 | os.rename(tmpPath, dstPath) | ||
1524 | elif not keep: | ||
1525 | os.remove(tmpPath) | ||
1526 | |||
1404 | def _Checkout(self, rev, quiet=False): | 1527 | def _Checkout(self, rev, quiet=False): |
1405 | cmd = ['checkout'] | 1528 | cmd = ['checkout'] |
1406 | if quiet: | 1529 | if quiet: |
@@ -28,7 +28,7 @@ if __name__ == '__main__': | |||
28 | del magic | 28 | del magic |
29 | 29 | ||
30 | # increment this whenever we make important changes to this script | 30 | # increment this whenever we make important changes to this script |
31 | VERSION = (1, 12) | 31 | VERSION = (1, 13) |
32 | 32 | ||
33 | # increment this if the MAINTAINER_KEYS block is modified | 33 | # increment this if the MAINTAINER_KEYS block is modified |
34 | KEYRING_VERSION = (1,0) | 34 | KEYRING_VERSION = (1,0) |
@@ -91,6 +91,7 @@ import re | |||
91 | import readline | 91 | import readline |
92 | import subprocess | 92 | import subprocess |
93 | import sys | 93 | import sys |
94 | import urllib2 | ||
94 | 95 | ||
95 | home_dot_repo = os.path.expanduser('~/.repoconfig') | 96 | home_dot_repo = os.path.expanduser('~/.repoconfig') |
96 | gpg_dir = os.path.join(home_dot_repo, 'gnupg') | 97 | gpg_dir = os.path.join(home_dot_repo, 'gnupg') |
@@ -187,10 +188,6 @@ def _Init(args): | |||
187 | else: | 188 | else: |
188 | can_verify = True | 189 | can_verify = True |
189 | 190 | ||
190 | if not opt.quiet: | ||
191 | print >>sys.stderr, 'Getting repo ...' | ||
192 | print >>sys.stderr, ' from %s' % url | ||
193 | |||
194 | dst = os.path.abspath(os.path.join(repodir, S_repo)) | 191 | dst = os.path.abspath(os.path.join(repodir, S_repo)) |
195 | _Clone(url, dst, opt.quiet) | 192 | _Clone(url, dst, opt.quiet) |
196 | 193 | ||
@@ -300,15 +297,42 @@ def _SetConfig(local, name, value): | |||
300 | raise CloneFailure() | 297 | raise CloneFailure() |
301 | 298 | ||
302 | 299 | ||
303 | def _Fetch(local, quiet, *args): | 300 | def _InitHttp(): |
301 | handlers = [] | ||
302 | |||
303 | mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() | ||
304 | try: | ||
305 | import netrc | ||
306 | n = netrc.netrc() | ||
307 | for host in n.hosts: | ||
308 | p = n.hosts[host] | ||
309 | mgr.add_password(None, 'http://%s/' % host, p[0], p[2]) | ||
310 | mgr.add_password(None, 'https://%s/' % host, p[0], p[2]) | ||
311 | except: | ||
312 | pass | ||
313 | handlers.append(urllib2.HTTPBasicAuthHandler(mgr)) | ||
314 | |||
315 | if 'http_proxy' in os.environ: | ||
316 | url = os.environ['http_proxy'] | ||
317 | handlers.append(urllib2.ProxyHandler({'http': url, 'https': url})) | ||
318 | if 'REPO_CURL_VERBOSE' in os.environ: | ||
319 | handlers.append(urllib2.HTTPHandler(debuglevel=1)) | ||
320 | handlers.append(urllib2.HTTPSHandler(debuglevel=1)) | ||
321 | urllib2.install_opener(urllib2.build_opener(*handlers)) | ||
322 | |||
323 | def _Fetch(url, local, src, quiet): | ||
324 | if not quiet: | ||
325 | print >>sys.stderr, 'Get %s' % url | ||
326 | |||
304 | cmd = [GIT, 'fetch'] | 327 | cmd = [GIT, 'fetch'] |
305 | if quiet: | 328 | if quiet: |
306 | cmd.append('--quiet') | 329 | cmd.append('--quiet') |
307 | err = subprocess.PIPE | 330 | err = subprocess.PIPE |
308 | else: | 331 | else: |
309 | err = None | 332 | err = None |
310 | cmd.extend(args) | 333 | cmd.append(src) |
311 | cmd.append('origin') | 334 | cmd.append('+refs/heads/*:refs/remotes/origin/*') |
335 | cmd.append('refs/tags/*:refs/tags/*') | ||
312 | 336 | ||
313 | proc = subprocess.Popen(cmd, cwd = local, stderr = err) | 337 | proc = subprocess.Popen(cmd, cwd = local, stderr = err) |
314 | if err: | 338 | if err: |
@@ -317,6 +341,62 @@ def _Fetch(local, quiet, *args): | |||
317 | if proc.wait() != 0: | 341 | if proc.wait() != 0: |
318 | raise CloneFailure() | 342 | raise CloneFailure() |
319 | 343 | ||
344 | def _DownloadBundle(url, local, quiet): | ||
345 | if not url.endswith('/'): | ||
346 | url += '/' | ||
347 | url += 'clone.bundle' | ||
348 | |||
349 | proc = subprocess.Popen( | ||
350 | [GIT, 'config', '--get-regexp', 'url.*.insteadof'], | ||
351 | cwd = local, | ||
352 | stdout = subprocess.PIPE) | ||
353 | for line in proc.stdout: | ||
354 | m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line) | ||
355 | if m: | ||
356 | new_url = m.group(1) | ||
357 | old_url = m.group(2) | ||
358 | if url.startswith(old_url): | ||
359 | url = new_url + url[len(old_url):] | ||
360 | break | ||
361 | proc.stdout.close() | ||
362 | proc.wait() | ||
363 | |||
364 | if not url.startswith('http:') and not url.startswith('https:'): | ||
365 | return False | ||
366 | |||
367 | dest = open(os.path.join(local, '.git', 'clone.bundle'), 'w+b') | ||
368 | try: | ||
369 | try: | ||
370 | r = urllib2.urlopen(url) | ||
371 | except urllib2.HTTPError, e: | ||
372 | if e.code == 404: | ||
373 | return False | ||
374 | print >>sys.stderr, 'fatal: Cannot get %s' % url | ||
375 | print >>sys.stderr, 'fatal: HTTP error %s' % e.code | ||
376 | raise CloneFailure() | ||
377 | except urllib2.URLError, e: | ||
378 | print >>sys.stderr, 'fatal: Cannot get %s' % url | ||
379 | print >>sys.stderr, 'fatal: error %s' % e.reason | ||
380 | raise CloneFailure() | ||
381 | try: | ||
382 | if not quiet: | ||
383 | print >>sys.stderr, 'Get %s' % url | ||
384 | while True: | ||
385 | buf = r.read(8192) | ||
386 | if buf == '': | ||
387 | return True | ||
388 | dest.write(buf) | ||
389 | finally: | ||
390 | r.close() | ||
391 | finally: | ||
392 | dest.close() | ||
393 | |||
394 | def _ImportBundle(local): | ||
395 | path = os.path.join(local, '.git', 'clone.bundle') | ||
396 | try: | ||
397 | _Fetch(local, local, path, True) | ||
398 | finally: | ||
399 | os.remove(path) | ||
320 | 400 | ||
321 | def _Clone(url, local, quiet): | 401 | def _Clone(url, local, quiet): |
322 | """Clones a git repository to a new subdirectory of repodir | 402 | """Clones a git repository to a new subdirectory of repodir |
@@ -344,11 +424,14 @@ def _Clone(url, local, quiet): | |||
344 | print >>sys.stderr, 'fatal: could not create %s' % local | 424 | print >>sys.stderr, 'fatal: could not create %s' % local |
345 | raise CloneFailure() | 425 | raise CloneFailure() |
346 | 426 | ||
427 | _InitHttp() | ||
347 | _SetConfig(local, 'remote.origin.url', url) | 428 | _SetConfig(local, 'remote.origin.url', url) |
348 | _SetConfig(local, 'remote.origin.fetch', | 429 | _SetConfig(local, 'remote.origin.fetch', |
349 | '+refs/heads/*:refs/remotes/origin/*') | 430 | '+refs/heads/*:refs/remotes/origin/*') |
350 | _Fetch(local, quiet) | 431 | if _DownloadBundle(url, local, quiet): |
351 | _Fetch(local, quiet, '--tags') | 432 | _ImportBundle(local) |
433 | else: | ||
434 | _Fetch(url, local, 'origin', quiet) | ||
352 | 435 | ||
353 | 436 | ||
354 | def _Verify(cwd, branch, quiet): | 437 | def _Verify(cwd, branch, quiet): |
diff --git a/subcmds/init.py b/subcmds/init.py index c35cc82c..9214aed5 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
@@ -21,6 +21,7 @@ from color import Coloring | |||
21 | from command import InteractiveCommand, MirrorSafeCommand | 21 | from command import InteractiveCommand, MirrorSafeCommand |
22 | from error import ManifestParseError | 22 | from error import ManifestParseError |
23 | from project import SyncBuffer | 23 | from project import SyncBuffer |
24 | from git_config import GitConfig | ||
24 | from git_command import git_require, MIN_GIT_VERSION | 25 | from git_command import git_require, MIN_GIT_VERSION |
25 | 26 | ||
26 | class Init(InteractiveCommand, MirrorSafeCommand): | 27 | class Init(InteractiveCommand, MirrorSafeCommand): |
@@ -108,8 +109,8 @@ to update the working directory files. | |||
108 | sys.exit(1) | 109 | sys.exit(1) |
109 | 110 | ||
110 | if not opt.quiet: | 111 | if not opt.quiet: |
111 | print >>sys.stderr, 'Getting manifest ...' | 112 | print >>sys.stderr, 'Get %s' \ |
112 | print >>sys.stderr, ' from %s' % opt.manifest_url | 113 | % GitConfig.ForUser().UrlInsteadOf(opt.manifest_url) |
113 | m._InitGitDir() | 114 | m._InitGitDir() |
114 | 115 | ||
115 | if opt.manifest_branch: | 116 | if opt.manifest_branch: |
@@ -138,7 +139,7 @@ to update the working directory files. | |||
138 | print >>sys.stderr, 'fatal: --mirror not supported on existing client' | 139 | print >>sys.stderr, 'fatal: --mirror not supported on existing client' |
139 | sys.exit(1) | 140 | sys.exit(1) |
140 | 141 | ||
141 | if not m.Sync_NetworkHalf(): | 142 | if not m.Sync_NetworkHalf(is_new=is_new): |
142 | r = m.GetRemote(m.remote.name) | 143 | r = m.GetRemote(m.remote.name) |
143 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url | 144 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url |
144 | 145 | ||