blob: 808df208ee0ed79be3330bdbbdadd80a7522d0dd [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
Mike Frysinger5a033082019-09-23 19:21:20 -0400371 def _Fetch(self, projects, opt, err_event):
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)
David James89ece422014-01-09 18:51:58 -0800383 for project_list in objdir_project_map.values():
384 # Check for any errors before running any more tasks.
385 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400386 if err_event.isSet() and opt.fail_fast:
David James89ece422014-01-09 18:51:58 -0800387 break
Doug Andersonfc06ced2011-03-16 15:49:18 -0700388
David James89ece422014-01-09 18:51:58 -0800389 sem.acquire()
390 kwargs = dict(opt=opt,
391 projects=project_list,
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500392 sem=sem,
David James89ece422014-01-09 18:51:58 -0800393 lock=lock,
394 fetched=fetched,
395 pm=pm,
Xin Li745be2e2019-06-03 11:24:30 -0700396 err_event=err_event,
397 clone_filter=self.manifest.CloneFilter)
David James89ece422014-01-09 18:51:58 -0800398 if self.jobs > 1:
David James8d201162013-10-11 17:03:19 -0700399 t = _threading.Thread(target = self._FetchProjectList,
David James89ece422014-01-09 18:51:58 -0800400 kwargs = kwargs)
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200401 # Ensure that Ctrl-C will not freeze the repo process.
402 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800403 threads.add(t)
404 t.start()
David James89ece422014-01-09 18:51:58 -0800405 else:
406 self._FetchProjectList(**kwargs)
Roy Lee18afd7f2010-05-09 04:32:08 +0800407
David James89ece422014-01-09 18:51:58 -0800408 for t in threads:
409 t.join()
Roy Lee18afd7f2010-05-09 04:32:08 +0800410
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700411 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700412 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700413
Julien Campergue335f5ef2013-10-16 11:02:35 +0200414 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400415 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200416
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700417 return fetched
418
Xin Li745be2e2019-06-03 11:24:30 -0700419 def _CheckoutWorker(self, opt, sem, project, *args, **kwargs):
420 """Main function of the fetch threads.
421
422 Delegates most of the work to _CheckoutOne.
423
424 Args:
425 opt: Program options returned from optparse. See _Options().
426 projects: Projects to fetch.
427 sem: We'll release() this semaphore when we exit so that another thread
428 can be started up.
429 *args, **kwargs: Remaining arguments to pass to _CheckoutOne. See the
430 _CheckoutOne docstring for details.
431 """
432 try:
Mike Frysingera34186e2019-08-07 18:07:31 -0400433 return self._CheckoutOne(opt, project, *args, **kwargs)
Xin Li745be2e2019-06-03 11:24:30 -0700434 finally:
435 sem.release()
436
Vadim Bendeburydff91942019-11-06 11:05:00 -0800437 def _CheckoutOne(self, opt, project, lock, pm, err_event, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700438 """Checkout work tree for one project
439
440 Args:
441 opt: Program options returned from optparse. See _Options().
442 project: Project object for the project to checkout.
443 lock: Lock for accessing objects that are shared amongst multiple
444 _CheckoutWorker() threads.
445 pm: Instance of a Project object. We will call pm.update() (with our
446 lock held).
447 err_event: We'll set this event in the case of an error (after printing
448 out info about the error).
Vadim Bendeburydff91942019-11-06 11:05:00 -0800449 err_results: A list of strings, paths to git repos where checkout
450 failed.
Xin Li745be2e2019-06-03 11:24:30 -0700451
452 Returns:
453 Whether the fetch was successful.
454 """
455 # We'll set to true once we've locked the lock.
456 did_lock = False
457
Xin Li745be2e2019-06-03 11:24:30 -0700458 # Encapsulate everything in a try/except/finally so that:
459 # - We always set err_event in the case of an exception.
460 # - We always make sure we unlock the lock if we locked it.
461 start = time.time()
462 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
463 detach_head=opt.detach_head)
464 success = False
465 try:
466 try:
467 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
Xin Li745be2e2019-06-03 11:24:30 -0700468
469 # Lock around all the rest of the code, since printing, updating a set
470 # and Progress.update() are not thread safe.
471 lock.acquire()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400472 success = syncbuf.Finish()
Xin Li745be2e2019-06-03 11:24:30 -0700473 did_lock = True
474
475 if not success:
476 err_event.set()
477 print('error: Cannot checkout %s' % (project.name),
478 file=sys.stderr)
479 raise _CheckoutError()
480
Mike Frysinger3538dd22019-08-26 15:32:06 -0400481 pm.update(msg=project.name)
Xin Li745be2e2019-06-03 11:24:30 -0700482 except _CheckoutError:
483 pass
484 except Exception as e:
485 print('error: Cannot checkout %s: %s: %s' %
486 (project.name, type(e).__name__, str(e)),
487 file=sys.stderr)
488 err_event.set()
489 raise
490 finally:
491 if did_lock:
Vadim Bendeburydff91942019-11-06 11:05:00 -0800492 if not success:
493 err_results.append(project.relpath)
Xin Li745be2e2019-06-03 11:24:30 -0700494 lock.release()
495 finish = time.time()
496 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
497 start, finish, success)
498
499 return success
500
Mike Frysinger5a033082019-09-23 19:21:20 -0400501 def _Checkout(self, all_projects, opt, err_event, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700502 """Checkout projects listed in all_projects
503
504 Args:
505 all_projects: List of all projects that should be checked out.
506 opt: Program options returned from optparse. See _Options().
Mike Frysinger5a033082019-09-23 19:21:20 -0400507 err_event: We'll set this event in the case of an error (after printing
508 out info about the error).
509 err_results: A list of strings, paths to git repos where checkout
510 failed.
Xin Li745be2e2019-06-03 11:24:30 -0700511 """
512
513 # Perform checkouts in multiple threads when we are using partial clone.
514 # Without partial clone, all needed git objects are already downloaded,
515 # in this situation it's better to use only one process because the checkout
516 # would be mostly disk I/O; with partial clone, the objects are only
517 # downloaded when demanded (at checkout time), which is similar to the
518 # Sync_NetworkHalf case and parallelism would be helpful.
519 if self.manifest.CloneFilter:
520 syncjobs = self.jobs
521 else:
522 syncjobs = 1
523
524 lock = _threading.Lock()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400525 pm = Progress('Checking out projects', len(all_projects))
Xin Li745be2e2019-06-03 11:24:30 -0700526
527 threads = set()
528 sem = _threading.Semaphore(syncjobs)
Xin Li745be2e2019-06-03 11:24:30 -0700529
530 for project in all_projects:
531 # Check for any errors before running any more tasks.
532 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400533 if err_event.isSet() and opt.fail_fast:
Xin Li745be2e2019-06-03 11:24:30 -0700534 break
535
536 sem.acquire()
537 if project.worktree:
538 kwargs = dict(opt=opt,
539 sem=sem,
540 project=project,
541 lock=lock,
542 pm=pm,
Vadim Bendeburydff91942019-11-06 11:05:00 -0800543 err_event=err_event,
544 err_results=err_results)
Xin Li745be2e2019-06-03 11:24:30 -0700545 if syncjobs > 1:
546 t = _threading.Thread(target=self._CheckoutWorker,
547 kwargs=kwargs)
548 # Ensure that Ctrl-C will not freeze the repo process.
549 t.daemon = True
550 threads.add(t)
551 t.start()
552 else:
553 self._CheckoutWorker(**kwargs)
554
555 for t in threads:
556 t.join()
557
558 pm.end()
Xin Li745be2e2019-06-03 11:24:30 -0700559
Mike Frysinger5a033082019-09-23 19:21:20 -0400560 def _GCProjects(self, projects, opt, err_event):
Gabe Black2ff30292014-10-09 17:54:35 -0700561 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700562 for project in projects:
Gabe Black2ff30292014-10-09 17:54:35 -0700563 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
564 print('Shared project %s found, disabling pruning.' % project.name)
565 project.bare_git.config('--replace-all', 'gc.pruneExpire', 'never')
566 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700567
Dave Borowitze2152672012-10-31 12:24:38 -0700568 has_dash_c = git_require((1, 7, 2))
569 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700570 cpu_count = multiprocessing.cpu_count()
571 else:
572 cpu_count = 1
573 jobs = min(self.jobs, cpu_count)
574
575 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700576 for bare_git in gc_gitdirs.values():
David James8d201162013-10-11 17:03:19 -0700577 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700578 return
579
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400580 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700581
582 threads = set()
583 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700584
David James8d201162013-10-11 17:03:19 -0700585 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700586 try:
587 try:
David James8d201162013-10-11 17:03:19 -0700588 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700589 except GitError:
590 err_event.set()
591 except:
592 err_event.set()
593 raise
594 finally:
595 sem.release()
596
Gabe Black2ff30292014-10-09 17:54:35 -0700597 for bare_git in gc_gitdirs.values():
Mike Frysinger5a033082019-09-23 19:21:20 -0400598 if err_event.isSet() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700599 break
600 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700601 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700602 t.daemon = True
603 threads.add(t)
604 t.start()
605
606 for t in threads:
607 t.join()
608
Tim Kilbourn07669002013-03-08 15:02:49 -0800609 def _ReloadManifest(self, manifest_name=None):
610 if manifest_name:
611 # Override calls _Unload already
612 self.manifest.Override(manifest_name)
613 else:
614 self.manifest._Unload()
615
Dan Willemsen43507912016-09-01 16:26:02 -0700616 def _DeleteProject(self, path):
617 print('Deleting obsolete path %s' % path, file=sys.stderr)
618
619 # Delete the .git directory first, so we're less likely to have a partially
620 # working git repository around. There shouldn't be any git projects here,
621 # so rmtree works.
622 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -0700623 platform_utils.rmtree(os.path.join(path, '.git'))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700624 except OSError as e:
625 print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700626 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
627 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400628 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700629
630 # Delete everything under the worktree, except for directories that contain
631 # another git project
632 dirs_to_remove = []
633 failed = False
Renaud Paquaybed8b622018-09-27 10:46:58 -0700634 for root, dirs, files in platform_utils.walk(path):
Dan Willemsen43507912016-09-01 16:26:02 -0700635 for f in files:
636 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800637 platform_utils.remove(os.path.join(root, f))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700638 except OSError as e:
639 print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700640 failed = True
641 dirs[:] = [d for d in dirs
642 if not os.path.lexists(os.path.join(root, d, '.git'))]
643 dirs_to_remove += [os.path.join(root, d) for d in dirs
644 if os.path.join(root, d) not in dirs_to_remove]
645 for d in reversed(dirs_to_remove):
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700646 if platform_utils.islink(d):
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700647 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800648 platform_utils.remove(d)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700649 except OSError as e:
650 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700651 failed = True
Renaud Paquaybed8b622018-09-27 10:46:58 -0700652 elif len(platform_utils.listdir(d)) == 0:
Dan Willemsen43507912016-09-01 16:26:02 -0700653 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700654 platform_utils.rmdir(d)
655 except OSError as e:
656 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700657 failed = True
658 continue
659 if failed:
660 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
661 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400662 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700663
664 # Try deleting parent dirs if they are empty
665 project_dir = path
666 while project_dir != self.manifest.topdir:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700667 if len(platform_utils.listdir(project_dir)) == 0:
668 platform_utils.rmdir(project_dir)
Dan Willemsen43507912016-09-01 16:26:02 -0700669 else:
670 break
671 project_dir = os.path.dirname(project_dir)
672
673 return 0
674
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500675 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700676 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700677 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700678 if project.relpath:
679 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700680 file_name = 'project.list'
681 file_path = os.path.join(self.manifest.repodir, file_name)
682 old_project_paths = []
683
684 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500685 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700686 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800687 # In reversed order, so subfolders are deleted before parent folder.
688 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700689 if not path:
690 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700691 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900692 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700693 gitdir = os.path.join(self.manifest.topdir, path, '.git')
694 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900695 project = Project(
696 manifest = self.manifest,
697 name = path,
698 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700699 gitdir = gitdir,
700 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900701 worktree = os.path.join(self.manifest.topdir, path),
702 relpath = path,
703 revisionExpr = 'HEAD',
704 revisionId = None,
705 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400706
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500707 if project.IsDirty() and opt.force_remove_dirty:
708 print('WARNING: Removing dirty project "%s": uncommitted changes '
709 'erased' % project.relpath, file=sys.stderr)
710 self._DeleteProject(project.worktree)
711 elif project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900712 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900713 'are present' % project.relpath, file=sys.stderr)
714 print(' commit changes, then run sync again',
715 file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400716 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700717 elif self._DeleteProject(project.worktree):
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
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400726 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
727 if not self.manifest.manifest_server:
728 print('error: cannot smart sync: no manifest server defined in '
729 'manifest', file=sys.stderr)
730 sys.exit(1)
731
732 manifest_server = self.manifest.manifest_server
733 if not opt.quiet:
734 print('Using manifest server %s' % manifest_server)
735
736 if not '@' in manifest_server:
737 username = None
738 password = None
739 if opt.manifest_server_username and opt.manifest_server_password:
740 username = opt.manifest_server_username
741 password = opt.manifest_server_password
742 else:
743 try:
744 info = netrc.netrc()
745 except IOError:
746 # .netrc file does not exist or could not be opened
747 pass
748 else:
749 try:
750 parse_result = urllib.parse.urlparse(manifest_server)
751 if parse_result.hostname:
752 auth = info.authenticators(parse_result.hostname)
753 if auth:
754 username, _account, password = auth
755 else:
756 print('No credentials found for %s in .netrc'
757 % parse_result.hostname, file=sys.stderr)
758 except netrc.NetrcParseError as e:
759 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
760
761 if (username and password):
762 manifest_server = manifest_server.replace('://', '://%s:%s@' %
763 (username, password),
764 1)
765
766 transport = PersistentTransport(manifest_server)
767 if manifest_server.startswith('persistent-'):
768 manifest_server = manifest_server[len('persistent-'):]
769
770 try:
771 server = xmlrpc.client.Server(manifest_server, transport=transport)
772 if opt.smart_sync:
773 p = self.manifest.manifestProject
774 b = p.GetBranch(p.CurrentBranch)
775 branch = b.merge
776 if branch.startswith(R_HEADS):
777 branch = branch[len(R_HEADS):]
778
779 env = os.environ.copy()
780 if 'SYNC_TARGET' in env:
781 target = env['SYNC_TARGET']
782 [success, manifest_str] = server.GetApprovedManifest(branch, target)
783 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
784 target = '%s-%s' % (env['TARGET_PRODUCT'],
785 env['TARGET_BUILD_VARIANT'])
786 [success, manifest_str] = server.GetApprovedManifest(branch, target)
787 else:
788 [success, manifest_str] = server.GetApprovedManifest(branch)
789 else:
790 assert(opt.smart_tag)
791 [success, manifest_str] = server.GetManifest(opt.smart_tag)
792
793 if success:
794 manifest_name = os.path.basename(smart_sync_manifest_path)
795 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500796 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400797 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400798 except IOError as e:
799 print('error: cannot write manifest to %s:\n%s'
800 % (smart_sync_manifest_path, e),
801 file=sys.stderr)
802 sys.exit(1)
803 self._ReloadManifest(manifest_name)
804 else:
805 print('error: manifest server RPC call failed: %s' %
806 manifest_str, file=sys.stderr)
807 sys.exit(1)
808 except (socket.error, IOError, xmlrpc.client.Fault) as e:
809 print('error: cannot connect to manifest server %s:\n%s'
810 % (self.manifest.manifest_server, e), file=sys.stderr)
811 sys.exit(1)
812 except xmlrpc.client.ProtocolError as e:
813 print('error: cannot connect to manifest server %s:\n%d %s'
814 % (self.manifest.manifest_server, e.errcode, e.errmsg),
815 file=sys.stderr)
816 sys.exit(1)
817
818 return manifest_name
819
Mike Frysingerfb527e32019-08-27 02:34:32 -0400820 def _UpdateManifestProject(self, opt, mp, manifest_name):
821 """Fetch & update the local manifest project."""
822 if not opt.local_only:
823 start = time.time()
824 success = mp.Sync_NetworkHalf(quiet=opt.quiet,
825 current_branch_only=opt.current_branch_only,
826 no_tags=opt.no_tags,
827 optimized_fetch=opt.optimized_fetch,
828 submodules=self.manifest.HasSubmodules,
829 clone_filter=self.manifest.CloneFilter)
830 finish = time.time()
831 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
832 start, finish, success)
833
834 if mp.HasChanges:
835 syncbuf = SyncBuffer(mp.config)
836 start = time.time()
837 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
838 clean = syncbuf.Finish()
839 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
840 start, time.time(), clean)
841 if not clean:
842 sys.exit(1)
843 self._ReloadManifest(opt.manifest_name)
844 if opt.jobs is None:
845 self.jobs = self.manifest.default.sync_j
846
Mike Frysingerae6cb082019-08-27 01:10:59 -0400847 def ValidateOptions(self, opt, args):
848 if opt.force_broken:
849 print('warning: -f/--force-broken is now the default behavior, and the '
850 'options are deprecated', file=sys.stderr)
851 if opt.network_only and opt.detach_head:
852 self.OptionParser.error('cannot combine -n and -d')
853 if opt.network_only and opt.local_only:
854 self.OptionParser.error('cannot combine -n and -l')
855 if opt.manifest_name and opt.smart_sync:
856 self.OptionParser.error('cannot combine -m and -s')
857 if opt.manifest_name and opt.smart_tag:
858 self.OptionParser.error('cannot combine -m and -t')
859 if opt.manifest_server_username or opt.manifest_server_password:
860 if not (opt.smart_sync or opt.smart_tag):
861 self.OptionParser.error('-u and -p may only be combined with -s or -t')
862 if None in [opt.manifest_server_username, opt.manifest_server_password]:
863 self.OptionParser.error('both -u and -p must be given')
864
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800866 if opt.jobs:
867 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700868 if self.jobs > 1:
869 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400870 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700871
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500872 if opt.manifest_name:
873 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700874
Chirayu Desaia892b102013-06-11 14:18:46 +0530875 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900876 smart_sync_manifest_path = os.path.join(
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400877 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530878
Victor Boivie08c880d2011-04-19 10:32:52 +0200879 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400880 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
881 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900882 if os.path.isfile(smart_sync_manifest_path):
883 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800884 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900885 except OSError as e:
886 print('error: failed to remove existing smart sync override manifest: %s' %
887 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700888
Mike Frysinger5a033082019-09-23 19:21:20 -0400889 err_event = _threading.Event()
890
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891 rp = self.manifest.repoProject
892 rp.PreSync()
893
894 mp = self.manifest.manifestProject
895 mp.PreSync()
896
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800897 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700898 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800899
Fredrik de Grootcc960972019-11-22 09:04:31 +0100900 if not opt.mp_update:
901 print('Skipping update of local manifest project.')
902 else:
903 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700904
Simran Basib9a1b732015-08-20 12:19:28 -0700905 if self.gitc_manifest:
906 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700907 missing_ok=True)
908 gitc_projects = []
909 opened_projects = []
910 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700911 if project.relpath in self.gitc_manifest.paths and \
912 self.gitc_manifest.paths[project.relpath].old_revision:
913 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700914 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700915 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700916
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700917 if not args:
918 gitc_projects = None
919
920 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700921 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700922 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
923 if manifest_name:
924 manifest.Override(manifest_name)
925 else:
926 manifest.Override(self.manifest.manifestFile)
927 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
928 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700929 gitc_projects)
930 print('GITC client successfully synced.')
931
932 # The opened projects need to be synced as normal, therefore we
933 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700934 # TODO: make this more reliable -- if there's a project name/path overlap,
935 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900936 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
937 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700938 if not args:
939 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800940 all_projects = self.GetProjects(args,
941 missing_ok=True,
942 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
Mike Frysinger5a033082019-09-23 19:21:20 -0400944 err_network_sync = False
945 err_update_projects = False
946 err_checkout = False
947
Dave Borowitz67700e92012-10-23 15:00:54 -0700948 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700949 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700950 to_fetch = []
951 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700952 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700953 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900954 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700955 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700956
Mike Frysinger5a033082019-09-23 19:21:20 -0400957 fetched = self._Fetch(to_fetch, opt, err_event)
958
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700959 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700960 if opt.network_only:
961 # bail out now; the rest touches the working tree
Mike Frysinger5a033082019-09-23 19:21:20 -0400962 if err_event.isSet():
963 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
964 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700965 return
966
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800967 # Iteratively fetch missing and/or nested unregistered submodules
968 previously_missing_set = set()
969 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100970 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800971 all_projects = self.GetProjects(args,
972 missing_ok=True,
973 submodules_ok=opt.fetch_submodules)
974 missing = []
975 for project in all_projects:
976 if project.gitdir not in fetched:
977 missing.append(project)
978 if not missing:
979 break
980 # Stop us from non-stopped fetching actually-missing repos: If set of
981 # missing repos has not been changed from last fetch, we break.
982 missing_set = set(p.name for p in missing)
983 if previously_missing_set == missing_set:
984 break
985 previously_missing_set = missing_set
Mike Frysinger5a033082019-09-23 19:21:20 -0400986 fetched.update(self._Fetch(missing, opt, err_event))
987
988 # If we saw an error, exit with code 1 so that other scripts can check.
989 if err_event.isSet():
990 err_network_sync = True
991 if opt.fail_fast:
992 print('\nerror: Exited sync due to fetch errors.\n'
993 'Local checkouts *not* updated. Resolve network issues & '
994 'retry.\n'
995 '`repo sync -l` will update some local checkouts.',
996 file=sys.stderr)
997 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800998
Julien Campergue335f5ef2013-10-16 11:02:35 +0200999 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001000 # bail out now, we have no working tree
1001 return
1002
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -05001003 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -04001004 err_event.set()
1005 err_update_projects = True
1006 if opt.fail_fast:
1007 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1008 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001009
Mike Frysinger5a033082019-09-23 19:21:20 -04001010 err_results = []
1011 self._Checkout(all_projects, opt, err_event, err_results)
1012 if err_event.isSet():
1013 err_checkout = True
1014 # NB: We don't exit here because this is the last step.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001015
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001016 # If there's a notice that's supposed to print at the end of the sync, print
1017 # it now...
1018 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001019 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001020
Mike Frysinger5a033082019-09-23 19:21:20 -04001021 # If we saw an error, exit with code 1 so that other scripts can check.
1022 if err_event.isSet():
1023 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1024 if err_network_sync:
1025 print('error: Downloading network changes failed.', file=sys.stderr)
1026 if err_update_projects:
1027 print('error: Updating local project lists failed.', file=sys.stderr)
1028 if err_checkout:
1029 print('error: Checking out local projects failed.', file=sys.stderr)
1030 if err_results:
1031 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1032 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1033 file=sys.stderr)
1034 sys.exit(1)
1035
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001036def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001037 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001038 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001039 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001040 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001041 if project.Exists:
1042 project.PostRepoUpgrade()
1043
1044def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
1045 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001046 print('info: A new version of repo is available', file=sys.stderr)
1047 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001048 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001049 syncbuf = SyncBuffer(rp.config)
1050 rp.Sync_LocalHalf(syncbuf)
1051 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001052 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -07001053 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001054 raise RepoChangedException(['--repo-upgraded'])
1055 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001056 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001057 else:
1058 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001059 print('repo version %s is current' % rp.work_git.describe(HEAD),
1060 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001061
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001062def _VerifyTag(project):
1063 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
1064 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -07001065 print('warning: GnuPG was not available during last "repo init"\n'
1066 'warning: Cannot automatically authenticate repo."""',
1067 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001068 return True
1069
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001071 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072 except GitError:
1073 cur = None
1074
1075 if not cur \
1076 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001077 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078 if rev.startswith(R_HEADS):
1079 rev = rev[len(R_HEADS):]
1080
Sarah Owenscecd1d82012-11-01 22:59:27 -07001081 print(file=sys.stderr)
1082 print("warning: project '%s' branch '%s' is not signed"
1083 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084 return False
1085
Shawn O. Pearcef18cb762010-12-07 11:41:05 -08001086 env = os.environ.copy()
1087 env['GIT_DIR'] = project.gitdir.encode()
1088 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089
1090 cmd = [GIT, 'tag', '-v', cur]
1091 proc = subprocess.Popen(cmd,
1092 stdout = subprocess.PIPE,
1093 stderr = subprocess.PIPE,
1094 env = env)
1095 out = proc.stdout.read()
1096 proc.stdout.close()
1097
1098 err = proc.stderr.read()
1099 proc.stderr.close()
1100
1101 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001102 print(file=sys.stderr)
1103 print(out, file=sys.stderr)
1104 print(err, file=sys.stderr)
1105 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106 return False
1107 return True
Dave Borowitz67700e92012-10-23 15:00:54 -07001108
David Rileye0684ad2017-04-05 00:02:59 -07001109
Dave Borowitz67700e92012-10-23 15:00:54 -07001110class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001111 _ALPHA = 0.5
1112
Dave Borowitz67700e92012-10-23 15:00:54 -07001113 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001114 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001115 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001116 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001117
1118 def Get(self, project):
1119 self._Load()
1120 return self._times.get(project.name, _ONE_DAY_S)
1121
1122 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001123 self._Load()
1124 name = project.name
1125 old = self._times.get(name, t)
1126 self._seen.add(name)
1127 a = self._ALPHA
1128 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001129
1130 def _Load(self):
1131 if self._times is None:
1132 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001133 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001134 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001135 except (IOError, ValueError):
1136 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001137 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001138 except OSError:
1139 pass
1140 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001141
1142 def Save(self):
1143 if self._times is None:
1144 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001145
1146 to_delete = []
1147 for name in self._times:
1148 if name not in self._seen:
1149 to_delete.append(name)
1150 for name in to_delete:
1151 del self._times[name]
1152
Dave Borowitz67700e92012-10-23 15:00:54 -07001153 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001154 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001155 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001156 except (IOError, TypeError):
1157 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001158 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001159 except OSError:
1160 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001161
1162# This is a replacement for xmlrpc.client.Transport using urllib2
1163# and supporting persistent-http[s]. It cannot change hosts from
1164# request to request like the normal transport, the real url
1165# is passed during initialization.
1166class PersistentTransport(xmlrpc.client.Transport):
1167 def __init__(self, orig_host):
1168 self.orig_host = orig_host
1169
1170 def request(self, host, handler, request_body, verbose=False):
1171 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1172 # Python doesn't understand cookies with the #HttpOnly_ prefix
1173 # Since we're only using them for HTTP, copy the file temporarily,
1174 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001175 if cookiefile:
1176 tmpcookiefile = tempfile.NamedTemporaryFile()
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001177 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001178 try:
1179 with open(cookiefile) as f:
1180 for line in f:
1181 if line.startswith("#HttpOnly_"):
1182 line = line[len("#HttpOnly_"):]
1183 tmpcookiefile.write(line)
1184 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001185
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001186 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001187 try:
1188 cookiejar.load()
1189 except cookielib.LoadError:
1190 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001191 finally:
1192 tmpcookiefile.close()
1193 else:
1194 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001195
1196 proxyhandler = urllib.request.ProxyHandler
1197 if proxy:
1198 proxyhandler = urllib.request.ProxyHandler({
1199 "http": proxy,
1200 "https": proxy })
1201
1202 opener = urllib.request.build_opener(
1203 urllib.request.HTTPCookieProcessor(cookiejar),
1204 proxyhandler)
1205
1206 url = urllib.parse.urljoin(self.orig_host, handler)
1207 parse_results = urllib.parse.urlparse(url)
1208
1209 scheme = parse_results.scheme
1210 if scheme == 'persistent-http':
1211 scheme = 'http'
1212 if scheme == 'persistent-https':
1213 # If we're proxying through persistent-https, use http. The
1214 # proxy itself will do the https.
1215 if proxy:
1216 scheme = 'http'
1217 else:
1218 scheme = 'https'
1219
1220 # Parse out any authentication information using the base class
1221 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1222
1223 url = urllib.parse.urlunparse((
1224 scheme,
1225 host,
1226 parse_results.path,
1227 parse_results.params,
1228 parse_results.query,
1229 parse_results.fragment))
1230
1231 request = urllib.request.Request(url, request_body)
1232 if extra_headers is not None:
1233 for (name, header) in extra_headers:
1234 request.add_header(name, header)
1235 request.add_header('Content-Type', 'text/xml')
1236 try:
1237 response = opener.open(request)
1238 except urllib.error.HTTPError as e:
1239 if e.code == 501:
1240 # We may have been redirected through a login process
1241 # but our POST turned into a GET. Retry.
1242 response = opener.open(request)
1243 else:
1244 raise
1245
1246 p, u = xmlrpc.client.getparser()
1247 while 1:
1248 data = response.read(1024)
1249 if not data:
1250 break
1251 p.feed(data)
1252 p.close()
1253 return u.close()
1254
1255 def close(self):
1256 pass
1257