blob: 74617544ae6514c7651d9d1233a2eb6bab1b7464 [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 Tennetiae86a462021-07-27 08:54:59 -0700304 if opt.local_only:
305 manifest_path = superproject.manifest_path
306 if manifest_path:
307 self._ReloadManifest(manifest_path, load_local_manifests)
308 return manifest_path
309
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800310 all_projects = self.GetProjects(args,
311 missing_ok=True,
312 submodules_ok=opt.fetch_submodules)
Raman Tenneti784e16f2021-06-11 17:29:45 -0700313 update_result = superproject.UpdateProjectsRevisionId(all_projects)
314 manifest_path = update_result.manifest_path
315 if manifest_path:
316 self._ReloadManifest(manifest_path, load_local_manifests)
317 else:
Raman Tenneti8db30d62021-07-06 21:30:06 -0700318 print('warning: Update of revisionId from superproject has failed, '
319 'repo sync will not use superproject to fetch the source. ',
320 'Please resync with the --no-use-superproject option to avoid this repo warning.',
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800321 file=sys.stderr)
Raman Tenneti8db30d62021-07-06 21:30:06 -0700322 if update_result.fatal and opt.use_superproject is not None:
Raman Tenneti784e16f2021-06-11 17:29:45 -0700323 sys.exit(1)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800324 return manifest_path
325
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500326 def _FetchProjectList(self, opt, projects):
327 """Main function of the fetch worker.
328
329 The projects we're given share the same underlying git object store, so we
330 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800331
David James8d201162013-10-11 17:03:19 -0700332 Delegates most of the work to _FetchHelper.
333
334 Args:
335 opt: Program options returned from optparse. See _Options().
336 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700337 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500338 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700339
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500340 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700341 """Fetch git objects for a single project.
342
David Pursehousec1b86a22012-11-14 11:36:51 +0900343 Args:
344 opt: Program options returned from optparse. See _Options().
345 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700346
347 Returns:
348 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900349 """
David Rileye0684ad2017-04-05 00:02:59 -0700350 start = time.time()
351 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500352 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900353 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500354 success = project.Sync_NetworkHalf(
355 quiet=opt.quiet,
356 verbose=opt.verbose,
357 output_redir=buf,
358 current_branch_only=self._GetCurrentBranchOnly(opt),
359 force_sync=opt.force_sync,
360 clone_bundle=opt.clone_bundle,
361 tags=opt.tags, archive=self.manifest.IsArchive,
362 optimized_fetch=opt.optimized_fetch,
363 retry_fetches=opt.retry_fetches,
364 prune=opt.prune,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400365 ssh_proxy=self.ssh_proxy,
Raman Tennetif32f2432021-04-12 20:57:25 -0700366 clone_filter=self.manifest.CloneFilter,
367 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700368
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500369 output = buf.getvalue()
Mike Frysinger58929732021-07-02 00:29:35 -0400370 if (opt.verbose or not success) and output:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500371 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700372
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500373 if not success:
374 print('error: Cannot fetch %s from %s'
375 % (project.name, project.remote.url),
376 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700377 except GitError as e:
378 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500379 except Exception as e:
380 print('error: Cannot fetch %s (%s: %s)'
381 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
382 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500383
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500384 finish = time.time()
385 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700386
Mike Frysinger339f2df2021-05-06 00:44:42 -0400387 @classmethod
388 def _FetchInitChild(cls, ssh_proxy):
389 cls.ssh_proxy = ssh_proxy
390
391 def _Fetch(self, projects, opt, err_event, ssh_proxy):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500392 ret = True
393
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400394 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700395 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400396 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800397
David James89ece422014-01-09 18:51:58 -0800398 objdir_project_map = dict()
399 for project in projects:
400 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500401 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700402
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500403 def _ProcessResults(results_sets):
404 ret = True
405 for results in results_sets:
406 for (success, project, start, finish) in results:
407 self._fetch_times.Set(project, finish - start)
408 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
409 start, finish, success)
410 # Check for any errors before running any more tasks.
411 # ...we'll let existing jobs finish, though.
412 if not success:
413 ret = False
414 else:
415 fetched.add(project.gitdir)
416 pm.update(msg=project.name)
417 if not ret and opt.fail_fast:
418 break
419 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700420
Mike Frysinger339f2df2021-05-06 00:44:42 -0400421 # We pass the ssh proxy settings via the class. This allows multiprocessing
422 # to pickle it up when spawning children. We can't pass it as an argument
423 # to _FetchProjectList below as multiprocessing is unable to pickle those.
424 Sync.ssh_proxy = None
425
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500426 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400427 if len(projects_list) == 1 or jobs == 1:
Mike Frysinger339f2df2021-05-06 00:44:42 -0400428 self._FetchInitChild(ssh_proxy)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500429 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
430 ret = False
431 else:
432 # Favor throughput over responsiveness when quiet. It seems that imap()
433 # will yield results in batches relative to chunksize, so even as the
434 # children finish a sync, we won't see the result until one child finishes
435 # ~chunksize jobs. When using a large --jobs with large chunksize, this
436 # can be jarring as there will be a large initial delay where repo looks
437 # like it isn't doing anything and sits at 0%, but then suddenly completes
438 # a lot of jobs all at once. Since this code is more network bound, we
439 # can accept a bit more CPU overhead with a smaller chunksize so that the
440 # user sees more immediate & continuous feedback.
441 if opt.quiet:
442 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800443 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500444 pm.update(inc=0, msg='warming up')
445 chunksize = 4
Mike Frysinger339f2df2021-05-06 00:44:42 -0400446 with multiprocessing.Pool(
447 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500448 results = pool.imap_unordered(
449 functools.partial(self._FetchProjectList, opt),
450 projects_list,
451 chunksize=chunksize)
452 if not _ProcessResults(results):
453 ret = False
454 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800455
Mike Frysinger339f2df2021-05-06 00:44:42 -0400456 # Cleanup the reference now that we're done with it, and we're going to
457 # release any resources it points to. If we don't, later multiprocessing
458 # usage (e.g. checkouts) will try to pickle and then crash.
459 del Sync.ssh_proxy
460
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700461 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700462 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700463
Julien Campergue335f5ef2013-10-16 11:02:35 +0200464 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400465 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200466
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500467 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700468
Mike Frysingerb4429432021-05-05 20:03:26 -0400469 def _FetchMain(self, opt, args, all_projects, err_event, manifest_name,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400470 load_local_manifests, ssh_proxy):
Mike Frysingerb4429432021-05-05 20:03:26 -0400471 """The main network fetch loop.
472
473 Args:
474 opt: Program options returned from optparse. See _Options().
475 args: Command line args used to filter out projects.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200476 all_projects: List of all projects that should be fetched.
Mike Frysingerb4429432021-05-05 20:03:26 -0400477 err_event: Whether an error was hit while processing.
478 manifest_name: Manifest file to be reloaded.
479 load_local_manifests: Whether to load local manifests.
Mike Frysinger339f2df2021-05-06 00:44:42 -0400480 ssh_proxy: SSH manager for clients & masters.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200481
482 Returns:
483 List of all projects that should be checked out.
Mike Frysingerb4429432021-05-05 20:03:26 -0400484 """
485 rp = self.manifest.repoProject
486
487 to_fetch = []
488 now = time.time()
489 if _ONE_DAY_S <= (now - rp.LastFetch):
490 to_fetch.append(rp)
491 to_fetch.extend(all_projects)
492 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
493
Mike Frysinger339f2df2021-05-06 00:44:42 -0400494 success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400495 if not success:
496 err_event.set()
497
498 _PostRepoFetch(rp, opt.repo_verify)
499 if opt.network_only:
500 # bail out now; the rest touches the working tree
501 if err_event.is_set():
502 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
503 sys.exit(1)
504 return
505
506 # Iteratively fetch missing and/or nested unregistered submodules
507 previously_missing_set = set()
508 while True:
509 self._ReloadManifest(manifest_name, load_local_manifests)
510 all_projects = self.GetProjects(args,
511 missing_ok=True,
512 submodules_ok=opt.fetch_submodules)
513 missing = []
514 for project in all_projects:
515 if project.gitdir not in fetched:
516 missing.append(project)
517 if not missing:
518 break
519 # Stop us from non-stopped fetching actually-missing repos: If set of
520 # missing repos has not been changed from last fetch, we break.
521 missing_set = set(p.name for p in missing)
522 if previously_missing_set == missing_set:
523 break
524 previously_missing_set = missing_set
Mike Frysinger339f2df2021-05-06 00:44:42 -0400525 success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400526 if not success:
527 err_event.set()
528 fetched.update(new_fetched)
529
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200530 return all_projects
531
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500532 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700533 """Checkout work tree for one project
534
535 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500536 detach_head: Whether to leave a detached HEAD.
537 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700538 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700539
540 Returns:
541 Whether the fetch was successful.
542 """
Xin Li745be2e2019-06-03 11:24:30 -0700543 start = time.time()
544 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500545 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700546 success = False
547 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500548 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500549 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700550 except GitError as e:
551 print('error.GitError: Cannot checkout %s: %s' %
552 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500553 except Exception as e:
554 print('error: Cannot checkout %s: %s: %s' %
555 (project.name, type(e).__name__, str(e)),
556 file=sys.stderr)
557 raise
Xin Li745be2e2019-06-03 11:24:30 -0700558
Mike Frysingerebf04a42021-02-23 20:48:04 -0500559 if not success:
560 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
561 finish = time.time()
562 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700563
Mike Frysingerebf04a42021-02-23 20:48:04 -0500564 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700565 """Checkout projects listed in all_projects
566
567 Args:
568 all_projects: List of all projects that should be checked out.
569 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500570 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700571 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500572 # Only checkout projects with worktrees.
573 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700574
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500575 def _ProcessResults(pool, pm, results):
576 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500577 for (success, project, start, finish) in results:
578 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
579 start, finish, success)
580 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500581 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500582 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500583 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500584 err_results.append(project.relpath)
585 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500586 if pool:
587 pool.close()
588 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500589 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500590 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700591
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500592 return self.ExecuteInParallel(
593 opt.jobs_checkout if opt.jobs_checkout else self.jobs,
594 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
595 all_projects,
596 callback=_ProcessResults,
597 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500598
Mike Frysinger5a033082019-09-23 19:21:20 -0400599 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400600 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400601 pm.update(inc=0, msg='prescan')
602
Gabe Black2ff30292014-10-09 17:54:35 -0700603 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700604 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500605 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500606 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900607 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100608 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400609 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100610 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500611 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500612 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500613 else:
614 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400615 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500616 'versions of git; please upgrade to git-2.7.0+.'
617 % (project.relpath,),
618 file=sys.stderr)
619 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700620 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700621
Mike Frysinger65af2602021-04-08 22:47:44 -0400622 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
623
624 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700625 jobs = min(self.jobs, cpu_count)
626
627 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700628 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400629 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700630 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400631 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700632 return
633
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400634 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700635
636 threads = set()
637 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700638
David James8d201162013-10-11 17:03:19 -0700639 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400640 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700641 try:
642 try:
David James8d201162013-10-11 17:03:19 -0700643 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700644 except GitError:
645 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900646 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700647 err_event.set()
648 raise
649 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400650 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700651 sem.release()
652
Gabe Black2ff30292014-10-09 17:54:35 -0700653 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500654 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700655 break
656 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700657 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700658 t.daemon = True
659 threads.add(t)
660 t.start()
661
662 for t in threads:
663 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400664 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700665
Raman Tennetifeb28912021-05-02 19:47:29 -0700666 def _ReloadManifest(self, manifest_name=None, load_local_manifests=True):
667 """Reload the manfiest from the file specified by the |manifest_name|.
668
669 It unloads the manifest if |manifest_name| is None.
670
671 Args:
672 manifest_name: Manifest file to be reloaded.
673 load_local_manifests: Whether to load local manifests.
674 """
Tim Kilbourn07669002013-03-08 15:02:49 -0800675 if manifest_name:
676 # Override calls _Unload already
Raman Tennetifeb28912021-05-02 19:47:29 -0700677 self.manifest.Override(manifest_name, load_local_manifests=load_local_manifests)
Tim Kilbourn07669002013-03-08 15:02:49 -0800678 else:
679 self.manifest._Unload()
680
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500681 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700682 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700683 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700684 if project.relpath:
685 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700686 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500687 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700688 old_project_paths = []
689
690 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500691 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700692 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800693 # In reversed order, so subfolders are deleted before parent folder.
694 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700695 if not path:
696 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700697 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900698 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700699 gitdir = os.path.join(self.manifest.topdir, path, '.git')
700 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900701 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900702 manifest=self.manifest,
703 name=path,
704 remote=RemoteSpec('origin'),
705 gitdir=gitdir,
706 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500707 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900708 worktree=os.path.join(self.manifest.topdir, path),
709 relpath=path,
710 revisionExpr='HEAD',
711 revisionId=None,
712 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500713 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900714 quiet=opt.quiet,
715 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400716 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700717
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700718 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500719 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700720 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700721 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700722 return 0
723
jiajia tanga590e642021-04-25 20:02:02 +0800724 def UpdateCopyLinkfileList(self):
725 """Save all dests of copyfile and linkfile, and update them if needed.
726
727 Returns:
728 Whether update was successful.
729 """
730 new_paths = {}
731 new_linkfile_paths = []
732 new_copyfile_paths = []
733 for project in self.GetProjects(None, missing_ok=True):
734 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
735 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
736
737 new_paths = {
738 'linkfile': new_linkfile_paths,
739 'copyfile': new_copyfile_paths,
740 }
741
742 copylinkfile_name = 'copy-link-files.json'
743 copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name)
744 old_copylinkfile_paths = {}
745
746 if os.path.exists(copylinkfile_path):
747 with open(copylinkfile_path, 'rb') as fp:
748 try:
749 old_copylinkfile_paths = json.load(fp)
750 except:
751 print('error: %s is not a json formatted file.' %
752 copylinkfile_path, file=sys.stderr)
753 platform_utils.remove(copylinkfile_path)
754 return False
755
756 need_remove_files = []
757 need_remove_files.extend(
758 set(old_copylinkfile_paths.get('linkfile', [])) -
759 set(new_linkfile_paths))
760 need_remove_files.extend(
761 set(old_copylinkfile_paths.get('copyfile', [])) -
762 set(new_copyfile_paths))
763
764 for need_remove_file in need_remove_files:
765 try:
766 platform_utils.remove(need_remove_file)
767 except OSError as e:
768 if e.errno == errno.ENOENT:
769 # Try to remove the updated copyfile or linkfile.
770 # So, if the file is not exist, nothing need to do.
771 pass
772
773 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
774 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
775 json.dump(new_paths, fp)
776 return True
777
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400778 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
779 if not self.manifest.manifest_server:
780 print('error: cannot smart sync: no manifest server defined in '
781 'manifest', file=sys.stderr)
782 sys.exit(1)
783
784 manifest_server = self.manifest.manifest_server
785 if not opt.quiet:
786 print('Using manifest server %s' % manifest_server)
787
David Pursehouseeeff3532020-02-12 11:24:10 +0900788 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400789 username = None
790 password = None
791 if opt.manifest_server_username and opt.manifest_server_password:
792 username = opt.manifest_server_username
793 password = opt.manifest_server_password
794 else:
795 try:
796 info = netrc.netrc()
797 except IOError:
798 # .netrc file does not exist or could not be opened
799 pass
800 else:
801 try:
802 parse_result = urllib.parse.urlparse(manifest_server)
803 if parse_result.hostname:
804 auth = info.authenticators(parse_result.hostname)
805 if auth:
806 username, _account, password = auth
807 else:
808 print('No credentials found for %s in .netrc'
809 % parse_result.hostname, file=sys.stderr)
810 except netrc.NetrcParseError as e:
811 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
812
813 if (username and password):
814 manifest_server = manifest_server.replace('://', '://%s:%s@' %
815 (username, password),
816 1)
817
818 transport = PersistentTransport(manifest_server)
819 if manifest_server.startswith('persistent-'):
820 manifest_server = manifest_server[len('persistent-'):]
821
822 try:
823 server = xmlrpc.client.Server(manifest_server, transport=transport)
824 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800825 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400826
Mike Frysinger56ce3462019-12-04 19:30:48 -0500827 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500828 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400829 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500830 elif ('TARGET_PRODUCT' in os.environ and
831 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500832 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
833 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400834 [success, manifest_str] = server.GetApprovedManifest(branch, target)
835 else:
836 [success, manifest_str] = server.GetApprovedManifest(branch)
837 else:
838 assert(opt.smart_tag)
839 [success, manifest_str] = server.GetManifest(opt.smart_tag)
840
841 if success:
842 manifest_name = os.path.basename(smart_sync_manifest_path)
843 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500844 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400845 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400846 except IOError as e:
847 print('error: cannot write manifest to %s:\n%s'
848 % (smart_sync_manifest_path, e),
849 file=sys.stderr)
850 sys.exit(1)
851 self._ReloadManifest(manifest_name)
852 else:
853 print('error: manifest server RPC call failed: %s' %
854 manifest_str, file=sys.stderr)
855 sys.exit(1)
856 except (socket.error, IOError, xmlrpc.client.Fault) as e:
857 print('error: cannot connect to manifest server %s:\n%s'
858 % (self.manifest.manifest_server, e), file=sys.stderr)
859 sys.exit(1)
860 except xmlrpc.client.ProtocolError as e:
861 print('error: cannot connect to manifest server %s:\n%d %s'
862 % (self.manifest.manifest_server, e.errcode, e.errmsg),
863 file=sys.stderr)
864 sys.exit(1)
865
866 return manifest_name
867
Mike Frysingerfb527e32019-08-27 02:34:32 -0400868 def _UpdateManifestProject(self, opt, mp, manifest_name):
869 """Fetch & update the local manifest project."""
870 if not opt.local_only:
871 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500872 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700873 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200874 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500875 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400876 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600877 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400878 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700879 clone_filter=self.manifest.CloneFilter,
880 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400881 finish = time.time()
882 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
883 start, finish, success)
884
885 if mp.HasChanges:
886 syncbuf = SyncBuffer(mp.config)
887 start = time.time()
888 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
889 clean = syncbuf.Finish()
890 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
891 start, time.time(), clean)
892 if not clean:
893 sys.exit(1)
Mike Frysinger05638bf2021-05-04 15:33:31 -0400894 self._ReloadManifest(manifest_name)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400895 if opt.jobs is None:
896 self.jobs = self.manifest.default.sync_j
897
Mike Frysingerae6cb082019-08-27 01:10:59 -0400898 def ValidateOptions(self, opt, args):
899 if opt.force_broken:
900 print('warning: -f/--force-broken is now the default behavior, and the '
901 'options are deprecated', file=sys.stderr)
902 if opt.network_only and opt.detach_head:
903 self.OptionParser.error('cannot combine -n and -d')
904 if opt.network_only and opt.local_only:
905 self.OptionParser.error('cannot combine -n and -l')
906 if opt.manifest_name and opt.smart_sync:
907 self.OptionParser.error('cannot combine -m and -s')
908 if opt.manifest_name and opt.smart_tag:
909 self.OptionParser.error('cannot combine -m and -t')
910 if opt.manifest_server_username or opt.manifest_server_password:
911 if not (opt.smart_sync or opt.smart_tag):
912 self.OptionParser.error('-u and -p may only be combined with -s or -t')
913 if None in [opt.manifest_server_username, opt.manifest_server_password]:
914 self.OptionParser.error('both -u and -p must be given')
915
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800917 if opt.jobs:
918 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700919 if self.jobs > 1:
920 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400921 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700922
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500923 if opt.manifest_name:
924 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700925
Chirayu Desaia892b102013-06-11 14:18:46 +0530926 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900927 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900928 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530929
Xin Lid79a4bc2020-05-20 16:03:45 -0700930 if opt.clone_bundle is None:
931 opt.clone_bundle = self.manifest.CloneBundle
932
Victor Boivie08c880d2011-04-19 10:32:52 +0200933 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400934 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
935 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900936 if os.path.isfile(smart_sync_manifest_path):
937 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800938 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900939 except OSError as e:
940 print('error: failed to remove existing smart sync override manifest: %s' %
941 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700942
Mike Frysingerc99322a2021-05-04 15:32:43 -0400943 err_event = multiprocessing.Event()
Mike Frysinger5a033082019-09-23 19:21:20 -0400944
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945 rp = self.manifest.repoProject
946 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500947 cb = rp.CurrentBranch
948 if cb:
949 base = rp.GetBranch(cb).merge
950 if not base or not base.startswith('refs/heads/'):
951 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400952 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500953 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
955 mp = self.manifest.manifestProject
956 mp.PreSync()
957
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800958 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700959 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800960
Fredrik de Grootcc960972019-11-22 09:04:31 +0100961 if not opt.mp_update:
962 print('Skipping update of local manifest project.')
963 else:
964 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700965
Raman Tennetifeb28912021-05-02 19:47:29 -0700966 load_local_manifests = not self.manifest.HasLocalManifests
Xin Li0cb6e922021-06-16 10:19:00 -0700967 if git_superproject.UseSuperproject(opt, self.manifest):
Xin Li927d29a2021-07-14 21:08:45 +0000968 manifest_name = self._UpdateProjectsRevisionId(opt, args, load_local_manifests) or opt.manifest_name
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800969
Simran Basib9a1b732015-08-20 12:19:28 -0700970 if self.gitc_manifest:
971 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700972 missing_ok=True)
973 gitc_projects = []
974 opened_projects = []
975 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700976 if project.relpath in self.gitc_manifest.paths and \
977 self.gitc_manifest.paths[project.relpath].old_revision:
978 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700979 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700980 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700981
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700982 if not args:
983 gitc_projects = None
984
985 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700986 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700987 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
988 if manifest_name:
989 manifest.Override(manifest_name)
990 else:
991 manifest.Override(self.manifest.manifestFile)
992 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
993 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700994 gitc_projects)
995 print('GITC client successfully synced.')
996
997 # The opened projects need to be synced as normal, therefore we
998 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700999 # TODO: make this more reliable -- if there's a project name/path overlap,
1000 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +09001001 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
1002 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -07001003 if not args:
1004 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001005 all_projects = self.GetProjects(args,
1006 missing_ok=True,
1007 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008
Mike Frysinger5a033082019-09-23 19:21:20 -04001009 err_network_sync = False
1010 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -04001011
Dave Borowitz67700e92012-10-23 15:00:54 -07001012 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -07001013 if not opt.local_only:
Mike Frysinger339f2df2021-05-06 00:44:42 -04001014 with multiprocessing.Manager() as manager:
1015 with ssh.ProxyManager(manager) as ssh_proxy:
1016 # Initialize the socket dir once in the parent.
1017 ssh_proxy.sock()
Peter Kjellerstedtd1776092021-05-19 19:37:23 +02001018 all_projects = self._FetchMain(opt, args, all_projects, err_event,
1019 manifest_name, load_local_manifests,
1020 ssh_proxy)
Mike Frysinger339f2df2021-05-06 00:44:42 -04001021
1022 if opt.network_only:
1023 return
Mike Frysinger5a033082019-09-23 19:21:20 -04001024
1025 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001026 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001027 err_network_sync = True
1028 if opt.fail_fast:
1029 print('\nerror: Exited sync due to fetch errors.\n'
1030 'Local checkouts *not* updated. Resolve network issues & '
1031 'retry.\n'
1032 '`repo sync -l` will update some local checkouts.',
1033 file=sys.stderr)
1034 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001035
Julien Campergue335f5ef2013-10-16 11:02:35 +02001036 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001037 # bail out now, we have no working tree
1038 return
1039
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -05001040 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -04001041 err_event.set()
1042 err_update_projects = True
1043 if opt.fail_fast:
1044 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1045 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001046
Mike Frysinger14208f42021-05-04 15:31:51 -04001047 err_update_linkfiles = not self.UpdateCopyLinkfileList()
1048 if err_update_linkfiles:
jiajia tanga590e642021-04-25 20:02:02 +08001049 err_event.set()
jiajia tanga590e642021-04-25 20:02:02 +08001050 if opt.fail_fast:
1051 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
1052 sys.exit(1)
1053
Mike Frysinger5a033082019-09-23 19:21:20 -04001054 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -05001055 # NB: We don't exit here because this is the last step.
1056 err_checkout = not self._Checkout(all_projects, opt, err_results)
1057 if err_checkout:
1058 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001059
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001060 # If there's a notice that's supposed to print at the end of the sync, print
1061 # it now...
1062 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001063 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001064
Mike Frysinger5a033082019-09-23 19:21:20 -04001065 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001066 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001067 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1068 if err_network_sync:
1069 print('error: Downloading network changes failed.', file=sys.stderr)
1070 if err_update_projects:
1071 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +08001072 if err_update_linkfiles:
1073 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -04001074 if err_checkout:
1075 print('error: Checking out local projects failed.', file=sys.stderr)
1076 if err_results:
1077 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1078 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1079 file=sys.stderr)
1080 sys.exit(1)
1081
Mike Frysingere19d9e12020-02-12 11:23:32 -05001082 if not opt.quiet:
1083 print('repo sync has finished successfully.')
1084
David Pursehouse819827a2020-02-12 15:20:19 +09001085
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001086def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001087 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001088 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001089 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001090 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001091 if project.Exists:
1092 project.PostRepoUpgrade()
1093
David Pursehouse819827a2020-02-12 15:20:19 +09001094
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001095def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001096 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001097 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001098 wrapper = Wrapper()
1099 try:
1100 rev = rp.bare_git.describe(rp.GetRevisionId())
1101 except GitError:
1102 rev = None
1103 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1104 # See if we're held back due to missing signed tag.
1105 current_revid = rp.bare_git.rev_parse('HEAD')
1106 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1107 if current_revid != new_revid:
1108 # We want to switch to the new rev, but also not trash any uncommitted
1109 # changes. This helps with local testing/hacking.
1110 # If a local change has been made, we will throw that away.
1111 # We also have to make sure this will switch to an older commit if that's
1112 # the latest tag in order to support release rollback.
1113 try:
1114 rp.work_git.reset('--keep', new_rev)
1115 except GitError as e:
1116 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001117 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001118 raise RepoChangedException(['--repo-upgraded'])
1119 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001120 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001121 else:
1122 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001123 print('repo version %s is current' % rp.work_git.describe(HEAD),
1124 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001125
David Pursehouse819827a2020-02-12 15:20:19 +09001126
Dave Borowitz67700e92012-10-23 15:00:54 -07001127class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001128 _ALPHA = 0.5
1129
Dave Borowitz67700e92012-10-23 15:00:54 -07001130 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001131 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001132 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001133 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001134
1135 def Get(self, project):
1136 self._Load()
1137 return self._times.get(project.name, _ONE_DAY_S)
1138
1139 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001140 self._Load()
1141 name = project.name
1142 old = self._times.get(name, t)
1143 self._seen.add(name)
1144 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001145 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001146
1147 def _Load(self):
1148 if self._times is None:
1149 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001150 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001151 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001152 except (IOError, ValueError):
1153 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001154 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001155 except OSError:
1156 pass
1157 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001158
1159 def Save(self):
1160 if self._times is None:
1161 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001162
1163 to_delete = []
1164 for name in self._times:
1165 if name not in self._seen:
1166 to_delete.append(name)
1167 for name in to_delete:
1168 del self._times[name]
1169
Dave Borowitz67700e92012-10-23 15:00:54 -07001170 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001171 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001172 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001173 except (IOError, TypeError):
1174 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001175 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001176 except OSError:
1177 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001178
1179# This is a replacement for xmlrpc.client.Transport using urllib2
1180# and supporting persistent-http[s]. It cannot change hosts from
1181# request to request like the normal transport, the real url
1182# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001183
1184
Dan Willemsen0745bb22015-08-17 13:41:45 -07001185class PersistentTransport(xmlrpc.client.Transport):
1186 def __init__(self, orig_host):
1187 self.orig_host = orig_host
1188
1189 def request(self, host, handler, request_body, verbose=False):
1190 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1191 # Python doesn't understand cookies with the #HttpOnly_ prefix
1192 # Since we're only using them for HTTP, copy the file temporarily,
1193 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001194 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001195 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001196 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001197 try:
1198 with open(cookiefile) as f:
1199 for line in f:
1200 if line.startswith("#HttpOnly_"):
1201 line = line[len("#HttpOnly_"):]
1202 tmpcookiefile.write(line)
1203 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001204
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001205 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001206 try:
1207 cookiejar.load()
1208 except cookielib.LoadError:
1209 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001210 finally:
1211 tmpcookiefile.close()
1212 else:
1213 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001214
1215 proxyhandler = urllib.request.ProxyHandler
1216 if proxy:
1217 proxyhandler = urllib.request.ProxyHandler({
1218 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001219 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001220
1221 opener = urllib.request.build_opener(
1222 urllib.request.HTTPCookieProcessor(cookiejar),
1223 proxyhandler)
1224
1225 url = urllib.parse.urljoin(self.orig_host, handler)
1226 parse_results = urllib.parse.urlparse(url)
1227
1228 scheme = parse_results.scheme
1229 if scheme == 'persistent-http':
1230 scheme = 'http'
1231 if scheme == 'persistent-https':
1232 # If we're proxying through persistent-https, use http. The
1233 # proxy itself will do the https.
1234 if proxy:
1235 scheme = 'http'
1236 else:
1237 scheme = 'https'
1238
1239 # Parse out any authentication information using the base class
1240 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1241
1242 url = urllib.parse.urlunparse((
1243 scheme,
1244 host,
1245 parse_results.path,
1246 parse_results.params,
1247 parse_results.query,
1248 parse_results.fragment))
1249
1250 request = urllib.request.Request(url, request_body)
1251 if extra_headers is not None:
1252 for (name, header) in extra_headers:
1253 request.add_header(name, header)
1254 request.add_header('Content-Type', 'text/xml')
1255 try:
1256 response = opener.open(request)
1257 except urllib.error.HTTPError as e:
1258 if e.code == 501:
1259 # We may have been redirected through a login process
1260 # but our POST turned into a GET. Retry.
1261 response = opener.open(request)
1262 else:
1263 raise
1264
1265 p, u = xmlrpc.client.getparser()
1266 while 1:
1267 data = response.read(1024)
1268 if not data:
1269 break
1270 p.feed(data)
1271 p.close()
1272 return u.close()
1273
1274 def close(self):
1275 pass