blob: b752cfbe6b3f14b9b7f77d4ef84fcdb4e58dfff7 [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
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500135The -f/--force-broken option can be used to proceed with syncing
136other projects if a project sync fails.
137
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',
202 dest='force_broken', action='store_true',
203 help="continue sync even if a project fails to sync")
Kevin Degiabaa7f32014-11-12 11:27:45 -0700204 p.add_option('--force-sync',
205 dest='force_sync', action='store_true',
206 help="overwrite an existing git directory if it needs to "
207 "point to a different object directory. WARNING: this "
208 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500209 p.add_option('--force-remove-dirty',
210 dest='force_remove_dirty', action='store_true',
211 help="force remove projects with uncommitted modifications if "
212 "projects no longer exist in the manifest. "
213 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900214 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700215 dest='local_only', action='store_true',
216 help="only update working tree, don't fetch")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900217 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700218 dest='network_only', action='store_true',
219 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900220 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700221 dest='detach_head', action='store_true',
222 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900223 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700224 dest='current_branch_only', action='store_true',
225 help='fetch only current branch from server')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900226 p.add_option('-q', '--quiet',
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700227 dest='quiet', action='store_true',
228 help='be more quiet')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900229 p.add_option('-j', '--jobs',
Roy Lee18afd7f2010-05-09 04:32:08 +0800230 dest='jobs', action='store', type='int',
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700231 help="projects to fetch simultaneously (default %d)" % self.jobs)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500232 p.add_option('-m', '--manifest-name',
233 dest='manifest_name',
234 help='temporary manifest to use for this sync', metavar='NAME.xml')
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700235 p.add_option('--no-clone-bundle',
236 dest='no_clone_bundle', action='store_true',
237 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800238 p.add_option('-u', '--manifest-server-username', action='store',
239 dest='manifest_server_username',
240 help='username to authenticate with the manifest server')
241 p.add_option('-p', '--manifest-server-password', action='store',
242 dest='manifest_server_password',
243 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800244 p.add_option('--fetch-submodules',
245 dest='fetch_submodules', action='store_true',
246 help='fetch submodules from server')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700247 p.add_option('--no-tags',
248 dest='no_tags', action='store_true',
249 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900250 p.add_option('--optimized-fetch',
251 dest='optimized_fetch', action='store_true',
252 help='only fetch projects fixed to sha1 if revision does not exist locally')
David Pursehouse74cfd272015-10-14 10:50:15 +0900253 p.add_option('--prune', dest='prune', action='store_true',
254 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700255 if show_smart:
256 p.add_option('-s', '--smart-sync',
257 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900258 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200259 p.add_option('-t', '--smart-tag',
260 dest='smart_tag', action='store',
261 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700262
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700263 g = p.add_option_group('repo Version options')
264 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265 dest='no_repo_verify', action='store_true',
266 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700267 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800268 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700269 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700270
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500271 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
Xin Li745be2e2019-06-03 11:24:30 -0700272 """Main function of the fetch threads.
Doug Andersonfc06ced2011-03-16 15:49:18 -0700273
David James8d201162013-10-11 17:03:19 -0700274 Delegates most of the work to _FetchHelper.
275
276 Args:
277 opt: Program options returned from optparse. See _Options().
278 projects: Projects to fetch.
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500279 sem: We'll release() this semaphore when we exit so that another thread
280 can be started up.
David James89ece422014-01-09 18:51:58 -0800281 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
David James8d201162013-10-11 17:03:19 -0700282 _FetchHelper docstring for details.
283 """
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500284 try:
285 for project in projects:
286 success = self._FetchHelper(opt, project, *args, **kwargs)
287 if not success and not opt.force_broken:
288 break
289 finally:
290 sem.release()
David James8d201162013-10-11 17:03:19 -0700291
Xin Li745be2e2019-06-03 11:24:30 -0700292 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event,
293 clone_filter):
David James8d201162013-10-11 17:03:19 -0700294 """Fetch git objects for a single project.
295
David Pursehousec1b86a22012-11-14 11:36:51 +0900296 Args:
297 opt: Program options returned from optparse. See _Options().
298 project: Project object for the project to fetch.
299 lock: Lock for accessing objects that are shared amongst multiple
300 _FetchHelper() threads.
301 fetched: set object that we will add project.gitdir to when we're done
302 (with our lock held).
303 pm: Instance of a Project object. We will call pm.update() (with our
304 lock held).
David Pursehousec1b86a22012-11-14 11:36:51 +0900305 err_event: We'll set this event in the case of an error (after printing
306 out info about the error).
Xin Li745be2e2019-06-03 11:24:30 -0700307 clone_filter: Filter for use in a partial clone.
David James8d201162013-10-11 17:03:19 -0700308
309 Returns:
310 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900311 """
312 # We'll set to true once we've locked the lock.
313 did_lock = False
Doug Andersonfc06ced2011-03-16 15:49:18 -0700314
Chirayu Desaifef4ae72013-04-12 14:54:32 +0530315 if not opt.quiet:
316 print('Fetching project %s' % project.name)
317
David Pursehousec1b86a22012-11-14 11:36:51 +0900318 # Encapsulate everything in a try/except/finally so that:
319 # - We always set err_event in the case of an exception.
David Pursehousec1b86a22012-11-14 11:36:51 +0900320 # - We always make sure we unlock the lock if we locked it.
David Rileye0684ad2017-04-05 00:02:59 -0700321 start = time.time()
322 success = False
David Pursehousec1b86a22012-11-14 11:36:51 +0900323 try:
Doug Andersonfc06ced2011-03-16 15:49:18 -0700324 try:
David Pursehousec1b86a22012-11-14 11:36:51 +0900325 success = project.Sync_NetworkHalf(
326 quiet=opt.quiet,
327 current_branch_only=opt.current_branch_only,
Kevin Degiabaa7f32014-11-12 11:27:45 -0700328 force_sync=opt.force_sync,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700329 clone_bundle=not opt.no_clone_bundle,
David Pursehouseb1553542014-09-04 21:28:09 +0900330 no_tags=opt.no_tags, archive=self.manifest.IsArchive,
David Pursehouse74cfd272015-10-14 10:50:15 +0900331 optimized_fetch=opt.optimized_fetch,
Xin Li745be2e2019-06-03 11:24:30 -0700332 prune=opt.prune,
333 clone_filter=clone_filter)
David Pursehousec1b86a22012-11-14 11:36:51 +0900334 self._fetch_times.Set(project, time.time() - start)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700335
David Pursehousec1b86a22012-11-14 11:36:51 +0900336 # Lock around all the rest of the code, since printing, updating a set
337 # and Progress.update() are not thread safe.
338 lock.acquire()
339 did_lock = True
Doug Andersonfc06ced2011-03-16 15:49:18 -0700340
David Pursehousec1b86a22012-11-14 11:36:51 +0900341 if not success:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800342 err_event.set()
Marc Herbertffb4b892017-04-04 22:03:53 -0700343 print('error: Cannot fetch %s from %s'
344 % (project.name, project.remote.url),
345 file=sys.stderr)
David Pursehousec1b86a22012-11-14 11:36:51 +0900346 if opt.force_broken:
347 print('warn: --force-broken, continuing to sync',
348 file=sys.stderr)
349 else:
350 raise _FetchError()
Roy Lee18afd7f2010-05-09 04:32:08 +0800351
David Pursehousec1b86a22012-11-14 11:36:51 +0900352 fetched.add(project.gitdir)
353 pm.update()
354 except _FetchError:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800355 pass
Dan Sandlerc5cd4332015-07-31 09:37:53 -0400356 except Exception as e:
357 print('error: Cannot fetch %s (%s: %s)' \
358 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
David Pursehousec1b86a22012-11-14 11:36:51 +0900359 err_event.set()
360 raise
361 finally:
362 if did_lock:
363 lock.release()
David Rileye0684ad2017-04-05 00:02:59 -0700364 finish = time.time()
365 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
366 start, finish, success)
Roy Lee18afd7f2010-05-09 04:32:08 +0800367
David James8d201162013-10-11 17:03:19 -0700368 return success
369
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700370 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700371 fetched = set()
David James89ece422014-01-09 18:51:58 -0800372 lock = _threading.Lock()
Tim Schumacher913327f2017-06-05 15:01:41 +0200373 pm = Progress('Fetching projects', len(projects),
Tim Schumacher7be072e2017-06-28 18:29:23 +0200374 print_newline=not(opt.quiet),
375 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.
387 if err_event.isSet() and not opt.force_broken:
388 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.
Nicolas Cornu8419ab22017-06-16 12:09:06 +0200413 if err_event.isSet() and not opt.force_broken:
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:
439 success = self._CheckoutOne(opt, project, *args, **kwargs)
440 if not success:
441 sys.exit(1)
442 finally:
443 sem.release()
444
445 def _CheckoutOne(self, opt, project, lock, pm, err_event):
446 """Checkout work tree for one project
447
448 Args:
449 opt: Program options returned from optparse. See _Options().
450 project: Project object for the project to checkout.
451 lock: Lock for accessing objects that are shared amongst multiple
452 _CheckoutWorker() threads.
453 pm: Instance of a Project object. We will call pm.update() (with our
454 lock held).
455 err_event: We'll set this event in the case of an error (after printing
456 out info about the error).
457
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
464 if not opt.quiet:
465 print('Checking out project %s' % project.name)
466
467 # Encapsulate everything in a try/except/finally so that:
468 # - We always set err_event in the case of an exception.
469 # - We always make sure we unlock the lock if we locked it.
470 start = time.time()
471 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
472 detach_head=opt.detach_head)
473 success = False
474 try:
475 try:
476 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
477 success = syncbuf.Finish()
478
479 # Lock around all the rest of the code, since printing, updating a set
480 # and Progress.update() are not thread safe.
481 lock.acquire()
482 did_lock = True
483
484 if not success:
485 err_event.set()
486 print('error: Cannot checkout %s' % (project.name),
487 file=sys.stderr)
488 raise _CheckoutError()
489
490 pm.update()
491 except _CheckoutError:
492 pass
493 except Exception as e:
494 print('error: Cannot checkout %s: %s: %s' %
495 (project.name, type(e).__name__, str(e)),
496 file=sys.stderr)
497 err_event.set()
498 raise
499 finally:
500 if did_lock:
501 lock.release()
502 finish = time.time()
503 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
504 start, finish, success)
505
506 return success
507
508 def _Checkout(self, all_projects, opt):
509 """Checkout projects listed in all_projects
510
511 Args:
512 all_projects: List of all projects that should be checked out.
513 opt: Program options returned from optparse. See _Options().
514 """
515
516 # Perform checkouts in multiple threads when we are using partial clone.
517 # Without partial clone, all needed git objects are already downloaded,
518 # in this situation it's better to use only one process because the checkout
519 # would be mostly disk I/O; with partial clone, the objects are only
520 # downloaded when demanded (at checkout time), which is similar to the
521 # Sync_NetworkHalf case and parallelism would be helpful.
522 if self.manifest.CloneFilter:
523 syncjobs = self.jobs
524 else:
525 syncjobs = 1
526
527 lock = _threading.Lock()
528 pm = Progress('Syncing work tree', len(all_projects))
529
530 threads = set()
531 sem = _threading.Semaphore(syncjobs)
532 err_event = _threading.Event()
533
534 for project in all_projects:
535 # Check for any errors before running any more tasks.
536 # ...we'll let existing threads finish, though.
537 if err_event.isSet() and not opt.force_broken:
538 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,
547 err_event=err_event)
548 if syncjobs > 1:
549 t = _threading.Thread(target=self._CheckoutWorker,
550 kwargs=kwargs)
551 # Ensure that Ctrl-C will not freeze the repo process.
552 t.daemon = True
553 threads.add(t)
554 t.start()
555 else:
556 self._CheckoutWorker(**kwargs)
557
558 for t in threads:
559 t.join()
560
561 pm.end()
562 # If we saw an error, exit with code 1 so that other scripts can check.
563 if err_event.isSet():
564 print('\nerror: Exited sync due to checkout errors', file=sys.stderr)
565 sys.exit(1)
566
Dave Borowitz18857212012-10-23 17:02:59 -0700567 def _GCProjects(self, projects):
Gabe Black2ff30292014-10-09 17:54:35 -0700568 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700569 for project in projects:
Gabe Black2ff30292014-10-09 17:54:35 -0700570 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
571 print('Shared project %s found, disabling pruning.' % project.name)
572 project.bare_git.config('--replace-all', 'gc.pruneExpire', 'never')
573 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700574
Dave Borowitze2152672012-10-31 12:24:38 -0700575 has_dash_c = git_require((1, 7, 2))
576 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700577 cpu_count = multiprocessing.cpu_count()
578 else:
579 cpu_count = 1
580 jobs = min(self.jobs, cpu_count)
581
582 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700583 for bare_git in gc_gitdirs.values():
David James8d201162013-10-11 17:03:19 -0700584 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700585 return
586
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400587 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700588
589 threads = set()
590 sem = _threading.Semaphore(jobs)
591 err_event = _threading.Event()
592
David James8d201162013-10-11 17:03:19 -0700593 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700594 try:
595 try:
David James8d201162013-10-11 17:03:19 -0700596 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700597 except GitError:
598 err_event.set()
599 except:
600 err_event.set()
601 raise
602 finally:
603 sem.release()
604
Gabe Black2ff30292014-10-09 17:54:35 -0700605 for bare_git in gc_gitdirs.values():
Dave Borowitz18857212012-10-23 17:02:59 -0700606 if err_event.isSet():
607 break
608 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700609 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700610 t.daemon = True
611 threads.add(t)
612 t.start()
613
614 for t in threads:
615 t.join()
616
617 if err_event.isSet():
Sarah Owenscecd1d82012-11-01 22:59:27 -0700618 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
Dave Borowitz18857212012-10-23 17:02:59 -0700619 sys.exit(1)
620
Tim Kilbourn07669002013-03-08 15:02:49 -0800621 def _ReloadManifest(self, manifest_name=None):
622 if manifest_name:
623 # Override calls _Unload already
624 self.manifest.Override(manifest_name)
625 else:
626 self.manifest._Unload()
627
Dan Willemsen43507912016-09-01 16:26:02 -0700628 def _DeleteProject(self, path):
629 print('Deleting obsolete path %s' % path, file=sys.stderr)
630
631 # Delete the .git directory first, so we're less likely to have a partially
632 # working git repository around. There shouldn't be any git projects here,
633 # so rmtree works.
634 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -0700635 platform_utils.rmtree(os.path.join(path, '.git'))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700636 except OSError as e:
637 print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700638 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
639 print(' remove manually, then run sync again', file=sys.stderr)
640 return -1
641
642 # Delete everything under the worktree, except for directories that contain
643 # another git project
644 dirs_to_remove = []
645 failed = False
Renaud Paquaybed8b622018-09-27 10:46:58 -0700646 for root, dirs, files in platform_utils.walk(path):
Dan Willemsen43507912016-09-01 16:26:02 -0700647 for f in files:
648 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800649 platform_utils.remove(os.path.join(root, f))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700650 except OSError as e:
651 print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700652 failed = True
653 dirs[:] = [d for d in dirs
654 if not os.path.lexists(os.path.join(root, d, '.git'))]
655 dirs_to_remove += [os.path.join(root, d) for d in dirs
656 if os.path.join(root, d) not in dirs_to_remove]
657 for d in reversed(dirs_to_remove):
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700658 if platform_utils.islink(d):
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700659 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800660 platform_utils.remove(d)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700661 except OSError as e:
662 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700663 failed = True
Renaud Paquaybed8b622018-09-27 10:46:58 -0700664 elif len(platform_utils.listdir(d)) == 0:
Dan Willemsen43507912016-09-01 16:26:02 -0700665 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700666 platform_utils.rmdir(d)
667 except OSError as e:
668 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700669 failed = True
670 continue
671 if failed:
672 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
673 print(' remove manually, then run sync again', file=sys.stderr)
674 return -1
675
676 # Try deleting parent dirs if they are empty
677 project_dir = path
678 while project_dir != self.manifest.topdir:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700679 if len(platform_utils.listdir(project_dir)) == 0:
680 platform_utils.rmdir(project_dir)
Dan Willemsen43507912016-09-01 16:26:02 -0700681 else:
682 break
683 project_dir = os.path.dirname(project_dir)
684
685 return 0
686
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500687 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700688 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700689 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700690 if project.relpath:
691 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700692 file_name = 'project.list'
693 file_path = os.path.join(self.manifest.repodir, file_name)
694 old_project_paths = []
695
696 if os.path.exists(file_path):
697 fd = open(file_path, 'r')
698 try:
699 old_project_paths = fd.read().split('\n')
700 finally:
701 fd.close()
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800702 # In reversed order, so subfolders are deleted before parent folder.
703 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700704 if not path:
705 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700706 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900707 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700708 gitdir = os.path.join(self.manifest.topdir, path, '.git')
709 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900710 project = Project(
711 manifest = self.manifest,
712 name = path,
713 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700714 gitdir = gitdir,
715 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900716 worktree = os.path.join(self.manifest.topdir, path),
717 relpath = path,
718 revisionExpr = 'HEAD',
719 revisionId = None,
720 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400721
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500722 if project.IsDirty() and opt.force_remove_dirty:
723 print('WARNING: Removing dirty project "%s": uncommitted changes '
724 'erased' % project.relpath, file=sys.stderr)
725 self._DeleteProject(project.worktree)
726 elif project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900727 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900728 'are present' % project.relpath, file=sys.stderr)
729 print(' commit changes, then run sync again',
730 file=sys.stderr)
731 return -1
Dan Willemsen43507912016-09-01 16:26:02 -0700732 elif self._DeleteProject(project.worktree):
733 return -1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700734
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700735 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700736 fd = open(file_path, 'w')
737 try:
738 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 finally:
741 fd.close()
742 return 0
743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800745 if opt.jobs:
746 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700747 if self.jobs > 1:
748 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400749 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700750
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700751 if opt.network_only and opt.detach_head:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700752 print('error: cannot combine -n and -d', file=sys.stderr)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700753 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700754 if opt.network_only and opt.local_only:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700755 print('error: cannot combine -n and -l', file=sys.stderr)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700756 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500757 if opt.manifest_name and opt.smart_sync:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700758 print('error: cannot combine -m and -s', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500759 sys.exit(1)
760 if opt.manifest_name and opt.smart_tag:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700761 print('error: cannot combine -m and -t', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500762 sys.exit(1)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900763 if opt.manifest_server_username or opt.manifest_server_password:
764 if not (opt.smart_sync or opt.smart_tag):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700765 print('error: -u and -p may only be combined with -s or -t',
766 file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900767 sys.exit(1)
768 if None in [opt.manifest_server_username, opt.manifest_server_password]:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700769 print('error: both -u and -p must be given', file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900770 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500771
772 if opt.manifest_name:
773 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700774
Chirayu Desaia892b102013-06-11 14:18:46 +0530775 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900776 smart_sync_manifest_name = "smart_sync_override.xml"
777 smart_sync_manifest_path = os.path.join(
778 self.manifest.manifestProject.worktree, smart_sync_manifest_name)
Chirayu Desaia892b102013-06-11 14:18:46 +0530779
Victor Boivie08c880d2011-04-19 10:32:52 +0200780 if opt.smart_sync or opt.smart_tag:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700781 if not self.manifest.manifest_server:
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900782 print('error: cannot smart sync: no manifest server defined in '
Sarah Owenscecd1d82012-11-01 22:59:27 -0700783 'manifest', file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700784 sys.exit(1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900785
786 manifest_server = self.manifest.manifest_server
David Pursehousefb99c712013-09-25 11:09:34 +0900787 if not opt.quiet:
788 print('Using manifest server %s' % manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900789
David Pursehouse86d973d2012-08-24 10:21:02 +0900790 if not '@' in manifest_server:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900791 username = None
792 password = None
793 if opt.manifest_server_username and opt.manifest_server_password:
794 username = opt.manifest_server_username
795 password = opt.manifest_server_password
David Pursehouse86d973d2012-08-24 10:21:02 +0900796 else:
797 try:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900798 info = netrc.netrc()
799 except IOError:
David Pursehouseba7bc732015-08-20 16:55:42 +0900800 # .netrc file does not exist or could not be opened
801 pass
David Pursehouse86d973d2012-08-24 10:21:02 +0900802 else:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900803 try:
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +0530804 parse_result = urllib.parse.urlparse(manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900805 if parse_result.hostname:
David Pursehouseba7bc732015-08-20 16:55:42 +0900806 auth = info.authenticators(parse_result.hostname)
807 if auth:
808 username, _account, password = auth
809 else:
810 print('No credentials found for %s in .netrc'
811 % parse_result.hostname, file=sys.stderr)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700812 except netrc.NetrcParseError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700813 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900814
815 if (username and password):
816 manifest_server = manifest_server.replace('://', '://%s:%s@' %
817 (username, password),
818 1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900819
Dan Willemsen0745bb22015-08-17 13:41:45 -0700820 transport = PersistentTransport(manifest_server)
821 if manifest_server.startswith('persistent-'):
822 manifest_server = manifest_server[len('persistent-'):]
823
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700824 try:
Dan Willemsen0745bb22015-08-17 13:41:45 -0700825 server = xmlrpc.client.Server(manifest_server, transport=transport)
Victor Boivie08c880d2011-04-19 10:32:52 +0200826 if opt.smart_sync:
827 p = self.manifest.manifestProject
828 b = p.GetBranch(p.CurrentBranch)
829 branch = b.merge
830 if branch.startswith(R_HEADS):
831 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700832
Victor Boivie08c880d2011-04-19 10:32:52 +0200833 env = os.environ.copy()
Jeff Davidson5cf16602014-10-02 10:13:38 -0700834 if 'SYNC_TARGET' in env:
835 target = env['SYNC_TARGET']
836 [success, manifest_str] = server.GetApprovedManifest(branch, target)
837 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
Victor Boivie08c880d2011-04-19 10:32:52 +0200838 target = '%s-%s' % (env['TARGET_PRODUCT'],
839 env['TARGET_BUILD_VARIANT'])
840 [success, manifest_str] = server.GetApprovedManifest(branch, target)
841 else:
842 [success, manifest_str] = server.GetApprovedManifest(branch)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700843 else:
Victor Boivie08c880d2011-04-19 10:32:52 +0200844 assert(opt.smart_tag)
845 [success, manifest_str] = server.GetManifest(opt.smart_tag)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700846
847 if success:
David Pursehouse59b41742015-05-07 14:36:09 +0900848 manifest_name = smart_sync_manifest_name
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700849 try:
David Pursehouse59b41742015-05-07 14:36:09 +0900850 f = open(smart_sync_manifest_path, 'w')
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700851 try:
852 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700853 finally:
854 f.close()
David Pursehouse727cc3e2015-05-07 14:16:49 +0900855 except IOError as e:
856 print('error: cannot write manifest to %s:\n%s'
David Pursehouse59b41742015-05-07 14:36:09 +0900857 % (smart_sync_manifest_path, e),
Sarah Owenscecd1d82012-11-01 22:59:27 -0700858 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700859 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100860 self._ReloadManifest(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700861 else:
David Pursehouse351fe2c2013-09-25 17:54:26 +0900862 print('error: manifest server RPC call failed: %s' %
863 manifest_str, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700864 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530865 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700866 print('error: cannot connect to manifest server %s:\n%s'
867 % (self.manifest.manifest_server, e), file=sys.stderr)
David Pursehousebd489c42012-08-23 10:21:26 +0900868 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530869 except xmlrpc.client.ProtocolError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700870 print('error: cannot connect to manifest server %s:\n%d %s'
871 % (self.manifest.manifest_server, e.errcode, e.errmsg),
872 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700873 sys.exit(1)
David Pursehouse59b41742015-05-07 14:36:09 +0900874 else: # Not smart sync or smart tag mode
875 if os.path.isfile(smart_sync_manifest_path):
876 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800877 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900878 except OSError as e:
879 print('error: failed to remove existing smart sync override manifest: %s' %
880 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700881
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882 rp = self.manifest.repoProject
883 rp.PreSync()
884
885 mp = self.manifest.manifestProject
886 mp.PreSync()
887
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800888 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700889 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800890
Nico Sallembien9bb18162009-12-07 15:38:01 -0800891 if not opt.local_only:
David Rileye0684ad2017-04-05 00:02:59 -0700892 start = time.time()
893 success = mp.Sync_NetworkHalf(quiet=opt.quiet,
894 current_branch_only=opt.current_branch_only,
895 no_tags=opt.no_tags,
896 optimized_fetch=opt.optimized_fetch,
Xin Li745be2e2019-06-03 11:24:30 -0700897 submodules=self.manifest.HasSubmodules,
898 clone_filter=self.manifest.CloneFilter)
David Rileye0684ad2017-04-05 00:02:59 -0700899 finish = time.time()
900 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
901 start, finish, success)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800902
903 if mp.HasChanges:
904 syncbuf = SyncBuffer(mp.config)
David Rileye0684ad2017-04-05 00:02:59 -0700905 start = time.time()
Martin Kellye4e94d22017-03-21 16:05:12 -0700906 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
David Rileye0684ad2017-04-05 00:02:59 -0700907 clean = syncbuf.Finish()
908 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
909 start, time.time(), clean)
910 if not clean:
Nico Sallembien9bb18162009-12-07 15:38:01 -0800911 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100912 self._ReloadManifest(manifest_name)
Shawn O. Pearcec4657962011-09-26 09:08:01 -0700913 if opt.jobs is None:
914 self.jobs = self.manifest.default.sync_j
Simran Basib9a1b732015-08-20 12:19:28 -0700915
Simran Basib9a1b732015-08-20 12:19:28 -0700916 if self.gitc_manifest:
917 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700918 missing_ok=True)
919 gitc_projects = []
920 opened_projects = []
921 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700922 if project.relpath in self.gitc_manifest.paths and \
923 self.gitc_manifest.paths[project.relpath].old_revision:
924 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700925 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700926 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700927
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700928 if not args:
929 gitc_projects = None
930
931 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700932 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700933 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
934 if manifest_name:
935 manifest.Override(manifest_name)
936 else:
937 manifest.Override(self.manifest.manifestFile)
938 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
939 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700940 gitc_projects)
941 print('GITC client successfully synced.')
942
943 # The opened projects need to be synced as normal, therefore we
944 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700945 # TODO: make this more reliable -- if there's a project name/path overlap,
946 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900947 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
948 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700949 if not args:
950 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800951 all_projects = self.GetProjects(args,
952 missing_ok=True,
953 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
Dave Borowitz67700e92012-10-23 15:00:54 -0700955 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700956 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700957 to_fetch = []
958 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700959 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700960 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900961 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700962 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700963
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800964 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700965 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700966 if opt.network_only:
967 # bail out now; the rest touches the working tree
968 return
969
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800970 # Iteratively fetch missing and/or nested unregistered submodules
971 previously_missing_set = set()
972 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100973 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800974 all_projects = self.GetProjects(args,
975 missing_ok=True,
976 submodules_ok=opt.fetch_submodules)
977 missing = []
978 for project in all_projects:
979 if project.gitdir not in fetched:
980 missing.append(project)
981 if not missing:
982 break
983 # Stop us from non-stopped fetching actually-missing repos: If set of
984 # missing repos has not been changed from last fetch, we break.
985 missing_set = set(p.name for p in missing)
986 if previously_missing_set == missing_set:
987 break
988 previously_missing_set = missing_set
989 fetched.update(self._Fetch(missing, opt))
990
Julien Campergue335f5ef2013-10-16 11:02:35 +0200991 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700992 # bail out now, we have no working tree
993 return
994
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500995 if self.UpdateProjectList(opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700996 sys.exit(1)
997
Xin Li745be2e2019-06-03 11:24:30 -0700998 self._Checkout(all_projects, opt)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700999
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001000 # If there's a notice that's supposed to print at the end of the sync, print
1001 # it now...
1002 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001003 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001004
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001005def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001006 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001007 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001008 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001009 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001010 if project.Exists:
1011 project.PostRepoUpgrade()
1012
1013def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
1014 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001015 print('info: A new version of repo is available', file=sys.stderr)
1016 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001017 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001018 syncbuf = SyncBuffer(rp.config)
1019 rp.Sync_LocalHalf(syncbuf)
1020 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001021 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -07001022 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001023 raise RepoChangedException(['--repo-upgraded'])
1024 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001025 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001026 else:
1027 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001028 print('repo version %s is current' % rp.work_git.describe(HEAD),
1029 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001030
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031def _VerifyTag(project):
1032 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
1033 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -07001034 print('warning: GnuPG was not available during last "repo init"\n'
1035 'warning: Cannot automatically authenticate repo."""',
1036 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037 return True
1038
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001040 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001041 except GitError:
1042 cur = None
1043
1044 if not cur \
1045 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001046 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047 if rev.startswith(R_HEADS):
1048 rev = rev[len(R_HEADS):]
1049
Sarah Owenscecd1d82012-11-01 22:59:27 -07001050 print(file=sys.stderr)
1051 print("warning: project '%s' branch '%s' is not signed"
1052 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001053 return False
1054
Shawn O. Pearcef18cb762010-12-07 11:41:05 -08001055 env = os.environ.copy()
1056 env['GIT_DIR'] = project.gitdir.encode()
1057 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058
1059 cmd = [GIT, 'tag', '-v', cur]
1060 proc = subprocess.Popen(cmd,
1061 stdout = subprocess.PIPE,
1062 stderr = subprocess.PIPE,
1063 env = env)
1064 out = proc.stdout.read()
1065 proc.stdout.close()
1066
1067 err = proc.stderr.read()
1068 proc.stderr.close()
1069
1070 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001071 print(file=sys.stderr)
1072 print(out, file=sys.stderr)
1073 print(err, file=sys.stderr)
1074 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075 return False
1076 return True
Dave Borowitz67700e92012-10-23 15:00:54 -07001077
David Rileye0684ad2017-04-05 00:02:59 -07001078
Dave Borowitz67700e92012-10-23 15:00:54 -07001079class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001080 _ALPHA = 0.5
1081
Dave Borowitz67700e92012-10-23 15:00:54 -07001082 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001083 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001084 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001085 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001086
1087 def Get(self, project):
1088 self._Load()
1089 return self._times.get(project.name, _ONE_DAY_S)
1090
1091 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001092 self._Load()
1093 name = project.name
1094 old = self._times.get(name, t)
1095 self._seen.add(name)
1096 a = self._ALPHA
1097 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001098
1099 def _Load(self):
1100 if self._times is None:
1101 try:
Anthony King85b24ac2014-05-06 15:57:48 +01001102 f = open(self._path)
Dave Borowitz67700e92012-10-23 15:00:54 -07001103 try:
Anthony King85b24ac2014-05-06 15:57:48 +01001104 self._times = json.load(f)
1105 finally:
1106 f.close()
1107 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:
Anthony King85b24ac2014-05-06 15:57:48 +01001126 f = open(self._path, 'w')
Dave Borowitz67700e92012-10-23 15:00:54 -07001127 try:
Anthony King85b24ac2014-05-06 15:57:48 +01001128 json.dump(self._times, f, indent=2)
1129 finally:
1130 f.close()
1131 except (IOError, TypeError):
1132 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001133 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001134 except OSError:
1135 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001136
1137# This is a replacement for xmlrpc.client.Transport using urllib2
1138# and supporting persistent-http[s]. It cannot change hosts from
1139# request to request like the normal transport, the real url
1140# is passed during initialization.
1141class PersistentTransport(xmlrpc.client.Transport):
1142 def __init__(self, orig_host):
1143 self.orig_host = orig_host
1144
1145 def request(self, host, handler, request_body, verbose=False):
1146 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1147 # Python doesn't understand cookies with the #HttpOnly_ prefix
1148 # Since we're only using them for HTTP, copy the file temporarily,
1149 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001150 if cookiefile:
1151 tmpcookiefile = tempfile.NamedTemporaryFile()
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001152 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001153 try:
1154 with open(cookiefile) as f:
1155 for line in f:
1156 if line.startswith("#HttpOnly_"):
1157 line = line[len("#HttpOnly_"):]
1158 tmpcookiefile.write(line)
1159 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001160
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001161 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001162 try:
1163 cookiejar.load()
1164 except cookielib.LoadError:
1165 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001166 finally:
1167 tmpcookiefile.close()
1168 else:
1169 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001170
1171 proxyhandler = urllib.request.ProxyHandler
1172 if proxy:
1173 proxyhandler = urllib.request.ProxyHandler({
1174 "http": proxy,
1175 "https": proxy })
1176
1177 opener = urllib.request.build_opener(
1178 urllib.request.HTTPCookieProcessor(cookiejar),
1179 proxyhandler)
1180
1181 url = urllib.parse.urljoin(self.orig_host, handler)
1182 parse_results = urllib.parse.urlparse(url)
1183
1184 scheme = parse_results.scheme
1185 if scheme == 'persistent-http':
1186 scheme = 'http'
1187 if scheme == 'persistent-https':
1188 # If we're proxying through persistent-https, use http. The
1189 # proxy itself will do the https.
1190 if proxy:
1191 scheme = 'http'
1192 else:
1193 scheme = 'https'
1194
1195 # Parse out any authentication information using the base class
1196 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1197
1198 url = urllib.parse.urlunparse((
1199 scheme,
1200 host,
1201 parse_results.path,
1202 parse_results.params,
1203 parse_results.query,
1204 parse_results.fragment))
1205
1206 request = urllib.request.Request(url, request_body)
1207 if extra_headers is not None:
1208 for (name, header) in extra_headers:
1209 request.add_header(name, header)
1210 request.add_header('Content-Type', 'text/xml')
1211 try:
1212 response = opener.open(request)
1213 except urllib.error.HTTPError as e:
1214 if e.code == 501:
1215 # We may have been redirected through a login process
1216 # but our POST turned into a GET. Retry.
1217 response = opener.open(request)
1218 else:
1219 raise
1220
1221 p, u = xmlrpc.client.getparser()
1222 while 1:
1223 data = response.read(1024)
1224 if not data:
1225 break
1226 p.feed(data)
1227 p.close()
1228 return u.close()
1229
1230 def close(self):
1231 pass
1232