blob: 8defc932bd2399824fc21af800b5f949c77d9c43 [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
Mike Frysinger19e409c2021-05-05 19:44:35 -040060import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080061from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070062from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063
Dave Borowitz67700e92012-10-23 15:00:54 -070064_ONE_DAY_S = 24 * 60 * 60
65
David Pursehouse819827a2020-02-12 15:20:19 +090066
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080067class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080068 jobs = 1
Mike Frysinger4f210542021-06-14 16:05:19 -040069 COMMON = True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070070 helpSummary = "Update working tree to the latest revision"
71 helpUsage = """
72%prog [<project>...]
73"""
74 helpDescription = """
75The '%prog' command synchronizes local project directories
76with the remote repositories specified in the manifest. If a local
77project does not yet exist, it will clone a new local directory from
78the remote repository and set up tracking branches as specified in
79the manifest. If the local project already exists, '%prog'
80will update the remote branches and rebase any new local changes
81on top of the new remote changes.
82
83'%prog' will synchronize all projects listed at the command
84line. Projects can be specified either by name, or by a relative
85or absolute path to the project's local directory. If no projects
86are specified, '%prog' will synchronize all projects listed in
87the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070088
89The -d/--detach option can be used to switch specified projects
90back to the manifest revision. This option is especially helpful
91if the project is currently on a topic branch, but the manifest
92revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070093
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070094The -s/--smart-sync option can be used to sync to a known good
95build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +020096manifest. The -t/--smart-tag option is similar and allows you to
97specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070098
David Pursehousecf76b1b2012-09-14 10:31:42 +090099The -u/--manifest-server-username and -p/--manifest-server-password
100options can be used to specify a username and password to authenticate
101with the manifest server when using the -s or -t option.
102
103If -u and -p are not specified when using the -s or -t option, '%prog'
104will attempt to read authentication credentials for the manifest server
105from the user's .netrc file.
106
107'%prog' will not use authentication credentials from -u/-p or .netrc
108if the manifest server specified in the manifest file already includes
109credentials.
110
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400111By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400112to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500113
Kevin Degiabaa7f32014-11-12 11:27:45 -0700114The --force-sync option can be used to overwrite existing git
115directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900116object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700117refs may be removed when overwriting.
118
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500119The --force-remove-dirty option can be used to remove previously used
120projects with uncommitted changes. WARNING: This may cause data to be
121lost since uncommitted changes may be removed with projects that no longer
122exist in the manifest.
123
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700124The --no-clone-bundle option disables any attempt to use
125$URL/clone.bundle to bootstrap a new Git repository from a
126resumeable bundle file on a content delivery network. This
127may be necessary if there are problems with the local Python
128HTTP client or proxy configuration, but the Git binary works.
129
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800130The --fetch-submodules option enables fetching Git submodules
131of a project from server.
132
David Pursehousef2fad612015-01-29 14:36:28 +0900133The -c/--current-branch option can be used to only fetch objects that
134are on the branch specified by a project's revision.
135
David Pursehouseb1553542014-09-04 21:28:09 +0900136The --optimized-fetch option can be used to only fetch projects that
137are fixed to a sha1 revision if the sha1 revision does not already
138exist locally.
139
David Pursehouse74cfd272015-10-14 10:50:15 +0900140The --prune option can be used to remove any refs that no longer
141exist on the remote.
142
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400143# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700144
145If at least one project remote URL uses an SSH connection (ssh://,
146git+ssh://, or user@host:path syntax) repo will automatically
147enable the SSH ControlMaster option when connecting to that host.
148This feature permits other projects in the same '%prog' session to
149reuse the same SSH tunnel, saving connection setup overheads.
150
151To disable this behavior on UNIX platforms, set the GIT_SSH
152environment variable to 'ssh'. For example:
153
154 export GIT_SSH=ssh
155 %prog
156
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400157# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700158
159This feature is automatically disabled on Windows, due to the lack
160of UNIX domain socket support.
161
162This feature is not compatible with url.insteadof rewrites in the
163user's ~/.gitconfig. '%prog' is currently not able to perform the
164rewrite early enough to establish the ControlMaster tunnel.
165
166If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
167later is required to fix a server side protocol bug.
168
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169"""
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500170 PARALLEL_JOBS = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171
Mike Frysinger9180a072021-04-13 14:57:40 -0400172 def _CommonOptions(self, p):
Mike Frysingerc177f942021-05-04 08:06:36 -0400173 if self.manifest:
174 try:
175 self.PARALLEL_JOBS = self.manifest.default.sync_j
176 except ManifestParseError:
177 pass
Mike Frysinger9180a072021-04-13 14:57:40 -0400178 super()._CommonOptions(p)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700179
Mike Frysinger9180a072021-04-13 14:57:40 -0400180 def _Options(self, p, show_smart=True):
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400181 p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
182 help='number of network jobs to run in parallel (defaults to --jobs)')
183 p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
184 help='number of local checkout jobs to run in parallel (defaults to --jobs)')
185
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500186 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200187 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400188 help='obsolete option (to be deleted in the future)')
189 p.add_option('--fail-fast',
190 dest='fail_fast', action='store_true',
191 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700192 p.add_option('--force-sync',
193 dest='force_sync', action='store_true',
194 help="overwrite an existing git directory if it needs to "
195 "point to a different object directory. WARNING: this "
196 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500197 p.add_option('--force-remove-dirty',
198 dest='force_remove_dirty', action='store_true',
199 help="force remove projects with uncommitted modifications if "
200 "projects no longer exist in the manifest. "
201 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900202 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700203 dest='local_only', action='store_true',
204 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900205 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100206 dest='mp_update', action='store_false', default='true',
207 help='use the existing manifest checkout as-is. '
208 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900209 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700210 dest='network_only', action='store_true',
211 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900212 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700213 dest='detach_head', action='store_true',
214 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900215 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700216 dest='current_branch_only', action='store_true',
217 help='fetch only current branch from server')
Mike Frysinger73561142021-05-03 01:10:09 -0400218 p.add_option('--no-current-branch',
219 dest='current_branch_only', action='store_false',
220 help='fetch all branches from server')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500221 p.add_option('-m', '--manifest-name',
222 dest='manifest_name',
223 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700224 p.add_option('--clone-bundle', action='store_true',
225 help='enable use of /clone.bundle on HTTP/HTTPS')
226 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700227 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800228 p.add_option('-u', '--manifest-server-username', action='store',
229 dest='manifest_server_username',
230 help='username to authenticate with the manifest server')
231 p.add_option('-p', '--manifest-server-password', action='store',
232 dest='manifest_server_password',
233 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800234 p.add_option('--fetch-submodules',
235 dest='fetch_submodules', action='store_true',
236 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800237 p.add_option('--use-superproject', action='store_true',
238 help='use the manifest superproject to sync projects')
Raman Tenneti23ea7542021-05-07 14:01:54 -0700239 p.add_option('--no-use-superproject', action='store_false',
240 dest='use_superproject',
241 help='disable use of manifest superprojects')
Mike Frysingerd68ed632021-05-03 01:21:35 -0400242 p.add_option('--tags',
243 action='store_false',
244 help='fetch tags')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700245 p.add_option('--no-tags',
Mike Frysingerd68ed632021-05-03 01:21:35 -0400246 dest='tags', action='store_false',
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700247 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900248 p.add_option('--optimized-fetch',
249 dest='optimized_fetch', action='store_true',
250 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600251 p.add_option('--retry-fetches',
252 default=0, action='store', type='int',
253 help='number of times to retry fetches on transient errors')
David Pursehouse74cfd272015-10-14 10:50:15 +0900254 p.add_option('--prune', dest='prune', action='store_true',
255 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700256 if show_smart:
257 p.add_option('-s', '--smart-sync',
258 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900259 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200260 p.add_option('-t', '--smart-tag',
261 dest='smart_tag', action='store',
262 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700263
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700264 g = p.add_option_group('repo Version options')
265 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500266 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700268 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800269 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700270 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700271
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800272 def _GetBranch(self):
273 """Returns the branch name for getting the approved manifest."""
274 p = self.manifest.manifestProject
275 b = p.GetBranch(p.CurrentBranch)
276 branch = b.merge
277 if branch.startswith(R_HEADS):
278 branch = branch[len(R_HEADS):]
279 return branch
280
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700281 def _GetCurrentBranchOnly(self, opt):
282 """Returns True if current-branch or use-superproject options are enabled."""
Xin Li0cb6e922021-06-16 10:19:00 -0700283 return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest)
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700284
Raman Tennetifeb28912021-05-02 19:47:29 -0700285 def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests):
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800286 """Update revisionId of every project with the SHA from superproject.
287
288 This function updates each project's revisionId with SHA from superproject.
289 It writes the updated manifest into a file and reloads the manifest from it.
290
291 Args:
292 opt: Program options returned from optparse. See _Options().
293 args: Arguments to pass to GetProjects. See the GetProjects
294 docstring for details.
Raman Tennetifeb28912021-05-02 19:47:29 -0700295 load_local_manifests: Whether to load local manifests.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800296
297 Returns:
Raman Tenneti784e16f2021-06-11 17:29:45 -0700298 Returns path to the overriding manifest file instead of None.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800299 """
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800300 superproject = git_superproject.Superproject(self.manifest,
Raman Tennetief99ec02021-03-04 10:29:40 -0800301 self.repodir,
Raman Tenneti784e16f2021-06-11 17:29:45 -0700302 self.git_event_log,
Raman Tennetief99ec02021-03-04 10:29:40 -0800303 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 Tenneti784e16f2021-06-11 17:29:45 -0700307 update_result = superproject.UpdateProjectsRevisionId(all_projects)
308 manifest_path = update_result.manifest_path
309 if manifest_path:
310 self._ReloadManifest(manifest_path, load_local_manifests)
311 else:
Raman Tennetif907ced2021-05-07 10:22:16 -0700312 print('error: Update of revsionId from superproject has failed. '
313 'Please resync with --no-use-superproject option',
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800314 file=sys.stderr)
Raman Tenneti784e16f2021-06-11 17:29:45 -0700315 if update_result.fatal:
316 sys.exit(1)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800317 return manifest_path
318
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500319 def _FetchProjectList(self, opt, projects):
320 """Main function of the fetch worker.
321
322 The projects we're given share the same underlying git object store, so we
323 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800324
David James8d201162013-10-11 17:03:19 -0700325 Delegates most of the work to _FetchHelper.
326
327 Args:
328 opt: Program options returned from optparse. See _Options().
329 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700330 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500331 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700332
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500333 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700334 """Fetch git objects for a single project.
335
David Pursehousec1b86a22012-11-14 11:36:51 +0900336 Args:
337 opt: Program options returned from optparse. See _Options().
338 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700339
340 Returns:
341 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900342 """
David Rileye0684ad2017-04-05 00:02:59 -0700343 start = time.time()
344 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500345 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900346 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500347 success = project.Sync_NetworkHalf(
348 quiet=opt.quiet,
349 verbose=opt.verbose,
350 output_redir=buf,
351 current_branch_only=self._GetCurrentBranchOnly(opt),
352 force_sync=opt.force_sync,
353 clone_bundle=opt.clone_bundle,
354 tags=opt.tags, archive=self.manifest.IsArchive,
355 optimized_fetch=opt.optimized_fetch,
356 retry_fetches=opt.retry_fetches,
357 prune=opt.prune,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400358 ssh_proxy=self.ssh_proxy,
Raman Tennetif32f2432021-04-12 20:57:25 -0700359 clone_filter=self.manifest.CloneFilter,
360 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700361
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500362 output = buf.getvalue()
363 if opt.verbose and output:
364 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700365
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500366 if not success:
367 print('error: Cannot fetch %s from %s'
368 % (project.name, project.remote.url),
369 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700370 except GitError as e:
371 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500372 except Exception as e:
373 print('error: Cannot fetch %s (%s: %s)'
374 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
375 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500376
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500377 finish = time.time()
378 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700379
Mike Frysinger339f2df2021-05-06 00:44:42 -0400380 @classmethod
381 def _FetchInitChild(cls, ssh_proxy):
382 cls.ssh_proxy = ssh_proxy
383
384 def _Fetch(self, projects, opt, err_event, ssh_proxy):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500385 ret = True
386
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400387 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700388 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400389 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800390
David James89ece422014-01-09 18:51:58 -0800391 objdir_project_map = dict()
392 for project in projects:
393 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500394 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700395
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500396 def _ProcessResults(results_sets):
397 ret = True
398 for results in results_sets:
399 for (success, project, start, finish) in results:
400 self._fetch_times.Set(project, finish - start)
401 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
402 start, finish, success)
403 # Check for any errors before running any more tasks.
404 # ...we'll let existing jobs finish, though.
405 if not success:
406 ret = False
407 else:
408 fetched.add(project.gitdir)
409 pm.update(msg=project.name)
410 if not ret and opt.fail_fast:
411 break
412 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700413
Mike Frysinger339f2df2021-05-06 00:44:42 -0400414 # We pass the ssh proxy settings via the class. This allows multiprocessing
415 # to pickle it up when spawning children. We can't pass it as an argument
416 # to _FetchProjectList below as multiprocessing is unable to pickle those.
417 Sync.ssh_proxy = None
418
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500419 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400420 if len(projects_list) == 1 or jobs == 1:
Mike Frysinger339f2df2021-05-06 00:44:42 -0400421 self._FetchInitChild(ssh_proxy)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500422 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
423 ret = False
424 else:
425 # Favor throughput over responsiveness when quiet. It seems that imap()
426 # will yield results in batches relative to chunksize, so even as the
427 # children finish a sync, we won't see the result until one child finishes
428 # ~chunksize jobs. When using a large --jobs with large chunksize, this
429 # can be jarring as there will be a large initial delay where repo looks
430 # like it isn't doing anything and sits at 0%, but then suddenly completes
431 # a lot of jobs all at once. Since this code is more network bound, we
432 # can accept a bit more CPU overhead with a smaller chunksize so that the
433 # user sees more immediate & continuous feedback.
434 if opt.quiet:
435 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800436 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500437 pm.update(inc=0, msg='warming up')
438 chunksize = 4
Mike Frysinger339f2df2021-05-06 00:44:42 -0400439 with multiprocessing.Pool(
440 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500441 results = pool.imap_unordered(
442 functools.partial(self._FetchProjectList, opt),
443 projects_list,
444 chunksize=chunksize)
445 if not _ProcessResults(results):
446 ret = False
447 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800448
Mike Frysinger339f2df2021-05-06 00:44:42 -0400449 # Cleanup the reference now that we're done with it, and we're going to
450 # release any resources it points to. If we don't, later multiprocessing
451 # usage (e.g. checkouts) will try to pickle and then crash.
452 del Sync.ssh_proxy
453
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700454 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700455 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700456
Julien Campergue335f5ef2013-10-16 11:02:35 +0200457 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400458 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200459
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500460 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700461
Mike Frysingerb4429432021-05-05 20:03:26 -0400462 def _FetchMain(self, opt, args, all_projects, err_event, manifest_name,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400463 load_local_manifests, ssh_proxy):
Mike Frysingerb4429432021-05-05 20:03:26 -0400464 """The main network fetch loop.
465
466 Args:
467 opt: Program options returned from optparse. See _Options().
468 args: Command line args used to filter out projects.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200469 all_projects: List of all projects that should be fetched.
Mike Frysingerb4429432021-05-05 20:03:26 -0400470 err_event: Whether an error was hit while processing.
471 manifest_name: Manifest file to be reloaded.
472 load_local_manifests: Whether to load local manifests.
Mike Frysinger339f2df2021-05-06 00:44:42 -0400473 ssh_proxy: SSH manager for clients & masters.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200474
475 Returns:
476 List of all projects that should be checked out.
Mike Frysingerb4429432021-05-05 20:03:26 -0400477 """
478 rp = self.manifest.repoProject
479
480 to_fetch = []
481 now = time.time()
482 if _ONE_DAY_S <= (now - rp.LastFetch):
483 to_fetch.append(rp)
484 to_fetch.extend(all_projects)
485 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
486
Mike Frysinger339f2df2021-05-06 00:44:42 -0400487 success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400488 if not success:
489 err_event.set()
490
491 _PostRepoFetch(rp, opt.repo_verify)
492 if opt.network_only:
493 # bail out now; the rest touches the working tree
494 if err_event.is_set():
495 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
496 sys.exit(1)
497 return
498
499 # Iteratively fetch missing and/or nested unregistered submodules
500 previously_missing_set = set()
501 while True:
502 self._ReloadManifest(manifest_name, load_local_manifests)
503 all_projects = self.GetProjects(args,
504 missing_ok=True,
505 submodules_ok=opt.fetch_submodules)
506 missing = []
507 for project in all_projects:
508 if project.gitdir not in fetched:
509 missing.append(project)
510 if not missing:
511 break
512 # Stop us from non-stopped fetching actually-missing repos: If set of
513 # missing repos has not been changed from last fetch, we break.
514 missing_set = set(p.name for p in missing)
515 if previously_missing_set == missing_set:
516 break
517 previously_missing_set = missing_set
Mike Frysinger339f2df2021-05-06 00:44:42 -0400518 success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400519 if not success:
520 err_event.set()
521 fetched.update(new_fetched)
522
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200523 return all_projects
524
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500525 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700526 """Checkout work tree for one project
527
528 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500529 detach_head: Whether to leave a detached HEAD.
530 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700531 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700532
533 Returns:
534 Whether the fetch was successful.
535 """
Xin Li745be2e2019-06-03 11:24:30 -0700536 start = time.time()
537 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500538 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700539 success = False
540 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500541 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500542 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700543 except GitError as e:
544 print('error.GitError: Cannot checkout %s: %s' %
545 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500546 except Exception as e:
547 print('error: Cannot checkout %s: %s: %s' %
548 (project.name, type(e).__name__, str(e)),
549 file=sys.stderr)
550 raise
Xin Li745be2e2019-06-03 11:24:30 -0700551
Mike Frysingerebf04a42021-02-23 20:48:04 -0500552 if not success:
553 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
554 finish = time.time()
555 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700556
Mike Frysingerebf04a42021-02-23 20:48:04 -0500557 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700558 """Checkout projects listed in all_projects
559
560 Args:
561 all_projects: List of all projects that should be checked out.
562 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500563 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700564 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500565 # Only checkout projects with worktrees.
566 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700567
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500568 def _ProcessResults(pool, pm, results):
569 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500570 for (success, project, start, finish) in results:
571 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
572 start, finish, success)
573 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500574 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500575 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500576 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500577 err_results.append(project.relpath)
578 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500579 if pool:
580 pool.close()
581 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500582 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500583 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700584
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500585 return self.ExecuteInParallel(
586 opt.jobs_checkout if opt.jobs_checkout else self.jobs,
587 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
588 all_projects,
589 callback=_ProcessResults,
590 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500591
Mike Frysinger5a033082019-09-23 19:21:20 -0400592 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400593 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400594 pm.update(inc=0, msg='prescan')
595
Gabe Black2ff30292014-10-09 17:54:35 -0700596 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700597 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500598 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500599 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900600 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100601 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400602 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100603 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500604 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500605 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500606 else:
607 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400608 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500609 'versions of git; please upgrade to git-2.7.0+.'
610 % (project.relpath,),
611 file=sys.stderr)
612 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700613 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700614
Mike Frysinger65af2602021-04-08 22:47:44 -0400615 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
616
617 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700618 jobs = min(self.jobs, cpu_count)
619
620 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700621 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400622 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700623 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400624 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700625 return
626
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400627 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700628
629 threads = set()
630 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700631
David James8d201162013-10-11 17:03:19 -0700632 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400633 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700634 try:
635 try:
David James8d201162013-10-11 17:03:19 -0700636 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700637 except GitError:
638 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900639 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700640 err_event.set()
641 raise
642 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400643 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700644 sem.release()
645
Gabe Black2ff30292014-10-09 17:54:35 -0700646 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500647 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700648 break
649 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700650 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700651 t.daemon = True
652 threads.add(t)
653 t.start()
654
655 for t in threads:
656 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400657 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700658
Raman Tennetifeb28912021-05-02 19:47:29 -0700659 def _ReloadManifest(self, manifest_name=None, load_local_manifests=True):
660 """Reload the manfiest from the file specified by the |manifest_name|.
661
662 It unloads the manifest if |manifest_name| is None.
663
664 Args:
665 manifest_name: Manifest file to be reloaded.
666 load_local_manifests: Whether to load local manifests.
667 """
Tim Kilbourn07669002013-03-08 15:02:49 -0800668 if manifest_name:
669 # Override calls _Unload already
Raman Tennetifeb28912021-05-02 19:47:29 -0700670 self.manifest.Override(manifest_name, load_local_manifests=load_local_manifests)
Tim Kilbourn07669002013-03-08 15:02:49 -0800671 else:
672 self.manifest._Unload()
673
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500674 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700675 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700676 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700677 if project.relpath:
678 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700679 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500680 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700681 old_project_paths = []
682
683 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500684 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700685 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800686 # In reversed order, so subfolders are deleted before parent folder.
687 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700688 if not path:
689 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700690 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900691 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700692 gitdir = os.path.join(self.manifest.topdir, path, '.git')
693 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900694 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900695 manifest=self.manifest,
696 name=path,
697 remote=RemoteSpec('origin'),
698 gitdir=gitdir,
699 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500700 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900701 worktree=os.path.join(self.manifest.topdir, path),
702 relpath=path,
703 revisionExpr='HEAD',
704 revisionId=None,
705 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500706 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900707 quiet=opt.quiet,
708 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400709 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700710
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700711 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500712 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700713 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700714 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700715 return 0
716
jiajia tanga590e642021-04-25 20:02:02 +0800717 def UpdateCopyLinkfileList(self):
718 """Save all dests of copyfile and linkfile, and update them if needed.
719
720 Returns:
721 Whether update was successful.
722 """
723 new_paths = {}
724 new_linkfile_paths = []
725 new_copyfile_paths = []
726 for project in self.GetProjects(None, missing_ok=True):
727 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
728 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
729
730 new_paths = {
731 'linkfile': new_linkfile_paths,
732 'copyfile': new_copyfile_paths,
733 }
734
735 copylinkfile_name = 'copy-link-files.json'
736 copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name)
737 old_copylinkfile_paths = {}
738
739 if os.path.exists(copylinkfile_path):
740 with open(copylinkfile_path, 'rb') as fp:
741 try:
742 old_copylinkfile_paths = json.load(fp)
743 except:
744 print('error: %s is not a json formatted file.' %
745 copylinkfile_path, file=sys.stderr)
746 platform_utils.remove(copylinkfile_path)
747 return False
748
749 need_remove_files = []
750 need_remove_files.extend(
751 set(old_copylinkfile_paths.get('linkfile', [])) -
752 set(new_linkfile_paths))
753 need_remove_files.extend(
754 set(old_copylinkfile_paths.get('copyfile', [])) -
755 set(new_copyfile_paths))
756
757 for need_remove_file in need_remove_files:
758 try:
759 platform_utils.remove(need_remove_file)
760 except OSError as e:
761 if e.errno == errno.ENOENT:
762 # Try to remove the updated copyfile or linkfile.
763 # So, if the file is not exist, nothing need to do.
764 pass
765
766 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
767 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
768 json.dump(new_paths, fp)
769 return True
770
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400771 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
772 if not self.manifest.manifest_server:
773 print('error: cannot smart sync: no manifest server defined in '
774 'manifest', file=sys.stderr)
775 sys.exit(1)
776
777 manifest_server = self.manifest.manifest_server
778 if not opt.quiet:
779 print('Using manifest server %s' % manifest_server)
780
David Pursehouseeeff3532020-02-12 11:24:10 +0900781 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400782 username = None
783 password = None
784 if opt.manifest_server_username and opt.manifest_server_password:
785 username = opt.manifest_server_username
786 password = opt.manifest_server_password
787 else:
788 try:
789 info = netrc.netrc()
790 except IOError:
791 # .netrc file does not exist or could not be opened
792 pass
793 else:
794 try:
795 parse_result = urllib.parse.urlparse(manifest_server)
796 if parse_result.hostname:
797 auth = info.authenticators(parse_result.hostname)
798 if auth:
799 username, _account, password = auth
800 else:
801 print('No credentials found for %s in .netrc'
802 % parse_result.hostname, file=sys.stderr)
803 except netrc.NetrcParseError as e:
804 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
805
806 if (username and password):
807 manifest_server = manifest_server.replace('://', '://%s:%s@' %
808 (username, password),
809 1)
810
811 transport = PersistentTransport(manifest_server)
812 if manifest_server.startswith('persistent-'):
813 manifest_server = manifest_server[len('persistent-'):]
814
815 try:
816 server = xmlrpc.client.Server(manifest_server, transport=transport)
817 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800818 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400819
Mike Frysinger56ce3462019-12-04 19:30:48 -0500820 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500821 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400822 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500823 elif ('TARGET_PRODUCT' in os.environ and
824 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500825 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
826 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400827 [success, manifest_str] = server.GetApprovedManifest(branch, target)
828 else:
829 [success, manifest_str] = server.GetApprovedManifest(branch)
830 else:
831 assert(opt.smart_tag)
832 [success, manifest_str] = server.GetManifest(opt.smart_tag)
833
834 if success:
835 manifest_name = os.path.basename(smart_sync_manifest_path)
836 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500837 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400838 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400839 except IOError as e:
840 print('error: cannot write manifest to %s:\n%s'
841 % (smart_sync_manifest_path, e),
842 file=sys.stderr)
843 sys.exit(1)
844 self._ReloadManifest(manifest_name)
845 else:
846 print('error: manifest server RPC call failed: %s' %
847 manifest_str, file=sys.stderr)
848 sys.exit(1)
849 except (socket.error, IOError, xmlrpc.client.Fault) as e:
850 print('error: cannot connect to manifest server %s:\n%s'
851 % (self.manifest.manifest_server, e), file=sys.stderr)
852 sys.exit(1)
853 except xmlrpc.client.ProtocolError as e:
854 print('error: cannot connect to manifest server %s:\n%d %s'
855 % (self.manifest.manifest_server, e.errcode, e.errmsg),
856 file=sys.stderr)
857 sys.exit(1)
858
859 return manifest_name
860
Mike Frysingerfb527e32019-08-27 02:34:32 -0400861 def _UpdateManifestProject(self, opt, mp, manifest_name):
862 """Fetch & update the local manifest project."""
863 if not opt.local_only:
864 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500865 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700866 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200867 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500868 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400869 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600870 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400871 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700872 clone_filter=self.manifest.CloneFilter,
873 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400874 finish = time.time()
875 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
876 start, finish, success)
877
878 if mp.HasChanges:
879 syncbuf = SyncBuffer(mp.config)
880 start = time.time()
881 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
882 clean = syncbuf.Finish()
883 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
884 start, time.time(), clean)
885 if not clean:
886 sys.exit(1)
Mike Frysinger05638bf2021-05-04 15:33:31 -0400887 self._ReloadManifest(manifest_name)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400888 if opt.jobs is None:
889 self.jobs = self.manifest.default.sync_j
890
Mike Frysingerae6cb082019-08-27 01:10:59 -0400891 def ValidateOptions(self, opt, args):
892 if opt.force_broken:
893 print('warning: -f/--force-broken is now the default behavior, and the '
894 'options are deprecated', file=sys.stderr)
895 if opt.network_only and opt.detach_head:
896 self.OptionParser.error('cannot combine -n and -d')
897 if opt.network_only and opt.local_only:
898 self.OptionParser.error('cannot combine -n and -l')
899 if opt.manifest_name and opt.smart_sync:
900 self.OptionParser.error('cannot combine -m and -s')
901 if opt.manifest_name and opt.smart_tag:
902 self.OptionParser.error('cannot combine -m and -t')
903 if opt.manifest_server_username or opt.manifest_server_password:
904 if not (opt.smart_sync or opt.smart_tag):
905 self.OptionParser.error('-u and -p may only be combined with -s or -t')
906 if None in [opt.manifest_server_username, opt.manifest_server_password]:
907 self.OptionParser.error('both -u and -p must be given')
908
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800910 if opt.jobs:
911 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700912 if self.jobs > 1:
913 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400914 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700915
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500916 if opt.manifest_name:
917 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700918
Chirayu Desaia892b102013-06-11 14:18:46 +0530919 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900920 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900921 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530922
Xin Lid79a4bc2020-05-20 16:03:45 -0700923 if opt.clone_bundle is None:
924 opt.clone_bundle = self.manifest.CloneBundle
925
Victor Boivie08c880d2011-04-19 10:32:52 +0200926 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400927 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
928 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900929 if os.path.isfile(smart_sync_manifest_path):
930 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800931 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900932 except OSError as e:
933 print('error: failed to remove existing smart sync override manifest: %s' %
934 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700935
Mike Frysingerc99322a2021-05-04 15:32:43 -0400936 err_event = multiprocessing.Event()
Mike Frysinger5a033082019-09-23 19:21:20 -0400937
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700938 rp = self.manifest.repoProject
939 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500940 cb = rp.CurrentBranch
941 if cb:
942 base = rp.GetBranch(cb).merge
943 if not base or not base.startswith('refs/heads/'):
944 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400945 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500946 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947
948 mp = self.manifest.manifestProject
949 mp.PreSync()
950
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800951 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700952 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800953
Fredrik de Grootcc960972019-11-22 09:04:31 +0100954 if not opt.mp_update:
955 print('Skipping update of local manifest project.')
956 else:
957 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700958
Raman Tennetifeb28912021-05-02 19:47:29 -0700959 load_local_manifests = not self.manifest.HasLocalManifests
Xin Li0cb6e922021-06-16 10:19:00 -0700960 if git_superproject.UseSuperproject(opt, self.manifest):
Raman Tenneti784e16f2021-06-11 17:29:45 -0700961 new_manifest_name = self._UpdateProjectsRevisionId(opt, args, load_local_manifests)
962 if not new_manifest_name:
963 manifest_name = new_manifest_name
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800964
Simran Basib9a1b732015-08-20 12:19:28 -0700965 if self.gitc_manifest:
966 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700967 missing_ok=True)
968 gitc_projects = []
969 opened_projects = []
970 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700971 if project.relpath in self.gitc_manifest.paths and \
972 self.gitc_manifest.paths[project.relpath].old_revision:
973 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700974 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700975 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700976
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700977 if not args:
978 gitc_projects = None
979
980 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700981 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700982 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
983 if manifest_name:
984 manifest.Override(manifest_name)
985 else:
986 manifest.Override(self.manifest.manifestFile)
987 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
988 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700989 gitc_projects)
990 print('GITC client successfully synced.')
991
992 # The opened projects need to be synced as normal, therefore we
993 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700994 # TODO: make this more reliable -- if there's a project name/path overlap,
995 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900996 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
997 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700998 if not args:
999 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001000 all_projects = self.GetProjects(args,
1001 missing_ok=True,
1002 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003
Mike Frysinger5a033082019-09-23 19:21:20 -04001004 err_network_sync = False
1005 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -04001006
Dave Borowitz67700e92012-10-23 15:00:54 -07001007 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -07001008 if not opt.local_only:
Mike Frysinger339f2df2021-05-06 00:44:42 -04001009 with multiprocessing.Manager() as manager:
1010 with ssh.ProxyManager(manager) as ssh_proxy:
1011 # Initialize the socket dir once in the parent.
1012 ssh_proxy.sock()
Peter Kjellerstedtd1776092021-05-19 19:37:23 +02001013 all_projects = self._FetchMain(opt, args, all_projects, err_event,
1014 manifest_name, load_local_manifests,
1015 ssh_proxy)
Mike Frysinger339f2df2021-05-06 00:44:42 -04001016
1017 if opt.network_only:
1018 return
Mike Frysinger5a033082019-09-23 19:21:20 -04001019
1020 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001021 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001022 err_network_sync = True
1023 if opt.fail_fast:
1024 print('\nerror: Exited sync due to fetch errors.\n'
1025 'Local checkouts *not* updated. Resolve network issues & '
1026 'retry.\n'
1027 '`repo sync -l` will update some local checkouts.',
1028 file=sys.stderr)
1029 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001030
Julien Campergue335f5ef2013-10-16 11:02:35 +02001031 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001032 # bail out now, we have no working tree
1033 return
1034
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -05001035 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -04001036 err_event.set()
1037 err_update_projects = True
1038 if opt.fail_fast:
1039 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1040 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001041
Mike Frysinger14208f42021-05-04 15:31:51 -04001042 err_update_linkfiles = not self.UpdateCopyLinkfileList()
1043 if err_update_linkfiles:
jiajia tanga590e642021-04-25 20:02:02 +08001044 err_event.set()
jiajia tanga590e642021-04-25 20:02:02 +08001045 if opt.fail_fast:
1046 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
1047 sys.exit(1)
1048
Mike Frysinger5a033082019-09-23 19:21:20 -04001049 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -05001050 # NB: We don't exit here because this is the last step.
1051 err_checkout = not self._Checkout(all_projects, opt, err_results)
1052 if err_checkout:
1053 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001055 # If there's a notice that's supposed to print at the end of the sync, print
1056 # it now...
1057 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001058 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001059
Mike Frysinger5a033082019-09-23 19:21:20 -04001060 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001061 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001062 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1063 if err_network_sync:
1064 print('error: Downloading network changes failed.', file=sys.stderr)
1065 if err_update_projects:
1066 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +08001067 if err_update_linkfiles:
1068 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -04001069 if err_checkout:
1070 print('error: Checking out local projects failed.', file=sys.stderr)
1071 if err_results:
1072 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1073 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1074 file=sys.stderr)
1075 sys.exit(1)
1076
Mike Frysingere19d9e12020-02-12 11:23:32 -05001077 if not opt.quiet:
1078 print('repo sync has finished successfully.')
1079
David Pursehouse819827a2020-02-12 15:20:19 +09001080
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001081def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001082 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001083 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001084 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001085 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001086 if project.Exists:
1087 project.PostRepoUpgrade()
1088
David Pursehouse819827a2020-02-12 15:20:19 +09001089
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001090def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001091 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001092 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001093 wrapper = Wrapper()
1094 try:
1095 rev = rp.bare_git.describe(rp.GetRevisionId())
1096 except GitError:
1097 rev = None
1098 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1099 # See if we're held back due to missing signed tag.
1100 current_revid = rp.bare_git.rev_parse('HEAD')
1101 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1102 if current_revid != new_revid:
1103 # We want to switch to the new rev, but also not trash any uncommitted
1104 # changes. This helps with local testing/hacking.
1105 # If a local change has been made, we will throw that away.
1106 # We also have to make sure this will switch to an older commit if that's
1107 # the latest tag in order to support release rollback.
1108 try:
1109 rp.work_git.reset('--keep', new_rev)
1110 except GitError as e:
1111 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001112 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001113 raise RepoChangedException(['--repo-upgraded'])
1114 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001115 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001116 else:
1117 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001118 print('repo version %s is current' % rp.work_git.describe(HEAD),
1119 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001120
David Pursehouse819827a2020-02-12 15:20:19 +09001121
Dave Borowitz67700e92012-10-23 15:00:54 -07001122class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001123 _ALPHA = 0.5
1124
Dave Borowitz67700e92012-10-23 15:00:54 -07001125 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001126 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001127 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001128 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001129
1130 def Get(self, project):
1131 self._Load()
1132 return self._times.get(project.name, _ONE_DAY_S)
1133
1134 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001135 self._Load()
1136 name = project.name
1137 old = self._times.get(name, t)
1138 self._seen.add(name)
1139 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001140 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001141
1142 def _Load(self):
1143 if self._times is None:
1144 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001145 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001146 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001147 except (IOError, ValueError):
1148 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001149 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001150 except OSError:
1151 pass
1152 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001153
1154 def Save(self):
1155 if self._times is None:
1156 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001157
1158 to_delete = []
1159 for name in self._times:
1160 if name not in self._seen:
1161 to_delete.append(name)
1162 for name in to_delete:
1163 del self._times[name]
1164
Dave Borowitz67700e92012-10-23 15:00:54 -07001165 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001166 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001167 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001168 except (IOError, TypeError):
1169 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001170 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001171 except OSError:
1172 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001173
1174# This is a replacement for xmlrpc.client.Transport using urllib2
1175# and supporting persistent-http[s]. It cannot change hosts from
1176# request to request like the normal transport, the real url
1177# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001178
1179
Dan Willemsen0745bb22015-08-17 13:41:45 -07001180class PersistentTransport(xmlrpc.client.Transport):
1181 def __init__(self, orig_host):
1182 self.orig_host = orig_host
1183
1184 def request(self, host, handler, request_body, verbose=False):
1185 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1186 # Python doesn't understand cookies with the #HttpOnly_ prefix
1187 # Since we're only using them for HTTP, copy the file temporarily,
1188 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001189 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001190 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001191 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001192 try:
1193 with open(cookiefile) as f:
1194 for line in f:
1195 if line.startswith("#HttpOnly_"):
1196 line = line[len("#HttpOnly_"):]
1197 tmpcookiefile.write(line)
1198 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001199
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001200 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001201 try:
1202 cookiejar.load()
1203 except cookielib.LoadError:
1204 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001205 finally:
1206 tmpcookiefile.close()
1207 else:
1208 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001209
1210 proxyhandler = urllib.request.ProxyHandler
1211 if proxy:
1212 proxyhandler = urllib.request.ProxyHandler({
1213 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001214 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001215
1216 opener = urllib.request.build_opener(
1217 urllib.request.HTTPCookieProcessor(cookiejar),
1218 proxyhandler)
1219
1220 url = urllib.parse.urljoin(self.orig_host, handler)
1221 parse_results = urllib.parse.urlparse(url)
1222
1223 scheme = parse_results.scheme
1224 if scheme == 'persistent-http':
1225 scheme = 'http'
1226 if scheme == 'persistent-https':
1227 # If we're proxying through persistent-https, use http. The
1228 # proxy itself will do the https.
1229 if proxy:
1230 scheme = 'http'
1231 else:
1232 scheme = 'https'
1233
1234 # Parse out any authentication information using the base class
1235 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1236
1237 url = urllib.parse.urlunparse((
1238 scheme,
1239 host,
1240 parse_results.path,
1241 parse_results.params,
1242 parse_results.query,
1243 parse_results.fragment))
1244
1245 request = urllib.request.Request(url, request_body)
1246 if extra_headers is not None:
1247 for (name, header) in extra_headers:
1248 request.add_header(name, header)
1249 request.add_header('Content-Type', 'text/xml')
1250 try:
1251 response = opener.open(request)
1252 except urllib.error.HTTPError as e:
1253 if e.code == 501:
1254 # We may have been redirected through a login process
1255 # but our POST turned into a GET. Retry.
1256 response = opener.open(request)
1257 else:
1258 raise
1259
1260 p, u = xmlrpc.client.getparser()
1261 while 1:
1262 data = response.read(1024)
1263 if not data:
1264 break
1265 p.feed(data)
1266 p.close()
1267 return u.close()
1268
1269 def close(self):
1270 pass