blob: 43d450be5b8b6a739fff3b70c0f866cb12967b73 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
Sarah Owenscecd1d82012-11-01 22:59:27 -070016from __future__ import print_function
Anthony King85b24ac2014-05-06 15:57:48 +010017import json
David Pursehouse86d973d2012-08-24 10:21:02 +090018import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070019from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import os
21import re
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070022import shutil
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070023import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import subprocess
25import sys
Shawn O. Pearcef6906872009-04-18 10:49:00 -070026import time
David Pursehouse59bbb582013-05-17 10:49:33 +090027
28from pyversion import is_python3
29if is_python3():
Chirayu Desai217ea7d2013-03-01 19:14:38 +053030 import urllib.parse
David Pursehouse59bbb582013-05-17 10:49:33 +090031 import xmlrpc.client
32else:
Chirayu Desai217ea7d2013-03-01 19:14:38 +053033 import imp
34 import urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090035 import xmlrpclib
Chirayu Desai217ea7d2013-03-01 19:14:38 +053036 urllib = imp.new_module('urllib')
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +053037 urllib.parse = urlparse
Chirayu Desai217ea7d2013-03-01 19:14:38 +053038 xmlrpc = imp.new_module('xmlrpc')
39 xmlrpc.client = xmlrpclib
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Roy Lee18afd7f2010-05-09 04:32:08 +080041try:
42 import threading as _threading
43except ImportError:
44 import dummy_threading as _threading
45
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070046try:
47 import resource
48 def _rlimit_nofile():
49 return resource.getrlimit(resource.RLIMIT_NOFILE)
50except ImportError:
51 def _rlimit_nofile():
52 return (256, 256)
53
Dave Borowitz18857212012-10-23 17:02:59 -070054try:
55 import multiprocessing
56except ImportError:
57 multiprocessing = None
58
Dave Borowitze2152672012-10-31 12:24:38 -070059from git_command import GIT, git_require
David Pursehoused94aaef2012-09-07 09:52:04 +090060from git_refs import R_HEADS, HEAD
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070061from project import Project
62from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080063from command import Command, MirrorSafeCommand
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +000064from error import RepoChangedException, GitError, ManifestParseError
Shawn O. Pearce350cde42009-04-16 11:21:18 -070065from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070066from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080067from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068
Dave Borowitz67700e92012-10-23 15:00:54 -070069_ONE_DAY_S = 24 * 60 * 60
70
Doug Andersonfc06ced2011-03-16 15:49:18 -070071class _FetchError(Exception):
72 """Internal error thrown in _FetchHelper() when we don't want stack trace."""
73 pass
74
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080075class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080076 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077 common = True
78 helpSummary = "Update working tree to the latest revision"
79 helpUsage = """
80%prog [<project>...]
81"""
82 helpDescription = """
83The '%prog' command synchronizes local project directories
84with the remote repositories specified in the manifest. If a local
85project does not yet exist, it will clone a new local directory from
86the remote repository and set up tracking branches as specified in
87the manifest. If the local project already exists, '%prog'
88will update the remote branches and rebase any new local changes
89on top of the new remote changes.
90
91'%prog' will synchronize all projects listed at the command
92line. Projects can be specified either by name, or by a relative
93or absolute path to the project's local directory. If no projects
94are specified, '%prog' will synchronize all projects listed in
95the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070096
97The -d/--detach option can be used to switch specified projects
98back to the manifest revision. This option is especially helpful
99if the project is currently on a topic branch, but the manifest
100revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700101
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700102The -s/--smart-sync option can be used to sync to a known good
103build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200104manifest. The -t/--smart-tag option is similar and allows you to
105specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700106
David Pursehousecf76b1b2012-09-14 10:31:42 +0900107The -u/--manifest-server-username and -p/--manifest-server-password
108options can be used to specify a username and password to authenticate
109with the manifest server when using the -s or -t option.
110
111If -u and -p are not specified when using the -s or -t option, '%prog'
112will attempt to read authentication credentials for the manifest server
113from the user's .netrc file.
114
115'%prog' will not use authentication credentials from -u/-p or .netrc
116if the manifest server specified in the manifest file already includes
117credentials.
118
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500119The -f/--force-broken option can be used to proceed with syncing
120other projects if a project sync fails.
121
Kevin Degiabaa7f32014-11-12 11:27:45 -0700122The --force-sync option can be used to overwrite existing git
123directories if they have previously been linked to a different
124object direcotry. WARNING: This may cause data to be lost since
125refs may be removed when overwriting.
126
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700127The --no-clone-bundle option disables any attempt to use
128$URL/clone.bundle to bootstrap a new Git repository from a
129resumeable bundle file on a content delivery network. This
130may be necessary if there are problems with the local Python
131HTTP client or proxy configuration, but the Git binary works.
132
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800133The --fetch-submodules option enables fetching Git submodules
134of a project from server.
135
David Pursehousef2fad612015-01-29 14:36:28 +0900136The -c/--current-branch option can be used to only fetch objects that
137are on the branch specified by a project's revision.
138
David Pursehouseb1553542014-09-04 21:28:09 +0900139The --optimized-fetch option can be used to only fetch projects that
140are fixed to a sha1 revision if the sha1 revision does not already
141exist locally.
142
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700143SSH Connections
144---------------
145
146If at least one project remote URL uses an SSH connection (ssh://,
147git+ssh://, or user@host:path syntax) repo will automatically
148enable the SSH ControlMaster option when connecting to that host.
149This feature permits other projects in the same '%prog' session to
150reuse the same SSH tunnel, saving connection setup overheads.
151
152To disable this behavior on UNIX platforms, set the GIT_SSH
153environment variable to 'ssh'. For example:
154
155 export GIT_SSH=ssh
156 %prog
157
158Compatibility
159~~~~~~~~~~~~~
160
161This feature is automatically disabled on Windows, due to the lack
162of UNIX domain socket support.
163
164This feature is not compatible with url.insteadof rewrites in the
165user's ~/.gitconfig. '%prog' is currently not able to perform the
166rewrite early enough to establish the ControlMaster tunnel.
167
168If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
169later is required to fix a server side protocol bug.
170
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171"""
172
Nico Sallembien6623b212010-05-11 12:57:01 -0700173 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000174 try:
175 self.jobs = self.manifest.default.sync_j
176 except ManifestParseError:
177 self.jobs = 1
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700178
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500179 p.add_option('-f', '--force-broken',
180 dest='force_broken', action='store_true',
181 help="continue sync even if a project fails to sync")
Kevin Degiabaa7f32014-11-12 11:27:45 -0700182 p.add_option('--force-sync',
183 dest='force_sync', action='store_true',
184 help="overwrite an existing git directory if it needs to "
185 "point to a different object directory. WARNING: this "
186 "may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900187 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700188 dest='local_only', action='store_true',
189 help="only update working tree, don't fetch")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900190 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700191 dest='network_only', action='store_true',
192 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900193 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700194 dest='detach_head', action='store_true',
195 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900196 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700197 dest='current_branch_only', action='store_true',
198 help='fetch only current branch from server')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900199 p.add_option('-q', '--quiet',
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700200 dest='quiet', action='store_true',
201 help='be more quiet')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900202 p.add_option('-j', '--jobs',
Roy Lee18afd7f2010-05-09 04:32:08 +0800203 dest='jobs', action='store', type='int',
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700204 help="projects to fetch simultaneously (default %d)" % self.jobs)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500205 p.add_option('-m', '--manifest-name',
206 dest='manifest_name',
207 help='temporary manifest to use for this sync', metavar='NAME.xml')
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700208 p.add_option('--no-clone-bundle',
209 dest='no_clone_bundle', action='store_true',
210 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800211 p.add_option('-u', '--manifest-server-username', action='store',
212 dest='manifest_server_username',
213 help='username to authenticate with the manifest server')
214 p.add_option('-p', '--manifest-server-password', action='store',
215 dest='manifest_server_password',
216 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800217 p.add_option('--fetch-submodules',
218 dest='fetch_submodules', action='store_true',
219 help='fetch submodules from server')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700220 p.add_option('--no-tags',
221 dest='no_tags', action='store_true',
222 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900223 p.add_option('--optimized-fetch',
224 dest='optimized_fetch', action='store_true',
225 help='only fetch projects fixed to sha1 if revision does not exist locally')
Nico Sallembien6623b212010-05-11 12:57:01 -0700226 if show_smart:
227 p.add_option('-s', '--smart-sync',
228 dest='smart_sync', action='store_true',
229 help='smart sync using manifest from a known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200230 p.add_option('-t', '--smart-tag',
231 dest='smart_tag', action='store',
232 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700233
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700234 g = p.add_option_group('repo Version options')
235 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236 dest='no_repo_verify', action='store_true',
237 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700238 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800239 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700240 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
David James89ece422014-01-09 18:51:58 -0800242 def _FetchProjectList(self, opt, projects, *args, **kwargs):
David Pursehousec1b86a22012-11-14 11:36:51 +0900243 """Main function of the fetch threads when jobs are > 1.
Roy Lee18afd7f2010-05-09 04:32:08 +0800244
David James8d201162013-10-11 17:03:19 -0700245 Delegates most of the work to _FetchHelper.
246
247 Args:
248 opt: Program options returned from optparse. See _Options().
249 projects: Projects to fetch.
David James89ece422014-01-09 18:51:58 -0800250 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
David James8d201162013-10-11 17:03:19 -0700251 _FetchHelper docstring for details.
252 """
253 for project in projects:
David James89ece422014-01-09 18:51:58 -0800254 success = self._FetchHelper(opt, project, *args, **kwargs)
David James8d201162013-10-11 17:03:19 -0700255 if not success and not opt.force_broken:
256 break
257
258 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
259 """Fetch git objects for a single project.
260
David Pursehousec1b86a22012-11-14 11:36:51 +0900261 Args:
262 opt: Program options returned from optparse. See _Options().
263 project: Project object for the project to fetch.
264 lock: Lock for accessing objects that are shared amongst multiple
265 _FetchHelper() threads.
266 fetched: set object that we will add project.gitdir to when we're done
267 (with our lock held).
268 pm: Instance of a Project object. We will call pm.update() (with our
269 lock held).
270 sem: We'll release() this semaphore when we exit so that another thread
271 can be started up.
272 err_event: We'll set this event in the case of an error (after printing
273 out info about the error).
David James8d201162013-10-11 17:03:19 -0700274
275 Returns:
276 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900277 """
278 # We'll set to true once we've locked the lock.
279 did_lock = False
Doug Andersonfc06ced2011-03-16 15:49:18 -0700280
Chirayu Desaifef4ae72013-04-12 14:54:32 +0530281 if not opt.quiet:
282 print('Fetching project %s' % project.name)
283
David Pursehousec1b86a22012-11-14 11:36:51 +0900284 # Encapsulate everything in a try/except/finally so that:
285 # - We always set err_event in the case of an exception.
286 # - We always make sure we call sem.release().
287 # - We always make sure we unlock the lock if we locked it.
288 try:
Doug Andersonfc06ced2011-03-16 15:49:18 -0700289 try:
David Pursehousec1b86a22012-11-14 11:36:51 +0900290 start = time.time()
291 success = project.Sync_NetworkHalf(
292 quiet=opt.quiet,
293 current_branch_only=opt.current_branch_only,
Kevin Degiabaa7f32014-11-12 11:27:45 -0700294 force_sync=opt.force_sync,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700295 clone_bundle=not opt.no_clone_bundle,
David Pursehouseb1553542014-09-04 21:28:09 +0900296 no_tags=opt.no_tags, archive=self.manifest.IsArchive,
297 optimized_fetch=opt.optimized_fetch)
David Pursehousec1b86a22012-11-14 11:36:51 +0900298 self._fetch_times.Set(project, time.time() - start)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700299
David Pursehousec1b86a22012-11-14 11:36:51 +0900300 # Lock around all the rest of the code, since printing, updating a set
301 # and Progress.update() are not thread safe.
302 lock.acquire()
303 did_lock = True
Doug Andersonfc06ced2011-03-16 15:49:18 -0700304
David Pursehousec1b86a22012-11-14 11:36:51 +0900305 if not success:
306 print('error: Cannot fetch %s' % project.name, file=sys.stderr)
307 if opt.force_broken:
308 print('warn: --force-broken, continuing to sync',
309 file=sys.stderr)
310 else:
311 raise _FetchError()
Doug Andersonfc06ced2011-03-16 15:49:18 -0700312
David Pursehousec1b86a22012-11-14 11:36:51 +0900313 fetched.add(project.gitdir)
314 pm.update()
315 except _FetchError:
316 err_event.set()
Dan Sandlerc5cd4332015-07-31 09:37:53 -0400317 except Exception as e:
318 print('error: Cannot fetch %s (%s: %s)' \
319 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
David Pursehousec1b86a22012-11-14 11:36:51 +0900320 err_event.set()
321 raise
322 finally:
323 if did_lock:
324 lock.release()
325 sem.release()
Roy Lee18afd7f2010-05-09 04:32:08 +0800326
David James8d201162013-10-11 17:03:19 -0700327 return success
328
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700329 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700330 fetched = set()
David James89ece422014-01-09 18:51:58 -0800331 lock = _threading.Lock()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700332 pm = Progress('Fetching projects', len(projects))
Roy Lee18afd7f2010-05-09 04:32:08 +0800333
David James89ece422014-01-09 18:51:58 -0800334 objdir_project_map = dict()
335 for project in projects:
336 objdir_project_map.setdefault(project.objdir, []).append(project)
David James8d201162013-10-11 17:03:19 -0700337
David James89ece422014-01-09 18:51:58 -0800338 threads = set()
339 sem = _threading.Semaphore(self.jobs)
340 err_event = _threading.Event()
341 for project_list in objdir_project_map.values():
342 # Check for any errors before running any more tasks.
343 # ...we'll let existing threads finish, though.
344 if err_event.isSet() and not opt.force_broken:
345 break
Doug Andersonfc06ced2011-03-16 15:49:18 -0700346
David James89ece422014-01-09 18:51:58 -0800347 sem.acquire()
348 kwargs = dict(opt=opt,
349 projects=project_list,
350 lock=lock,
351 fetched=fetched,
352 pm=pm,
353 sem=sem,
354 err_event=err_event)
355 if self.jobs > 1:
David James8d201162013-10-11 17:03:19 -0700356 t = _threading.Thread(target = self._FetchProjectList,
David James89ece422014-01-09 18:51:58 -0800357 kwargs = kwargs)
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200358 # Ensure that Ctrl-C will not freeze the repo process.
359 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800360 threads.add(t)
361 t.start()
David James89ece422014-01-09 18:51:58 -0800362 else:
363 self._FetchProjectList(**kwargs)
Roy Lee18afd7f2010-05-09 04:32:08 +0800364
David James89ece422014-01-09 18:51:58 -0800365 for t in threads:
366 t.join()
Roy Lee18afd7f2010-05-09 04:32:08 +0800367
David James89ece422014-01-09 18:51:58 -0800368 # If we saw an error, exit with code 1 so that other scripts can check.
369 if err_event.isSet():
370 print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
371 sys.exit(1)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700372
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700373 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700374 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700375
Julien Campergue335f5ef2013-10-16 11:02:35 +0200376 if not self.manifest.IsArchive:
377 self._GCProjects(projects)
378
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700379 return fetched
380
Dave Borowitz18857212012-10-23 17:02:59 -0700381 def _GCProjects(self, projects):
David James8d201162013-10-11 17:03:19 -0700382 gitdirs = {}
383 for project in projects:
384 gitdirs[project.gitdir] = project.bare_git
385
Dave Borowitze2152672012-10-31 12:24:38 -0700386 has_dash_c = git_require((1, 7, 2))
387 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700388 cpu_count = multiprocessing.cpu_count()
389 else:
390 cpu_count = 1
391 jobs = min(self.jobs, cpu_count)
392
393 if jobs < 2:
David James8d201162013-10-11 17:03:19 -0700394 for bare_git in gitdirs.values():
395 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700396 return
397
398 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
399
400 threads = set()
401 sem = _threading.Semaphore(jobs)
402 err_event = _threading.Event()
403
David James8d201162013-10-11 17:03:19 -0700404 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700405 try:
406 try:
David James8d201162013-10-11 17:03:19 -0700407 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700408 except GitError:
409 err_event.set()
410 except:
411 err_event.set()
412 raise
413 finally:
414 sem.release()
415
David James8d201162013-10-11 17:03:19 -0700416 for bare_git in gitdirs.values():
Dave Borowitz18857212012-10-23 17:02:59 -0700417 if err_event.isSet():
418 break
419 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700420 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700421 t.daemon = True
422 threads.add(t)
423 t.start()
424
425 for t in threads:
426 t.join()
427
428 if err_event.isSet():
Sarah Owenscecd1d82012-11-01 22:59:27 -0700429 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
Dave Borowitz18857212012-10-23 17:02:59 -0700430 sys.exit(1)
431
Tim Kilbourn07669002013-03-08 15:02:49 -0800432 def _ReloadManifest(self, manifest_name=None):
433 if manifest_name:
434 # Override calls _Unload already
435 self.manifest.Override(manifest_name)
436 else:
437 self.manifest._Unload()
438
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700439 def UpdateProjectList(self):
440 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700441 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700442 if project.relpath:
443 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700444 file_name = 'project.list'
445 file_path = os.path.join(self.manifest.repodir, file_name)
446 old_project_paths = []
447
448 if os.path.exists(file_path):
449 fd = open(file_path, 'r')
450 try:
451 old_project_paths = fd.read().split('\n')
452 finally:
453 fd.close()
454 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700455 if not path:
456 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700457 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900458 # If the path has already been deleted, we don't need to do it
Anthonyf3fdf822009-09-26 13:38:52 -0400459 if os.path.exists(self.manifest.topdir + '/' + path):
David James8d201162013-10-11 17:03:19 -0700460 gitdir = os.path.join(self.manifest.topdir, path, '.git')
David Pursehousec1b86a22012-11-14 11:36:51 +0900461 project = Project(
462 manifest = self.manifest,
463 name = path,
464 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700465 gitdir = gitdir,
466 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900467 worktree = os.path.join(self.manifest.topdir, path),
468 relpath = path,
469 revisionExpr = 'HEAD',
470 revisionId = None,
471 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400472
David Pursehousec1b86a22012-11-14 11:36:51 +0900473 if project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900474 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900475 'are present' % project.relpath, file=sys.stderr)
476 print(' commit changes, then run sync again',
477 file=sys.stderr)
478 return -1
479 else:
480 print('Deleting obsolete path %s' % project.worktree,
481 file=sys.stderr)
482 shutil.rmtree(project.worktree)
483 # Try deleting parent subdirs if they are empty
484 project_dir = os.path.dirname(project.worktree)
485 while project_dir != self.manifest.topdir:
486 try:
487 os.rmdir(project_dir)
488 except OSError:
489 break
490 project_dir = os.path.dirname(project_dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700491
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700492 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700493 fd = open(file_path, 'w')
494 try:
495 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700496 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700497 finally:
498 fd.close()
499 return 0
500
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700501 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800502 if opt.jobs:
503 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700504 if self.jobs > 1:
505 soft_limit, _ = _rlimit_nofile()
506 self.jobs = min(self.jobs, (soft_limit - 5) / 3)
507
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700508 if opt.network_only and opt.detach_head:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700509 print('error: cannot combine -n and -d', file=sys.stderr)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700510 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700511 if opt.network_only and opt.local_only:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700512 print('error: cannot combine -n and -l', file=sys.stderr)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700513 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500514 if opt.manifest_name and opt.smart_sync:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700515 print('error: cannot combine -m and -s', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500516 sys.exit(1)
517 if opt.manifest_name and opt.smart_tag:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700518 print('error: cannot combine -m and -t', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500519 sys.exit(1)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900520 if opt.manifest_server_username or opt.manifest_server_password:
521 if not (opt.smart_sync or opt.smart_tag):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700522 print('error: -u and -p may only be combined with -s or -t',
523 file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900524 sys.exit(1)
525 if None in [opt.manifest_server_username, opt.manifest_server_password]:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700526 print('error: both -u and -p must be given', file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900527 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500528
529 if opt.manifest_name:
530 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700531
Chirayu Desaia892b102013-06-11 14:18:46 +0530532 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900533 smart_sync_manifest_name = "smart_sync_override.xml"
534 smart_sync_manifest_path = os.path.join(
535 self.manifest.manifestProject.worktree, smart_sync_manifest_name)
Chirayu Desaia892b102013-06-11 14:18:46 +0530536
Victor Boivie08c880d2011-04-19 10:32:52 +0200537 if opt.smart_sync or opt.smart_tag:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700538 if not self.manifest.manifest_server:
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900539 print('error: cannot smart sync: no manifest server defined in '
Sarah Owenscecd1d82012-11-01 22:59:27 -0700540 'manifest', file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700541 sys.exit(1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900542
543 manifest_server = self.manifest.manifest_server
David Pursehousefb99c712013-09-25 11:09:34 +0900544 if not opt.quiet:
545 print('Using manifest server %s' % manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900546
David Pursehouse86d973d2012-08-24 10:21:02 +0900547 if not '@' in manifest_server:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900548 username = None
549 password = None
550 if opt.manifest_server_username and opt.manifest_server_password:
551 username = opt.manifest_server_username
552 password = opt.manifest_server_password
David Pursehouse86d973d2012-08-24 10:21:02 +0900553 else:
554 try:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900555 info = netrc.netrc()
556 except IOError:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700557 print('.netrc file does not exist or could not be opened',
558 file=sys.stderr)
David Pursehouse86d973d2012-08-24 10:21:02 +0900559 else:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900560 try:
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +0530561 parse_result = urllib.parse.urlparse(manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900562 if parse_result.hostname:
563 username, _account, password = \
564 info.authenticators(parse_result.hostname)
565 except TypeError:
566 # TypeError is raised when the given hostname is not present
567 # in the .netrc file.
Sarah Owenscecd1d82012-11-01 22:59:27 -0700568 print('No credentials found for %s in .netrc'
569 % parse_result.hostname, file=sys.stderr)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700570 except netrc.NetrcParseError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700571 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900572
573 if (username and password):
574 manifest_server = manifest_server.replace('://', '://%s:%s@' %
575 (username, password),
576 1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900577
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700578 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530579 server = xmlrpc.client.Server(manifest_server)
Victor Boivie08c880d2011-04-19 10:32:52 +0200580 if opt.smart_sync:
581 p = self.manifest.manifestProject
582 b = p.GetBranch(p.CurrentBranch)
583 branch = b.merge
584 if branch.startswith(R_HEADS):
585 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700586
Victor Boivie08c880d2011-04-19 10:32:52 +0200587 env = os.environ.copy()
Jeff Davidson5cf16602014-10-02 10:13:38 -0700588 if 'SYNC_TARGET' in env:
589 target = env['SYNC_TARGET']
590 [success, manifest_str] = server.GetApprovedManifest(branch, target)
591 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
Victor Boivie08c880d2011-04-19 10:32:52 +0200592 target = '%s-%s' % (env['TARGET_PRODUCT'],
593 env['TARGET_BUILD_VARIANT'])
594 [success, manifest_str] = server.GetApprovedManifest(branch, target)
595 else:
596 [success, manifest_str] = server.GetApprovedManifest(branch)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700597 else:
Victor Boivie08c880d2011-04-19 10:32:52 +0200598 assert(opt.smart_tag)
599 [success, manifest_str] = server.GetManifest(opt.smart_tag)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700600
601 if success:
David Pursehouse59b41742015-05-07 14:36:09 +0900602 manifest_name = smart_sync_manifest_name
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700603 try:
David Pursehouse59b41742015-05-07 14:36:09 +0900604 f = open(smart_sync_manifest_path, 'w')
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700605 try:
606 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700607 finally:
608 f.close()
David Pursehouse727cc3e2015-05-07 14:16:49 +0900609 except IOError as e:
610 print('error: cannot write manifest to %s:\n%s'
David Pursehouse59b41742015-05-07 14:36:09 +0900611 % (smart_sync_manifest_path, e),
Sarah Owenscecd1d82012-11-01 22:59:27 -0700612 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700613 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100614 self._ReloadManifest(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700615 else:
David Pursehouse351fe2c2013-09-25 17:54:26 +0900616 print('error: manifest server RPC call failed: %s' %
617 manifest_str, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700618 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530619 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700620 print('error: cannot connect to manifest server %s:\n%s'
621 % (self.manifest.manifest_server, e), file=sys.stderr)
David Pursehousebd489c42012-08-23 10:21:26 +0900622 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530623 except xmlrpc.client.ProtocolError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700624 print('error: cannot connect to manifest server %s:\n%d %s'
625 % (self.manifest.manifest_server, e.errcode, e.errmsg),
626 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700627 sys.exit(1)
David Pursehouse59b41742015-05-07 14:36:09 +0900628 else: # Not smart sync or smart tag mode
629 if os.path.isfile(smart_sync_manifest_path):
630 try:
631 os.remove(smart_sync_manifest_path)
632 except OSError as e:
633 print('error: failed to remove existing smart sync override manifest: %s' %
634 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700635
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700636 rp = self.manifest.repoProject
637 rp.PreSync()
638
639 mp = self.manifest.manifestProject
640 mp.PreSync()
641
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800642 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700643 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800644
Nico Sallembien9bb18162009-12-07 15:38:01 -0800645 if not opt.local_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700646 mp.Sync_NetworkHalf(quiet=opt.quiet,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700647 current_branch_only=opt.current_branch_only,
David Pursehouseb1553542014-09-04 21:28:09 +0900648 no_tags=opt.no_tags,
649 optimized_fetch=opt.optimized_fetch)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800650
651 if mp.HasChanges:
652 syncbuf = SyncBuffer(mp.config)
653 mp.Sync_LocalHalf(syncbuf)
654 if not syncbuf.Finish():
655 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100656 self._ReloadManifest(manifest_name)
Shawn O. Pearcec4657962011-09-26 09:08:01 -0700657 if opt.jobs is None:
658 self.jobs = self.manifest.default.sync_j
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800659 all_projects = self.GetProjects(args,
660 missing_ok=True,
661 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662
Dave Borowitz67700e92012-10-23 15:00:54 -0700663 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700664 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700665 to_fetch = []
666 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700667 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700668 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900669 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700670 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700671
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800672 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700673 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700674 if opt.network_only:
675 # bail out now; the rest touches the working tree
676 return
677
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800678 # Iteratively fetch missing and/or nested unregistered submodules
679 previously_missing_set = set()
680 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100681 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800682 all_projects = self.GetProjects(args,
683 missing_ok=True,
684 submodules_ok=opt.fetch_submodules)
685 missing = []
686 for project in all_projects:
687 if project.gitdir not in fetched:
688 missing.append(project)
689 if not missing:
690 break
691 # Stop us from non-stopped fetching actually-missing repos: If set of
692 # missing repos has not been changed from last fetch, we break.
693 missing_set = set(p.name for p in missing)
694 if previously_missing_set == missing_set:
695 break
696 previously_missing_set = missing_set
697 fetched.update(self._Fetch(missing, opt))
698
Julien Campergue335f5ef2013-10-16 11:02:35 +0200699 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700700 # bail out now, we have no working tree
701 return
702
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700703 if self.UpdateProjectList():
704 sys.exit(1)
705
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700706 syncbuf = SyncBuffer(mp.config,
707 detach_head = opt.detach_head)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900708 pm = Progress('Syncing work tree', len(all_projects))
709 for project in all_projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700710 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800711 if project.worktree:
Kevin Degiabaa7f32014-11-12 11:27:45 -0700712 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700713 pm.end()
Sarah Owenscecd1d82012-11-01 22:59:27 -0700714 print(file=sys.stderr)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700715 if not syncbuf.Finish():
716 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700718 # If there's a notice that's supposed to print at the end of the sync, print
719 # it now...
720 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700721 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700722
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700723def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -0800724 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -0700725 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700726 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800727 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700728 if project.Exists:
729 project.PostRepoUpgrade()
730
731def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
732 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700733 print('info: A new version of repo is available', file=sys.stderr)
734 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700735 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700736 syncbuf = SyncBuffer(rp.config)
737 rp.Sync_LocalHalf(syncbuf)
738 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700739 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700740 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700741 raise RepoChangedException(['--repo-upgraded'])
742 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700743 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700744 else:
745 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700746 print('repo version %s is current' % rp.work_git.describe(HEAD),
747 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700748
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749def _VerifyTag(project):
750 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
751 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700752 print('warning: GnuPG was not available during last "repo init"\n'
753 'warning: Cannot automatically authenticate repo."""',
754 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 return True
756
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700758 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 except GitError:
760 cur = None
761
762 if not cur \
763 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700764 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 if rev.startswith(R_HEADS):
766 rev = rev[len(R_HEADS):]
767
Sarah Owenscecd1d82012-11-01 22:59:27 -0700768 print(file=sys.stderr)
769 print("warning: project '%s' branch '%s' is not signed"
770 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771 return False
772
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800773 env = os.environ.copy()
774 env['GIT_DIR'] = project.gitdir.encode()
775 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700776
777 cmd = [GIT, 'tag', '-v', cur]
778 proc = subprocess.Popen(cmd,
779 stdout = subprocess.PIPE,
780 stderr = subprocess.PIPE,
781 env = env)
782 out = proc.stdout.read()
783 proc.stdout.close()
784
785 err = proc.stderr.read()
786 proc.stderr.close()
787
788 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700789 print(file=sys.stderr)
790 print(out, file=sys.stderr)
791 print(err, file=sys.stderr)
792 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793 return False
794 return True
Dave Borowitz67700e92012-10-23 15:00:54 -0700795
796class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -0700797 _ALPHA = 0.5
798
Dave Borowitz67700e92012-10-23 15:00:54 -0700799 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +0100800 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -0700801 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -0700802 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -0700803
804 def Get(self, project):
805 self._Load()
806 return self._times.get(project.name, _ONE_DAY_S)
807
808 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -0700809 self._Load()
810 name = project.name
811 old = self._times.get(name, t)
812 self._seen.add(name)
813 a = self._ALPHA
814 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -0700815
816 def _Load(self):
817 if self._times is None:
818 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100819 f = open(self._path)
Dave Borowitz67700e92012-10-23 15:00:54 -0700820 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100821 self._times = json.load(f)
822 finally:
823 f.close()
824 except (IOError, ValueError):
825 try:
826 os.remove(self._path)
827 except OSError:
828 pass
829 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -0700830
831 def Save(self):
832 if self._times is None:
833 return
Dave Borowitzd9478582012-10-23 16:35:39 -0700834
835 to_delete = []
836 for name in self._times:
837 if name not in self._seen:
838 to_delete.append(name)
839 for name in to_delete:
840 del self._times[name]
841
Dave Borowitz67700e92012-10-23 15:00:54 -0700842 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100843 f = open(self._path, 'w')
Dave Borowitz67700e92012-10-23 15:00:54 -0700844 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100845 json.dump(self._times, f, indent=2)
846 finally:
847 f.close()
848 except (IOError, TypeError):
849 try:
850 os.remove(self._path)
851 except OSError:
852 pass