blob: ca4b97b316a1033884fe21d32fffe5c5e59edc6d [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Anthony King85b24ac2014-05-06 15:57:48 +010018import json
David Pursehouse86d973d2012-08-24 10:21:02 +090019import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070020from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import os
22import re
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070023import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import subprocess
25import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
David Pursehouse59bbb582013-05-17 10:49:33 +090028
29from pyversion import is_python3
30if is_python3():
Dan Willemsen0745bb22015-08-17 13:41:45 -070031 import http.cookiejar as cookielib
32 import urllib.error
Chirayu Desai217ea7d2013-03-01 19:14:38 +053033 import urllib.parse
Dan Willemsen0745bb22015-08-17 13:41:45 -070034 import urllib.request
David Pursehouse59bbb582013-05-17 10:49:33 +090035 import xmlrpc.client
36else:
Dan Willemsen0745bb22015-08-17 13:41:45 -070037 import cookielib
Chirayu Desai217ea7d2013-03-01 19:14:38 +053038 import imp
Dan Willemsen0745bb22015-08-17 13:41:45 -070039 import urllib2
Chirayu Desai217ea7d2013-03-01 19:14:38 +053040 import urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090041 import xmlrpclib
Chirayu Desai217ea7d2013-03-01 19:14:38 +053042 urllib = imp.new_module('urllib')
Dan Willemsen0745bb22015-08-17 13:41:45 -070043 urllib.error = urllib2
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +053044 urllib.parse = urlparse
Dan Willemsen0745bb22015-08-17 13:41:45 -070045 urllib.request = urllib2
Chirayu Desai217ea7d2013-03-01 19:14:38 +053046 xmlrpc = imp.new_module('xmlrpc')
47 xmlrpc.client = xmlrpclib
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048
Roy Lee18afd7f2010-05-09 04:32:08 +080049try:
50 import threading as _threading
51except ImportError:
52 import dummy_threading as _threading
53
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070054try:
55 import resource
56 def _rlimit_nofile():
57 return resource.getrlimit(resource.RLIMIT_NOFILE)
58except ImportError:
59 def _rlimit_nofile():
60 return (256, 256)
61
Dave Borowitz18857212012-10-23 17:02:59 -070062try:
63 import multiprocessing
64except ImportError:
65 multiprocessing = None
66
David Rileye0684ad2017-04-05 00:02:59 -070067import event_log
Dave Borowitze2152672012-10-31 12:24:38 -070068from git_command import GIT, git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090069from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090070from git_refs import R_HEADS, HEAD
Simran Basibdb52712015-08-10 13:23:23 -070071import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070072from project import Project
73from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080074from command import Command, MirrorSafeCommand
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +000075from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070076import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070077from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070078from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080079from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070080from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081
Dave Borowitz67700e92012-10-23 15:00:54 -070082_ONE_DAY_S = 24 * 60 * 60
83
Doug Andersonfc06ced2011-03-16 15:49:18 -070084class _FetchError(Exception):
85 """Internal error thrown in _FetchHelper() when we don't want stack trace."""
86 pass
87
Xin Li745be2e2019-06-03 11:24:30 -070088class _CheckoutError(Exception):
89 """Internal error thrown in _CheckoutOne() when we don't want stack trace."""
90
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080091class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080092 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093 common = True
94 helpSummary = "Update working tree to the latest revision"
95 helpUsage = """
96%prog [<project>...]
97"""
98 helpDescription = """
99The '%prog' command synchronizes local project directories
100with the remote repositories specified in the manifest. If a local
101project does not yet exist, it will clone a new local directory from
102the remote repository and set up tracking branches as specified in
103the manifest. If the local project already exists, '%prog'
104will update the remote branches and rebase any new local changes
105on top of the new remote changes.
106
107'%prog' will synchronize all projects listed at the command
108line. Projects can be specified either by name, or by a relative
109or absolute path to the project's local directory. If no projects
110are specified, '%prog' will synchronize all projects listed in
111the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700112
113The -d/--detach option can be used to switch specified projects
114back to the manifest revision. This option is especially helpful
115if the project is currently on a topic branch, but the manifest
116revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700117
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700118The -s/--smart-sync option can be used to sync to a known good
119build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200120manifest. The -t/--smart-tag option is similar and allows you to
121specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700122
David Pursehousecf76b1b2012-09-14 10:31:42 +0900123The -u/--manifest-server-username and -p/--manifest-server-password
124options can be used to specify a username and password to authenticate
125with the manifest server when using the -s or -t option.
126
127If -u and -p are not specified when using the -s or -t option, '%prog'
128will attempt to read authentication credentials for the manifest server
129from the user's .netrc file.
130
131'%prog' will not use authentication credentials from -u/-p or .netrc
132if the manifest server specified in the manifest file already includes
133credentials.
134
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400135By default, all projects will be synced. The --fail-fast option can be used
136to halt syncing as soon as possible when the the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500137
Kevin Degiabaa7f32014-11-12 11:27:45 -0700138The --force-sync option can be used to overwrite existing git
139directories if they have previously been linked to a different
140object direcotry. WARNING: This may cause data to be lost since
141refs may be removed when overwriting.
142
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500143The --force-remove-dirty option can be used to remove previously used
144projects with uncommitted changes. WARNING: This may cause data to be
145lost since uncommitted changes may be removed with projects that no longer
146exist in the manifest.
147
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700148The --no-clone-bundle option disables any attempt to use
149$URL/clone.bundle to bootstrap a new Git repository from a
150resumeable bundle file on a content delivery network. This
151may be necessary if there are problems with the local Python
152HTTP client or proxy configuration, but the Git binary works.
153
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800154The --fetch-submodules option enables fetching Git submodules
155of a project from server.
156
David Pursehousef2fad612015-01-29 14:36:28 +0900157The -c/--current-branch option can be used to only fetch objects that
158are on the branch specified by a project's revision.
159
David Pursehouseb1553542014-09-04 21:28:09 +0900160The --optimized-fetch option can be used to only fetch projects that
161are fixed to a sha1 revision if the sha1 revision does not already
162exist locally.
163
David Pursehouse74cfd272015-10-14 10:50:15 +0900164The --prune option can be used to remove any refs that no longer
165exist on the remote.
166
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400167# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700168
169If at least one project remote URL uses an SSH connection (ssh://,
170git+ssh://, or user@host:path syntax) repo will automatically
171enable the SSH ControlMaster option when connecting to that host.
172This feature permits other projects in the same '%prog' session to
173reuse the same SSH tunnel, saving connection setup overheads.
174
175To disable this behavior on UNIX platforms, set the GIT_SSH
176environment variable to 'ssh'. For example:
177
178 export GIT_SSH=ssh
179 %prog
180
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400181# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700182
183This feature is automatically disabled on Windows, due to the lack
184of UNIX domain socket support.
185
186This feature is not compatible with url.insteadof rewrites in the
187user's ~/.gitconfig. '%prog' is currently not able to perform the
188rewrite early enough to establish the ControlMaster tunnel.
189
190If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
191later is required to fix a server side protocol bug.
192
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193"""
194
Nico Sallembien6623b212010-05-11 12:57:01 -0700195 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000196 try:
197 self.jobs = self.manifest.default.sync_j
198 except ManifestParseError:
199 self.jobs = 1
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700200
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500201 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200202 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400203 help='obsolete option (to be deleted in the future)')
204 p.add_option('--fail-fast',
205 dest='fail_fast', action='store_true',
206 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700207 p.add_option('--force-sync',
208 dest='force_sync', action='store_true',
209 help="overwrite an existing git directory if it needs to "
210 "point to a different object directory. WARNING: this "
211 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500212 p.add_option('--force-remove-dirty',
213 dest='force_remove_dirty', action='store_true',
214 help="force remove projects with uncommitted modifications if "
215 "projects no longer exist in the manifest. "
216 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900217 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700218 dest='local_only', action='store_true',
219 help="only update working tree, don't fetch")
Fredrik de Grootcc960972019-11-22 09:04:31 +0100220 p.add_option('--no-manifest-update','--nmu',
221 dest='mp_update', action='store_false', default='true',
222 help='use the existing manifest checkout as-is. '
223 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900224 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700225 dest='network_only', action='store_true',
226 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900227 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700228 dest='detach_head', action='store_true',
229 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900230 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700231 dest='current_branch_only', action='store_true',
232 help='fetch only current branch from server')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900233 p.add_option('-q', '--quiet',
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700234 dest='quiet', action='store_true',
235 help='be more quiet')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900236 p.add_option('-j', '--jobs',
Roy Lee18afd7f2010-05-09 04:32:08 +0800237 dest='jobs', action='store', type='int',
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700238 help="projects to fetch simultaneously (default %d)" % self.jobs)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500239 p.add_option('-m', '--manifest-name',
240 dest='manifest_name',
241 help='temporary manifest to use for this sync', metavar='NAME.xml')
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700242 p.add_option('--no-clone-bundle',
243 dest='no_clone_bundle', action='store_true',
244 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800245 p.add_option('-u', '--manifest-server-username', action='store',
246 dest='manifest_server_username',
247 help='username to authenticate with the manifest server')
248 p.add_option('-p', '--manifest-server-password', action='store',
249 dest='manifest_server_password',
250 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800251 p.add_option('--fetch-submodules',
252 dest='fetch_submodules', action='store_true',
253 help='fetch submodules from server')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700254 p.add_option('--no-tags',
255 dest='no_tags', action='store_true',
256 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900257 p.add_option('--optimized-fetch',
258 dest='optimized_fetch', action='store_true',
259 help='only fetch projects fixed to sha1 if revision does not exist locally')
David Pursehouse74cfd272015-10-14 10:50:15 +0900260 p.add_option('--prune', dest='prune', action='store_true',
261 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700262 if show_smart:
263 p.add_option('-s', '--smart-sync',
264 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900265 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200266 p.add_option('-t', '--smart-tag',
267 dest='smart_tag', action='store',
268 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700269
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700270 g = p.add_option_group('repo Version options')
271 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700272 dest='no_repo_verify', action='store_true',
273 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700274 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800275 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700276 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700277
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500278 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
Xin Li745be2e2019-06-03 11:24:30 -0700279 """Main function of the fetch threads.
Roy Lee18afd7f2010-05-09 04:32:08 +0800280
David James8d201162013-10-11 17:03:19 -0700281 Delegates most of the work to _FetchHelper.
282
283 Args:
284 opt: Program options returned from optparse. See _Options().
285 projects: Projects to fetch.
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500286 sem: We'll release() this semaphore when we exit so that another thread
287 can be started up.
David James89ece422014-01-09 18:51:58 -0800288 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
David James8d201162013-10-11 17:03:19 -0700289 _FetchHelper docstring for details.
290 """
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500291 try:
292 for project in projects:
293 success = self._FetchHelper(opt, project, *args, **kwargs)
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400294 if not success and opt.fail_fast:
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500295 break
296 finally:
297 sem.release()
David James8d201162013-10-11 17:03:19 -0700298
Xin Li745be2e2019-06-03 11:24:30 -0700299 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event,
300 clone_filter):
David James8d201162013-10-11 17:03:19 -0700301 """Fetch git objects for a single project.
302
David Pursehousec1b86a22012-11-14 11:36:51 +0900303 Args:
304 opt: Program options returned from optparse. See _Options().
305 project: Project object for the project to fetch.
306 lock: Lock for accessing objects that are shared amongst multiple
307 _FetchHelper() threads.
308 fetched: set object that we will add project.gitdir to when we're done
309 (with our lock held).
310 pm: Instance of a Project object. We will call pm.update() (with our
311 lock held).
David Pursehousec1b86a22012-11-14 11:36:51 +0900312 err_event: We'll set this event in the case of an error (after printing
313 out info about the error).
Xin Li745be2e2019-06-03 11:24:30 -0700314 clone_filter: Filter for use in a partial clone.
David James8d201162013-10-11 17:03:19 -0700315
316 Returns:
317 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900318 """
319 # We'll set to true once we've locked the lock.
320 did_lock = False
Doug Andersonfc06ced2011-03-16 15:49:18 -0700321
David Pursehousec1b86a22012-11-14 11:36:51 +0900322 # Encapsulate everything in a try/except/finally so that:
323 # - We always set err_event in the case of an exception.
David Pursehousec1b86a22012-11-14 11:36:51 +0900324 # - We always make sure we unlock the lock if we locked it.
David Rileye0684ad2017-04-05 00:02:59 -0700325 start = time.time()
326 success = False
David Pursehousec1b86a22012-11-14 11:36:51 +0900327 try:
Doug Andersonfc06ced2011-03-16 15:49:18 -0700328 try:
David Pursehousec1b86a22012-11-14 11:36:51 +0900329 success = project.Sync_NetworkHalf(
330 quiet=opt.quiet,
331 current_branch_only=opt.current_branch_only,
Kevin Degiabaa7f32014-11-12 11:27:45 -0700332 force_sync=opt.force_sync,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700333 clone_bundle=not opt.no_clone_bundle,
David Pursehouseb1553542014-09-04 21:28:09 +0900334 no_tags=opt.no_tags, archive=self.manifest.IsArchive,
David Pursehouse74cfd272015-10-14 10:50:15 +0900335 optimized_fetch=opt.optimized_fetch,
Xin Li745be2e2019-06-03 11:24:30 -0700336 prune=opt.prune,
337 clone_filter=clone_filter)
David Pursehousec1b86a22012-11-14 11:36:51 +0900338 self._fetch_times.Set(project, time.time() - start)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700339
David Pursehousec1b86a22012-11-14 11:36:51 +0900340 # Lock around all the rest of the code, since printing, updating a set
341 # and Progress.update() are not thread safe.
342 lock.acquire()
343 did_lock = True
Doug Andersonfc06ced2011-03-16 15:49:18 -0700344
David Pursehousec1b86a22012-11-14 11:36:51 +0900345 if not success:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800346 err_event.set()
Marc Herbertffb4b892017-04-04 22:03:53 -0700347 print('error: Cannot fetch %s from %s'
348 % (project.name, project.remote.url),
349 file=sys.stderr)
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400350 if opt.fail_fast:
David Pursehousec1b86a22012-11-14 11:36:51 +0900351 raise _FetchError()
Doug Andersonfc06ced2011-03-16 15:49:18 -0700352
David Pursehousec1b86a22012-11-14 11:36:51 +0900353 fetched.add(project.gitdir)
Mike Frysinger3538dd22019-08-26 15:32:06 -0400354 pm.update(msg=project.name)
David Pursehousec1b86a22012-11-14 11:36:51 +0900355 except _FetchError:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800356 pass
Dan Sandlerc5cd4332015-07-31 09:37:53 -0400357 except Exception as e:
358 print('error: Cannot fetch %s (%s: %s)' \
359 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
David Pursehousec1b86a22012-11-14 11:36:51 +0900360 err_event.set()
361 raise
362 finally:
363 if did_lock:
364 lock.release()
David Rileye0684ad2017-04-05 00:02:59 -0700365 finish = time.time()
366 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
367 start, finish, success)
Roy Lee18afd7f2010-05-09 04:32:08 +0800368
David James8d201162013-10-11 17:03:19 -0700369 return success
370
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700371 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372 fetched = set()
David James89ece422014-01-09 18:51:58 -0800373 lock = _threading.Lock()
Tim Schumacher913327f2017-06-05 15:01:41 +0200374 pm = Progress('Fetching projects', len(projects),
Tim Schumacher7be072e2017-06-28 18:29:23 +0200375 always_print_percentage=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800376
David James89ece422014-01-09 18:51:58 -0800377 objdir_project_map = dict()
378 for project in projects:
379 objdir_project_map.setdefault(project.objdir, []).append(project)
David James8d201162013-10-11 17:03:19 -0700380
David James89ece422014-01-09 18:51:58 -0800381 threads = set()
382 sem = _threading.Semaphore(self.jobs)
383 err_event = _threading.Event()
384 for project_list in objdir_project_map.values():
385 # Check for any errors before running any more tasks.
386 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400387 if err_event.isSet() and opt.fail_fast:
David James89ece422014-01-09 18:51:58 -0800388 break
Doug Andersonfc06ced2011-03-16 15:49:18 -0700389
David James89ece422014-01-09 18:51:58 -0800390 sem.acquire()
391 kwargs = dict(opt=opt,
392 projects=project_list,
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500393 sem=sem,
David James89ece422014-01-09 18:51:58 -0800394 lock=lock,
395 fetched=fetched,
396 pm=pm,
Xin Li745be2e2019-06-03 11:24:30 -0700397 err_event=err_event,
398 clone_filter=self.manifest.CloneFilter)
David James89ece422014-01-09 18:51:58 -0800399 if self.jobs > 1:
David James8d201162013-10-11 17:03:19 -0700400 t = _threading.Thread(target = self._FetchProjectList,
David James89ece422014-01-09 18:51:58 -0800401 kwargs = kwargs)
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200402 # Ensure that Ctrl-C will not freeze the repo process.
403 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800404 threads.add(t)
405 t.start()
David James89ece422014-01-09 18:51:58 -0800406 else:
407 self._FetchProjectList(**kwargs)
Roy Lee18afd7f2010-05-09 04:32:08 +0800408
David James89ece422014-01-09 18:51:58 -0800409 for t in threads:
410 t.join()
Roy Lee18afd7f2010-05-09 04:32:08 +0800411
David James89ece422014-01-09 18:51:58 -0800412 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400413 if err_event.isSet() and opt.fail_fast:
David James89ece422014-01-09 18:51:58 -0800414 print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
415 sys.exit(1)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700416
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700417 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700418 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700419
Julien Campergue335f5ef2013-10-16 11:02:35 +0200420 if not self.manifest.IsArchive:
421 self._GCProjects(projects)
422
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700423 return fetched
424
Xin Li745be2e2019-06-03 11:24:30 -0700425 def _CheckoutWorker(self, opt, sem, project, *args, **kwargs):
426 """Main function of the fetch threads.
427
428 Delegates most of the work to _CheckoutOne.
429
430 Args:
431 opt: Program options returned from optparse. See _Options().
432 projects: Projects to fetch.
433 sem: We'll release() this semaphore when we exit so that another thread
434 can be started up.
435 *args, **kwargs: Remaining arguments to pass to _CheckoutOne. See the
436 _CheckoutOne docstring for details.
437 """
438 try:
Mike Frysingera34186e2019-08-07 18:07:31 -0400439 return self._CheckoutOne(opt, project, *args, **kwargs)
Xin Li745be2e2019-06-03 11:24:30 -0700440 finally:
441 sem.release()
442
Vadim Bendeburydff91942019-11-06 11:05:00 -0800443 def _CheckoutOne(self, opt, project, lock, pm, err_event, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700444 """Checkout work tree for one project
445
446 Args:
447 opt: Program options returned from optparse. See _Options().
448 project: Project object for the project to checkout.
449 lock: Lock for accessing objects that are shared amongst multiple
450 _CheckoutWorker() threads.
451 pm: Instance of a Project object. We will call pm.update() (with our
452 lock held).
453 err_event: We'll set this event in the case of an error (after printing
454 out info about the error).
Vadim Bendeburydff91942019-11-06 11:05:00 -0800455 err_results: A list of strings, paths to git repos where checkout
456 failed.
Xin Li745be2e2019-06-03 11:24:30 -0700457
458 Returns:
459 Whether the fetch was successful.
460 """
461 # We'll set to true once we've locked the lock.
462 did_lock = False
463
Xin Li745be2e2019-06-03 11:24:30 -0700464 # Encapsulate everything in a try/except/finally so that:
465 # - We always set err_event in the case of an exception.
466 # - We always make sure we unlock the lock if we locked it.
467 start = time.time()
468 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
469 detach_head=opt.detach_head)
470 success = False
471 try:
472 try:
473 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
Xin Li745be2e2019-06-03 11:24:30 -0700474
475 # Lock around all the rest of the code, since printing, updating a set
476 # and Progress.update() are not thread safe.
477 lock.acquire()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400478 success = syncbuf.Finish()
Xin Li745be2e2019-06-03 11:24:30 -0700479 did_lock = True
480
481 if not success:
482 err_event.set()
483 print('error: Cannot checkout %s' % (project.name),
484 file=sys.stderr)
485 raise _CheckoutError()
486
Mike Frysinger3538dd22019-08-26 15:32:06 -0400487 pm.update(msg=project.name)
Xin Li745be2e2019-06-03 11:24:30 -0700488 except _CheckoutError:
489 pass
490 except Exception as e:
491 print('error: Cannot checkout %s: %s: %s' %
492 (project.name, type(e).__name__, str(e)),
493 file=sys.stderr)
494 err_event.set()
495 raise
496 finally:
497 if did_lock:
Vadim Bendeburydff91942019-11-06 11:05:00 -0800498 if not success:
499 err_results.append(project.relpath)
Xin Li745be2e2019-06-03 11:24:30 -0700500 lock.release()
501 finish = time.time()
502 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
503 start, finish, success)
504
505 return success
506
507 def _Checkout(self, all_projects, opt):
508 """Checkout projects listed in all_projects
509
510 Args:
511 all_projects: List of all projects that should be checked out.
512 opt: Program options returned from optparse. See _Options().
513 """
514
515 # Perform checkouts in multiple threads when we are using partial clone.
516 # Without partial clone, all needed git objects are already downloaded,
517 # in this situation it's better to use only one process because the checkout
518 # would be mostly disk I/O; with partial clone, the objects are only
519 # downloaded when demanded (at checkout time), which is similar to the
520 # Sync_NetworkHalf case and parallelism would be helpful.
521 if self.manifest.CloneFilter:
522 syncjobs = self.jobs
523 else:
524 syncjobs = 1
525
526 lock = _threading.Lock()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400527 pm = Progress('Checking out projects', len(all_projects))
Xin Li745be2e2019-06-03 11:24:30 -0700528
529 threads = set()
530 sem = _threading.Semaphore(syncjobs)
531 err_event = _threading.Event()
Vadim Bendeburydff91942019-11-06 11:05:00 -0800532 err_results = []
Xin Li745be2e2019-06-03 11:24:30 -0700533
534 for project in all_projects:
535 # Check for any errors before running any more tasks.
536 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400537 if err_event.isSet() and opt.fail_fast:
Xin Li745be2e2019-06-03 11:24:30 -0700538 break
539
540 sem.acquire()
541 if project.worktree:
542 kwargs = dict(opt=opt,
543 sem=sem,
544 project=project,
545 lock=lock,
546 pm=pm,
Vadim Bendeburydff91942019-11-06 11:05:00 -0800547 err_event=err_event,
548 err_results=err_results)
Xin Li745be2e2019-06-03 11:24:30 -0700549 if syncjobs > 1:
550 t = _threading.Thread(target=self._CheckoutWorker,
551 kwargs=kwargs)
552 # Ensure that Ctrl-C will not freeze the repo process.
553 t.daemon = True
554 threads.add(t)
555 t.start()
556 else:
557 self._CheckoutWorker(**kwargs)
558
559 for t in threads:
560 t.join()
561
562 pm.end()
563 # If we saw an error, exit with code 1 so that other scripts can check.
564 if err_event.isSet():
565 print('\nerror: Exited sync due to checkout errors', file=sys.stderr)
Vadim Bendeburydff91942019-11-06 11:05:00 -0800566 if err_results:
567 print('Failing repos:\n%s' % '\n'.join(err_results),
568 file=sys.stderr)
Xin Li745be2e2019-06-03 11:24:30 -0700569 sys.exit(1)
570
Dave Borowitz18857212012-10-23 17:02:59 -0700571 def _GCProjects(self, projects):
Gabe Black2ff30292014-10-09 17:54:35 -0700572 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700573 for project in projects:
Gabe Black2ff30292014-10-09 17:54:35 -0700574 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
575 print('Shared project %s found, disabling pruning.' % project.name)
576 project.bare_git.config('--replace-all', 'gc.pruneExpire', 'never')
577 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700578
Dave Borowitze2152672012-10-31 12:24:38 -0700579 has_dash_c = git_require((1, 7, 2))
580 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700581 cpu_count = multiprocessing.cpu_count()
582 else:
583 cpu_count = 1
584 jobs = min(self.jobs, cpu_count)
585
586 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700587 for bare_git in gc_gitdirs.values():
David James8d201162013-10-11 17:03:19 -0700588 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700589 return
590
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400591 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700592
593 threads = set()
594 sem = _threading.Semaphore(jobs)
595 err_event = _threading.Event()
596
David James8d201162013-10-11 17:03:19 -0700597 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700598 try:
599 try:
David James8d201162013-10-11 17:03:19 -0700600 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700601 except GitError:
602 err_event.set()
603 except:
604 err_event.set()
605 raise
606 finally:
607 sem.release()
608
Gabe Black2ff30292014-10-09 17:54:35 -0700609 for bare_git in gc_gitdirs.values():
Dave Borowitz18857212012-10-23 17:02:59 -0700610 if err_event.isSet():
611 break
612 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700613 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700614 t.daemon = True
615 threads.add(t)
616 t.start()
617
618 for t in threads:
619 t.join()
620
621 if err_event.isSet():
Sarah Owenscecd1d82012-11-01 22:59:27 -0700622 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
Dave Borowitz18857212012-10-23 17:02:59 -0700623 sys.exit(1)
624
Tim Kilbourn07669002013-03-08 15:02:49 -0800625 def _ReloadManifest(self, manifest_name=None):
626 if manifest_name:
627 # Override calls _Unload already
628 self.manifest.Override(manifest_name)
629 else:
630 self.manifest._Unload()
631
Dan Willemsen43507912016-09-01 16:26:02 -0700632 def _DeleteProject(self, path):
633 print('Deleting obsolete path %s' % path, file=sys.stderr)
634
635 # Delete the .git directory first, so we're less likely to have a partially
636 # working git repository around. There shouldn't be any git projects here,
637 # so rmtree works.
638 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -0700639 platform_utils.rmtree(os.path.join(path, '.git'))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700640 except OSError as e:
641 print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700642 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
643 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400644 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700645
646 # Delete everything under the worktree, except for directories that contain
647 # another git project
648 dirs_to_remove = []
649 failed = False
Renaud Paquaybed8b622018-09-27 10:46:58 -0700650 for root, dirs, files in platform_utils.walk(path):
Dan Willemsen43507912016-09-01 16:26:02 -0700651 for f in files:
652 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800653 platform_utils.remove(os.path.join(root, f))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700654 except OSError as e:
655 print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700656 failed = True
657 dirs[:] = [d for d in dirs
658 if not os.path.lexists(os.path.join(root, d, '.git'))]
659 dirs_to_remove += [os.path.join(root, d) for d in dirs
660 if os.path.join(root, d) not in dirs_to_remove]
661 for d in reversed(dirs_to_remove):
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700662 if platform_utils.islink(d):
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700663 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800664 platform_utils.remove(d)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700665 except OSError as e:
666 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700667 failed = True
Renaud Paquaybed8b622018-09-27 10:46:58 -0700668 elif len(platform_utils.listdir(d)) == 0:
Dan Willemsen43507912016-09-01 16:26:02 -0700669 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700670 platform_utils.rmdir(d)
671 except OSError as e:
672 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700673 failed = True
674 continue
675 if failed:
676 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
677 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400678 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700679
680 # Try deleting parent dirs if they are empty
681 project_dir = path
682 while project_dir != self.manifest.topdir:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700683 if len(platform_utils.listdir(project_dir)) == 0:
684 platform_utils.rmdir(project_dir)
Dan Willemsen43507912016-09-01 16:26:02 -0700685 else:
686 break
687 project_dir = os.path.dirname(project_dir)
688
689 return 0
690
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500691 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700692 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700693 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700694 if project.relpath:
695 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700696 file_name = 'project.list'
697 file_path = os.path.join(self.manifest.repodir, file_name)
698 old_project_paths = []
699
700 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500701 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700702 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800703 # In reversed order, so subfolders are deleted before parent folder.
704 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700705 if not path:
706 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700707 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900708 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700709 gitdir = os.path.join(self.manifest.topdir, path, '.git')
710 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900711 project = Project(
712 manifest = self.manifest,
713 name = path,
714 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700715 gitdir = gitdir,
716 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900717 worktree = os.path.join(self.manifest.topdir, path),
718 relpath = path,
719 revisionExpr = 'HEAD',
720 revisionId = None,
721 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400722
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500723 if project.IsDirty() and opt.force_remove_dirty:
724 print('WARNING: Removing dirty project "%s": uncommitted changes '
725 'erased' % project.relpath, file=sys.stderr)
726 self._DeleteProject(project.worktree)
727 elif project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900728 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900729 'are present' % project.relpath, file=sys.stderr)
730 print(' commit changes, then run sync again',
731 file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400732 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700733 elif self._DeleteProject(project.worktree):
Mike Frysingera850ca22019-08-07 17:19:24 -0400734 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700735
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700736 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500737 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700738 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700739 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700740 return 0
741
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400742 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
743 if not self.manifest.manifest_server:
744 print('error: cannot smart sync: no manifest server defined in '
745 'manifest', file=sys.stderr)
746 sys.exit(1)
747
748 manifest_server = self.manifest.manifest_server
749 if not opt.quiet:
750 print('Using manifest server %s' % manifest_server)
751
752 if not '@' in manifest_server:
753 username = None
754 password = None
755 if opt.manifest_server_username and opt.manifest_server_password:
756 username = opt.manifest_server_username
757 password = opt.manifest_server_password
758 else:
759 try:
760 info = netrc.netrc()
761 except IOError:
762 # .netrc file does not exist or could not be opened
763 pass
764 else:
765 try:
766 parse_result = urllib.parse.urlparse(manifest_server)
767 if parse_result.hostname:
768 auth = info.authenticators(parse_result.hostname)
769 if auth:
770 username, _account, password = auth
771 else:
772 print('No credentials found for %s in .netrc'
773 % parse_result.hostname, file=sys.stderr)
774 except netrc.NetrcParseError as e:
775 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
776
777 if (username and password):
778 manifest_server = manifest_server.replace('://', '://%s:%s@' %
779 (username, password),
780 1)
781
782 transport = PersistentTransport(manifest_server)
783 if manifest_server.startswith('persistent-'):
784 manifest_server = manifest_server[len('persistent-'):]
785
786 try:
787 server = xmlrpc.client.Server(manifest_server, transport=transport)
788 if opt.smart_sync:
789 p = self.manifest.manifestProject
790 b = p.GetBranch(p.CurrentBranch)
791 branch = b.merge
792 if branch.startswith(R_HEADS):
793 branch = branch[len(R_HEADS):]
794
795 env = os.environ.copy()
796 if 'SYNC_TARGET' in env:
797 target = env['SYNC_TARGET']
798 [success, manifest_str] = server.GetApprovedManifest(branch, target)
799 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
800 target = '%s-%s' % (env['TARGET_PRODUCT'],
801 env['TARGET_BUILD_VARIANT'])
802 [success, manifest_str] = server.GetApprovedManifest(branch, target)
803 else:
804 [success, manifest_str] = server.GetApprovedManifest(branch)
805 else:
806 assert(opt.smart_tag)
807 [success, manifest_str] = server.GetManifest(opt.smart_tag)
808
809 if success:
810 manifest_name = os.path.basename(smart_sync_manifest_path)
811 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500812 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400813 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400814 except IOError as e:
815 print('error: cannot write manifest to %s:\n%s'
816 % (smart_sync_manifest_path, e),
817 file=sys.stderr)
818 sys.exit(1)
819 self._ReloadManifest(manifest_name)
820 else:
821 print('error: manifest server RPC call failed: %s' %
822 manifest_str, file=sys.stderr)
823 sys.exit(1)
824 except (socket.error, IOError, xmlrpc.client.Fault) as e:
825 print('error: cannot connect to manifest server %s:\n%s'
826 % (self.manifest.manifest_server, e), file=sys.stderr)
827 sys.exit(1)
828 except xmlrpc.client.ProtocolError as e:
829 print('error: cannot connect to manifest server %s:\n%d %s'
830 % (self.manifest.manifest_server, e.errcode, e.errmsg),
831 file=sys.stderr)
832 sys.exit(1)
833
834 return manifest_name
835
Mike Frysingerfb527e32019-08-27 02:34:32 -0400836 def _UpdateManifestProject(self, opt, mp, manifest_name):
837 """Fetch & update the local manifest project."""
838 if not opt.local_only:
839 start = time.time()
840 success = mp.Sync_NetworkHalf(quiet=opt.quiet,
841 current_branch_only=opt.current_branch_only,
842 no_tags=opt.no_tags,
843 optimized_fetch=opt.optimized_fetch,
844 submodules=self.manifest.HasSubmodules,
845 clone_filter=self.manifest.CloneFilter)
846 finish = time.time()
847 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
848 start, finish, success)
849
850 if mp.HasChanges:
851 syncbuf = SyncBuffer(mp.config)
852 start = time.time()
853 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
854 clean = syncbuf.Finish()
855 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
856 start, time.time(), clean)
857 if not clean:
858 sys.exit(1)
859 self._ReloadManifest(opt.manifest_name)
860 if opt.jobs is None:
861 self.jobs = self.manifest.default.sync_j
862
Mike Frysingerae6cb082019-08-27 01:10:59 -0400863 def ValidateOptions(self, opt, args):
864 if opt.force_broken:
865 print('warning: -f/--force-broken is now the default behavior, and the '
866 'options are deprecated', file=sys.stderr)
867 if opt.network_only and opt.detach_head:
868 self.OptionParser.error('cannot combine -n and -d')
869 if opt.network_only and opt.local_only:
870 self.OptionParser.error('cannot combine -n and -l')
871 if opt.manifest_name and opt.smart_sync:
872 self.OptionParser.error('cannot combine -m and -s')
873 if opt.manifest_name and opt.smart_tag:
874 self.OptionParser.error('cannot combine -m and -t')
875 if opt.manifest_server_username or opt.manifest_server_password:
876 if not (opt.smart_sync or opt.smart_tag):
877 self.OptionParser.error('-u and -p may only be combined with -s or -t')
878 if None in [opt.manifest_server_username, opt.manifest_server_password]:
879 self.OptionParser.error('both -u and -p must be given')
880
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800882 if opt.jobs:
883 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700884 if self.jobs > 1:
885 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400886 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700887
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500888 if opt.manifest_name:
889 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700890
Chirayu Desaia892b102013-06-11 14:18:46 +0530891 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900892 smart_sync_manifest_path = os.path.join(
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400893 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530894
Victor Boivie08c880d2011-04-19 10:32:52 +0200895 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400896 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
897 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900898 if os.path.isfile(smart_sync_manifest_path):
899 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800900 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900901 except OSError as e:
902 print('error: failed to remove existing smart sync override manifest: %s' %
903 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700904
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 rp = self.manifest.repoProject
906 rp.PreSync()
907
908 mp = self.manifest.manifestProject
909 mp.PreSync()
910
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800911 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700912 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800913
Fredrik de Grootcc960972019-11-22 09:04:31 +0100914 if not opt.mp_update:
915 print('Skipping update of local manifest project.')
916 else:
917 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700918
Simran Basib9a1b732015-08-20 12:19:28 -0700919 if self.gitc_manifest:
920 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700921 missing_ok=True)
922 gitc_projects = []
923 opened_projects = []
924 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700925 if project.relpath in self.gitc_manifest.paths and \
926 self.gitc_manifest.paths[project.relpath].old_revision:
927 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700928 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700929 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700930
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700931 if not args:
932 gitc_projects = None
933
934 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700935 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700936 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
937 if manifest_name:
938 manifest.Override(manifest_name)
939 else:
940 manifest.Override(self.manifest.manifestFile)
941 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
942 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700943 gitc_projects)
944 print('GITC client successfully synced.')
945
946 # The opened projects need to be synced as normal, therefore we
947 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700948 # TODO: make this more reliable -- if there's a project name/path overlap,
949 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900950 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
951 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700952 if not args:
953 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800954 all_projects = self.GetProjects(args,
955 missing_ok=True,
956 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
Dave Borowitz67700e92012-10-23 15:00:54 -0700958 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700959 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700960 to_fetch = []
961 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700962 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700963 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900964 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700965 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700966
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800967 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700968 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700969 if opt.network_only:
970 # bail out now; the rest touches the working tree
971 return
972
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800973 # Iteratively fetch missing and/or nested unregistered submodules
974 previously_missing_set = set()
975 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100976 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800977 all_projects = self.GetProjects(args,
978 missing_ok=True,
979 submodules_ok=opt.fetch_submodules)
980 missing = []
981 for project in all_projects:
982 if project.gitdir not in fetched:
983 missing.append(project)
984 if not missing:
985 break
986 # Stop us from non-stopped fetching actually-missing repos: If set of
987 # missing repos has not been changed from last fetch, we break.
988 missing_set = set(p.name for p in missing)
989 if previously_missing_set == missing_set:
990 break
991 previously_missing_set = missing_set
992 fetched.update(self._Fetch(missing, opt))
993
Julien Campergue335f5ef2013-10-16 11:02:35 +0200994 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700995 # bail out now, we have no working tree
996 return
997
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500998 if self.UpdateProjectList(opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700999 sys.exit(1)
1000
Xin Li745be2e2019-06-03 11:24:30 -07001001 self._Checkout(all_projects, opt)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001003 # If there's a notice that's supposed to print at the end of the sync, print
1004 # it now...
1005 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001006 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001007
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001008def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001009 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001010 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001011 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001012 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001013 if project.Exists:
1014 project.PostRepoUpgrade()
1015
1016def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
1017 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001018 print('info: A new version of repo is available', file=sys.stderr)
1019 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001020 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001021 syncbuf = SyncBuffer(rp.config)
1022 rp.Sync_LocalHalf(syncbuf)
1023 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001024 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -07001025 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001026 raise RepoChangedException(['--repo-upgraded'])
1027 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001028 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001029 else:
1030 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001031 print('repo version %s is current' % rp.work_git.describe(HEAD),
1032 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001034def _VerifyTag(project):
1035 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
1036 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -07001037 print('warning: GnuPG was not available during last "repo init"\n'
1038 'warning: Cannot automatically authenticate repo."""',
1039 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001040 return True
1041
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001043 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001044 except GitError:
1045 cur = None
1046
1047 if not cur \
1048 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001049 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001050 if rev.startswith(R_HEADS):
1051 rev = rev[len(R_HEADS):]
1052
Sarah Owenscecd1d82012-11-01 22:59:27 -07001053 print(file=sys.stderr)
1054 print("warning: project '%s' branch '%s' is not signed"
1055 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 return False
1057
Shawn O. Pearcef18cb762010-12-07 11:41:05 -08001058 env = os.environ.copy()
1059 env['GIT_DIR'] = project.gitdir.encode()
1060 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061
1062 cmd = [GIT, 'tag', '-v', cur]
1063 proc = subprocess.Popen(cmd,
1064 stdout = subprocess.PIPE,
1065 stderr = subprocess.PIPE,
1066 env = env)
1067 out = proc.stdout.read()
1068 proc.stdout.close()
1069
1070 err = proc.stderr.read()
1071 proc.stderr.close()
1072
1073 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001074 print(file=sys.stderr)
1075 print(out, file=sys.stderr)
1076 print(err, file=sys.stderr)
1077 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 return False
1079 return True
Dave Borowitz67700e92012-10-23 15:00:54 -07001080
David Rileye0684ad2017-04-05 00:02:59 -07001081
Dave Borowitz67700e92012-10-23 15:00:54 -07001082class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001083 _ALPHA = 0.5
1084
Dave Borowitz67700e92012-10-23 15:00:54 -07001085 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001086 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001087 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001088 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001089
1090 def Get(self, project):
1091 self._Load()
1092 return self._times.get(project.name, _ONE_DAY_S)
1093
1094 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001095 self._Load()
1096 name = project.name
1097 old = self._times.get(name, t)
1098 self._seen.add(name)
1099 a = self._ALPHA
1100 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001101
1102 def _Load(self):
1103 if self._times is None:
1104 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001105 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001106 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001107 except (IOError, ValueError):
1108 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001109 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001110 except OSError:
1111 pass
1112 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001113
1114 def Save(self):
1115 if self._times is None:
1116 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001117
1118 to_delete = []
1119 for name in self._times:
1120 if name not in self._seen:
1121 to_delete.append(name)
1122 for name in to_delete:
1123 del self._times[name]
1124
Dave Borowitz67700e92012-10-23 15:00:54 -07001125 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001126 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001127 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001128 except (IOError, TypeError):
1129 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001130 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001131 except OSError:
1132 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001133
1134# This is a replacement for xmlrpc.client.Transport using urllib2
1135# and supporting persistent-http[s]. It cannot change hosts from
1136# request to request like the normal transport, the real url
1137# is passed during initialization.
1138class PersistentTransport(xmlrpc.client.Transport):
1139 def __init__(self, orig_host):
1140 self.orig_host = orig_host
1141
1142 def request(self, host, handler, request_body, verbose=False):
1143 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1144 # Python doesn't understand cookies with the #HttpOnly_ prefix
1145 # Since we're only using them for HTTP, copy the file temporarily,
1146 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001147 if cookiefile:
1148 tmpcookiefile = tempfile.NamedTemporaryFile()
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001149 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001150 try:
1151 with open(cookiefile) as f:
1152 for line in f:
1153 if line.startswith("#HttpOnly_"):
1154 line = line[len("#HttpOnly_"):]
1155 tmpcookiefile.write(line)
1156 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001157
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001158 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001159 try:
1160 cookiejar.load()
1161 except cookielib.LoadError:
1162 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001163 finally:
1164 tmpcookiefile.close()
1165 else:
1166 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001167
1168 proxyhandler = urllib.request.ProxyHandler
1169 if proxy:
1170 proxyhandler = urllib.request.ProxyHandler({
1171 "http": proxy,
1172 "https": proxy })
1173
1174 opener = urllib.request.build_opener(
1175 urllib.request.HTTPCookieProcessor(cookiejar),
1176 proxyhandler)
1177
1178 url = urllib.parse.urljoin(self.orig_host, handler)
1179 parse_results = urllib.parse.urlparse(url)
1180
1181 scheme = parse_results.scheme
1182 if scheme == 'persistent-http':
1183 scheme = 'http'
1184 if scheme == 'persistent-https':
1185 # If we're proxying through persistent-https, use http. The
1186 # proxy itself will do the https.
1187 if proxy:
1188 scheme = 'http'
1189 else:
1190 scheme = 'https'
1191
1192 # Parse out any authentication information using the base class
1193 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1194
1195 url = urllib.parse.urlunparse((
1196 scheme,
1197 host,
1198 parse_results.path,
1199 parse_results.params,
1200 parse_results.query,
1201 parse_results.fragment))
1202
1203 request = urllib.request.Request(url, request_body)
1204 if extra_headers is not None:
1205 for (name, header) in extra_headers:
1206 request.add_header(name, header)
1207 request.add_header('Content-Type', 'text/xml')
1208 try:
1209 response = opener.open(request)
1210 except urllib.error.HTTPError as e:
1211 if e.code == 501:
1212 # We may have been redirected through a login process
1213 # but our POST turned into a GET. Retry.
1214 response = opener.open(request)
1215 else:
1216 raise
1217
1218 p, u = xmlrpc.client.getparser()
1219 while 1:
1220 data = response.read(1024)
1221 if not data:
1222 break
1223 p.feed(data)
1224 p.close()
1225 return u.close()
1226
1227 def close(self):
1228 pass
1229