blob: e5280865788f17c0d80083fc6dacf89bb160e547 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
jiajia tanga590e642021-04-25 20:02:02 +080015import errno
Mike Frysingerebf04a42021-02-23 20:48:04 -050016import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040017import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050018import io
Anthony King85b24ac2014-05-06 15:57:48 +010019import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050020import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090021import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070022from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040028import urllib.error
29import urllib.parse
30import urllib.request
31import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
Roy Lee18afd7f2010-05-09 04:32:08 +080033try:
34 import threading as _threading
35except ImportError:
36 import dummy_threading as _threading
37
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070038try:
39 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090040
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070041 def _rlimit_nofile():
42 return resource.getrlimit(resource.RLIMIT_NOFILE)
43except ImportError:
44 def _rlimit_nofile():
45 return (256, 256)
46
David Rileye0684ad2017-04-05 00:02:59 -070047import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040048from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090049from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090050from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080051import git_superproject
Simran Basibdb52712015-08-10 13:23:23 -070052import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070053from project import Project
54from project import RemoteSpec
Mike Frysingerd41eed02021-04-20 23:21:29 -040055from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE
Raman Tenneti1fd7bc22021-02-04 14:39:38 -080056from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070057import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070058from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070059from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080060from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070061from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070062
Dave Borowitz67700e92012-10-23 15:00:54 -070063_ONE_DAY_S = 24 * 60 * 60
64
David Pursehouse819827a2020-02-12 15:20:19 +090065
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080066class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080067 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068 common = True
69 helpSummary = "Update working tree to the latest revision"
70 helpUsage = """
71%prog [<project>...]
72"""
73 helpDescription = """
74The '%prog' command synchronizes local project directories
75with the remote repositories specified in the manifest. If a local
76project does not yet exist, it will clone a new local directory from
77the remote repository and set up tracking branches as specified in
78the manifest. If the local project already exists, '%prog'
79will update the remote branches and rebase any new local changes
80on top of the new remote changes.
81
82'%prog' will synchronize all projects listed at the command
83line. Projects can be specified either by name, or by a relative
84or absolute path to the project's local directory. If no projects
85are specified, '%prog' will synchronize all projects listed in
86the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070087
88The -d/--detach option can be used to switch specified projects
89back to the manifest revision. This option is especially helpful
90if the project is currently on a topic branch, but the manifest
91revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070092
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070093The -s/--smart-sync option can be used to sync to a known good
94build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +020095manifest. The -t/--smart-tag option is similar and allows you to
96specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070097
David Pursehousecf76b1b2012-09-14 10:31:42 +090098The -u/--manifest-server-username and -p/--manifest-server-password
99options can be used to specify a username and password to authenticate
100with the manifest server when using the -s or -t option.
101
102If -u and -p are not specified when using the -s or -t option, '%prog'
103will attempt to read authentication credentials for the manifest server
104from the user's .netrc file.
105
106'%prog' will not use authentication credentials from -u/-p or .netrc
107if the manifest server specified in the manifest file already includes
108credentials.
109
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400110By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400111to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500112
Kevin Degiabaa7f32014-11-12 11:27:45 -0700113The --force-sync option can be used to overwrite existing git
114directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900115object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700116refs may be removed when overwriting.
117
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500118The --force-remove-dirty option can be used to remove previously used
119projects with uncommitted changes. WARNING: This may cause data to be
120lost since uncommitted changes may be removed with projects that no longer
121exist in the manifest.
122
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700123The --no-clone-bundle option disables any attempt to use
124$URL/clone.bundle to bootstrap a new Git repository from a
125resumeable bundle file on a content delivery network. This
126may be necessary if there are problems with the local Python
127HTTP client or proxy configuration, but the Git binary works.
128
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800129The --fetch-submodules option enables fetching Git submodules
130of a project from server.
131
David Pursehousef2fad612015-01-29 14:36:28 +0900132The -c/--current-branch option can be used to only fetch objects that
133are on the branch specified by a project's revision.
134
David Pursehouseb1553542014-09-04 21:28:09 +0900135The --optimized-fetch option can be used to only fetch projects that
136are fixed to a sha1 revision if the sha1 revision does not already
137exist locally.
138
David Pursehouse74cfd272015-10-14 10:50:15 +0900139The --prune option can be used to remove any refs that no longer
140exist on the remote.
141
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400142# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700143
144If at least one project remote URL uses an SSH connection (ssh://,
145git+ssh://, or user@host:path syntax) repo will automatically
146enable the SSH ControlMaster option when connecting to that host.
147This feature permits other projects in the same '%prog' session to
148reuse the same SSH tunnel, saving connection setup overheads.
149
150To disable this behavior on UNIX platforms, set the GIT_SSH
151environment variable to 'ssh'. For example:
152
153 export GIT_SSH=ssh
154 %prog
155
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400156# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700157
158This feature is automatically disabled on Windows, due to the lack
159of UNIX domain socket support.
160
161This feature is not compatible with url.insteadof rewrites in the
162user's ~/.gitconfig. '%prog' is currently not able to perform the
163rewrite early enough to establish the ControlMaster tunnel.
164
165If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
166later is required to fix a server side protocol bug.
167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168"""
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500169 PARALLEL_JOBS = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170
Mike Frysinger9180a072021-04-13 14:57:40 -0400171 def _CommonOptions(self, p):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000172 try:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500173 self.PARALLEL_JOBS = self.manifest.default.sync_j
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000174 except ManifestParseError:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500175 pass
Mike Frysinger9180a072021-04-13 14:57:40 -0400176 super()._CommonOptions(p)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700177
Mike Frysinger9180a072021-04-13 14:57:40 -0400178 def _Options(self, p, show_smart=True):
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400179 p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
180 help='number of network jobs to run in parallel (defaults to --jobs)')
181 p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
182 help='number of local checkout jobs to run in parallel (defaults to --jobs)')
183
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500184 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200185 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400186 help='obsolete option (to be deleted in the future)')
187 p.add_option('--fail-fast',
188 dest='fail_fast', action='store_true',
189 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700190 p.add_option('--force-sync',
191 dest='force_sync', action='store_true',
192 help="overwrite an existing git directory if it needs to "
193 "point to a different object directory. WARNING: this "
194 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500195 p.add_option('--force-remove-dirty',
196 dest='force_remove_dirty', action='store_true',
197 help="force remove projects with uncommitted modifications if "
198 "projects no longer exist in the manifest. "
199 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900200 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700201 dest='local_only', action='store_true',
202 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900203 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100204 dest='mp_update', action='store_false', default='true',
205 help='use the existing manifest checkout as-is. '
206 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900207 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700208 dest='network_only', action='store_true',
209 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900210 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700211 dest='detach_head', action='store_true',
212 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900213 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700214 dest='current_branch_only', action='store_true',
215 help='fetch only current branch from server')
Mike Frysinger73561142021-05-03 01:10:09 -0400216 p.add_option('--no-current-branch',
217 dest='current_branch_only', action='store_false',
218 help='fetch all branches from server')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500219 p.add_option('-m', '--manifest-name',
220 dest='manifest_name',
221 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700222 p.add_option('--clone-bundle', action='store_true',
223 help='enable use of /clone.bundle on HTTP/HTTPS')
224 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700225 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800226 p.add_option('-u', '--manifest-server-username', action='store',
227 dest='manifest_server_username',
228 help='username to authenticate with the manifest server')
229 p.add_option('-p', '--manifest-server-password', action='store',
230 dest='manifest_server_password',
231 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800232 p.add_option('--fetch-submodules',
233 dest='fetch_submodules', action='store_true',
234 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800235 p.add_option('--use-superproject', action='store_true',
236 help='use the manifest superproject to sync projects')
Mike Frysingerd68ed632021-05-03 01:21:35 -0400237 p.add_option('--tags',
238 action='store_false',
239 help='fetch tags')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700240 p.add_option('--no-tags',
Mike Frysingerd68ed632021-05-03 01:21:35 -0400241 dest='tags', action='store_false',
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700242 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900243 p.add_option('--optimized-fetch',
244 dest='optimized_fetch', action='store_true',
245 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600246 p.add_option('--retry-fetches',
247 default=0, action='store', type='int',
248 help='number of times to retry fetches on transient errors')
David Pursehouse74cfd272015-10-14 10:50:15 +0900249 p.add_option('--prune', dest='prune', action='store_true',
250 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700251 if show_smart:
252 p.add_option('-s', '--smart-sync',
253 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900254 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200255 p.add_option('-t', '--smart-tag',
256 dest='smart_tag', action='store',
257 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700258
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700259 g = p.add_option_group('repo Version options')
260 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500261 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700263 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800264 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700265 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800267 def _GetBranch(self):
268 """Returns the branch name for getting the approved manifest."""
269 p = self.manifest.manifestProject
270 b = p.GetBranch(p.CurrentBranch)
271 branch = b.merge
272 if branch.startswith(R_HEADS):
273 branch = branch[len(R_HEADS):]
274 return branch
275
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700276 def _UseSuperproject(self, opt):
277 """Returns True if use-superproject option is enabled"""
278 return (opt.use_superproject or
279 self.manifest.manifestProject.config.GetBoolean(
280 'repo.superproject'))
281
282 def _GetCurrentBranchOnly(self, opt):
283 """Returns True if current-branch or use-superproject options are enabled."""
284 return opt.current_branch_only or self._UseSuperproject(opt)
285
Raman Tennetifeb28912021-05-02 19:47:29 -0700286 def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests):
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800287 """Update revisionId of every project with the SHA from superproject.
288
289 This function updates each project's revisionId with SHA from superproject.
290 It writes the updated manifest into a file and reloads the manifest from it.
291
292 Args:
293 opt: Program options returned from optparse. See _Options().
294 args: Arguments to pass to GetProjects. See the GetProjects
295 docstring for details.
Raman Tennetifeb28912021-05-02 19:47:29 -0700296 load_local_manifests: Whether to load local manifests.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800297
298 Returns:
299 Returns path to the overriding manifest file.
300 """
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800301 superproject = git_superproject.Superproject(self.manifest,
Raman Tennetief99ec02021-03-04 10:29:40 -0800302 self.repodir,
303 quiet=opt.quiet)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800304 all_projects = self.GetProjects(args,
305 missing_ok=True,
306 submodules_ok=opt.fetch_submodules)
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800307 manifest_path = superproject.UpdateProjectsRevisionId(all_projects)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800308 if not manifest_path:
309 print('error: Update of revsionId from superproject has failed',
310 file=sys.stderr)
311 sys.exit(1)
Raman Tennetifeb28912021-05-02 19:47:29 -0700312 self._ReloadManifest(manifest_path, load_local_manifests)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800313 return manifest_path
314
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500315 def _FetchProjectList(self, opt, projects):
316 """Main function of the fetch worker.
317
318 The projects we're given share the same underlying git object store, so we
319 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800320
David James8d201162013-10-11 17:03:19 -0700321 Delegates most of the work to _FetchHelper.
322
323 Args:
324 opt: Program options returned from optparse. See _Options().
325 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700326 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500327 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700328
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500329 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700330 """Fetch git objects for a single project.
331
David Pursehousec1b86a22012-11-14 11:36:51 +0900332 Args:
333 opt: Program options returned from optparse. See _Options().
334 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700335
336 Returns:
337 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900338 """
David Rileye0684ad2017-04-05 00:02:59 -0700339 start = time.time()
340 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500341 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900342 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500343 success = project.Sync_NetworkHalf(
344 quiet=opt.quiet,
345 verbose=opt.verbose,
346 output_redir=buf,
347 current_branch_only=self._GetCurrentBranchOnly(opt),
348 force_sync=opt.force_sync,
349 clone_bundle=opt.clone_bundle,
350 tags=opt.tags, archive=self.manifest.IsArchive,
351 optimized_fetch=opt.optimized_fetch,
352 retry_fetches=opt.retry_fetches,
353 prune=opt.prune,
Raman Tennetif32f2432021-04-12 20:57:25 -0700354 clone_filter=self.manifest.CloneFilter,
355 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700356
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500357 output = buf.getvalue()
358 if opt.verbose and output:
359 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700360
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500361 if not success:
362 print('error: Cannot fetch %s from %s'
363 % (project.name, project.remote.url),
364 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700365 except GitError as e:
366 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500367 except Exception as e:
368 print('error: Cannot fetch %s (%s: %s)'
369 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
370 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500371
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500372 finish = time.time()
373 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700374
Mike Frysinger5a033082019-09-23 19:21:20 -0400375 def _Fetch(self, projects, opt, err_event):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500376 ret = True
377
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400378 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700379 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400380 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800381
David James89ece422014-01-09 18:51:58 -0800382 objdir_project_map = dict()
383 for project in projects:
384 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500385 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700386
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500387 def _ProcessResults(results_sets):
388 ret = True
389 for results in results_sets:
390 for (success, project, start, finish) in results:
391 self._fetch_times.Set(project, finish - start)
392 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
393 start, finish, success)
394 # Check for any errors before running any more tasks.
395 # ...we'll let existing jobs finish, though.
396 if not success:
397 ret = False
398 else:
399 fetched.add(project.gitdir)
400 pm.update(msg=project.name)
401 if not ret and opt.fail_fast:
402 break
403 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700404
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500405 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400406 if len(projects_list) == 1 or jobs == 1:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500407 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
408 ret = False
409 else:
410 # Favor throughput over responsiveness when quiet. It seems that imap()
411 # will yield results in batches relative to chunksize, so even as the
412 # children finish a sync, we won't see the result until one child finishes
413 # ~chunksize jobs. When using a large --jobs with large chunksize, this
414 # can be jarring as there will be a large initial delay where repo looks
415 # like it isn't doing anything and sits at 0%, but then suddenly completes
416 # a lot of jobs all at once. Since this code is more network bound, we
417 # can accept a bit more CPU overhead with a smaller chunksize so that the
418 # user sees more immediate & continuous feedback.
419 if opt.quiet:
420 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800421 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500422 pm.update(inc=0, msg='warming up')
423 chunksize = 4
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400424 with multiprocessing.Pool(jobs) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500425 results = pool.imap_unordered(
426 functools.partial(self._FetchProjectList, opt),
427 projects_list,
428 chunksize=chunksize)
429 if not _ProcessResults(results):
430 ret = False
431 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800432
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700433 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700434 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700435
Julien Campergue335f5ef2013-10-16 11:02:35 +0200436 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400437 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200438
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500439 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700440
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500441 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700442 """Checkout work tree for one project
443
444 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500445 detach_head: Whether to leave a detached HEAD.
446 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700447 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700448
449 Returns:
450 Whether the fetch was successful.
451 """
Xin Li745be2e2019-06-03 11:24:30 -0700452 start = time.time()
453 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500454 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700455 success = False
456 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500457 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500458 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700459 except GitError as e:
460 print('error.GitError: Cannot checkout %s: %s' %
461 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500462 except Exception as e:
463 print('error: Cannot checkout %s: %s: %s' %
464 (project.name, type(e).__name__, str(e)),
465 file=sys.stderr)
466 raise
Xin Li745be2e2019-06-03 11:24:30 -0700467
Mike Frysingerebf04a42021-02-23 20:48:04 -0500468 if not success:
469 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
470 finish = time.time()
471 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700472
Mike Frysingerebf04a42021-02-23 20:48:04 -0500473 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700474 """Checkout projects listed in all_projects
475
476 Args:
477 all_projects: List of all projects that should be checked out.
478 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500479 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700480 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500481 # Only checkout projects with worktrees.
482 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700483
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500484 def _ProcessResults(pool, pm, results):
485 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500486 for (success, project, start, finish) in results:
487 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
488 start, finish, success)
489 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500490 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500491 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500492 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500493 err_results.append(project.relpath)
494 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500495 if pool:
496 pool.close()
497 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500498 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500499 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700500
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500501 return self.ExecuteInParallel(
502 opt.jobs_checkout if opt.jobs_checkout else self.jobs,
503 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
504 all_projects,
505 callback=_ProcessResults,
506 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500507
Mike Frysinger5a033082019-09-23 19:21:20 -0400508 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400509 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400510 pm.update(inc=0, msg='prescan')
511
Gabe Black2ff30292014-10-09 17:54:35 -0700512 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700513 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500514 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500515 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900516 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100517 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400518 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100519 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500520 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500521 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500522 else:
523 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400524 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500525 'versions of git; please upgrade to git-2.7.0+.'
526 % (project.relpath,),
527 file=sys.stderr)
528 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700529 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700530
Mike Frysinger65af2602021-04-08 22:47:44 -0400531 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
532
533 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700534 jobs = min(self.jobs, cpu_count)
535
536 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700537 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400538 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700539 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400540 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700541 return
542
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400543 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700544
545 threads = set()
546 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700547
David James8d201162013-10-11 17:03:19 -0700548 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400549 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700550 try:
551 try:
David James8d201162013-10-11 17:03:19 -0700552 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700553 except GitError:
554 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900555 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700556 err_event.set()
557 raise
558 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400559 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700560 sem.release()
561
Gabe Black2ff30292014-10-09 17:54:35 -0700562 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500563 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700564 break
565 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700566 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700567 t.daemon = True
568 threads.add(t)
569 t.start()
570
571 for t in threads:
572 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400573 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700574
Raman Tennetifeb28912021-05-02 19:47:29 -0700575 def _ReloadManifest(self, manifest_name=None, load_local_manifests=True):
576 """Reload the manfiest from the file specified by the |manifest_name|.
577
578 It unloads the manifest if |manifest_name| is None.
579
580 Args:
581 manifest_name: Manifest file to be reloaded.
582 load_local_manifests: Whether to load local manifests.
583 """
Tim Kilbourn07669002013-03-08 15:02:49 -0800584 if manifest_name:
585 # Override calls _Unload already
Raman Tennetifeb28912021-05-02 19:47:29 -0700586 self.manifest.Override(manifest_name, load_local_manifests=load_local_manifests)
Tim Kilbourn07669002013-03-08 15:02:49 -0800587 else:
588 self.manifest._Unload()
589
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500590 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700591 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700592 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700593 if project.relpath:
594 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700595 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500596 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700597 old_project_paths = []
598
599 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500600 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700601 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800602 # In reversed order, so subfolders are deleted before parent folder.
603 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700604 if not path:
605 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700606 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900607 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700608 gitdir = os.path.join(self.manifest.topdir, path, '.git')
609 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900610 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900611 manifest=self.manifest,
612 name=path,
613 remote=RemoteSpec('origin'),
614 gitdir=gitdir,
615 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500616 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900617 worktree=os.path.join(self.manifest.topdir, path),
618 relpath=path,
619 revisionExpr='HEAD',
620 revisionId=None,
621 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500622 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900623 quiet=opt.quiet,
624 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400625 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700626
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700627 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500628 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700629 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700630 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700631 return 0
632
jiajia tanga590e642021-04-25 20:02:02 +0800633 def UpdateCopyLinkfileList(self):
634 """Save all dests of copyfile and linkfile, and update them if needed.
635
636 Returns:
637 Whether update was successful.
638 """
639 new_paths = {}
640 new_linkfile_paths = []
641 new_copyfile_paths = []
642 for project in self.GetProjects(None, missing_ok=True):
643 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
644 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
645
646 new_paths = {
647 'linkfile': new_linkfile_paths,
648 'copyfile': new_copyfile_paths,
649 }
650
651 copylinkfile_name = 'copy-link-files.json'
652 copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name)
653 old_copylinkfile_paths = {}
654
655 if os.path.exists(copylinkfile_path):
656 with open(copylinkfile_path, 'rb') as fp:
657 try:
658 old_copylinkfile_paths = json.load(fp)
659 except:
660 print('error: %s is not a json formatted file.' %
661 copylinkfile_path, file=sys.stderr)
662 platform_utils.remove(copylinkfile_path)
663 return False
664
665 need_remove_files = []
666 need_remove_files.extend(
667 set(old_copylinkfile_paths.get('linkfile', [])) -
668 set(new_linkfile_paths))
669 need_remove_files.extend(
670 set(old_copylinkfile_paths.get('copyfile', [])) -
671 set(new_copyfile_paths))
672
673 for need_remove_file in need_remove_files:
674 try:
675 platform_utils.remove(need_remove_file)
676 except OSError as e:
677 if e.errno == errno.ENOENT:
678 # Try to remove the updated copyfile or linkfile.
679 # So, if the file is not exist, nothing need to do.
680 pass
681
682 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
683 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
684 json.dump(new_paths, fp)
685 return True
686
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400687 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
688 if not self.manifest.manifest_server:
689 print('error: cannot smart sync: no manifest server defined in '
690 'manifest', file=sys.stderr)
691 sys.exit(1)
692
693 manifest_server = self.manifest.manifest_server
694 if not opt.quiet:
695 print('Using manifest server %s' % manifest_server)
696
David Pursehouseeeff3532020-02-12 11:24:10 +0900697 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400698 username = None
699 password = None
700 if opt.manifest_server_username and opt.manifest_server_password:
701 username = opt.manifest_server_username
702 password = opt.manifest_server_password
703 else:
704 try:
705 info = netrc.netrc()
706 except IOError:
707 # .netrc file does not exist or could not be opened
708 pass
709 else:
710 try:
711 parse_result = urllib.parse.urlparse(manifest_server)
712 if parse_result.hostname:
713 auth = info.authenticators(parse_result.hostname)
714 if auth:
715 username, _account, password = auth
716 else:
717 print('No credentials found for %s in .netrc'
718 % parse_result.hostname, file=sys.stderr)
719 except netrc.NetrcParseError as e:
720 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
721
722 if (username and password):
723 manifest_server = manifest_server.replace('://', '://%s:%s@' %
724 (username, password),
725 1)
726
727 transport = PersistentTransport(manifest_server)
728 if manifest_server.startswith('persistent-'):
729 manifest_server = manifest_server[len('persistent-'):]
730
731 try:
732 server = xmlrpc.client.Server(manifest_server, transport=transport)
733 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800734 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400735
Mike Frysinger56ce3462019-12-04 19:30:48 -0500736 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500737 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400738 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500739 elif ('TARGET_PRODUCT' in os.environ and
740 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500741 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
742 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400743 [success, manifest_str] = server.GetApprovedManifest(branch, target)
744 else:
745 [success, manifest_str] = server.GetApprovedManifest(branch)
746 else:
747 assert(opt.smart_tag)
748 [success, manifest_str] = server.GetManifest(opt.smart_tag)
749
750 if success:
751 manifest_name = os.path.basename(smart_sync_manifest_path)
752 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500753 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400754 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400755 except IOError as e:
756 print('error: cannot write manifest to %s:\n%s'
757 % (smart_sync_manifest_path, e),
758 file=sys.stderr)
759 sys.exit(1)
760 self._ReloadManifest(manifest_name)
761 else:
762 print('error: manifest server RPC call failed: %s' %
763 manifest_str, file=sys.stderr)
764 sys.exit(1)
765 except (socket.error, IOError, xmlrpc.client.Fault) as e:
766 print('error: cannot connect to manifest server %s:\n%s'
767 % (self.manifest.manifest_server, e), file=sys.stderr)
768 sys.exit(1)
769 except xmlrpc.client.ProtocolError as e:
770 print('error: cannot connect to manifest server %s:\n%d %s'
771 % (self.manifest.manifest_server, e.errcode, e.errmsg),
772 file=sys.stderr)
773 sys.exit(1)
774
775 return manifest_name
776
Mike Frysingerfb527e32019-08-27 02:34:32 -0400777 def _UpdateManifestProject(self, opt, mp, manifest_name):
778 """Fetch & update the local manifest project."""
779 if not opt.local_only:
780 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500781 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700782 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200783 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500784 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400785 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600786 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400787 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700788 clone_filter=self.manifest.CloneFilter,
789 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400790 finish = time.time()
791 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
792 start, finish, success)
793
794 if mp.HasChanges:
795 syncbuf = SyncBuffer(mp.config)
796 start = time.time()
797 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
798 clean = syncbuf.Finish()
799 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
800 start, time.time(), clean)
801 if not clean:
802 sys.exit(1)
803 self._ReloadManifest(opt.manifest_name)
804 if opt.jobs is None:
805 self.jobs = self.manifest.default.sync_j
806
Mike Frysingerae6cb082019-08-27 01:10:59 -0400807 def ValidateOptions(self, opt, args):
808 if opt.force_broken:
809 print('warning: -f/--force-broken is now the default behavior, and the '
810 'options are deprecated', file=sys.stderr)
811 if opt.network_only and opt.detach_head:
812 self.OptionParser.error('cannot combine -n and -d')
813 if opt.network_only and opt.local_only:
814 self.OptionParser.error('cannot combine -n and -l')
815 if opt.manifest_name and opt.smart_sync:
816 self.OptionParser.error('cannot combine -m and -s')
817 if opt.manifest_name and opt.smart_tag:
818 self.OptionParser.error('cannot combine -m and -t')
819 if opt.manifest_server_username or opt.manifest_server_password:
820 if not (opt.smart_sync or opt.smart_tag):
821 self.OptionParser.error('-u and -p may only be combined with -s or -t')
822 if None in [opt.manifest_server_username, opt.manifest_server_password]:
823 self.OptionParser.error('both -u and -p must be given')
824
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800826 if opt.jobs:
827 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700828 if self.jobs > 1:
829 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400830 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700831
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500832 if opt.manifest_name:
833 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700834
Chirayu Desaia892b102013-06-11 14:18:46 +0530835 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900836 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900837 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530838
Xin Lid79a4bc2020-05-20 16:03:45 -0700839 if opt.clone_bundle is None:
840 opt.clone_bundle = self.manifest.CloneBundle
841
Victor Boivie08c880d2011-04-19 10:32:52 +0200842 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400843 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
844 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900845 if os.path.isfile(smart_sync_manifest_path):
846 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800847 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900848 except OSError as e:
849 print('error: failed to remove existing smart sync override manifest: %s' %
850 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700851
Mike Frysinger5a033082019-09-23 19:21:20 -0400852 err_event = _threading.Event()
853
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 rp = self.manifest.repoProject
855 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500856 cb = rp.CurrentBranch
857 if cb:
858 base = rp.GetBranch(cb).merge
859 if not base or not base.startswith('refs/heads/'):
860 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400861 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500862 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863
864 mp = self.manifest.manifestProject
865 mp.PreSync()
866
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800867 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700868 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800869
Fredrik de Grootcc960972019-11-22 09:04:31 +0100870 if not opt.mp_update:
871 print('Skipping update of local manifest project.')
872 else:
873 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700874
Raman Tennetifeb28912021-05-02 19:47:29 -0700875 load_local_manifests = not self.manifest.HasLocalManifests
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700876 if self._UseSuperproject(opt):
Raman Tennetifeb28912021-05-02 19:47:29 -0700877 manifest_name = self._UpdateProjectsRevisionId(opt, args, load_local_manifests)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800878
Simran Basib9a1b732015-08-20 12:19:28 -0700879 if self.gitc_manifest:
880 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700881 missing_ok=True)
882 gitc_projects = []
883 opened_projects = []
884 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700885 if project.relpath in self.gitc_manifest.paths and \
886 self.gitc_manifest.paths[project.relpath].old_revision:
887 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700888 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700889 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700890
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700891 if not args:
892 gitc_projects = None
893
894 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700895 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700896 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
897 if manifest_name:
898 manifest.Override(manifest_name)
899 else:
900 manifest.Override(self.manifest.manifestFile)
901 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
902 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700903 gitc_projects)
904 print('GITC client successfully synced.')
905
906 # The opened projects need to be synced as normal, therefore we
907 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700908 # TODO: make this more reliable -- if there's a project name/path overlap,
909 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900910 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
911 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700912 if not args:
913 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800914 all_projects = self.GetProjects(args,
915 missing_ok=True,
916 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
Mike Frysinger5a033082019-09-23 19:21:20 -0400918 err_network_sync = False
919 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -0400920
Dave Borowitz67700e92012-10-23 15:00:54 -0700921 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700922 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700923 to_fetch = []
924 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700925 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700926 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900927 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700928 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700929
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500930 success, fetched = self._Fetch(to_fetch, opt, err_event)
931 if not success:
932 err_event.set()
Mike Frysinger5a033082019-09-23 19:21:20 -0400933
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500934 _PostRepoFetch(rp, opt.repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700935 if opt.network_only:
936 # bail out now; the rest touches the working tree
Mike Frysingerbe24a542021-02-23 03:24:12 -0500937 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400938 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
939 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700940 return
941
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800942 # Iteratively fetch missing and/or nested unregistered submodules
943 previously_missing_set = set()
944 while True:
Raman Tennetifeb28912021-05-02 19:47:29 -0700945 self._ReloadManifest(manifest_name, load_local_manifests)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800946 all_projects = self.GetProjects(args,
947 missing_ok=True,
948 submodules_ok=opt.fetch_submodules)
949 missing = []
950 for project in all_projects:
951 if project.gitdir not in fetched:
952 missing.append(project)
953 if not missing:
954 break
955 # Stop us from non-stopped fetching actually-missing repos: If set of
956 # missing repos has not been changed from last fetch, we break.
957 missing_set = set(p.name for p in missing)
958 if previously_missing_set == missing_set:
959 break
960 previously_missing_set = missing_set
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500961 success, new_fetched = self._Fetch(to_fetch, opt, err_event)
962 if not success:
963 err_event.set()
964 fetched.update(new_fetched)
Mike Frysinger5a033082019-09-23 19:21:20 -0400965
966 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500967 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400968 err_network_sync = True
969 if opt.fail_fast:
970 print('\nerror: Exited sync due to fetch errors.\n'
971 'Local checkouts *not* updated. Resolve network issues & '
972 'retry.\n'
973 '`repo sync -l` will update some local checkouts.',
974 file=sys.stderr)
975 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800976
Julien Campergue335f5ef2013-10-16 11:02:35 +0200977 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700978 # bail out now, we have no working tree
979 return
980
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500981 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -0400982 err_event.set()
983 err_update_projects = True
984 if opt.fail_fast:
985 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
986 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700987
jiajia tanga590e642021-04-25 20:02:02 +0800988 if not self.UpdateCopyLinkfileList():
989 err_event.set()
990 err_update_linkfiles = True
991 if opt.fail_fast:
992 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
993 sys.exit(1)
994
Mike Frysinger5a033082019-09-23 19:21:20 -0400995 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -0500996 # NB: We don't exit here because this is the last step.
997 err_checkout = not self._Checkout(all_projects, opt, err_results)
998 if err_checkout:
999 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001000
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001001 # If there's a notice that's supposed to print at the end of the sync, print
1002 # it now...
1003 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001004 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001005
Mike Frysinger5a033082019-09-23 19:21:20 -04001006 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001007 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001008 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1009 if err_network_sync:
1010 print('error: Downloading network changes failed.', file=sys.stderr)
1011 if err_update_projects:
1012 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +08001013 if err_update_linkfiles:
1014 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -04001015 if err_checkout:
1016 print('error: Checking out local projects failed.', file=sys.stderr)
1017 if err_results:
1018 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1019 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1020 file=sys.stderr)
1021 sys.exit(1)
1022
Mike Frysingere19d9e12020-02-12 11:23:32 -05001023 if not opt.quiet:
1024 print('repo sync has finished successfully.')
1025
David Pursehouse819827a2020-02-12 15:20:19 +09001026
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001027def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001028 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001029 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001030 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001031 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001032 if project.Exists:
1033 project.PostRepoUpgrade()
1034
David Pursehouse819827a2020-02-12 15:20:19 +09001035
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001036def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001037 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001038 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001039 wrapper = Wrapper()
1040 try:
1041 rev = rp.bare_git.describe(rp.GetRevisionId())
1042 except GitError:
1043 rev = None
1044 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1045 # See if we're held back due to missing signed tag.
1046 current_revid = rp.bare_git.rev_parse('HEAD')
1047 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1048 if current_revid != new_revid:
1049 # We want to switch to the new rev, but also not trash any uncommitted
1050 # changes. This helps with local testing/hacking.
1051 # If a local change has been made, we will throw that away.
1052 # We also have to make sure this will switch to an older commit if that's
1053 # the latest tag in order to support release rollback.
1054 try:
1055 rp.work_git.reset('--keep', new_rev)
1056 except GitError as e:
1057 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001058 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001059 raise RepoChangedException(['--repo-upgraded'])
1060 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001061 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001062 else:
1063 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001064 print('repo version %s is current' % rp.work_git.describe(HEAD),
1065 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001066
David Pursehouse819827a2020-02-12 15:20:19 +09001067
Dave Borowitz67700e92012-10-23 15:00:54 -07001068class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001069 _ALPHA = 0.5
1070
Dave Borowitz67700e92012-10-23 15:00:54 -07001071 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001072 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001073 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001074 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001075
1076 def Get(self, project):
1077 self._Load()
1078 return self._times.get(project.name, _ONE_DAY_S)
1079
1080 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001081 self._Load()
1082 name = project.name
1083 old = self._times.get(name, t)
1084 self._seen.add(name)
1085 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001086 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001087
1088 def _Load(self):
1089 if self._times is None:
1090 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001091 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001092 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001093 except (IOError, ValueError):
1094 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001095 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001096 except OSError:
1097 pass
1098 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001099
1100 def Save(self):
1101 if self._times is None:
1102 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001103
1104 to_delete = []
1105 for name in self._times:
1106 if name not in self._seen:
1107 to_delete.append(name)
1108 for name in to_delete:
1109 del self._times[name]
1110
Dave Borowitz67700e92012-10-23 15:00:54 -07001111 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001112 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001113 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001114 except (IOError, TypeError):
1115 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001116 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001117 except OSError:
1118 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001119
1120# This is a replacement for xmlrpc.client.Transport using urllib2
1121# and supporting persistent-http[s]. It cannot change hosts from
1122# request to request like the normal transport, the real url
1123# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001124
1125
Dan Willemsen0745bb22015-08-17 13:41:45 -07001126class PersistentTransport(xmlrpc.client.Transport):
1127 def __init__(self, orig_host):
1128 self.orig_host = orig_host
1129
1130 def request(self, host, handler, request_body, verbose=False):
1131 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1132 # Python doesn't understand cookies with the #HttpOnly_ prefix
1133 # Since we're only using them for HTTP, copy the file temporarily,
1134 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001135 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001136 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001137 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001138 try:
1139 with open(cookiefile) as f:
1140 for line in f:
1141 if line.startswith("#HttpOnly_"):
1142 line = line[len("#HttpOnly_"):]
1143 tmpcookiefile.write(line)
1144 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001145
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001146 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001147 try:
1148 cookiejar.load()
1149 except cookielib.LoadError:
1150 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001151 finally:
1152 tmpcookiefile.close()
1153 else:
1154 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001155
1156 proxyhandler = urllib.request.ProxyHandler
1157 if proxy:
1158 proxyhandler = urllib.request.ProxyHandler({
1159 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001160 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001161
1162 opener = urllib.request.build_opener(
1163 urllib.request.HTTPCookieProcessor(cookiejar),
1164 proxyhandler)
1165
1166 url = urllib.parse.urljoin(self.orig_host, handler)
1167 parse_results = urllib.parse.urlparse(url)
1168
1169 scheme = parse_results.scheme
1170 if scheme == 'persistent-http':
1171 scheme = 'http'
1172 if scheme == 'persistent-https':
1173 # If we're proxying through persistent-https, use http. The
1174 # proxy itself will do the https.
1175 if proxy:
1176 scheme = 'http'
1177 else:
1178 scheme = 'https'
1179
1180 # Parse out any authentication information using the base class
1181 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1182
1183 url = urllib.parse.urlunparse((
1184 scheme,
1185 host,
1186 parse_results.path,
1187 parse_results.params,
1188 parse_results.query,
1189 parse_results.fragment))
1190
1191 request = urllib.request.Request(url, request_body)
1192 if extra_headers is not None:
1193 for (name, header) in extra_headers:
1194 request.add_header(name, header)
1195 request.add_header('Content-Type', 'text/xml')
1196 try:
1197 response = opener.open(request)
1198 except urllib.error.HTTPError as e:
1199 if e.code == 501:
1200 # We may have been redirected through a login process
1201 # but our POST turned into a GET. Retry.
1202 response = opener.open(request)
1203 else:
1204 raise
1205
1206 p, u = xmlrpc.client.getparser()
1207 while 1:
1208 data = response.read(1024)
1209 if not data:
1210 break
1211 p.feed(data)
1212 p.close()
1213 return u.close()
1214
1215 def close(self):
1216 pass