blob: ed656b8ccd38ce04b20d7d5105fd172ca65fcc02 [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 Tenneti7954de12021-07-28 14:36:49 -0700285 def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data):
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 Tenneti7954de12021-07-28 14:36:49 -0700296 superproject_logging_data: A dictionary of superproject data that is to be logged.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800297
298 Returns:
Raman Tenneti784e16f2021-06-11 17:29:45 -0700299 Returns path to the overriding manifest file instead of None.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800300 """
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800301 superproject = git_superproject.Superproject(self.manifest,
Raman Tennetief99ec02021-03-04 10:29:40 -0800302 self.repodir,
Raman Tenneti784e16f2021-06-11 17:29:45 -0700303 self.git_event_log,
Raman Tennetief99ec02021-03-04 10:29:40 -0800304 quiet=opt.quiet)
Raman Tennetiae86a462021-07-27 08:54:59 -0700305 if opt.local_only:
306 manifest_path = superproject.manifest_path
307 if manifest_path:
308 self._ReloadManifest(manifest_path, load_local_manifests)
309 return manifest_path
310
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800311 all_projects = self.GetProjects(args,
312 missing_ok=True,
313 submodules_ok=opt.fetch_submodules)
Raman Tenneti784e16f2021-06-11 17:29:45 -0700314 update_result = superproject.UpdateProjectsRevisionId(all_projects)
315 manifest_path = update_result.manifest_path
Raman Tenneti7954de12021-07-28 14:36:49 -0700316 superproject_logging_data['updatedrevisionid'] = bool(manifest_path)
Raman Tenneti784e16f2021-06-11 17:29:45 -0700317 if manifest_path:
318 self._ReloadManifest(manifest_path, load_local_manifests)
319 else:
Raman Tenneti8db30d62021-07-06 21:30:06 -0700320 print('warning: Update of revisionId from superproject has failed, '
321 'repo sync will not use superproject to fetch the source. ',
322 'Please resync with the --no-use-superproject option to avoid this repo warning.',
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800323 file=sys.stderr)
Raman Tenneti8db30d62021-07-06 21:30:06 -0700324 if update_result.fatal and opt.use_superproject is not None:
Raman Tenneti784e16f2021-06-11 17:29:45 -0700325 sys.exit(1)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800326 return manifest_path
327
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500328 def _FetchProjectList(self, opt, projects):
329 """Main function of the fetch worker.
330
331 The projects we're given share the same underlying git object store, so we
332 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800333
David James8d201162013-10-11 17:03:19 -0700334 Delegates most of the work to _FetchHelper.
335
336 Args:
337 opt: Program options returned from optparse. See _Options().
338 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700339 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500340 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700341
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500342 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700343 """Fetch git objects for a single project.
344
David Pursehousec1b86a22012-11-14 11:36:51 +0900345 Args:
346 opt: Program options returned from optparse. See _Options().
347 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700348
349 Returns:
350 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900351 """
David Rileye0684ad2017-04-05 00:02:59 -0700352 start = time.time()
353 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500354 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900355 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500356 success = project.Sync_NetworkHalf(
357 quiet=opt.quiet,
358 verbose=opt.verbose,
359 output_redir=buf,
360 current_branch_only=self._GetCurrentBranchOnly(opt),
361 force_sync=opt.force_sync,
362 clone_bundle=opt.clone_bundle,
363 tags=opt.tags, archive=self.manifest.IsArchive,
364 optimized_fetch=opt.optimized_fetch,
365 retry_fetches=opt.retry_fetches,
366 prune=opt.prune,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400367 ssh_proxy=self.ssh_proxy,
Raman Tennetif32f2432021-04-12 20:57:25 -0700368 clone_filter=self.manifest.CloneFilter,
369 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700370
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500371 output = buf.getvalue()
Mike Frysinger58929732021-07-02 00:29:35 -0400372 if (opt.verbose or not success) and output:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500373 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700374
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500375 if not success:
376 print('error: Cannot fetch %s from %s'
377 % (project.name, project.remote.url),
378 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700379 except GitError as e:
380 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500381 except Exception as e:
382 print('error: Cannot fetch %s (%s: %s)'
383 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
384 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500385
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500386 finish = time.time()
387 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700388
Mike Frysinger339f2df2021-05-06 00:44:42 -0400389 @classmethod
390 def _FetchInitChild(cls, ssh_proxy):
391 cls.ssh_proxy = ssh_proxy
392
393 def _Fetch(self, projects, opt, err_event, ssh_proxy):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500394 ret = True
395
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400396 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700397 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400398 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800399
David James89ece422014-01-09 18:51:58 -0800400 objdir_project_map = dict()
401 for project in projects:
402 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500403 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700404
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500405 def _ProcessResults(results_sets):
406 ret = True
407 for results in results_sets:
408 for (success, project, start, finish) in results:
409 self._fetch_times.Set(project, finish - start)
410 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
411 start, finish, success)
412 # Check for any errors before running any more tasks.
413 # ...we'll let existing jobs finish, though.
414 if not success:
415 ret = False
416 else:
417 fetched.add(project.gitdir)
418 pm.update(msg=project.name)
419 if not ret and opt.fail_fast:
420 break
421 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700422
Mike Frysinger339f2df2021-05-06 00:44:42 -0400423 # We pass the ssh proxy settings via the class. This allows multiprocessing
424 # to pickle it up when spawning children. We can't pass it as an argument
425 # to _FetchProjectList below as multiprocessing is unable to pickle those.
426 Sync.ssh_proxy = None
427
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500428 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400429 if len(projects_list) == 1 or jobs == 1:
Mike Frysinger339f2df2021-05-06 00:44:42 -0400430 self._FetchInitChild(ssh_proxy)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500431 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
432 ret = False
433 else:
434 # Favor throughput over responsiveness when quiet. It seems that imap()
435 # will yield results in batches relative to chunksize, so even as the
436 # children finish a sync, we won't see the result until one child finishes
437 # ~chunksize jobs. When using a large --jobs with large chunksize, this
438 # can be jarring as there will be a large initial delay where repo looks
439 # like it isn't doing anything and sits at 0%, but then suddenly completes
440 # a lot of jobs all at once. Since this code is more network bound, we
441 # can accept a bit more CPU overhead with a smaller chunksize so that the
442 # user sees more immediate & continuous feedback.
443 if opt.quiet:
444 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800445 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500446 pm.update(inc=0, msg='warming up')
447 chunksize = 4
Mike Frysinger339f2df2021-05-06 00:44:42 -0400448 with multiprocessing.Pool(
449 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500450 results = pool.imap_unordered(
451 functools.partial(self._FetchProjectList, opt),
452 projects_list,
453 chunksize=chunksize)
454 if not _ProcessResults(results):
455 ret = False
456 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800457
Mike Frysinger339f2df2021-05-06 00:44:42 -0400458 # Cleanup the reference now that we're done with it, and we're going to
459 # release any resources it points to. If we don't, later multiprocessing
460 # usage (e.g. checkouts) will try to pickle and then crash.
461 del Sync.ssh_proxy
462
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700463 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700464 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700465
Julien Campergue335f5ef2013-10-16 11:02:35 +0200466 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400467 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200468
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500469 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700470
Mike Frysingerb4429432021-05-05 20:03:26 -0400471 def _FetchMain(self, opt, args, all_projects, err_event, manifest_name,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400472 load_local_manifests, ssh_proxy):
Mike Frysingerb4429432021-05-05 20:03:26 -0400473 """The main network fetch loop.
474
475 Args:
476 opt: Program options returned from optparse. See _Options().
477 args: Command line args used to filter out projects.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200478 all_projects: List of all projects that should be fetched.
Mike Frysingerb4429432021-05-05 20:03:26 -0400479 err_event: Whether an error was hit while processing.
480 manifest_name: Manifest file to be reloaded.
481 load_local_manifests: Whether to load local manifests.
Mike Frysinger339f2df2021-05-06 00:44:42 -0400482 ssh_proxy: SSH manager for clients & masters.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200483
484 Returns:
485 List of all projects that should be checked out.
Mike Frysingerb4429432021-05-05 20:03:26 -0400486 """
487 rp = self.manifest.repoProject
488
489 to_fetch = []
490 now = time.time()
491 if _ONE_DAY_S <= (now - rp.LastFetch):
492 to_fetch.append(rp)
493 to_fetch.extend(all_projects)
494 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
495
Mike Frysinger339f2df2021-05-06 00:44:42 -0400496 success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400497 if not success:
498 err_event.set()
499
500 _PostRepoFetch(rp, opt.repo_verify)
501 if opt.network_only:
502 # bail out now; the rest touches the working tree
503 if err_event.is_set():
504 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
505 sys.exit(1)
506 return
507
508 # Iteratively fetch missing and/or nested unregistered submodules
509 previously_missing_set = set()
510 while True:
511 self._ReloadManifest(manifest_name, load_local_manifests)
512 all_projects = self.GetProjects(args,
513 missing_ok=True,
514 submodules_ok=opt.fetch_submodules)
515 missing = []
516 for project in all_projects:
517 if project.gitdir not in fetched:
518 missing.append(project)
519 if not missing:
520 break
521 # Stop us from non-stopped fetching actually-missing repos: If set of
522 # missing repos has not been changed from last fetch, we break.
523 missing_set = set(p.name for p in missing)
524 if previously_missing_set == missing_set:
525 break
526 previously_missing_set = missing_set
Mike Frysinger339f2df2021-05-06 00:44:42 -0400527 success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400528 if not success:
529 err_event.set()
530 fetched.update(new_fetched)
531
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200532 return all_projects
533
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500534 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700535 """Checkout work tree for one project
536
537 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500538 detach_head: Whether to leave a detached HEAD.
539 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700540 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700541
542 Returns:
543 Whether the fetch was successful.
544 """
Xin Li745be2e2019-06-03 11:24:30 -0700545 start = time.time()
546 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500547 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700548 success = False
549 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500550 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500551 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700552 except GitError as e:
553 print('error.GitError: Cannot checkout %s: %s' %
554 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500555 except Exception as e:
556 print('error: Cannot checkout %s: %s: %s' %
557 (project.name, type(e).__name__, str(e)),
558 file=sys.stderr)
559 raise
Xin Li745be2e2019-06-03 11:24:30 -0700560
Mike Frysingerebf04a42021-02-23 20:48:04 -0500561 if not success:
562 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
563 finish = time.time()
564 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700565
Mike Frysingerebf04a42021-02-23 20:48:04 -0500566 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700567 """Checkout projects listed in all_projects
568
569 Args:
570 all_projects: List of all projects that should be checked out.
571 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500572 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700573 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500574 # Only checkout projects with worktrees.
575 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700576
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500577 def _ProcessResults(pool, pm, results):
578 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500579 for (success, project, start, finish) in results:
580 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
581 start, finish, success)
582 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500583 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500584 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500585 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500586 err_results.append(project.relpath)
587 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500588 if pool:
589 pool.close()
590 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500591 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500592 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700593
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500594 return self.ExecuteInParallel(
595 opt.jobs_checkout if opt.jobs_checkout else self.jobs,
596 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
597 all_projects,
598 callback=_ProcessResults,
599 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500600
Mike Frysinger5a033082019-09-23 19:21:20 -0400601 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400602 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400603 pm.update(inc=0, msg='prescan')
604
Gabe Black2ff30292014-10-09 17:54:35 -0700605 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700606 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500607 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500608 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900609 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100610 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400611 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100612 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500613 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500614 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500615 else:
616 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400617 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500618 'versions of git; please upgrade to git-2.7.0+.'
619 % (project.relpath,),
620 file=sys.stderr)
621 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700622 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700623
Mike Frysinger65af2602021-04-08 22:47:44 -0400624 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
625
626 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700627 jobs = min(self.jobs, cpu_count)
628
629 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700630 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400631 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700632 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400633 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700634 return
635
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400636 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700637
638 threads = set()
639 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700640
David James8d201162013-10-11 17:03:19 -0700641 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400642 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700643 try:
644 try:
David James8d201162013-10-11 17:03:19 -0700645 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700646 except GitError:
647 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900648 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700649 err_event.set()
650 raise
651 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400652 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700653 sem.release()
654
Gabe Black2ff30292014-10-09 17:54:35 -0700655 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500656 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700657 break
658 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700659 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700660 t.daemon = True
661 threads.add(t)
662 t.start()
663
664 for t in threads:
665 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400666 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700667
Raman Tennetifeb28912021-05-02 19:47:29 -0700668 def _ReloadManifest(self, manifest_name=None, load_local_manifests=True):
669 """Reload the manfiest from the file specified by the |manifest_name|.
670
671 It unloads the manifest if |manifest_name| is None.
672
673 Args:
674 manifest_name: Manifest file to be reloaded.
675 load_local_manifests: Whether to load local manifests.
676 """
Tim Kilbourn07669002013-03-08 15:02:49 -0800677 if manifest_name:
678 # Override calls _Unload already
Raman Tennetifeb28912021-05-02 19:47:29 -0700679 self.manifest.Override(manifest_name, load_local_manifests=load_local_manifests)
Tim Kilbourn07669002013-03-08 15:02:49 -0800680 else:
681 self.manifest._Unload()
682
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500683 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700684 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700685 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700686 if project.relpath:
687 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700688 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500689 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700690 old_project_paths = []
691
692 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500693 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700694 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800695 # In reversed order, so subfolders are deleted before parent folder.
696 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700697 if not path:
698 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700699 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900700 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700701 gitdir = os.path.join(self.manifest.topdir, path, '.git')
702 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900703 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900704 manifest=self.manifest,
705 name=path,
706 remote=RemoteSpec('origin'),
707 gitdir=gitdir,
708 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500709 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900710 worktree=os.path.join(self.manifest.topdir, path),
711 relpath=path,
712 revisionExpr='HEAD',
713 revisionId=None,
714 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500715 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900716 quiet=opt.quiet,
717 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400718 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700719
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700720 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500721 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700722 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700723 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700724 return 0
725
jiajia tanga590e642021-04-25 20:02:02 +0800726 def UpdateCopyLinkfileList(self):
727 """Save all dests of copyfile and linkfile, and update them if needed.
728
729 Returns:
730 Whether update was successful.
731 """
732 new_paths = {}
733 new_linkfile_paths = []
734 new_copyfile_paths = []
735 for project in self.GetProjects(None, missing_ok=True):
736 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
737 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
738
739 new_paths = {
740 'linkfile': new_linkfile_paths,
741 'copyfile': new_copyfile_paths,
742 }
743
744 copylinkfile_name = 'copy-link-files.json'
745 copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name)
746 old_copylinkfile_paths = {}
747
748 if os.path.exists(copylinkfile_path):
749 with open(copylinkfile_path, 'rb') as fp:
750 try:
751 old_copylinkfile_paths = json.load(fp)
752 except:
753 print('error: %s is not a json formatted file.' %
754 copylinkfile_path, file=sys.stderr)
755 platform_utils.remove(copylinkfile_path)
756 return False
757
758 need_remove_files = []
759 need_remove_files.extend(
760 set(old_copylinkfile_paths.get('linkfile', [])) -
761 set(new_linkfile_paths))
762 need_remove_files.extend(
763 set(old_copylinkfile_paths.get('copyfile', [])) -
764 set(new_copyfile_paths))
765
766 for need_remove_file in need_remove_files:
767 try:
768 platform_utils.remove(need_remove_file)
769 except OSError as e:
770 if e.errno == errno.ENOENT:
771 # Try to remove the updated copyfile or linkfile.
772 # So, if the file is not exist, nothing need to do.
773 pass
774
775 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
776 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
777 json.dump(new_paths, fp)
778 return True
779
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400780 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
781 if not self.manifest.manifest_server:
782 print('error: cannot smart sync: no manifest server defined in '
783 'manifest', file=sys.stderr)
784 sys.exit(1)
785
786 manifest_server = self.manifest.manifest_server
787 if not opt.quiet:
788 print('Using manifest server %s' % manifest_server)
789
David Pursehouseeeff3532020-02-12 11:24:10 +0900790 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400791 username = None
792 password = None
793 if opt.manifest_server_username and opt.manifest_server_password:
794 username = opt.manifest_server_username
795 password = opt.manifest_server_password
796 else:
797 try:
798 info = netrc.netrc()
799 except IOError:
800 # .netrc file does not exist or could not be opened
801 pass
802 else:
803 try:
804 parse_result = urllib.parse.urlparse(manifest_server)
805 if parse_result.hostname:
806 auth = info.authenticators(parse_result.hostname)
807 if auth:
808 username, _account, password = auth
809 else:
810 print('No credentials found for %s in .netrc'
811 % parse_result.hostname, file=sys.stderr)
812 except netrc.NetrcParseError as e:
813 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
814
815 if (username and password):
816 manifest_server = manifest_server.replace('://', '://%s:%s@' %
817 (username, password),
818 1)
819
820 transport = PersistentTransport(manifest_server)
821 if manifest_server.startswith('persistent-'):
822 manifest_server = manifest_server[len('persistent-'):]
823
824 try:
825 server = xmlrpc.client.Server(manifest_server, transport=transport)
826 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800827 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400828
Mike Frysinger56ce3462019-12-04 19:30:48 -0500829 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500830 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400831 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500832 elif ('TARGET_PRODUCT' in os.environ and
833 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500834 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
835 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400836 [success, manifest_str] = server.GetApprovedManifest(branch, target)
837 else:
838 [success, manifest_str] = server.GetApprovedManifest(branch)
839 else:
840 assert(opt.smart_tag)
841 [success, manifest_str] = server.GetManifest(opt.smart_tag)
842
843 if success:
844 manifest_name = os.path.basename(smart_sync_manifest_path)
845 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500846 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400847 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400848 except IOError as e:
849 print('error: cannot write manifest to %s:\n%s'
850 % (smart_sync_manifest_path, e),
851 file=sys.stderr)
852 sys.exit(1)
853 self._ReloadManifest(manifest_name)
854 else:
855 print('error: manifest server RPC call failed: %s' %
856 manifest_str, file=sys.stderr)
857 sys.exit(1)
858 except (socket.error, IOError, xmlrpc.client.Fault) as e:
859 print('error: cannot connect to manifest server %s:\n%s'
860 % (self.manifest.manifest_server, e), file=sys.stderr)
861 sys.exit(1)
862 except xmlrpc.client.ProtocolError as e:
863 print('error: cannot connect to manifest server %s:\n%d %s'
864 % (self.manifest.manifest_server, e.errcode, e.errmsg),
865 file=sys.stderr)
866 sys.exit(1)
867
868 return manifest_name
869
Mike Frysingerfb527e32019-08-27 02:34:32 -0400870 def _UpdateManifestProject(self, opt, mp, manifest_name):
871 """Fetch & update the local manifest project."""
872 if not opt.local_only:
873 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500874 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700875 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200876 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500877 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400878 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600879 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400880 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700881 clone_filter=self.manifest.CloneFilter,
882 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400883 finish = time.time()
884 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
885 start, finish, success)
886
887 if mp.HasChanges:
888 syncbuf = SyncBuffer(mp.config)
889 start = time.time()
890 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
891 clean = syncbuf.Finish()
892 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
893 start, time.time(), clean)
894 if not clean:
895 sys.exit(1)
Mike Frysinger05638bf2021-05-04 15:33:31 -0400896 self._ReloadManifest(manifest_name)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400897 if opt.jobs is None:
898 self.jobs = self.manifest.default.sync_j
899
Mike Frysingerae6cb082019-08-27 01:10:59 -0400900 def ValidateOptions(self, opt, args):
901 if opt.force_broken:
902 print('warning: -f/--force-broken is now the default behavior, and the '
903 'options are deprecated', file=sys.stderr)
904 if opt.network_only and opt.detach_head:
905 self.OptionParser.error('cannot combine -n and -d')
906 if opt.network_only and opt.local_only:
907 self.OptionParser.error('cannot combine -n and -l')
908 if opt.manifest_name and opt.smart_sync:
909 self.OptionParser.error('cannot combine -m and -s')
910 if opt.manifest_name and opt.smart_tag:
911 self.OptionParser.error('cannot combine -m and -t')
912 if opt.manifest_server_username or opt.manifest_server_password:
913 if not (opt.smart_sync or opt.smart_tag):
914 self.OptionParser.error('-u and -p may only be combined with -s or -t')
915 if None in [opt.manifest_server_username, opt.manifest_server_password]:
916 self.OptionParser.error('both -u and -p must be given')
917
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800919 if opt.jobs:
920 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700921 if self.jobs > 1:
922 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400923 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700924
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500925 if opt.manifest_name:
926 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700927
Chirayu Desaia892b102013-06-11 14:18:46 +0530928 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900929 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900930 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530931
Xin Lid79a4bc2020-05-20 16:03:45 -0700932 if opt.clone_bundle is None:
933 opt.clone_bundle = self.manifest.CloneBundle
934
Victor Boivie08c880d2011-04-19 10:32:52 +0200935 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400936 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
937 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900938 if os.path.isfile(smart_sync_manifest_path):
939 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800940 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900941 except OSError as e:
942 print('error: failed to remove existing smart sync override manifest: %s' %
943 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700944
Mike Frysingerc99322a2021-05-04 15:32:43 -0400945 err_event = multiprocessing.Event()
Mike Frysinger5a033082019-09-23 19:21:20 -0400946
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947 rp = self.manifest.repoProject
948 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500949 cb = rp.CurrentBranch
950 if cb:
951 base = rp.GetBranch(cb).merge
952 if not base or not base.startswith('refs/heads/'):
953 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400954 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500955 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956
957 mp = self.manifest.manifestProject
958 mp.PreSync()
959
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800960 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700961 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800962
Fredrik de Grootcc960972019-11-22 09:04:31 +0100963 if not opt.mp_update:
964 print('Skipping update of local manifest project.')
965 else:
966 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700967
Raman Tennetifeb28912021-05-02 19:47:29 -0700968 load_local_manifests = not self.manifest.HasLocalManifests
Raman Tenneti7954de12021-07-28 14:36:49 -0700969 use_superproject = git_superproject.UseSuperproject(opt, self.manifest)
970 superproject_logging_data = {
971 'superproject': use_superproject,
972 'haslocalmanifests': bool(self.manifest.HasLocalManifests),
973 }
974 if use_superproject:
975 manifest_name = self._UpdateProjectsRevisionId(
976 opt, args, load_local_manifests, superproject_logging_data) or opt.manifest_name
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800977
Simran Basib9a1b732015-08-20 12:19:28 -0700978 if self.gitc_manifest:
979 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700980 missing_ok=True)
981 gitc_projects = []
982 opened_projects = []
983 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700984 if project.relpath in self.gitc_manifest.paths and \
985 self.gitc_manifest.paths[project.relpath].old_revision:
986 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700987 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700988 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700989
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700990 if not args:
991 gitc_projects = None
992
993 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700994 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700995 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
996 if manifest_name:
997 manifest.Override(manifest_name)
998 else:
999 manifest.Override(self.manifest.manifestFile)
1000 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
1001 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -07001002 gitc_projects)
1003 print('GITC client successfully synced.')
1004
1005 # The opened projects need to be synced as normal, therefore we
1006 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001007 # TODO: make this more reliable -- if there's a project name/path overlap,
1008 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +09001009 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
1010 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -07001011 if not args:
1012 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001013 all_projects = self.GetProjects(args,
1014 missing_ok=True,
1015 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016
Mike Frysinger5a033082019-09-23 19:21:20 -04001017 err_network_sync = False
1018 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -04001019
Dave Borowitz67700e92012-10-23 15:00:54 -07001020 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -07001021 if not opt.local_only:
Mike Frysinger339f2df2021-05-06 00:44:42 -04001022 with multiprocessing.Manager() as manager:
1023 with ssh.ProxyManager(manager) as ssh_proxy:
1024 # Initialize the socket dir once in the parent.
1025 ssh_proxy.sock()
Peter Kjellerstedtd1776092021-05-19 19:37:23 +02001026 all_projects = self._FetchMain(opt, args, all_projects, err_event,
1027 manifest_name, load_local_manifests,
1028 ssh_proxy)
Mike Frysinger339f2df2021-05-06 00:44:42 -04001029
1030 if opt.network_only:
1031 return
Mike Frysinger5a033082019-09-23 19:21:20 -04001032
1033 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001034 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001035 err_network_sync = True
1036 if opt.fail_fast:
1037 print('\nerror: Exited sync due to fetch errors.\n'
1038 'Local checkouts *not* updated. Resolve network issues & '
1039 'retry.\n'
1040 '`repo sync -l` will update some local checkouts.',
1041 file=sys.stderr)
1042 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001043
Julien Campergue335f5ef2013-10-16 11:02:35 +02001044 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001045 # bail out now, we have no working tree
1046 return
1047
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -05001048 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -04001049 err_event.set()
1050 err_update_projects = True
1051 if opt.fail_fast:
1052 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1053 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001054
Mike Frysinger14208f42021-05-04 15:31:51 -04001055 err_update_linkfiles = not self.UpdateCopyLinkfileList()
1056 if err_update_linkfiles:
jiajia tanga590e642021-04-25 20:02:02 +08001057 err_event.set()
jiajia tanga590e642021-04-25 20:02:02 +08001058 if opt.fail_fast:
1059 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
1060 sys.exit(1)
1061
Mike Frysinger5a033082019-09-23 19:21:20 -04001062 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -05001063 # NB: We don't exit here because this is the last step.
1064 err_checkout = not self._Checkout(all_projects, opt, err_results)
1065 if err_checkout:
1066 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001067
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001068 # If there's a notice that's supposed to print at the end of the sync, print
1069 # it now...
1070 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001071 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001072
Mike Frysinger5a033082019-09-23 19:21:20 -04001073 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001074 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001075 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1076 if err_network_sync:
1077 print('error: Downloading network changes failed.', file=sys.stderr)
1078 if err_update_projects:
1079 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +08001080 if err_update_linkfiles:
1081 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -04001082 if err_checkout:
1083 print('error: Checking out local projects failed.', file=sys.stderr)
1084 if err_results:
1085 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1086 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1087 file=sys.stderr)
1088 sys.exit(1)
1089
Raman Tenneti7954de12021-07-28 14:36:49 -07001090 # Log the previous sync analysis state from the config.
1091 self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
1092 'previous_sync_state')
1093
1094 # Update and log with the new sync analysis state.
1095 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1096 self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(),
1097 'current_sync_state')
1098
Mike Frysingere19d9e12020-02-12 11:23:32 -05001099 if not opt.quiet:
1100 print('repo sync has finished successfully.')
1101
David Pursehouse819827a2020-02-12 15:20:19 +09001102
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001103def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001104 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001105 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001106 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001107 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001108 if project.Exists:
1109 project.PostRepoUpgrade()
1110
David Pursehouse819827a2020-02-12 15:20:19 +09001111
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001112def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001113 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001114 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001115 wrapper = Wrapper()
1116 try:
1117 rev = rp.bare_git.describe(rp.GetRevisionId())
1118 except GitError:
1119 rev = None
1120 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1121 # See if we're held back due to missing signed tag.
1122 current_revid = rp.bare_git.rev_parse('HEAD')
1123 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1124 if current_revid != new_revid:
1125 # We want to switch to the new rev, but also not trash any uncommitted
1126 # changes. This helps with local testing/hacking.
1127 # If a local change has been made, we will throw that away.
1128 # We also have to make sure this will switch to an older commit if that's
1129 # the latest tag in order to support release rollback.
1130 try:
1131 rp.work_git.reset('--keep', new_rev)
1132 except GitError as e:
1133 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001134 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001135 raise RepoChangedException(['--repo-upgraded'])
1136 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001137 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001138 else:
1139 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001140 print('repo version %s is current' % rp.work_git.describe(HEAD),
1141 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001142
David Pursehouse819827a2020-02-12 15:20:19 +09001143
Dave Borowitz67700e92012-10-23 15:00:54 -07001144class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001145 _ALPHA = 0.5
1146
Dave Borowitz67700e92012-10-23 15:00:54 -07001147 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001148 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001149 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001150 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001151
1152 def Get(self, project):
1153 self._Load()
1154 return self._times.get(project.name, _ONE_DAY_S)
1155
1156 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001157 self._Load()
1158 name = project.name
1159 old = self._times.get(name, t)
1160 self._seen.add(name)
1161 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001162 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001163
1164 def _Load(self):
1165 if self._times is None:
1166 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001167 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001168 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001169 except (IOError, ValueError):
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
1174 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001175
1176 def Save(self):
1177 if self._times is None:
1178 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001179
1180 to_delete = []
1181 for name in self._times:
1182 if name not in self._seen:
1183 to_delete.append(name)
1184 for name in to_delete:
1185 del self._times[name]
1186
Dave Borowitz67700e92012-10-23 15:00:54 -07001187 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001188 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001189 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001190 except (IOError, TypeError):
1191 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001192 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001193 except OSError:
1194 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001195
1196# This is a replacement for xmlrpc.client.Transport using urllib2
1197# and supporting persistent-http[s]. It cannot change hosts from
1198# request to request like the normal transport, the real url
1199# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001200
1201
Dan Willemsen0745bb22015-08-17 13:41:45 -07001202class PersistentTransport(xmlrpc.client.Transport):
1203 def __init__(self, orig_host):
1204 self.orig_host = orig_host
1205
1206 def request(self, host, handler, request_body, verbose=False):
1207 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1208 # Python doesn't understand cookies with the #HttpOnly_ prefix
1209 # Since we're only using them for HTTP, copy the file temporarily,
1210 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001211 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001212 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001213 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001214 try:
1215 with open(cookiefile) as f:
1216 for line in f:
1217 if line.startswith("#HttpOnly_"):
1218 line = line[len("#HttpOnly_"):]
1219 tmpcookiefile.write(line)
1220 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001221
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001222 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001223 try:
1224 cookiejar.load()
1225 except cookielib.LoadError:
1226 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001227 finally:
1228 tmpcookiefile.close()
1229 else:
1230 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001231
1232 proxyhandler = urllib.request.ProxyHandler
1233 if proxy:
1234 proxyhandler = urllib.request.ProxyHandler({
1235 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001236 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001237
1238 opener = urllib.request.build_opener(
1239 urllib.request.HTTPCookieProcessor(cookiejar),
1240 proxyhandler)
1241
1242 url = urllib.parse.urljoin(self.orig_host, handler)
1243 parse_results = urllib.parse.urlparse(url)
1244
1245 scheme = parse_results.scheme
1246 if scheme == 'persistent-http':
1247 scheme = 'http'
1248 if scheme == 'persistent-https':
1249 # If we're proxying through persistent-https, use http. The
1250 # proxy itself will do the https.
1251 if proxy:
1252 scheme = 'http'
1253 else:
1254 scheme = 'https'
1255
1256 # Parse out any authentication information using the base class
1257 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1258
1259 url = urllib.parse.urlunparse((
1260 scheme,
1261 host,
1262 parse_results.path,
1263 parse_results.params,
1264 parse_results.query,
1265 parse_results.fragment))
1266
1267 request = urllib.request.Request(url, request_body)
1268 if extra_headers is not None:
1269 for (name, header) in extra_headers:
1270 request.add_header(name, header)
1271 request.add_header('Content-Type', 'text/xml')
1272 try:
1273 response = opener.open(request)
1274 except urllib.error.HTTPError as e:
1275 if e.code == 501:
1276 # We may have been redirected through a login process
1277 # but our POST turned into a GET. Retry.
1278 response = opener.open(request)
1279 else:
1280 raise
1281
1282 p, u = xmlrpc.client.getparser()
1283 while 1:
1284 data = response.read(1024)
1285 if not data:
1286 break
1287 p.feed(data)
1288 p.close()
1289 return u.close()
1290
1291 def close(self):
1292 pass