blob: e6dbf1c8fc08e5b3cfd82017111d9e753acf55dc [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 Tenneti8db30d62021-07-06 21:30:06 -0700312 print('warning: Update of revisionId from superproject has failed, '
313 'repo sync will not use superproject to fetch the source. ',
314 'Please resync with the --no-use-superproject option to avoid this repo warning.',
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800315 file=sys.stderr)
Raman Tenneti8db30d62021-07-06 21:30:06 -0700316 if update_result.fatal and opt.use_superproject is not None:
Raman Tenneti784e16f2021-06-11 17:29:45 -0700317 sys.exit(1)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800318 return manifest_path
319
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500320 def _FetchProjectList(self, opt, projects):
321 """Main function of the fetch worker.
322
323 The projects we're given share the same underlying git object store, so we
324 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800325
David James8d201162013-10-11 17:03:19 -0700326 Delegates most of the work to _FetchHelper.
327
328 Args:
329 opt: Program options returned from optparse. See _Options().
330 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700331 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500332 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700333
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500334 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700335 """Fetch git objects for a single project.
336
David Pursehousec1b86a22012-11-14 11:36:51 +0900337 Args:
338 opt: Program options returned from optparse. See _Options().
339 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700340
341 Returns:
342 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900343 """
David Rileye0684ad2017-04-05 00:02:59 -0700344 start = time.time()
345 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500346 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900347 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500348 success = project.Sync_NetworkHalf(
349 quiet=opt.quiet,
350 verbose=opt.verbose,
351 output_redir=buf,
352 current_branch_only=self._GetCurrentBranchOnly(opt),
353 force_sync=opt.force_sync,
354 clone_bundle=opt.clone_bundle,
355 tags=opt.tags, archive=self.manifest.IsArchive,
356 optimized_fetch=opt.optimized_fetch,
357 retry_fetches=opt.retry_fetches,
358 prune=opt.prune,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400359 ssh_proxy=self.ssh_proxy,
Raman Tennetif32f2432021-04-12 20:57:25 -0700360 clone_filter=self.manifest.CloneFilter,
361 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700362
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500363 output = buf.getvalue()
Mike Frysinger58929732021-07-02 00:29:35 -0400364 if (opt.verbose or not success) and output:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500365 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700366
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500367 if not success:
368 print('error: Cannot fetch %s from %s'
369 % (project.name, project.remote.url),
370 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700371 except GitError as e:
372 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500373 except Exception as e:
374 print('error: Cannot fetch %s (%s: %s)'
375 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
376 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500377
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500378 finish = time.time()
379 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700380
Mike Frysinger339f2df2021-05-06 00:44:42 -0400381 @classmethod
382 def _FetchInitChild(cls, ssh_proxy):
383 cls.ssh_proxy = ssh_proxy
384
385 def _Fetch(self, projects, opt, err_event, ssh_proxy):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500386 ret = True
387
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400388 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700389 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400390 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800391
David James89ece422014-01-09 18:51:58 -0800392 objdir_project_map = dict()
393 for project in projects:
394 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500395 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700396
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500397 def _ProcessResults(results_sets):
398 ret = True
399 for results in results_sets:
400 for (success, project, start, finish) in results:
401 self._fetch_times.Set(project, finish - start)
402 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
403 start, finish, success)
404 # Check for any errors before running any more tasks.
405 # ...we'll let existing jobs finish, though.
406 if not success:
407 ret = False
408 else:
409 fetched.add(project.gitdir)
410 pm.update(msg=project.name)
411 if not ret and opt.fail_fast:
412 break
413 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700414
Mike Frysinger339f2df2021-05-06 00:44:42 -0400415 # We pass the ssh proxy settings via the class. This allows multiprocessing
416 # to pickle it up when spawning children. We can't pass it as an argument
417 # to _FetchProjectList below as multiprocessing is unable to pickle those.
418 Sync.ssh_proxy = None
419
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500420 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400421 if len(projects_list) == 1 or jobs == 1:
Mike Frysinger339f2df2021-05-06 00:44:42 -0400422 self._FetchInitChild(ssh_proxy)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500423 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
424 ret = False
425 else:
426 # Favor throughput over responsiveness when quiet. It seems that imap()
427 # will yield results in batches relative to chunksize, so even as the
428 # children finish a sync, we won't see the result until one child finishes
429 # ~chunksize jobs. When using a large --jobs with large chunksize, this
430 # can be jarring as there will be a large initial delay where repo looks
431 # like it isn't doing anything and sits at 0%, but then suddenly completes
432 # a lot of jobs all at once. Since this code is more network bound, we
433 # can accept a bit more CPU overhead with a smaller chunksize so that the
434 # user sees more immediate & continuous feedback.
435 if opt.quiet:
436 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800437 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500438 pm.update(inc=0, msg='warming up')
439 chunksize = 4
Mike Frysinger339f2df2021-05-06 00:44:42 -0400440 with multiprocessing.Pool(
441 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500442 results = pool.imap_unordered(
443 functools.partial(self._FetchProjectList, opt),
444 projects_list,
445 chunksize=chunksize)
446 if not _ProcessResults(results):
447 ret = False
448 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800449
Mike Frysinger339f2df2021-05-06 00:44:42 -0400450 # Cleanup the reference now that we're done with it, and we're going to
451 # release any resources it points to. If we don't, later multiprocessing
452 # usage (e.g. checkouts) will try to pickle and then crash.
453 del Sync.ssh_proxy
454
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700455 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700456 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700457
Julien Campergue335f5ef2013-10-16 11:02:35 +0200458 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400459 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200460
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500461 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700462
Mike Frysingerb4429432021-05-05 20:03:26 -0400463 def _FetchMain(self, opt, args, all_projects, err_event, manifest_name,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400464 load_local_manifests, ssh_proxy):
Mike Frysingerb4429432021-05-05 20:03:26 -0400465 """The main network fetch loop.
466
467 Args:
468 opt: Program options returned from optparse. See _Options().
469 args: Command line args used to filter out projects.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200470 all_projects: List of all projects that should be fetched.
Mike Frysingerb4429432021-05-05 20:03:26 -0400471 err_event: Whether an error was hit while processing.
472 manifest_name: Manifest file to be reloaded.
473 load_local_manifests: Whether to load local manifests.
Mike Frysinger339f2df2021-05-06 00:44:42 -0400474 ssh_proxy: SSH manager for clients & masters.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200475
476 Returns:
477 List of all projects that should be checked out.
Mike Frysingerb4429432021-05-05 20:03:26 -0400478 """
479 rp = self.manifest.repoProject
480
481 to_fetch = []
482 now = time.time()
483 if _ONE_DAY_S <= (now - rp.LastFetch):
484 to_fetch.append(rp)
485 to_fetch.extend(all_projects)
486 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
487
Mike Frysinger339f2df2021-05-06 00:44:42 -0400488 success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400489 if not success:
490 err_event.set()
491
492 _PostRepoFetch(rp, opt.repo_verify)
493 if opt.network_only:
494 # bail out now; the rest touches the working tree
495 if err_event.is_set():
496 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
497 sys.exit(1)
498 return
499
500 # Iteratively fetch missing and/or nested unregistered submodules
501 previously_missing_set = set()
502 while True:
503 self._ReloadManifest(manifest_name, load_local_manifests)
504 all_projects = self.GetProjects(args,
505 missing_ok=True,
506 submodules_ok=opt.fetch_submodules)
507 missing = []
508 for project in all_projects:
509 if project.gitdir not in fetched:
510 missing.append(project)
511 if not missing:
512 break
513 # Stop us from non-stopped fetching actually-missing repos: If set of
514 # missing repos has not been changed from last fetch, we break.
515 missing_set = set(p.name for p in missing)
516 if previously_missing_set == missing_set:
517 break
518 previously_missing_set = missing_set
Mike Frysinger339f2df2021-05-06 00:44:42 -0400519 success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400520 if not success:
521 err_event.set()
522 fetched.update(new_fetched)
523
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200524 return all_projects
525
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500526 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700527 """Checkout work tree for one project
528
529 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500530 detach_head: Whether to leave a detached HEAD.
531 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700532 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700533
534 Returns:
535 Whether the fetch was successful.
536 """
Xin Li745be2e2019-06-03 11:24:30 -0700537 start = time.time()
538 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500539 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700540 success = False
541 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500542 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500543 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700544 except GitError as e:
545 print('error.GitError: Cannot checkout %s: %s' %
546 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500547 except Exception as e:
548 print('error: Cannot checkout %s: %s: %s' %
549 (project.name, type(e).__name__, str(e)),
550 file=sys.stderr)
551 raise
Xin Li745be2e2019-06-03 11:24:30 -0700552
Mike Frysingerebf04a42021-02-23 20:48:04 -0500553 if not success:
554 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
555 finish = time.time()
556 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700557
Mike Frysingerebf04a42021-02-23 20:48:04 -0500558 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700559 """Checkout projects listed in all_projects
560
561 Args:
562 all_projects: List of all projects that should be checked out.
563 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500564 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700565 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500566 # Only checkout projects with worktrees.
567 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700568
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500569 def _ProcessResults(pool, pm, results):
570 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500571 for (success, project, start, finish) in results:
572 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
573 start, finish, success)
574 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500575 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500576 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500577 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500578 err_results.append(project.relpath)
579 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500580 if pool:
581 pool.close()
582 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500583 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500584 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700585
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500586 return self.ExecuteInParallel(
587 opt.jobs_checkout if opt.jobs_checkout else self.jobs,
588 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
589 all_projects,
590 callback=_ProcessResults,
591 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500592
Mike Frysinger5a033082019-09-23 19:21:20 -0400593 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400594 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400595 pm.update(inc=0, msg='prescan')
596
Gabe Black2ff30292014-10-09 17:54:35 -0700597 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700598 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500599 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500600 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900601 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100602 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400603 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100604 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500605 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500606 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500607 else:
608 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400609 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500610 'versions of git; please upgrade to git-2.7.0+.'
611 % (project.relpath,),
612 file=sys.stderr)
613 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700614 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700615
Mike Frysinger65af2602021-04-08 22:47:44 -0400616 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
617
618 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700619 jobs = min(self.jobs, cpu_count)
620
621 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700622 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400623 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700624 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400625 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700626 return
627
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400628 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700629
630 threads = set()
631 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700632
David James8d201162013-10-11 17:03:19 -0700633 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400634 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700635 try:
636 try:
David James8d201162013-10-11 17:03:19 -0700637 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700638 except GitError:
639 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900640 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700641 err_event.set()
642 raise
643 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400644 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700645 sem.release()
646
Gabe Black2ff30292014-10-09 17:54:35 -0700647 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500648 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700649 break
650 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700651 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700652 t.daemon = True
653 threads.add(t)
654 t.start()
655
656 for t in threads:
657 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400658 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700659
Raman Tennetifeb28912021-05-02 19:47:29 -0700660 def _ReloadManifest(self, manifest_name=None, load_local_manifests=True):
661 """Reload the manfiest from the file specified by the |manifest_name|.
662
663 It unloads the manifest if |manifest_name| is None.
664
665 Args:
666 manifest_name: Manifest file to be reloaded.
667 load_local_manifests: Whether to load local manifests.
668 """
Tim Kilbourn07669002013-03-08 15:02:49 -0800669 if manifest_name:
670 # Override calls _Unload already
Raman Tennetifeb28912021-05-02 19:47:29 -0700671 self.manifest.Override(manifest_name, load_local_manifests=load_local_manifests)
Tim Kilbourn07669002013-03-08 15:02:49 -0800672 else:
673 self.manifest._Unload()
674
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500675 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700676 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700677 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700678 if project.relpath:
679 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700680 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500681 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700682 old_project_paths = []
683
684 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500685 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700686 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800687 # In reversed order, so subfolders are deleted before parent folder.
688 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700689 if not path:
690 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700691 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900692 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700693 gitdir = os.path.join(self.manifest.topdir, path, '.git')
694 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900695 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900696 manifest=self.manifest,
697 name=path,
698 remote=RemoteSpec('origin'),
699 gitdir=gitdir,
700 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500701 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900702 worktree=os.path.join(self.manifest.topdir, path),
703 relpath=path,
704 revisionExpr='HEAD',
705 revisionId=None,
706 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500707 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900708 quiet=opt.quiet,
709 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400710 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700711
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700712 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500713 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700714 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700715 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700716 return 0
717
jiajia tanga590e642021-04-25 20:02:02 +0800718 def UpdateCopyLinkfileList(self):
719 """Save all dests of copyfile and linkfile, and update them if needed.
720
721 Returns:
722 Whether update was successful.
723 """
724 new_paths = {}
725 new_linkfile_paths = []
726 new_copyfile_paths = []
727 for project in self.GetProjects(None, missing_ok=True):
728 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
729 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
730
731 new_paths = {
732 'linkfile': new_linkfile_paths,
733 'copyfile': new_copyfile_paths,
734 }
735
736 copylinkfile_name = 'copy-link-files.json'
737 copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name)
738 old_copylinkfile_paths = {}
739
740 if os.path.exists(copylinkfile_path):
741 with open(copylinkfile_path, 'rb') as fp:
742 try:
743 old_copylinkfile_paths = json.load(fp)
744 except:
745 print('error: %s is not a json formatted file.' %
746 copylinkfile_path, file=sys.stderr)
747 platform_utils.remove(copylinkfile_path)
748 return False
749
750 need_remove_files = []
751 need_remove_files.extend(
752 set(old_copylinkfile_paths.get('linkfile', [])) -
753 set(new_linkfile_paths))
754 need_remove_files.extend(
755 set(old_copylinkfile_paths.get('copyfile', [])) -
756 set(new_copyfile_paths))
757
758 for need_remove_file in need_remove_files:
759 try:
760 platform_utils.remove(need_remove_file)
761 except OSError as e:
762 if e.errno == errno.ENOENT:
763 # Try to remove the updated copyfile or linkfile.
764 # So, if the file is not exist, nothing need to do.
765 pass
766
767 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
768 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
769 json.dump(new_paths, fp)
770 return True
771
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400772 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
773 if not self.manifest.manifest_server:
774 print('error: cannot smart sync: no manifest server defined in '
775 'manifest', file=sys.stderr)
776 sys.exit(1)
777
778 manifest_server = self.manifest.manifest_server
779 if not opt.quiet:
780 print('Using manifest server %s' % manifest_server)
781
David Pursehouseeeff3532020-02-12 11:24:10 +0900782 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400783 username = None
784 password = None
785 if opt.manifest_server_username and opt.manifest_server_password:
786 username = opt.manifest_server_username
787 password = opt.manifest_server_password
788 else:
789 try:
790 info = netrc.netrc()
791 except IOError:
792 # .netrc file does not exist or could not be opened
793 pass
794 else:
795 try:
796 parse_result = urllib.parse.urlparse(manifest_server)
797 if parse_result.hostname:
798 auth = info.authenticators(parse_result.hostname)
799 if auth:
800 username, _account, password = auth
801 else:
802 print('No credentials found for %s in .netrc'
803 % parse_result.hostname, file=sys.stderr)
804 except netrc.NetrcParseError as e:
805 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
806
807 if (username and password):
808 manifest_server = manifest_server.replace('://', '://%s:%s@' %
809 (username, password),
810 1)
811
812 transport = PersistentTransport(manifest_server)
813 if manifest_server.startswith('persistent-'):
814 manifest_server = manifest_server[len('persistent-'):]
815
816 try:
817 server = xmlrpc.client.Server(manifest_server, transport=transport)
818 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800819 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400820
Mike Frysinger56ce3462019-12-04 19:30:48 -0500821 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500822 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400823 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500824 elif ('TARGET_PRODUCT' in os.environ and
825 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500826 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
827 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400828 [success, manifest_str] = server.GetApprovedManifest(branch, target)
829 else:
830 [success, manifest_str] = server.GetApprovedManifest(branch)
831 else:
832 assert(opt.smart_tag)
833 [success, manifest_str] = server.GetManifest(opt.smart_tag)
834
835 if success:
836 manifest_name = os.path.basename(smart_sync_manifest_path)
837 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500838 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400839 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400840 except IOError as e:
841 print('error: cannot write manifest to %s:\n%s'
842 % (smart_sync_manifest_path, e),
843 file=sys.stderr)
844 sys.exit(1)
845 self._ReloadManifest(manifest_name)
846 else:
847 print('error: manifest server RPC call failed: %s' %
848 manifest_str, file=sys.stderr)
849 sys.exit(1)
850 except (socket.error, IOError, xmlrpc.client.Fault) as e:
851 print('error: cannot connect to manifest server %s:\n%s'
852 % (self.manifest.manifest_server, e), file=sys.stderr)
853 sys.exit(1)
854 except xmlrpc.client.ProtocolError as e:
855 print('error: cannot connect to manifest server %s:\n%d %s'
856 % (self.manifest.manifest_server, e.errcode, e.errmsg),
857 file=sys.stderr)
858 sys.exit(1)
859
860 return manifest_name
861
Mike Frysingerfb527e32019-08-27 02:34:32 -0400862 def _UpdateManifestProject(self, opt, mp, manifest_name):
863 """Fetch & update the local manifest project."""
864 if not opt.local_only:
865 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500866 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700867 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200868 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500869 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400870 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600871 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400872 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700873 clone_filter=self.manifest.CloneFilter,
874 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400875 finish = time.time()
876 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
877 start, finish, success)
878
879 if mp.HasChanges:
880 syncbuf = SyncBuffer(mp.config)
881 start = time.time()
882 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
883 clean = syncbuf.Finish()
884 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
885 start, time.time(), clean)
886 if not clean:
887 sys.exit(1)
Mike Frysinger05638bf2021-05-04 15:33:31 -0400888 self._ReloadManifest(manifest_name)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400889 if opt.jobs is None:
890 self.jobs = self.manifest.default.sync_j
891
Mike Frysingerae6cb082019-08-27 01:10:59 -0400892 def ValidateOptions(self, opt, args):
893 if opt.force_broken:
894 print('warning: -f/--force-broken is now the default behavior, and the '
895 'options are deprecated', file=sys.stderr)
896 if opt.network_only and opt.detach_head:
897 self.OptionParser.error('cannot combine -n and -d')
898 if opt.network_only and opt.local_only:
899 self.OptionParser.error('cannot combine -n and -l')
900 if opt.manifest_name and opt.smart_sync:
901 self.OptionParser.error('cannot combine -m and -s')
902 if opt.manifest_name and opt.smart_tag:
903 self.OptionParser.error('cannot combine -m and -t')
904 if opt.manifest_server_username or opt.manifest_server_password:
905 if not (opt.smart_sync or opt.smart_tag):
906 self.OptionParser.error('-u and -p may only be combined with -s or -t')
907 if None in [opt.manifest_server_username, opt.manifest_server_password]:
908 self.OptionParser.error('both -u and -p must be given')
909
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700910 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800911 if opt.jobs:
912 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700913 if self.jobs > 1:
914 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400915 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700916
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500917 if opt.manifest_name:
918 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700919
Chirayu Desaia892b102013-06-11 14:18:46 +0530920 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900921 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900922 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530923
Xin Lid79a4bc2020-05-20 16:03:45 -0700924 if opt.clone_bundle is None:
925 opt.clone_bundle = self.manifest.CloneBundle
926
Victor Boivie08c880d2011-04-19 10:32:52 +0200927 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400928 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
929 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900930 if os.path.isfile(smart_sync_manifest_path):
931 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800932 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900933 except OSError as e:
934 print('error: failed to remove existing smart sync override manifest: %s' %
935 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700936
Mike Frysingerc99322a2021-05-04 15:32:43 -0400937 err_event = multiprocessing.Event()
Mike Frysinger5a033082019-09-23 19:21:20 -0400938
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 rp = self.manifest.repoProject
940 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500941 cb = rp.CurrentBranch
942 if cb:
943 base = rp.GetBranch(cb).merge
944 if not base or not base.startswith('refs/heads/'):
945 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400946 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500947 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
949 mp = self.manifest.manifestProject
950 mp.PreSync()
951
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800952 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700953 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800954
Fredrik de Grootcc960972019-11-22 09:04:31 +0100955 if not opt.mp_update:
956 print('Skipping update of local manifest project.')
957 else:
958 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700959
Raman Tennetifeb28912021-05-02 19:47:29 -0700960 load_local_manifests = not self.manifest.HasLocalManifests
Xin Li0cb6e922021-06-16 10:19:00 -0700961 if git_superproject.UseSuperproject(opt, self.manifest):
Raman Tenneti784e16f2021-06-11 17:29:45 -0700962 new_manifest_name = self._UpdateProjectsRevisionId(opt, args, load_local_manifests)
963 if not new_manifest_name:
Xin Lie39d8b32021-07-13 18:21:49 +0000964 manifest_name = opt.manifest_name
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800965
Simran Basib9a1b732015-08-20 12:19:28 -0700966 if self.gitc_manifest:
967 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700968 missing_ok=True)
969 gitc_projects = []
970 opened_projects = []
971 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700972 if project.relpath in self.gitc_manifest.paths and \
973 self.gitc_manifest.paths[project.relpath].old_revision:
974 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700975 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700976 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700977
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700978 if not args:
979 gitc_projects = None
980
981 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700982 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700983 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
984 if manifest_name:
985 manifest.Override(manifest_name)
986 else:
987 manifest.Override(self.manifest.manifestFile)
988 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
989 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700990 gitc_projects)
991 print('GITC client successfully synced.')
992
993 # The opened projects need to be synced as normal, therefore we
994 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700995 # TODO: make this more reliable -- if there's a project name/path overlap,
996 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900997 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
998 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700999 if not args:
1000 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001001 all_projects = self.GetProjects(args,
1002 missing_ok=True,
1003 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001004
Mike Frysinger5a033082019-09-23 19:21:20 -04001005 err_network_sync = False
1006 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -04001007
Dave Borowitz67700e92012-10-23 15:00:54 -07001008 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -07001009 if not opt.local_only:
Mike Frysinger339f2df2021-05-06 00:44:42 -04001010 with multiprocessing.Manager() as manager:
1011 with ssh.ProxyManager(manager) as ssh_proxy:
1012 # Initialize the socket dir once in the parent.
1013 ssh_proxy.sock()
Peter Kjellerstedtd1776092021-05-19 19:37:23 +02001014 all_projects = self._FetchMain(opt, args, all_projects, err_event,
1015 manifest_name, load_local_manifests,
1016 ssh_proxy)
Mike Frysinger339f2df2021-05-06 00:44:42 -04001017
1018 if opt.network_only:
1019 return
Mike Frysinger5a033082019-09-23 19:21:20 -04001020
1021 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001022 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001023 err_network_sync = True
1024 if opt.fail_fast:
1025 print('\nerror: Exited sync due to fetch errors.\n'
1026 'Local checkouts *not* updated. Resolve network issues & '
1027 'retry.\n'
1028 '`repo sync -l` will update some local checkouts.',
1029 file=sys.stderr)
1030 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001031
Julien Campergue335f5ef2013-10-16 11:02:35 +02001032 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001033 # bail out now, we have no working tree
1034 return
1035
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -05001036 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -04001037 err_event.set()
1038 err_update_projects = True
1039 if opt.fail_fast:
1040 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1041 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001042
Mike Frysinger14208f42021-05-04 15:31:51 -04001043 err_update_linkfiles = not self.UpdateCopyLinkfileList()
1044 if err_update_linkfiles:
jiajia tanga590e642021-04-25 20:02:02 +08001045 err_event.set()
jiajia tanga590e642021-04-25 20:02:02 +08001046 if opt.fail_fast:
1047 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
1048 sys.exit(1)
1049
Mike Frysinger5a033082019-09-23 19:21:20 -04001050 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -05001051 # NB: We don't exit here because this is the last step.
1052 err_checkout = not self._Checkout(all_projects, opt, err_results)
1053 if err_checkout:
1054 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001055
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001056 # If there's a notice that's supposed to print at the end of the sync, print
1057 # it now...
1058 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001059 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001060
Mike Frysinger5a033082019-09-23 19:21:20 -04001061 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001062 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001063 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1064 if err_network_sync:
1065 print('error: Downloading network changes failed.', file=sys.stderr)
1066 if err_update_projects:
1067 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +08001068 if err_update_linkfiles:
1069 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -04001070 if err_checkout:
1071 print('error: Checking out local projects failed.', file=sys.stderr)
1072 if err_results:
1073 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1074 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1075 file=sys.stderr)
1076 sys.exit(1)
1077
Mike Frysingere19d9e12020-02-12 11:23:32 -05001078 if not opt.quiet:
1079 print('repo sync has finished successfully.')
1080
David Pursehouse819827a2020-02-12 15:20:19 +09001081
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001082def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001083 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001084 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001085 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001086 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001087 if project.Exists:
1088 project.PostRepoUpgrade()
1089
David Pursehouse819827a2020-02-12 15:20:19 +09001090
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001091def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001092 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001093 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001094 wrapper = Wrapper()
1095 try:
1096 rev = rp.bare_git.describe(rp.GetRevisionId())
1097 except GitError:
1098 rev = None
1099 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1100 # See if we're held back due to missing signed tag.
1101 current_revid = rp.bare_git.rev_parse('HEAD')
1102 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1103 if current_revid != new_revid:
1104 # We want to switch to the new rev, but also not trash any uncommitted
1105 # changes. This helps with local testing/hacking.
1106 # If a local change has been made, we will throw that away.
1107 # We also have to make sure this will switch to an older commit if that's
1108 # the latest tag in order to support release rollback.
1109 try:
1110 rp.work_git.reset('--keep', new_rev)
1111 except GitError as e:
1112 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001113 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001114 raise RepoChangedException(['--repo-upgraded'])
1115 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001116 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001117 else:
1118 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001119 print('repo version %s is current' % rp.work_git.describe(HEAD),
1120 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001121
David Pursehouse819827a2020-02-12 15:20:19 +09001122
Dave Borowitz67700e92012-10-23 15:00:54 -07001123class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001124 _ALPHA = 0.5
1125
Dave Borowitz67700e92012-10-23 15:00:54 -07001126 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001127 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001128 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001129 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001130
1131 def Get(self, project):
1132 self._Load()
1133 return self._times.get(project.name, _ONE_DAY_S)
1134
1135 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001136 self._Load()
1137 name = project.name
1138 old = self._times.get(name, t)
1139 self._seen.add(name)
1140 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001141 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001142
1143 def _Load(self):
1144 if self._times is None:
1145 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001146 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001147 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001148 except (IOError, ValueError):
1149 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001150 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001151 except OSError:
1152 pass
1153 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001154
1155 def Save(self):
1156 if self._times is None:
1157 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001158
1159 to_delete = []
1160 for name in self._times:
1161 if name not in self._seen:
1162 to_delete.append(name)
1163 for name in to_delete:
1164 del self._times[name]
1165
Dave Borowitz67700e92012-10-23 15:00:54 -07001166 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001167 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001168 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001169 except (IOError, TypeError):
1170 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001171 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001172 except OSError:
1173 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001174
1175# This is a replacement for xmlrpc.client.Transport using urllib2
1176# and supporting persistent-http[s]. It cannot change hosts from
1177# request to request like the normal transport, the real url
1178# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001179
1180
Dan Willemsen0745bb22015-08-17 13:41:45 -07001181class PersistentTransport(xmlrpc.client.Transport):
1182 def __init__(self, orig_host):
1183 self.orig_host = orig_host
1184
1185 def request(self, host, handler, request_body, verbose=False):
1186 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1187 # Python doesn't understand cookies with the #HttpOnly_ prefix
1188 # Since we're only using them for HTTP, copy the file temporarily,
1189 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001190 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001191 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001192 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001193 try:
1194 with open(cookiefile) as f:
1195 for line in f:
1196 if line.startswith("#HttpOnly_"):
1197 line = line[len("#HttpOnly_"):]
1198 tmpcookiefile.write(line)
1199 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001200
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001201 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001202 try:
1203 cookiejar.load()
1204 except cookielib.LoadError:
1205 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001206 finally:
1207 tmpcookiefile.close()
1208 else:
1209 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001210
1211 proxyhandler = urllib.request.ProxyHandler
1212 if proxy:
1213 proxyhandler = urllib.request.ProxyHandler({
1214 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001215 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001216
1217 opener = urllib.request.build_opener(
1218 urllib.request.HTTPCookieProcessor(cookiejar),
1219 proxyhandler)
1220
1221 url = urllib.parse.urljoin(self.orig_host, handler)
1222 parse_results = urllib.parse.urlparse(url)
1223
1224 scheme = parse_results.scheme
1225 if scheme == 'persistent-http':
1226 scheme = 'http'
1227 if scheme == 'persistent-https':
1228 # If we're proxying through persistent-https, use http. The
1229 # proxy itself will do the https.
1230 if proxy:
1231 scheme = 'http'
1232 else:
1233 scheme = 'https'
1234
1235 # Parse out any authentication information using the base class
1236 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1237
1238 url = urllib.parse.urlunparse((
1239 scheme,
1240 host,
1241 parse_results.path,
1242 parse_results.params,
1243 parse_results.query,
1244 parse_results.fragment))
1245
1246 request = urllib.request.Request(url, request_body)
1247 if extra_headers is not None:
1248 for (name, header) in extra_headers:
1249 request.add_header(name, header)
1250 request.add_header('Content-Type', 'text/xml')
1251 try:
1252 response = opener.open(request)
1253 except urllib.error.HTTPError as e:
1254 if e.code == 501:
1255 # We may have been redirected through a login process
1256 # but our POST turned into a GET. Retry.
1257 response = opener.open(request)
1258 else:
1259 raise
1260
1261 p, u = xmlrpc.client.getparser()
1262 while 1:
1263 data = response.read(1024)
1264 if not data:
1265 break
1266 p.feed(data)
1267 p.close()
1268 return u.close()
1269
1270 def close(self):
1271 pass