summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/gitc_init.py73
-rw-r--r--subcmds/help.py13
-rw-r--r--subcmds/start.py42
-rw-r--r--subcmds/sync.py163
4 files changed, 219 insertions, 72 deletions
diff --git a/subcmds/gitc_init.py b/subcmds/gitc_init.py
index 9b9cefda..c243bb3b 100644
--- a/subcmds/gitc_init.py
+++ b/subcmds/gitc_init.py
@@ -15,19 +15,15 @@
15 15
16from __future__ import print_function 16from __future__ import print_function
17import os 17import os
18import shutil
19import sys 18import sys
20 19
21import git_command 20import gitc_utils
21from command import RequiresGitcCommand
22from manifest_xml import GitcManifest
22from subcmds import init 23from subcmds import init
23 24
24 25
25GITC_MANIFEST_DIR = '/usr/local/google/gitc' 26class GitcInit(init.Init, RequiresGitcCommand):
26GITC_FS_ROOT_DIR = '/gitc/sha/rw'
27NUM_BATCH_RETRIEVE_REVISIONID = 300
28
29
30class GitcInit(init.Init):
31 common = True 27 common = True
32 helpSummary = "Initialize a GITC Client." 28 helpSummary = "Initialize a GITC Client."
33 helpUsage = """ 29 helpUsage = """
@@ -39,7 +35,7 @@ with the GITC file system.
39 35
40This command will setup the client directory, initialize repo, just 36This command will setup the client directory, initialize repo, just
41like repo init does, and then downloads the manifest collection 37like repo init does, and then downloads the manifest collection
42and installs in in the .repo/directory of the GITC client. 38and installs it in the .repo/directory of the GITC client.
43 39
44Once this is done, a GITC manifest is generated by pulling the HEAD 40Once this is done, a GITC manifest is generated by pulling the HEAD
45SHA for each project and generates the properly formatted XML file 41SHA for each project and generates the properly formatted XML file
@@ -65,59 +61,24 @@ use for this GITC client.
65 if not opt.gitc_client: 61 if not opt.gitc_client:
66 print('fatal: gitc client (-c) is required', file=sys.stderr) 62 print('fatal: gitc client (-c) is required', file=sys.stderr)
67 sys.exit(1) 63 sys.exit(1)
68 self.client_dir = os.path.join(GITC_MANIFEST_DIR, opt.gitc_client) 64 self.client_dir = os.path.join(gitc_utils.get_gitc_manifest_dir(),
69 if not os.path.exists(GITC_MANIFEST_DIR): 65 opt.gitc_client)
70 os.makedirs(GITC_MANIFEST_DIR) 66 if not os.path.exists(gitc_utils.get_gitc_manifest_dir()):
67 os.makedirs(gitc_utils.get_gitc_manifest_dir())
71 if not os.path.exists(self.client_dir): 68 if not os.path.exists(self.client_dir):
72 os.mkdir(self.client_dir) 69 os.mkdir(self.client_dir)
73 super(GitcInit, self).Execute(opt, args) 70 super(GitcInit, self).Execute(opt, args)
71
72 manifest_file = self.manifest.manifestFile
74 if opt.manifest_file: 73 if opt.manifest_file:
75 if not os.path.exists(opt.manifest_file): 74 if not os.path.exists(opt.manifest_file):
76 print('fatal: Specified manifest file %s does not exist.' % 75 print('fatal: Specified manifest file %s does not exist.' %
77 opt.manifest_file) 76 opt.manifest_file)
78 sys.exit(1) 77 sys.exit(1)
79 shutil.copyfile(opt.manifest_file, 78 manifest_file = opt.manifest_file
80 os.path.join(self.client_dir, '.manifest'))
81 else:
82 self._GenerateGITCManifest()
83 print('Please run `cd %s` to view your GITC client.' %
84 os.path.join(GITC_FS_ROOT_DIR, opt.gitc_client))
85
86 def _SetProjectRevisions(self, projects, branch):
87 """Sets the revisionExpr for a list of projects.
88 79
89 Because of the limit of open file descriptors allowed, length of projects 80 manifest = GitcManifest(self.repodir, opt.gitc_client)
90 should not be overly large. Recommend calling this function multiple times 81 manifest.Override(manifest_file)
91 with each call not exceeding NUM_BATCH_RETRIEVE_REVISIONID projects. 82 gitc_utils.generate_gitc_manifest(None, manifest)
92 83 print('Please run `cd %s` to view your GITC client.' %
93 @param projects: List of project objects to set the revionExpr for. 84 os.path.join(gitc_utils.GITC_FS_ROOT_DIR, opt.gitc_client))
94 @param branch: The remote branch to retrieve the SHA from. If branch is
95 None, 'HEAD' is used.
96 """
97 project_gitcmds = [(
98 project, git_command.GitCommand(None,
99 ['ls-remote',
100 project.remote.url,
101 branch], capture_stdout=True))
102 for project in projects]
103 for proj, gitcmd in project_gitcmds:
104 if gitcmd.Wait():
105 print('FATAL: Failed to retrieve revisionID for %s' % project)
106 sys.exit(1)
107 proj.revisionExpr = gitcmd.stdout.split('\t')[0]
108
109 def _GenerateGITCManifest(self):
110 """Generate a manifest for shafsd to use for this GITC client."""
111 print('Generating GITC Manifest by fetching revision SHAs for each '
112 'project.')
113 manifest = self.manifest
114 project_gitcmd_dict = {}
115 index = 0
116 while index < len(manifest.projects):
117 self._SetProjectRevisions(
118 manifest.projects[index:(index+NUM_BATCH_RETRIEVE_REVISIONID)],
119 manifest.default.revisionExpr)
120 index += NUM_BATCH_RETRIEVE_REVISIONID
121 # Save the manifest.
122 with open(os.path.join(self.client_dir, '.manifest'), 'w') as f:
123 manifest.Save(f)
diff --git a/subcmds/help.py b/subcmds/help.py
index 4aa3f863..ae5b8f08 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -19,7 +19,8 @@ import sys
19from formatter import AbstractFormatter, DumbWriter 19from formatter import AbstractFormatter, DumbWriter
20 20
21from color import Coloring 21from color import Coloring
22from command import PagedCommand, MirrorSafeCommand 22from command import PagedCommand, MirrorSafeCommand, RequiresGitcCommand
23import gitc_utils
23 24
24class Help(PagedCommand, MirrorSafeCommand): 25class Help(PagedCommand, MirrorSafeCommand):
25 common = False 26 common = False
@@ -54,9 +55,17 @@ Displays detailed usage information about a command.
54 def _PrintCommonCommands(self): 55 def _PrintCommonCommands(self):
55 print('usage: repo COMMAND [ARGS]') 56 print('usage: repo COMMAND [ARGS]')
56 print('The most commonly used repo commands are:') 57 print('The most commonly used repo commands are:')
58
59 def gitc_supported(cmd):
60 if not isinstance(cmd, RequiresGitcCommand):
61 return True
62 if gitc_utils.get_gitc_manifest_dir():
63 return True
64 return False
65
57 commandNames = list(sorted([name 66 commandNames = list(sorted([name
58 for name, command in self.commands.items() 67 for name, command in self.commands.items()
59 if command.common])) 68 if command.common and gitc_supported(command)]))
60 69
61 maxlen = 0 70 maxlen = 0
62 for name in commandNames: 71 for name in commandNames:
diff --git a/subcmds/start.py b/subcmds/start.py
index 60ad41e0..940c3413 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -14,11 +14,15 @@
14# limitations under the License. 14# limitations under the License.
15 15
16from __future__ import print_function 16from __future__ import print_function
17import os
17import sys 18import sys
19
18from command import Command 20from command import Command
19from git_config import IsId 21from git_config import IsId
20from git_command import git 22from git_command import git
23import gitc_utils
21from progress import Progress 24from progress import Progress
25from project import SyncBuffer
22 26
23class Start(Command): 27class Start(Command):
24 common = True 28 common = True
@@ -53,20 +57,50 @@ revision specified in the manifest.
53 print("error: at least one project must be specified", file=sys.stderr) 57 print("error: at least one project must be specified", file=sys.stderr)
54 sys.exit(1) 58 sys.exit(1)
55 59
56 all_projects = self.GetProjects(projects) 60 if self.gitc_manifest:
61 all_projects = self.GetProjects(projects, manifest=self.gitc_manifest,
62 missing_ok=True)
63 for project in all_projects:
64 if project.old_revision:
65 project.already_synced = True
66 else:
67 project.already_synced = False
68 project.old_revision = project.revisionExpr
69 project.revisionExpr = None
70 # Save the GITC manifest.
71 gitc_utils.save_manifest(self.gitc_manifest)
57 72
73 all_projects = self.GetProjects(projects,
74 missing_ok=bool(self.gitc_manifest))
58 pm = Progress('Starting %s' % nb, len(all_projects)) 75 pm = Progress('Starting %s' % nb, len(all_projects))
59 for project in all_projects: 76 for project in all_projects:
60 pm.update() 77 pm.update()
78
79 if self.gitc_manifest:
80 gitc_project = self.gitc_manifest.paths[project.relpath]
81 # Sync projects that have not been opened.
82 if not gitc_project.already_synced:
83 proj_localdir = os.path.join(self.gitc_manifest.gitc_client_dir,
84 project.relpath)
85 project.worktree = proj_localdir
86 if not os.path.exists(proj_localdir):
87 os.makedirs(proj_localdir)
88 project.Sync_NetworkHalf()
89 sync_buf = SyncBuffer(self.manifest.manifestProject.config)
90 project.Sync_LocalHalf(sync_buf)
91 project.revisionId = gitc_project.old_revision
92
61 # If the current revision is a specific SHA1 then we can't push back 93 # If the current revision is a specific SHA1 then we can't push back
62 # to it; so substitute with dest_branch if defined, or with manifest 94 # to it; so substitute with dest_branch if defined, or with manifest
63 # default revision instead. 95 # default revision instead.
96 branch_merge = ''
64 if IsId(project.revisionExpr): 97 if IsId(project.revisionExpr):
65 if project.dest_branch: 98 if project.dest_branch:
66 project.revisionExpr = project.dest_branch 99 branch_merge = project.dest_branch
67 else: 100 else:
68 project.revisionExpr = self.manifest.default.revisionExpr 101 branch_merge = self.manifest.default.revisionExpr
69 if not project.StartBranch(nb): 102
103 if not project.StartBranch(nb, branch_merge=branch_merge):
70 err.append(project) 104 err.append(project)
71 pm.end() 105 pm.end()
72 106
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 43d450be..a99d7e74 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -23,18 +23,26 @@ import shutil
23import socket 23import socket
24import subprocess 24import subprocess
25import sys 25import sys
26import tempfile
26import time 27import time
27 28
28from pyversion import is_python3 29from pyversion import is_python3
29if is_python3(): 30if is_python3():
31 import http.cookiejar as cookielib
32 import urllib.error
30 import urllib.parse 33 import urllib.parse
34 import urllib.request
31 import xmlrpc.client 35 import xmlrpc.client
32else: 36else:
37 import cookielib
33 import imp 38 import imp
39 import urllib2
34 import urlparse 40 import urlparse
35 import xmlrpclib 41 import xmlrpclib
36 urllib = imp.new_module('urllib') 42 urllib = imp.new_module('urllib')
43 urllib.error = urllib2
37 urllib.parse = urlparse 44 urllib.parse = urlparse
45 urllib.request = urllib2
38 xmlrpc = imp.new_module('xmlrpc') 46 xmlrpc = imp.new_module('xmlrpc')
39 xmlrpc.client = xmlrpclib 47 xmlrpc.client = xmlrpclib
40 48
@@ -57,7 +65,9 @@ except ImportError:
57 multiprocessing = None 65 multiprocessing = None
58 66
59from git_command import GIT, git_require 67from git_command import GIT, git_require
68from git_config import GetUrlCookieFile
60from git_refs import R_HEADS, HEAD 69from git_refs import R_HEADS, HEAD
70import gitc_utils
61from project import Project 71from project import Project
62from project import RemoteSpec 72from project import RemoteSpec
63from command import Command, MirrorSafeCommand 73from command import Command, MirrorSafeCommand
@@ -65,6 +75,7 @@ from error import RepoChangedException, GitError, ManifestParseError
65from project import SyncBuffer 75from project import SyncBuffer
66from progress import Progress 76from progress import Progress
67from wrapper import Wrapper 77from wrapper import Wrapper
78from manifest_xml import GitcManifest
68 79
69_ONE_DAY_S = 24 * 60 * 60 80_ONE_DAY_S = 24 * 60 * 60
70 81
@@ -554,19 +565,18 @@ later is required to fix a server side protocol bug.
554 try: 565 try:
555 info = netrc.netrc() 566 info = netrc.netrc()
556 except IOError: 567 except IOError:
557 print('.netrc file does not exist or could not be opened', 568 # .netrc file does not exist or could not be opened
558 file=sys.stderr) 569 pass
559 else: 570 else:
560 try: 571 try:
561 parse_result = urllib.parse.urlparse(manifest_server) 572 parse_result = urllib.parse.urlparse(manifest_server)
562 if parse_result.hostname: 573 if parse_result.hostname:
563 username, _account, password = \ 574 auth = info.authenticators(parse_result.hostname)
564 info.authenticators(parse_result.hostname) 575 if auth:
565 except TypeError: 576 username, _account, password = auth
566 # TypeError is raised when the given hostname is not present 577 else:
567 # in the .netrc file. 578 print('No credentials found for %s in .netrc'
568 print('No credentials found for %s in .netrc' 579 % parse_result.hostname, file=sys.stderr)
569 % parse_result.hostname, file=sys.stderr)
570 except netrc.NetrcParseError as e: 580 except netrc.NetrcParseError as e:
571 print('Error parsing .netrc file: %s' % e, file=sys.stderr) 581 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
572 582
@@ -575,8 +585,12 @@ later is required to fix a server side protocol bug.
575 (username, password), 585 (username, password),
576 1) 586 1)
577 587
588 transport = PersistentTransport(manifest_server)
589 if manifest_server.startswith('persistent-'):
590 manifest_server = manifest_server[len('persistent-'):]
591
578 try: 592 try:
579 server = xmlrpc.client.Server(manifest_server) 593 server = xmlrpc.client.Server(manifest_server, transport=transport)
580 if opt.smart_sync: 594 if opt.smart_sync:
581 p = self.manifest.manifestProject 595 p = self.manifest.manifestProject
582 b = p.GetBranch(p.CurrentBranch) 596 b = p.GetBranch(p.CurrentBranch)
@@ -656,6 +670,42 @@ later is required to fix a server side protocol bug.
656 self._ReloadManifest(manifest_name) 670 self._ReloadManifest(manifest_name)
657 if opt.jobs is None: 671 if opt.jobs is None:
658 self.jobs = self.manifest.default.sync_j 672 self.jobs = self.manifest.default.sync_j
673
674 if self.gitc_manifest:
675 gitc_manifest_projects = self.GetProjects(args,
676 missing_ok=True)
677 gitc_projects = []
678 opened_projects = []
679 for project in gitc_manifest_projects:
680 if project.relpath in self.gitc_manifest.paths and \
681 self.gitc_manifest.paths[project.relpath].old_revision:
682 opened_projects.append(project.relpath)
683 else:
684 gitc_projects.append(project.relpath)
685
686 if not args:
687 gitc_projects = None
688
689 if gitc_projects != [] and not opt.local_only:
690 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
691 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
692 if manifest_name:
693 manifest.Override(manifest_name)
694 else:
695 manifest.Override(self.manifest.manifestFile)
696 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
697 manifest,
698 gitc_projects)
699 print('GITC client successfully synced.')
700
701 # The opened projects need to be synced as normal, therefore we
702 # generate a new args list to represent the opened projects.
703 # TODO: make this more reliable -- if there's a project name/path overlap,
704 # this may choose the wrong project.
705 args = [os.path.relpath(self.manifest.paths[p].worktree, os.getcwd())
706 for p in opened_projects]
707 if not args:
708 return
659 all_projects = self.GetProjects(args, 709 all_projects = self.GetProjects(args,
660 missing_ok=True, 710 missing_ok=True,
661 submodules_ok=opt.fetch_submodules) 711 submodules_ok=opt.fetch_submodules)
@@ -850,3 +900,96 @@ class _FetchTimes(object):
850 os.remove(self._path) 900 os.remove(self._path)
851 except OSError: 901 except OSError:
852 pass 902 pass
903
904# This is a replacement for xmlrpc.client.Transport using urllib2
905# and supporting persistent-http[s]. It cannot change hosts from
906# request to request like the normal transport, the real url
907# is passed during initialization.
908class PersistentTransport(xmlrpc.client.Transport):
909 def __init__(self, orig_host):
910 self.orig_host = orig_host
911
912 def request(self, host, handler, request_body, verbose=False):
913 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
914 # Python doesn't understand cookies with the #HttpOnly_ prefix
915 # Since we're only using them for HTTP, copy the file temporarily,
916 # stripping those prefixes away.
917 if cookiefile:
918 tmpcookiefile = tempfile.NamedTemporaryFile()
919 try:
920 with open(cookiefile) as f:
921 for line in f:
922 if line.startswith("#HttpOnly_"):
923 line = line[len("#HttpOnly_"):]
924 tmpcookiefile.write(line)
925 tmpcookiefile.flush()
926
927 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
928 cookiejar.load()
929 finally:
930 tmpcookiefile.close()
931 else:
932 cookiejar = cookielib.CookieJar()
933
934 proxyhandler = urllib.request.ProxyHandler
935 if proxy:
936 proxyhandler = urllib.request.ProxyHandler({
937 "http": proxy,
938 "https": proxy })
939
940 opener = urllib.request.build_opener(
941 urllib.request.HTTPCookieProcessor(cookiejar),
942 proxyhandler)
943
944 url = urllib.parse.urljoin(self.orig_host, handler)
945 parse_results = urllib.parse.urlparse(url)
946
947 scheme = parse_results.scheme
948 if scheme == 'persistent-http':
949 scheme = 'http'
950 if scheme == 'persistent-https':
951 # If we're proxying through persistent-https, use http. The
952 # proxy itself will do the https.
953 if proxy:
954 scheme = 'http'
955 else:
956 scheme = 'https'
957
958 # Parse out any authentication information using the base class
959 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
960
961 url = urllib.parse.urlunparse((
962 scheme,
963 host,
964 parse_results.path,
965 parse_results.params,
966 parse_results.query,
967 parse_results.fragment))
968
969 request = urllib.request.Request(url, request_body)
970 if extra_headers is not None:
971 for (name, header) in extra_headers:
972 request.add_header(name, header)
973 request.add_header('Content-Type', 'text/xml')
974 try:
975 response = opener.open(request)
976 except urllib.error.HTTPError as e:
977 if e.code == 501:
978 # We may have been redirected through a login process
979 # but our POST turned into a GET. Retry.
980 response = opener.open(request)
981 else:
982 raise
983
984 p, u = xmlrpc.client.getparser()
985 while 1:
986 data = response.read(1024)
987 if not data:
988 break
989 p.feed(data)
990 p.close()
991 return u.close()
992
993 def close(self):
994 pass
995