blob: 0ac308e6951ad75c02adec819c6229d8393c22cc [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Anthony King85b24ac2014-05-06 15:57:48 +010018import json
David Pursehouse86d973d2012-08-24 10:21:02 +090019import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070020from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import os
22import re
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070023import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import subprocess
25import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
David Pursehouse59bbb582013-05-17 10:49:33 +090028
29from pyversion import is_python3
30if is_python3():
Dan Willemsen0745bb22015-08-17 13:41:45 -070031 import http.cookiejar as cookielib
32 import urllib.error
Chirayu Desai217ea7d2013-03-01 19:14:38 +053033 import urllib.parse
Dan Willemsen0745bb22015-08-17 13:41:45 -070034 import urllib.request
David Pursehouse59bbb582013-05-17 10:49:33 +090035 import xmlrpc.client
36else:
Dan Willemsen0745bb22015-08-17 13:41:45 -070037 import cookielib
Chirayu Desai217ea7d2013-03-01 19:14:38 +053038 import imp
Dan Willemsen0745bb22015-08-17 13:41:45 -070039 import urllib2
Chirayu Desai217ea7d2013-03-01 19:14:38 +053040 import urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090041 import xmlrpclib
Chirayu Desai217ea7d2013-03-01 19:14:38 +053042 urllib = imp.new_module('urllib')
Dan Willemsen0745bb22015-08-17 13:41:45 -070043 urllib.error = urllib2
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +053044 urllib.parse = urlparse
Dan Willemsen0745bb22015-08-17 13:41:45 -070045 urllib.request = urllib2
Chirayu Desai217ea7d2013-03-01 19:14:38 +053046 xmlrpc = imp.new_module('xmlrpc')
47 xmlrpc.client = xmlrpclib
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048
Roy Lee18afd7f2010-05-09 04:32:08 +080049try:
50 import threading as _threading
51except ImportError:
52 import dummy_threading as _threading
53
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070054try:
55 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090056
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070057 def _rlimit_nofile():
58 return resource.getrlimit(resource.RLIMIT_NOFILE)
59except ImportError:
60 def _rlimit_nofile():
61 return (256, 256)
62
Dave Borowitz18857212012-10-23 17:02:59 -070063try:
64 import multiprocessing
65except ImportError:
66 multiprocessing = None
67
David Rileye0684ad2017-04-05 00:02:59 -070068import event_log
Dave Borowitze2152672012-10-31 12:24:38 -070069from git_command import GIT, git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090070from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090071from git_refs import R_HEADS, HEAD
Simran Basibdb52712015-08-10 13:23:23 -070072import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070073from project import Project
74from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080075from command import Command, MirrorSafeCommand
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +000076from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070077import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070078from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070079from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080080from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070081from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082
Dave Borowitz67700e92012-10-23 15:00:54 -070083_ONE_DAY_S = 24 * 60 * 60
84
David Pursehouse819827a2020-02-12 15:20:19 +090085
Doug Andersonfc06ced2011-03-16 15:49:18 -070086class _FetchError(Exception):
87 """Internal error thrown in _FetchHelper() when we don't want stack trace."""
88 pass
89
David Pursehouse819827a2020-02-12 15:20:19 +090090
Xin Li745be2e2019-06-03 11:24:30 -070091class _CheckoutError(Exception):
92 """Internal error thrown in _CheckoutOne() when we don't want stack trace."""
93
David Pursehouse819827a2020-02-12 15:20:19 +090094
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080095class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080096 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070097 common = True
98 helpSummary = "Update working tree to the latest revision"
99 helpUsage = """
100%prog [<project>...]
101"""
102 helpDescription = """
103The '%prog' command synchronizes local project directories
104with the remote repositories specified in the manifest. If a local
105project does not yet exist, it will clone a new local directory from
106the remote repository and set up tracking branches as specified in
107the manifest. If the local project already exists, '%prog'
108will update the remote branches and rebase any new local changes
109on top of the new remote changes.
110
111'%prog' will synchronize all projects listed at the command
112line. Projects can be specified either by name, or by a relative
113or absolute path to the project's local directory. If no projects
114are specified, '%prog' will synchronize all projects listed in
115the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700116
117The -d/--detach option can be used to switch specified projects
118back to the manifest revision. This option is especially helpful
119if the project is currently on a topic branch, but the manifest
120revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700121
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700122The -s/--smart-sync option can be used to sync to a known good
123build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200124manifest. The -t/--smart-tag option is similar and allows you to
125specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700126
David Pursehousecf76b1b2012-09-14 10:31:42 +0900127The -u/--manifest-server-username and -p/--manifest-server-password
128options can be used to specify a username and password to authenticate
129with the manifest server when using the -s or -t option.
130
131If -u and -p are not specified when using the -s or -t option, '%prog'
132will attempt to read authentication credentials for the manifest server
133from the user's .netrc file.
134
135'%prog' will not use authentication credentials from -u/-p or .netrc
136if the manifest server specified in the manifest file already includes
137credentials.
138
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400139By default, all projects will be synced. The --fail-fast option can be used
140to halt syncing as soon as possible when the the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500141
Kevin Degiabaa7f32014-11-12 11:27:45 -0700142The --force-sync option can be used to overwrite existing git
143directories if they have previously been linked to a different
144object direcotry. WARNING: This may cause data to be lost since
145refs may be removed when overwriting.
146
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500147The --force-remove-dirty option can be used to remove previously used
148projects with uncommitted changes. WARNING: This may cause data to be
149lost since uncommitted changes may be removed with projects that no longer
150exist in the manifest.
151
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700152The --no-clone-bundle option disables any attempt to use
153$URL/clone.bundle to bootstrap a new Git repository from a
154resumeable bundle file on a content delivery network. This
155may be necessary if there are problems with the local Python
156HTTP client or proxy configuration, but the Git binary works.
157
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800158The --fetch-submodules option enables fetching Git submodules
159of a project from server.
160
David Pursehousef2fad612015-01-29 14:36:28 +0900161The -c/--current-branch option can be used to only fetch objects that
162are on the branch specified by a project's revision.
163
David Pursehouseb1553542014-09-04 21:28:09 +0900164The --optimized-fetch option can be used to only fetch projects that
165are fixed to a sha1 revision if the sha1 revision does not already
166exist locally.
167
David Pursehouse74cfd272015-10-14 10:50:15 +0900168The --prune option can be used to remove any refs that no longer
169exist on the remote.
170
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400171# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700172
173If at least one project remote URL uses an SSH connection (ssh://,
174git+ssh://, or user@host:path syntax) repo will automatically
175enable the SSH ControlMaster option when connecting to that host.
176This feature permits other projects in the same '%prog' session to
177reuse the same SSH tunnel, saving connection setup overheads.
178
179To disable this behavior on UNIX platforms, set the GIT_SSH
180environment variable to 'ssh'. For example:
181
182 export GIT_SSH=ssh
183 %prog
184
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400185# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700186
187This feature is automatically disabled on Windows, due to the lack
188of UNIX domain socket support.
189
190This feature is not compatible with url.insteadof rewrites in the
191user's ~/.gitconfig. '%prog' is currently not able to perform the
192rewrite early enough to establish the ControlMaster tunnel.
193
194If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
195later is required to fix a server side protocol bug.
196
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197"""
198
Nico Sallembien6623b212010-05-11 12:57:01 -0700199 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000200 try:
201 self.jobs = self.manifest.default.sync_j
202 except ManifestParseError:
203 self.jobs = 1
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700204
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500205 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200206 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400207 help='obsolete option (to be deleted in the future)')
208 p.add_option('--fail-fast',
209 dest='fail_fast', action='store_true',
210 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700211 p.add_option('--force-sync',
212 dest='force_sync', action='store_true',
213 help="overwrite an existing git directory if it needs to "
214 "point to a different object directory. WARNING: this "
215 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500216 p.add_option('--force-remove-dirty',
217 dest='force_remove_dirty', action='store_true',
218 help="force remove projects with uncommitted modifications if "
219 "projects no longer exist in the manifest. "
220 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900221 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700222 dest='local_only', action='store_true',
223 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900224 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100225 dest='mp_update', action='store_false', default='true',
226 help='use the existing manifest checkout as-is. '
227 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900228 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700229 dest='network_only', action='store_true',
230 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900231 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700232 dest='detach_head', action='store_true',
233 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900234 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700235 dest='current_branch_only', action='store_true',
236 help='fetch only current branch from server')
Mike Frysinger521d01b2020-02-17 01:51:49 -0500237 p.add_option('-v', '--verbose',
238 dest='output_mode', action='store_true',
239 help='show all sync output')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900240 p.add_option('-q', '--quiet',
Mike Frysinger521d01b2020-02-17 01:51:49 -0500241 dest='output_mode', action='store_false',
242 help='only show errors')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900243 p.add_option('-j', '--jobs',
Roy Lee18afd7f2010-05-09 04:32:08 +0800244 dest='jobs', action='store', type='int',
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700245 help="projects to fetch simultaneously (default %d)" % self.jobs)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500246 p.add_option('-m', '--manifest-name',
247 dest='manifest_name',
248 help='temporary manifest to use for this sync', metavar='NAME.xml')
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700249 p.add_option('--no-clone-bundle',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500250 dest='clone_bundle', default=True, action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700251 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800252 p.add_option('-u', '--manifest-server-username', action='store',
253 dest='manifest_server_username',
254 help='username to authenticate with the manifest server')
255 p.add_option('-p', '--manifest-server-password', action='store',
256 dest='manifest_server_password',
257 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800258 p.add_option('--fetch-submodules',
259 dest='fetch_submodules', action='store_true',
260 help='fetch submodules from server')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700261 p.add_option('--no-tags',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500262 dest='tags', default=True, action='store_false',
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700263 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900264 p.add_option('--optimized-fetch',
265 dest='optimized_fetch', action='store_true',
266 help='only fetch projects fixed to sha1 if revision does not exist locally')
David Pursehouse74cfd272015-10-14 10:50:15 +0900267 p.add_option('--prune', dest='prune', action='store_true',
268 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700269 if show_smart:
270 p.add_option('-s', '--smart-sync',
271 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900272 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200273 p.add_option('-t', '--smart-tag',
274 dest='smart_tag', action='store',
275 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700276
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700277 g = p.add_option_group('repo Version options')
278 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500279 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700280 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700281 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800282 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700283 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700284
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500285 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
Xin Li745be2e2019-06-03 11:24:30 -0700286 """Main function of the fetch threads.
Roy Lee18afd7f2010-05-09 04:32:08 +0800287
David James8d201162013-10-11 17:03:19 -0700288 Delegates most of the work to _FetchHelper.
289
290 Args:
291 opt: Program options returned from optparse. See _Options().
292 projects: Projects to fetch.
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500293 sem: We'll release() this semaphore when we exit so that another thread
294 can be started up.
David James89ece422014-01-09 18:51:58 -0800295 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
David James8d201162013-10-11 17:03:19 -0700296 _FetchHelper docstring for details.
297 """
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500298 try:
299 for project in projects:
300 success = self._FetchHelper(opt, project, *args, **kwargs)
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400301 if not success and opt.fail_fast:
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500302 break
303 finally:
304 sem.release()
David James8d201162013-10-11 17:03:19 -0700305
Xin Li745be2e2019-06-03 11:24:30 -0700306 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event,
307 clone_filter):
David James8d201162013-10-11 17:03:19 -0700308 """Fetch git objects for a single project.
309
David Pursehousec1b86a22012-11-14 11:36:51 +0900310 Args:
311 opt: Program options returned from optparse. See _Options().
312 project: Project object for the project to fetch.
313 lock: Lock for accessing objects that are shared amongst multiple
314 _FetchHelper() threads.
315 fetched: set object that we will add project.gitdir to when we're done
316 (with our lock held).
317 pm: Instance of a Project object. We will call pm.update() (with our
318 lock held).
David Pursehousec1b86a22012-11-14 11:36:51 +0900319 err_event: We'll set this event in the case of an error (after printing
320 out info about the error).
Xin Li745be2e2019-06-03 11:24:30 -0700321 clone_filter: Filter for use in a partial clone.
David James8d201162013-10-11 17:03:19 -0700322
323 Returns:
324 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900325 """
326 # We'll set to true once we've locked the lock.
327 did_lock = False
Doug Andersonfc06ced2011-03-16 15:49:18 -0700328
David Pursehousec1b86a22012-11-14 11:36:51 +0900329 # Encapsulate everything in a try/except/finally so that:
330 # - We always set err_event in the case of an exception.
David Pursehousec1b86a22012-11-14 11:36:51 +0900331 # - We always make sure we unlock the lock if we locked it.
David Rileye0684ad2017-04-05 00:02:59 -0700332 start = time.time()
333 success = False
David Pursehousec1b86a22012-11-14 11:36:51 +0900334 try:
Doug Andersonfc06ced2011-03-16 15:49:18 -0700335 try:
David Pursehousec1b86a22012-11-14 11:36:51 +0900336 success = project.Sync_NetworkHalf(
David Pursehouseabdf7502020-02-12 14:58:39 +0900337 quiet=opt.quiet,
Mike Frysinger521d01b2020-02-17 01:51:49 -0500338 verbose=opt.verbose,
David Pursehouseabdf7502020-02-12 14:58:39 +0900339 current_branch_only=opt.current_branch_only,
340 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500341 clone_bundle=opt.clone_bundle,
342 tags=opt.tags, archive=self.manifest.IsArchive,
David Pursehouseabdf7502020-02-12 14:58:39 +0900343 optimized_fetch=opt.optimized_fetch,
344 prune=opt.prune,
345 clone_filter=clone_filter)
David Pursehousec1b86a22012-11-14 11:36:51 +0900346 self._fetch_times.Set(project, time.time() - start)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700347
David Pursehousec1b86a22012-11-14 11:36:51 +0900348 # Lock around all the rest of the code, since printing, updating a set
349 # and Progress.update() are not thread safe.
350 lock.acquire()
351 did_lock = True
Doug Andersonfc06ced2011-03-16 15:49:18 -0700352
David Pursehousec1b86a22012-11-14 11:36:51 +0900353 if not success:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800354 err_event.set()
Marc Herbertffb4b892017-04-04 22:03:53 -0700355 print('error: Cannot fetch %s from %s'
356 % (project.name, project.remote.url),
357 file=sys.stderr)
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400358 if opt.fail_fast:
David Pursehousec1b86a22012-11-14 11:36:51 +0900359 raise _FetchError()
Doug Andersonfc06ced2011-03-16 15:49:18 -0700360
David Pursehousec1b86a22012-11-14 11:36:51 +0900361 fetched.add(project.gitdir)
Mike Frysinger3538dd22019-08-26 15:32:06 -0400362 pm.update(msg=project.name)
David Pursehousec1b86a22012-11-14 11:36:51 +0900363 except _FetchError:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800364 pass
Dan Sandlerc5cd4332015-07-31 09:37:53 -0400365 except Exception as e:
David Pursehouse42339d72020-02-12 14:37:15 +0900366 print('error: Cannot fetch %s (%s: %s)'
David Pursehouseabdf7502020-02-12 14:58:39 +0900367 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
David Pursehousec1b86a22012-11-14 11:36:51 +0900368 err_event.set()
369 raise
370 finally:
371 if did_lock:
372 lock.release()
David Rileye0684ad2017-04-05 00:02:59 -0700373 finish = time.time()
374 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
375 start, finish, success)
Roy Lee18afd7f2010-05-09 04:32:08 +0800376
David James8d201162013-10-11 17:03:19 -0700377 return success
378
Mike Frysinger5a033082019-09-23 19:21:20 -0400379 def _Fetch(self, projects, opt, err_event):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700380 fetched = set()
David James89ece422014-01-09 18:51:58 -0800381 lock = _threading.Lock()
Tim Schumacher913327f2017-06-05 15:01:41 +0200382 pm = Progress('Fetching projects', len(projects),
Tim Schumacher7be072e2017-06-28 18:29:23 +0200383 always_print_percentage=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800384
David James89ece422014-01-09 18:51:58 -0800385 objdir_project_map = dict()
386 for project in projects:
387 objdir_project_map.setdefault(project.objdir, []).append(project)
David James8d201162013-10-11 17:03:19 -0700388
David James89ece422014-01-09 18:51:58 -0800389 threads = set()
390 sem = _threading.Semaphore(self.jobs)
David James89ece422014-01-09 18:51:58 -0800391 for project_list in objdir_project_map.values():
392 # Check for any errors before running any more tasks.
393 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400394 if err_event.isSet() and opt.fail_fast:
David James89ece422014-01-09 18:51:58 -0800395 break
Doug Andersonfc06ced2011-03-16 15:49:18 -0700396
David James89ece422014-01-09 18:51:58 -0800397 sem.acquire()
398 kwargs = dict(opt=opt,
399 projects=project_list,
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500400 sem=sem,
David James89ece422014-01-09 18:51:58 -0800401 lock=lock,
402 fetched=fetched,
403 pm=pm,
Xin Li745be2e2019-06-03 11:24:30 -0700404 err_event=err_event,
405 clone_filter=self.manifest.CloneFilter)
David James89ece422014-01-09 18:51:58 -0800406 if self.jobs > 1:
David Pursehousee5913ae2020-02-12 13:56:59 +0900407 t = _threading.Thread(target=self._FetchProjectList,
408 kwargs=kwargs)
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200409 # Ensure that Ctrl-C will not freeze the repo process.
410 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800411 threads.add(t)
412 t.start()
David James89ece422014-01-09 18:51:58 -0800413 else:
414 self._FetchProjectList(**kwargs)
Roy Lee18afd7f2010-05-09 04:32:08 +0800415
David James89ece422014-01-09 18:51:58 -0800416 for t in threads:
417 t.join()
Roy Lee18afd7f2010-05-09 04:32:08 +0800418
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700419 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700420 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700421
Julien Campergue335f5ef2013-10-16 11:02:35 +0200422 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400423 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200424
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700425 return fetched
426
Xin Li745be2e2019-06-03 11:24:30 -0700427 def _CheckoutWorker(self, opt, sem, project, *args, **kwargs):
428 """Main function of the fetch threads.
429
430 Delegates most of the work to _CheckoutOne.
431
432 Args:
433 opt: Program options returned from optparse. See _Options().
434 projects: Projects to fetch.
435 sem: We'll release() this semaphore when we exit so that another thread
436 can be started up.
437 *args, **kwargs: Remaining arguments to pass to _CheckoutOne. See the
438 _CheckoutOne docstring for details.
439 """
440 try:
Mike Frysingera34186e2019-08-07 18:07:31 -0400441 return self._CheckoutOne(opt, project, *args, **kwargs)
Xin Li745be2e2019-06-03 11:24:30 -0700442 finally:
443 sem.release()
444
Vadim Bendeburydff91942019-11-06 11:05:00 -0800445 def _CheckoutOne(self, opt, project, lock, pm, err_event, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700446 """Checkout work tree for one project
447
448 Args:
449 opt: Program options returned from optparse. See _Options().
450 project: Project object for the project to checkout.
451 lock: Lock for accessing objects that are shared amongst multiple
452 _CheckoutWorker() threads.
453 pm: Instance of a Project object. We will call pm.update() (with our
454 lock held).
455 err_event: We'll set this event in the case of an error (after printing
456 out info about the error).
Vadim Bendeburydff91942019-11-06 11:05:00 -0800457 err_results: A list of strings, paths to git repos where checkout
458 failed.
Xin Li745be2e2019-06-03 11:24:30 -0700459
460 Returns:
461 Whether the fetch was successful.
462 """
463 # We'll set to true once we've locked the lock.
464 did_lock = False
465
Xin Li745be2e2019-06-03 11:24:30 -0700466 # Encapsulate everything in a try/except/finally so that:
467 # - We always set err_event in the case of an exception.
468 # - We always make sure we unlock the lock if we locked it.
469 start = time.time()
470 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
471 detach_head=opt.detach_head)
472 success = False
473 try:
474 try:
475 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
Xin Li745be2e2019-06-03 11:24:30 -0700476
477 # Lock around all the rest of the code, since printing, updating a set
478 # and Progress.update() are not thread safe.
479 lock.acquire()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400480 success = syncbuf.Finish()
Xin Li745be2e2019-06-03 11:24:30 -0700481 did_lock = True
482
483 if not success:
484 err_event.set()
485 print('error: Cannot checkout %s' % (project.name),
486 file=sys.stderr)
487 raise _CheckoutError()
488
Mike Frysinger3538dd22019-08-26 15:32:06 -0400489 pm.update(msg=project.name)
Xin Li745be2e2019-06-03 11:24:30 -0700490 except _CheckoutError:
491 pass
492 except Exception as e:
493 print('error: Cannot checkout %s: %s: %s' %
494 (project.name, type(e).__name__, str(e)),
495 file=sys.stderr)
496 err_event.set()
497 raise
498 finally:
499 if did_lock:
Vadim Bendeburydff91942019-11-06 11:05:00 -0800500 if not success:
501 err_results.append(project.relpath)
Xin Li745be2e2019-06-03 11:24:30 -0700502 lock.release()
503 finish = time.time()
504 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
505 start, finish, success)
506
507 return success
508
Mike Frysinger5a033082019-09-23 19:21:20 -0400509 def _Checkout(self, all_projects, opt, err_event, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700510 """Checkout projects listed in all_projects
511
512 Args:
513 all_projects: List of all projects that should be checked out.
514 opt: Program options returned from optparse. See _Options().
Mike Frysinger5a033082019-09-23 19:21:20 -0400515 err_event: We'll set this event in the case of an error (after printing
516 out info about the error).
517 err_results: A list of strings, paths to git repos where checkout
518 failed.
Xin Li745be2e2019-06-03 11:24:30 -0700519 """
520
521 # Perform checkouts in multiple threads when we are using partial clone.
522 # Without partial clone, all needed git objects are already downloaded,
523 # in this situation it's better to use only one process because the checkout
524 # would be mostly disk I/O; with partial clone, the objects are only
525 # downloaded when demanded (at checkout time), which is similar to the
526 # Sync_NetworkHalf case and parallelism would be helpful.
527 if self.manifest.CloneFilter:
528 syncjobs = self.jobs
529 else:
530 syncjobs = 1
531
532 lock = _threading.Lock()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400533 pm = Progress('Checking out projects', len(all_projects))
Xin Li745be2e2019-06-03 11:24:30 -0700534
535 threads = set()
536 sem = _threading.Semaphore(syncjobs)
Xin Li745be2e2019-06-03 11:24:30 -0700537
538 for project in all_projects:
539 # Check for any errors before running any more tasks.
540 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400541 if err_event.isSet() and opt.fail_fast:
Xin Li745be2e2019-06-03 11:24:30 -0700542 break
543
544 sem.acquire()
545 if project.worktree:
546 kwargs = dict(opt=opt,
547 sem=sem,
548 project=project,
549 lock=lock,
550 pm=pm,
Vadim Bendeburydff91942019-11-06 11:05:00 -0800551 err_event=err_event,
552 err_results=err_results)
Xin Li745be2e2019-06-03 11:24:30 -0700553 if syncjobs > 1:
554 t = _threading.Thread(target=self._CheckoutWorker,
555 kwargs=kwargs)
556 # Ensure that Ctrl-C will not freeze the repo process.
557 t.daemon = True
558 threads.add(t)
559 t.start()
560 else:
561 self._CheckoutWorker(**kwargs)
562
563 for t in threads:
564 t.join()
565
566 pm.end()
Xin Li745be2e2019-06-03 11:24:30 -0700567
Mike Frysinger5a033082019-09-23 19:21:20 -0400568 def _GCProjects(self, projects, opt, err_event):
Gabe Black2ff30292014-10-09 17:54:35 -0700569 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700570 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500571 # Make sure pruning never kicks in with shared projects.
Gabe Black2ff30292014-10-09 17:54:35 -0700572 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500573 print('%s: Shared project %s found, disabling pruning.' %
574 (project.relpath, project.name))
575 if git_require((2, 7, 0)):
576 project.config.SetString('core.repositoryFormatVersion', '1')
577 project.config.SetString('extensions.preciousObjects', 'true')
578 else:
579 # This isn't perfect, but it's the best we can do with old git.
580 print('%s: WARNING: shared projects are unreliable when using old '
581 'versions of git; please upgrade to git-2.7.0+.'
582 % (project.relpath,),
583 file=sys.stderr)
584 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700585 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700586
Mike Frysinger6f1c6262020-02-04 00:09:23 -0500587 if multiprocessing:
Dave Borowitz18857212012-10-23 17:02:59 -0700588 cpu_count = multiprocessing.cpu_count()
589 else:
590 cpu_count = 1
591 jobs = min(self.jobs, cpu_count)
592
593 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700594 for bare_git in gc_gitdirs.values():
David James8d201162013-10-11 17:03:19 -0700595 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700596 return
597
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400598 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700599
600 threads = set()
601 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700602
David James8d201162013-10-11 17:03:19 -0700603 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700604 try:
605 try:
David James8d201162013-10-11 17:03:19 -0700606 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700607 except GitError:
608 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900609 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700610 err_event.set()
611 raise
612 finally:
613 sem.release()
614
Gabe Black2ff30292014-10-09 17:54:35 -0700615 for bare_git in gc_gitdirs.values():
Mike Frysinger5a033082019-09-23 19:21:20 -0400616 if err_event.isSet() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700617 break
618 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700619 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700620 t.daemon = True
621 threads.add(t)
622 t.start()
623
624 for t in threads:
625 t.join()
626
Tim Kilbourn07669002013-03-08 15:02:49 -0800627 def _ReloadManifest(self, manifest_name=None):
628 if manifest_name:
629 # Override calls _Unload already
630 self.manifest.Override(manifest_name)
631 else:
632 self.manifest._Unload()
633
Dan Willemsen43507912016-09-01 16:26:02 -0700634 def _DeleteProject(self, path):
635 print('Deleting obsolete path %s' % path, file=sys.stderr)
636
637 # Delete the .git directory first, so we're less likely to have a partially
638 # working git repository around. There shouldn't be any git projects here,
639 # so rmtree works.
640 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -0700641 platform_utils.rmtree(os.path.join(path, '.git'))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700642 except OSError as e:
643 print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700644 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
645 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400646 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700647
648 # Delete everything under the worktree, except for directories that contain
649 # another git project
650 dirs_to_remove = []
651 failed = False
Renaud Paquaybed8b622018-09-27 10:46:58 -0700652 for root, dirs, files in platform_utils.walk(path):
Dan Willemsen43507912016-09-01 16:26:02 -0700653 for f in files:
654 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800655 platform_utils.remove(os.path.join(root, f))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700656 except OSError as e:
657 print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700658 failed = True
659 dirs[:] = [d for d in dirs
660 if not os.path.lexists(os.path.join(root, d, '.git'))]
661 dirs_to_remove += [os.path.join(root, d) for d in dirs
662 if os.path.join(root, d) not in dirs_to_remove]
663 for d in reversed(dirs_to_remove):
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700664 if platform_utils.islink(d):
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700665 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800666 platform_utils.remove(d)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700667 except OSError as e:
668 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700669 failed = True
Renaud Paquaybed8b622018-09-27 10:46:58 -0700670 elif len(platform_utils.listdir(d)) == 0:
Dan Willemsen43507912016-09-01 16:26:02 -0700671 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700672 platform_utils.rmdir(d)
673 except OSError as e:
674 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700675 failed = True
676 continue
677 if failed:
678 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
679 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400680 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700681
682 # Try deleting parent dirs if they are empty
683 project_dir = path
684 while project_dir != self.manifest.topdir:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700685 if len(platform_utils.listdir(project_dir)) == 0:
686 platform_utils.rmdir(project_dir)
Dan Willemsen43507912016-09-01 16:26:02 -0700687 else:
688 break
689 project_dir = os.path.dirname(project_dir)
690
691 return 0
692
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500693 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700694 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700695 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700696 if project.relpath:
697 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700698 file_name = 'project.list'
699 file_path = os.path.join(self.manifest.repodir, file_name)
700 old_project_paths = []
701
702 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500703 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700704 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800705 # In reversed order, so subfolders are deleted before parent folder.
706 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700707 if not path:
708 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700709 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900710 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700711 gitdir = os.path.join(self.manifest.topdir, path, '.git')
712 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900713 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900714 manifest=self.manifest,
715 name=path,
716 remote=RemoteSpec('origin'),
717 gitdir=gitdir,
718 objdir=gitdir,
719 worktree=os.path.join(self.manifest.topdir, path),
720 relpath=path,
721 revisionExpr='HEAD',
722 revisionId=None,
723 groups=None)
Anthonyf3fdf822009-09-26 13:38:52 -0400724
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500725 if project.IsDirty() and opt.force_remove_dirty:
726 print('WARNING: Removing dirty project "%s": uncommitted changes '
727 'erased' % project.relpath, file=sys.stderr)
728 self._DeleteProject(project.worktree)
729 elif project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900730 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900731 'are present' % project.relpath, file=sys.stderr)
732 print(' commit changes, then run sync again',
733 file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400734 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700735 elif self._DeleteProject(project.worktree):
Mike Frysingera850ca22019-08-07 17:19:24 -0400736 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700737
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700738 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500739 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700740 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700741 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700742 return 0
743
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400744 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
745 if not self.manifest.manifest_server:
746 print('error: cannot smart sync: no manifest server defined in '
747 'manifest', file=sys.stderr)
748 sys.exit(1)
749
750 manifest_server = self.manifest.manifest_server
751 if not opt.quiet:
752 print('Using manifest server %s' % manifest_server)
753
David Pursehouseeeff3532020-02-12 11:24:10 +0900754 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400755 username = None
756 password = None
757 if opt.manifest_server_username and opt.manifest_server_password:
758 username = opt.manifest_server_username
759 password = opt.manifest_server_password
760 else:
761 try:
762 info = netrc.netrc()
763 except IOError:
764 # .netrc file does not exist or could not be opened
765 pass
766 else:
767 try:
768 parse_result = urllib.parse.urlparse(manifest_server)
769 if parse_result.hostname:
770 auth = info.authenticators(parse_result.hostname)
771 if auth:
772 username, _account, password = auth
773 else:
774 print('No credentials found for %s in .netrc'
775 % parse_result.hostname, file=sys.stderr)
776 except netrc.NetrcParseError as e:
777 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
778
779 if (username and password):
780 manifest_server = manifest_server.replace('://', '://%s:%s@' %
781 (username, password),
782 1)
783
784 transport = PersistentTransport(manifest_server)
785 if manifest_server.startswith('persistent-'):
786 manifest_server = manifest_server[len('persistent-'):]
787
788 try:
789 server = xmlrpc.client.Server(manifest_server, transport=transport)
790 if opt.smart_sync:
791 p = self.manifest.manifestProject
792 b = p.GetBranch(p.CurrentBranch)
793 branch = b.merge
794 if branch.startswith(R_HEADS):
795 branch = branch[len(R_HEADS):]
796
Mike Frysinger56ce3462019-12-04 19:30:48 -0500797 if 'SYNC_TARGET' in os.environ:
798 target = os.environ('SYNC_TARGET')
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400799 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500800 elif ('TARGET_PRODUCT' in os.environ and
801 'TARGET_BUILD_VARIANT' in os.environ):
802 target = '%s-%s' % (os.environ('TARGET_PRODUCT'),
803 os.environ('TARGET_BUILD_VARIANT'))
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400804 [success, manifest_str] = server.GetApprovedManifest(branch, target)
805 else:
806 [success, manifest_str] = server.GetApprovedManifest(branch)
807 else:
808 assert(opt.smart_tag)
809 [success, manifest_str] = server.GetManifest(opt.smart_tag)
810
811 if success:
812 manifest_name = os.path.basename(smart_sync_manifest_path)
813 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500814 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400815 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400816 except IOError as e:
817 print('error: cannot write manifest to %s:\n%s'
818 % (smart_sync_manifest_path, e),
819 file=sys.stderr)
820 sys.exit(1)
821 self._ReloadManifest(manifest_name)
822 else:
823 print('error: manifest server RPC call failed: %s' %
824 manifest_str, file=sys.stderr)
825 sys.exit(1)
826 except (socket.error, IOError, xmlrpc.client.Fault) as e:
827 print('error: cannot connect to manifest server %s:\n%s'
828 % (self.manifest.manifest_server, e), file=sys.stderr)
829 sys.exit(1)
830 except xmlrpc.client.ProtocolError as e:
831 print('error: cannot connect to manifest server %s:\n%d %s'
832 % (self.manifest.manifest_server, e.errcode, e.errmsg),
833 file=sys.stderr)
834 sys.exit(1)
835
836 return manifest_name
837
Mike Frysingerfb527e32019-08-27 02:34:32 -0400838 def _UpdateManifestProject(self, opt, mp, manifest_name):
839 """Fetch & update the local manifest project."""
840 if not opt.local_only:
841 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500842 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400843 current_branch_only=opt.current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500844 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400845 optimized_fetch=opt.optimized_fetch,
846 submodules=self.manifest.HasSubmodules,
847 clone_filter=self.manifest.CloneFilter)
848 finish = time.time()
849 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
850 start, finish, success)
851
852 if mp.HasChanges:
853 syncbuf = SyncBuffer(mp.config)
854 start = time.time()
855 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
856 clean = syncbuf.Finish()
857 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
858 start, time.time(), clean)
859 if not clean:
860 sys.exit(1)
861 self._ReloadManifest(opt.manifest_name)
862 if opt.jobs is None:
863 self.jobs = self.manifest.default.sync_j
864
Mike Frysingerae6cb082019-08-27 01:10:59 -0400865 def ValidateOptions(self, opt, args):
866 if opt.force_broken:
867 print('warning: -f/--force-broken is now the default behavior, and the '
868 'options are deprecated', file=sys.stderr)
869 if opt.network_only and opt.detach_head:
870 self.OptionParser.error('cannot combine -n and -d')
871 if opt.network_only and opt.local_only:
872 self.OptionParser.error('cannot combine -n and -l')
873 if opt.manifest_name and opt.smart_sync:
874 self.OptionParser.error('cannot combine -m and -s')
875 if opt.manifest_name and opt.smart_tag:
876 self.OptionParser.error('cannot combine -m and -t')
877 if opt.manifest_server_username or opt.manifest_server_password:
878 if not (opt.smart_sync or opt.smart_tag):
879 self.OptionParser.error('-u and -p may only be combined with -s or -t')
880 if None in [opt.manifest_server_username, opt.manifest_server_password]:
881 self.OptionParser.error('both -u and -p must be given')
882
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700883 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800884 if opt.jobs:
885 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700886 if self.jobs > 1:
887 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400888 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700889
Mike Frysinger521d01b2020-02-17 01:51:49 -0500890 opt.quiet = opt.output_mode is False
891 opt.verbose = opt.output_mode is True
892
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500893 if opt.manifest_name:
894 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700895
Chirayu Desaia892b102013-06-11 14:18:46 +0530896 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900897 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900898 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530899
Victor Boivie08c880d2011-04-19 10:32:52 +0200900 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400901 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
902 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900903 if os.path.isfile(smart_sync_manifest_path):
904 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800905 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900906 except OSError as e:
907 print('error: failed to remove existing smart sync override manifest: %s' %
908 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700909
Mike Frysinger5a033082019-09-23 19:21:20 -0400910 err_event = _threading.Event()
911
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 rp = self.manifest.repoProject
913 rp.PreSync()
914
915 mp = self.manifest.manifestProject
916 mp.PreSync()
917
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800918 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700919 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800920
Fredrik de Grootcc960972019-11-22 09:04:31 +0100921 if not opt.mp_update:
922 print('Skipping update of local manifest project.')
923 else:
924 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700925
Simran Basib9a1b732015-08-20 12:19:28 -0700926 if self.gitc_manifest:
927 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700928 missing_ok=True)
929 gitc_projects = []
930 opened_projects = []
931 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700932 if project.relpath in self.gitc_manifest.paths and \
933 self.gitc_manifest.paths[project.relpath].old_revision:
934 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700935 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700936 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700937
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700938 if not args:
939 gitc_projects = None
940
941 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700942 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700943 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
944 if manifest_name:
945 manifest.Override(manifest_name)
946 else:
947 manifest.Override(self.manifest.manifestFile)
948 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
949 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700950 gitc_projects)
951 print('GITC client successfully synced.')
952
953 # The opened projects need to be synced as normal, therefore we
954 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700955 # TODO: make this more reliable -- if there's a project name/path overlap,
956 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900957 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
958 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700959 if not args:
960 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800961 all_projects = self.GetProjects(args,
962 missing_ok=True,
963 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964
Mike Frysinger5a033082019-09-23 19:21:20 -0400965 err_network_sync = False
966 err_update_projects = False
967 err_checkout = False
968
Dave Borowitz67700e92012-10-23 15:00:54 -0700969 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700970 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700971 to_fetch = []
972 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700973 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700974 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900975 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700976 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700977
Mike Frysinger5a033082019-09-23 19:21:20 -0400978 fetched = self._Fetch(to_fetch, opt, err_event)
979
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500980 _PostRepoFetch(rp, opt.repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700981 if opt.network_only:
982 # bail out now; the rest touches the working tree
Mike Frysinger5a033082019-09-23 19:21:20 -0400983 if err_event.isSet():
984 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
985 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700986 return
987
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800988 # Iteratively fetch missing and/or nested unregistered submodules
989 previously_missing_set = set()
990 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100991 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800992 all_projects = self.GetProjects(args,
993 missing_ok=True,
994 submodules_ok=opt.fetch_submodules)
995 missing = []
996 for project in all_projects:
997 if project.gitdir not in fetched:
998 missing.append(project)
999 if not missing:
1000 break
1001 # Stop us from non-stopped fetching actually-missing repos: If set of
1002 # missing repos has not been changed from last fetch, we break.
1003 missing_set = set(p.name for p in missing)
1004 if previously_missing_set == missing_set:
1005 break
1006 previously_missing_set = missing_set
Mike Frysinger5a033082019-09-23 19:21:20 -04001007 fetched.update(self._Fetch(missing, opt, err_event))
1008
1009 # If we saw an error, exit with code 1 so that other scripts can check.
1010 if err_event.isSet():
1011 err_network_sync = True
1012 if opt.fail_fast:
1013 print('\nerror: Exited sync due to fetch errors.\n'
1014 'Local checkouts *not* updated. Resolve network issues & '
1015 'retry.\n'
1016 '`repo sync -l` will update some local checkouts.',
1017 file=sys.stderr)
1018 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001019
Julien Campergue335f5ef2013-10-16 11:02:35 +02001020 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001021 # bail out now, we have no working tree
1022 return
1023
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -05001024 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -04001025 err_event.set()
1026 err_update_projects = True
1027 if opt.fail_fast:
1028 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1029 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001030
Mike Frysinger5a033082019-09-23 19:21:20 -04001031 err_results = []
1032 self._Checkout(all_projects, opt, err_event, err_results)
1033 if err_event.isSet():
1034 err_checkout = True
1035 # NB: We don't exit here because this is the last step.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001037 # If there's a notice that's supposed to print at the end of the sync, print
1038 # it now...
1039 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001040 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001041
Mike Frysinger5a033082019-09-23 19:21:20 -04001042 # If we saw an error, exit with code 1 so that other scripts can check.
1043 if err_event.isSet():
1044 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1045 if err_network_sync:
1046 print('error: Downloading network changes failed.', file=sys.stderr)
1047 if err_update_projects:
1048 print('error: Updating local project lists failed.', file=sys.stderr)
1049 if err_checkout:
1050 print('error: Checking out local projects failed.', file=sys.stderr)
1051 if err_results:
1052 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1053 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1054 file=sys.stderr)
1055 sys.exit(1)
1056
Mike Frysingere19d9e12020-02-12 11:23:32 -05001057 if not opt.quiet:
1058 print('repo sync has finished successfully.')
1059
David Pursehouse819827a2020-02-12 15:20:19 +09001060
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001061def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001062 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001063 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001064 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001065 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001066 if project.Exists:
1067 project.PostRepoUpgrade()
1068
David Pursehouse819827a2020-02-12 15:20:19 +09001069
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001070def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001071 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001072 print('info: A new version of repo is available', file=sys.stderr)
1073 print(file=sys.stderr)
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001074 if not repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001075 syncbuf = SyncBuffer(rp.config)
1076 rp.Sync_LocalHalf(syncbuf)
1077 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001078 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -07001079 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001080 raise RepoChangedException(['--repo-upgraded'])
1081 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001082 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001083 else:
1084 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001085 print('repo version %s is current' % rp.work_git.describe(HEAD),
1086 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001087
David Pursehouse819827a2020-02-12 15:20:19 +09001088
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089def _VerifyTag(project):
1090 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
1091 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -07001092 print('warning: GnuPG was not available during last "repo init"\n'
1093 'warning: Cannot automatically authenticate repo."""',
1094 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095 return True
1096
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001097 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001098 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 except GitError:
1100 cur = None
1101
1102 if not cur \
1103 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001104 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105 if rev.startswith(R_HEADS):
1106 rev = rev[len(R_HEADS):]
1107
Sarah Owenscecd1d82012-11-01 22:59:27 -07001108 print(file=sys.stderr)
1109 print("warning: project '%s' branch '%s' is not signed"
1110 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 return False
1112
Shawn O. Pearcef18cb762010-12-07 11:41:05 -08001113 env = os.environ.copy()
Mike Frysinger56ce3462019-12-04 19:30:48 -05001114 env['GIT_DIR'] = project.gitdir
1115 env['GNUPGHOME'] = gpg_dir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
1117 cmd = [GIT, 'tag', '-v', cur]
1118 proc = subprocess.Popen(cmd,
David Pursehousee5913ae2020-02-12 13:56:59 +09001119 stdout=subprocess.PIPE,
1120 stderr=subprocess.PIPE,
1121 env=env)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001122 out = proc.stdout.read()
1123 proc.stdout.close()
1124
1125 err = proc.stderr.read()
1126 proc.stderr.close()
1127
1128 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001129 print(file=sys.stderr)
1130 print(out, file=sys.stderr)
1131 print(err, file=sys.stderr)
1132 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133 return False
1134 return True
Dave Borowitz67700e92012-10-23 15:00:54 -07001135
David Rileye0684ad2017-04-05 00:02:59 -07001136
Dave Borowitz67700e92012-10-23 15:00:54 -07001137class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001138 _ALPHA = 0.5
1139
Dave Borowitz67700e92012-10-23 15:00:54 -07001140 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001141 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001142 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001143 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001144
1145 def Get(self, project):
1146 self._Load()
1147 return self._times.get(project.name, _ONE_DAY_S)
1148
1149 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001150 self._Load()
1151 name = project.name
1152 old = self._times.get(name, t)
1153 self._seen.add(name)
1154 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001155 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001156
1157 def _Load(self):
1158 if self._times is None:
1159 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001160 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001161 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001162 except (IOError, ValueError):
1163 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001164 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001165 except OSError:
1166 pass
1167 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001168
1169 def Save(self):
1170 if self._times is None:
1171 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001172
1173 to_delete = []
1174 for name in self._times:
1175 if name not in self._seen:
1176 to_delete.append(name)
1177 for name in to_delete:
1178 del self._times[name]
1179
Dave Borowitz67700e92012-10-23 15:00:54 -07001180 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001181 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001182 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001183 except (IOError, TypeError):
1184 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001185 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001186 except OSError:
1187 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001188
1189# This is a replacement for xmlrpc.client.Transport using urllib2
1190# and supporting persistent-http[s]. It cannot change hosts from
1191# request to request like the normal transport, the real url
1192# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001193
1194
Dan Willemsen0745bb22015-08-17 13:41:45 -07001195class PersistentTransport(xmlrpc.client.Transport):
1196 def __init__(self, orig_host):
1197 self.orig_host = orig_host
1198
1199 def request(self, host, handler, request_body, verbose=False):
1200 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1201 # Python doesn't understand cookies with the #HttpOnly_ prefix
1202 # Since we're only using them for HTTP, copy the file temporarily,
1203 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001204 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001205 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001206 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001207 try:
1208 with open(cookiefile) as f:
1209 for line in f:
1210 if line.startswith("#HttpOnly_"):
1211 line = line[len("#HttpOnly_"):]
1212 tmpcookiefile.write(line)
1213 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001214
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001215 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001216 try:
1217 cookiejar.load()
1218 except cookielib.LoadError:
1219 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001220 finally:
1221 tmpcookiefile.close()
1222 else:
1223 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001224
1225 proxyhandler = urllib.request.ProxyHandler
1226 if proxy:
1227 proxyhandler = urllib.request.ProxyHandler({
1228 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001229 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001230
1231 opener = urllib.request.build_opener(
1232 urllib.request.HTTPCookieProcessor(cookiejar),
1233 proxyhandler)
1234
1235 url = urllib.parse.urljoin(self.orig_host, handler)
1236 parse_results = urllib.parse.urlparse(url)
1237
1238 scheme = parse_results.scheme
1239 if scheme == 'persistent-http':
1240 scheme = 'http'
1241 if scheme == 'persistent-https':
1242 # If we're proxying through persistent-https, use http. The
1243 # proxy itself will do the https.
1244 if proxy:
1245 scheme = 'http'
1246 else:
1247 scheme = 'https'
1248
1249 # Parse out any authentication information using the base class
1250 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1251
1252 url = urllib.parse.urlunparse((
1253 scheme,
1254 host,
1255 parse_results.path,
1256 parse_results.params,
1257 parse_results.query,
1258 parse_results.fragment))
1259
1260 request = urllib.request.Request(url, request_body)
1261 if extra_headers is not None:
1262 for (name, header) in extra_headers:
1263 request.add_header(name, header)
1264 request.add_header('Content-Type', 'text/xml')
1265 try:
1266 response = opener.open(request)
1267 except urllib.error.HTTPError as e:
1268 if e.code == 501:
1269 # We may have been redirected through a login process
1270 # but our POST turned into a GET. Retry.
1271 response = opener.open(request)
1272 else:
1273 raise
1274
1275 p, u = xmlrpc.client.getparser()
1276 while 1:
1277 data = response.read(1024)
1278 if not data:
1279 break
1280 p.feed(data)
1281 p.close()
1282 return u.close()
1283
1284 def close(self):
1285 pass