blob: a8074a4082c12a4d7ea292993f01d4264a5422f9 [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()
317 except:
318 err_event.set()
319 raise
320 finally:
321 if did_lock:
322 lock.release()
323 sem.release()
Roy Lee18afd7f2010-05-09 04:32:08 +0800324
David James8d201162013-10-11 17:03:19 -0700325 return success
326
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700327 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328 fetched = set()
David James89ece422014-01-09 18:51:58 -0800329 lock = _threading.Lock()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700330 pm = Progress('Fetching projects', len(projects))
Roy Lee18afd7f2010-05-09 04:32:08 +0800331
David James89ece422014-01-09 18:51:58 -0800332 objdir_project_map = dict()
333 for project in projects:
334 objdir_project_map.setdefault(project.objdir, []).append(project)
David James8d201162013-10-11 17:03:19 -0700335
David James89ece422014-01-09 18:51:58 -0800336 threads = set()
337 sem = _threading.Semaphore(self.jobs)
338 err_event = _threading.Event()
339 for project_list in objdir_project_map.values():
340 # Check for any errors before running any more tasks.
341 # ...we'll let existing threads finish, though.
342 if err_event.isSet() and not opt.force_broken:
343 break
Doug Andersonfc06ced2011-03-16 15:49:18 -0700344
David James89ece422014-01-09 18:51:58 -0800345 sem.acquire()
346 kwargs = dict(opt=opt,
347 projects=project_list,
348 lock=lock,
349 fetched=fetched,
350 pm=pm,
351 sem=sem,
352 err_event=err_event)
353 if self.jobs > 1:
David James8d201162013-10-11 17:03:19 -0700354 t = _threading.Thread(target = self._FetchProjectList,
David James89ece422014-01-09 18:51:58 -0800355 kwargs = kwargs)
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200356 # Ensure that Ctrl-C will not freeze the repo process.
357 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800358 threads.add(t)
359 t.start()
David James89ece422014-01-09 18:51:58 -0800360 else:
361 self._FetchProjectList(**kwargs)
Roy Lee18afd7f2010-05-09 04:32:08 +0800362
David James89ece422014-01-09 18:51:58 -0800363 for t in threads:
364 t.join()
Roy Lee18afd7f2010-05-09 04:32:08 +0800365
David James89ece422014-01-09 18:51:58 -0800366 # If we saw an error, exit with code 1 so that other scripts can check.
367 if err_event.isSet():
368 print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
369 sys.exit(1)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700370
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700371 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700372 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700373
Julien Campergue335f5ef2013-10-16 11:02:35 +0200374 if not self.manifest.IsArchive:
375 self._GCProjects(projects)
376
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700377 return fetched
378
Dave Borowitz18857212012-10-23 17:02:59 -0700379 def _GCProjects(self, projects):
David James8d201162013-10-11 17:03:19 -0700380 gitdirs = {}
381 for project in projects:
382 gitdirs[project.gitdir] = project.bare_git
383
Dave Borowitze2152672012-10-31 12:24:38 -0700384 has_dash_c = git_require((1, 7, 2))
385 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700386 cpu_count = multiprocessing.cpu_count()
387 else:
388 cpu_count = 1
389 jobs = min(self.jobs, cpu_count)
390
391 if jobs < 2:
David James8d201162013-10-11 17:03:19 -0700392 for bare_git in gitdirs.values():
393 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700394 return
395
396 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
397
398 threads = set()
399 sem = _threading.Semaphore(jobs)
400 err_event = _threading.Event()
401
David James8d201162013-10-11 17:03:19 -0700402 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700403 try:
404 try:
David James8d201162013-10-11 17:03:19 -0700405 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700406 except GitError:
407 err_event.set()
408 except:
409 err_event.set()
410 raise
411 finally:
412 sem.release()
413
David James8d201162013-10-11 17:03:19 -0700414 for bare_git in gitdirs.values():
Dave Borowitz18857212012-10-23 17:02:59 -0700415 if err_event.isSet():
416 break
417 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700418 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700419 t.daemon = True
420 threads.add(t)
421 t.start()
422
423 for t in threads:
424 t.join()
425
426 if err_event.isSet():
Sarah Owenscecd1d82012-11-01 22:59:27 -0700427 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
Dave Borowitz18857212012-10-23 17:02:59 -0700428 sys.exit(1)
429
Tim Kilbourn07669002013-03-08 15:02:49 -0800430 def _ReloadManifest(self, manifest_name=None):
431 if manifest_name:
432 # Override calls _Unload already
433 self.manifest.Override(manifest_name)
434 else:
435 self.manifest._Unload()
436
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700437 def UpdateProjectList(self):
438 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700439 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700440 if project.relpath:
441 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700442 file_name = 'project.list'
443 file_path = os.path.join(self.manifest.repodir, file_name)
444 old_project_paths = []
445
446 if os.path.exists(file_path):
447 fd = open(file_path, 'r')
448 try:
449 old_project_paths = fd.read().split('\n')
450 finally:
451 fd.close()
452 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700453 if not path:
454 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700455 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900456 # If the path has already been deleted, we don't need to do it
Anthonyf3fdf822009-09-26 13:38:52 -0400457 if os.path.exists(self.manifest.topdir + '/' + path):
David James8d201162013-10-11 17:03:19 -0700458 gitdir = os.path.join(self.manifest.topdir, path, '.git')
David Pursehousec1b86a22012-11-14 11:36:51 +0900459 project = Project(
460 manifest = self.manifest,
461 name = path,
462 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700463 gitdir = gitdir,
464 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900465 worktree = os.path.join(self.manifest.topdir, path),
466 relpath = path,
467 revisionExpr = 'HEAD',
468 revisionId = None,
469 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400470
David Pursehousec1b86a22012-11-14 11:36:51 +0900471 if project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900472 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900473 'are present' % project.relpath, file=sys.stderr)
474 print(' commit changes, then run sync again',
475 file=sys.stderr)
476 return -1
477 else:
478 print('Deleting obsolete path %s' % project.worktree,
479 file=sys.stderr)
480 shutil.rmtree(project.worktree)
481 # Try deleting parent subdirs if they are empty
482 project_dir = os.path.dirname(project.worktree)
483 while project_dir != self.manifest.topdir:
484 try:
485 os.rmdir(project_dir)
486 except OSError:
487 break
488 project_dir = os.path.dirname(project_dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700489
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700490 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700491 fd = open(file_path, 'w')
492 try:
493 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700494 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700495 finally:
496 fd.close()
497 return 0
498
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700499 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800500 if opt.jobs:
501 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700502 if self.jobs > 1:
503 soft_limit, _ = _rlimit_nofile()
504 self.jobs = min(self.jobs, (soft_limit - 5) / 3)
505
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700506 if opt.network_only and opt.detach_head:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700507 print('error: cannot combine -n and -d', file=sys.stderr)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700508 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700509 if opt.network_only and opt.local_only:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700510 print('error: cannot combine -n and -l', file=sys.stderr)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700511 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500512 if opt.manifest_name and opt.smart_sync:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700513 print('error: cannot combine -m and -s', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500514 sys.exit(1)
515 if opt.manifest_name and opt.smart_tag:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700516 print('error: cannot combine -m and -t', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500517 sys.exit(1)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900518 if opt.manifest_server_username or opt.manifest_server_password:
519 if not (opt.smart_sync or opt.smart_tag):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700520 print('error: -u and -p may only be combined with -s or -t',
521 file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900522 sys.exit(1)
523 if None in [opt.manifest_server_username, opt.manifest_server_password]:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700524 print('error: both -u and -p must be given', file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900525 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500526
527 if opt.manifest_name:
528 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700529
Chirayu Desaia892b102013-06-11 14:18:46 +0530530 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900531 smart_sync_manifest_name = "smart_sync_override.xml"
532 smart_sync_manifest_path = os.path.join(
533 self.manifest.manifestProject.worktree, smart_sync_manifest_name)
Chirayu Desaia892b102013-06-11 14:18:46 +0530534
Victor Boivie08c880d2011-04-19 10:32:52 +0200535 if opt.smart_sync or opt.smart_tag:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700536 if not self.manifest.manifest_server:
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900537 print('error: cannot smart sync: no manifest server defined in '
Sarah Owenscecd1d82012-11-01 22:59:27 -0700538 'manifest', file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700539 sys.exit(1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900540
541 manifest_server = self.manifest.manifest_server
David Pursehousefb99c712013-09-25 11:09:34 +0900542 if not opt.quiet:
543 print('Using manifest server %s' % manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900544
David Pursehouse86d973d2012-08-24 10:21:02 +0900545 if not '@' in manifest_server:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900546 username = None
547 password = None
548 if opt.manifest_server_username and opt.manifest_server_password:
549 username = opt.manifest_server_username
550 password = opt.manifest_server_password
David Pursehouse86d973d2012-08-24 10:21:02 +0900551 else:
552 try:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900553 info = netrc.netrc()
554 except IOError:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700555 print('.netrc file does not exist or could not be opened',
556 file=sys.stderr)
David Pursehouse86d973d2012-08-24 10:21:02 +0900557 else:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900558 try:
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +0530559 parse_result = urllib.parse.urlparse(manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900560 if parse_result.hostname:
561 username, _account, password = \
562 info.authenticators(parse_result.hostname)
563 except TypeError:
564 # TypeError is raised when the given hostname is not present
565 # in the .netrc file.
Sarah Owenscecd1d82012-11-01 22:59:27 -0700566 print('No credentials found for %s in .netrc'
567 % parse_result.hostname, file=sys.stderr)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700568 except netrc.NetrcParseError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700569 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900570
571 if (username and password):
572 manifest_server = manifest_server.replace('://', '://%s:%s@' %
573 (username, password),
574 1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900575
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700576 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530577 server = xmlrpc.client.Server(manifest_server)
Victor Boivie08c880d2011-04-19 10:32:52 +0200578 if opt.smart_sync:
579 p = self.manifest.manifestProject
580 b = p.GetBranch(p.CurrentBranch)
581 branch = b.merge
582 if branch.startswith(R_HEADS):
583 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700584
Victor Boivie08c880d2011-04-19 10:32:52 +0200585 env = os.environ.copy()
Jeff Davidson5cf16602014-10-02 10:13:38 -0700586 if 'SYNC_TARGET' in env:
587 target = env['SYNC_TARGET']
588 [success, manifest_str] = server.GetApprovedManifest(branch, target)
589 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
Victor Boivie08c880d2011-04-19 10:32:52 +0200590 target = '%s-%s' % (env['TARGET_PRODUCT'],
591 env['TARGET_BUILD_VARIANT'])
592 [success, manifest_str] = server.GetApprovedManifest(branch, target)
593 else:
594 [success, manifest_str] = server.GetApprovedManifest(branch)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700595 else:
Victor Boivie08c880d2011-04-19 10:32:52 +0200596 assert(opt.smart_tag)
597 [success, manifest_str] = server.GetManifest(opt.smart_tag)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700598
599 if success:
David Pursehouse59b41742015-05-07 14:36:09 +0900600 manifest_name = smart_sync_manifest_name
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700601 try:
David Pursehouse59b41742015-05-07 14:36:09 +0900602 f = open(smart_sync_manifest_path, 'w')
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700603 try:
604 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700605 finally:
606 f.close()
David Pursehouse727cc3e2015-05-07 14:16:49 +0900607 except IOError as e:
608 print('error: cannot write manifest to %s:\n%s'
David Pursehouse59b41742015-05-07 14:36:09 +0900609 % (smart_sync_manifest_path, e),
Sarah Owenscecd1d82012-11-01 22:59:27 -0700610 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700611 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100612 self._ReloadManifest(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700613 else:
David Pursehouse351fe2c2013-09-25 17:54:26 +0900614 print('error: manifest server RPC call failed: %s' %
615 manifest_str, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700616 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530617 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700618 print('error: cannot connect to manifest server %s:\n%s'
619 % (self.manifest.manifest_server, e), file=sys.stderr)
David Pursehousebd489c42012-08-23 10:21:26 +0900620 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530621 except xmlrpc.client.ProtocolError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700622 print('error: cannot connect to manifest server %s:\n%d %s'
623 % (self.manifest.manifest_server, e.errcode, e.errmsg),
624 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700625 sys.exit(1)
David Pursehouse59b41742015-05-07 14:36:09 +0900626 else: # Not smart sync or smart tag mode
627 if os.path.isfile(smart_sync_manifest_path):
628 try:
629 os.remove(smart_sync_manifest_path)
630 except OSError as e:
631 print('error: failed to remove existing smart sync override manifest: %s' %
632 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700633
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634 rp = self.manifest.repoProject
635 rp.PreSync()
636
637 mp = self.manifest.manifestProject
638 mp.PreSync()
639
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800640 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700641 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800642
Nico Sallembien9bb18162009-12-07 15:38:01 -0800643 if not opt.local_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700644 mp.Sync_NetworkHalf(quiet=opt.quiet,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700645 current_branch_only=opt.current_branch_only,
David Pursehouseb1553542014-09-04 21:28:09 +0900646 no_tags=opt.no_tags,
647 optimized_fetch=opt.optimized_fetch)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800648
649 if mp.HasChanges:
650 syncbuf = SyncBuffer(mp.config)
651 mp.Sync_LocalHalf(syncbuf)
652 if not syncbuf.Finish():
653 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100654 self._ReloadManifest(manifest_name)
Shawn O. Pearcec4657962011-09-26 09:08:01 -0700655 if opt.jobs is None:
656 self.jobs = self.manifest.default.sync_j
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800657 all_projects = self.GetProjects(args,
658 missing_ok=True,
659 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700660
Dave Borowitz67700e92012-10-23 15:00:54 -0700661 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700662 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700663 to_fetch = []
664 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700665 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700666 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700668 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700669
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800670 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700671 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700672 if opt.network_only:
673 # bail out now; the rest touches the working tree
674 return
675
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800676 # Iteratively fetch missing and/or nested unregistered submodules
677 previously_missing_set = set()
678 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100679 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800680 all_projects = self.GetProjects(args,
681 missing_ok=True,
682 submodules_ok=opt.fetch_submodules)
683 missing = []
684 for project in all_projects:
685 if project.gitdir not in fetched:
686 missing.append(project)
687 if not missing:
688 break
689 # Stop us from non-stopped fetching actually-missing repos: If set of
690 # missing repos has not been changed from last fetch, we break.
691 missing_set = set(p.name for p in missing)
692 if previously_missing_set == missing_set:
693 break
694 previously_missing_set = missing_set
695 fetched.update(self._Fetch(missing, opt))
696
Julien Campergue335f5ef2013-10-16 11:02:35 +0200697 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700698 # bail out now, we have no working tree
699 return
700
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700701 if self.UpdateProjectList():
702 sys.exit(1)
703
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700704 syncbuf = SyncBuffer(mp.config,
705 detach_head = opt.detach_head)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900706 pm = Progress('Syncing work tree', len(all_projects))
707 for project in all_projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700708 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800709 if project.worktree:
Kevin Degiabaa7f32014-11-12 11:27:45 -0700710 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700711 pm.end()
Sarah Owenscecd1d82012-11-01 22:59:27 -0700712 print(file=sys.stderr)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700713 if not syncbuf.Finish():
714 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700715
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700716 # If there's a notice that's supposed to print at the end of the sync, print
717 # it now...
718 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700719 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700720
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700721def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -0800722 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -0700723 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700724 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800725 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700726 if project.Exists:
727 project.PostRepoUpgrade()
728
729def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
730 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700731 print('info: A new version of repo is available', file=sys.stderr)
732 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700733 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700734 syncbuf = SyncBuffer(rp.config)
735 rp.Sync_LocalHalf(syncbuf)
736 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700737 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700738 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700739 raise RepoChangedException(['--repo-upgraded'])
740 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700741 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700742 else:
743 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700744 print('repo version %s is current' % rp.work_git.describe(HEAD),
745 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700746
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747def _VerifyTag(project):
748 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
749 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700750 print('warning: GnuPG was not available during last "repo init"\n'
751 'warning: Cannot automatically authenticate repo."""',
752 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700753 return True
754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700756 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 except GitError:
758 cur = None
759
760 if not cur \
761 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700762 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 if rev.startswith(R_HEADS):
764 rev = rev[len(R_HEADS):]
765
Sarah Owenscecd1d82012-11-01 22:59:27 -0700766 print(file=sys.stderr)
767 print("warning: project '%s' branch '%s' is not signed"
768 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 return False
770
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800771 env = os.environ.copy()
772 env['GIT_DIR'] = project.gitdir.encode()
773 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774
775 cmd = [GIT, 'tag', '-v', cur]
776 proc = subprocess.Popen(cmd,
777 stdout = subprocess.PIPE,
778 stderr = subprocess.PIPE,
779 env = env)
780 out = proc.stdout.read()
781 proc.stdout.close()
782
783 err = proc.stderr.read()
784 proc.stderr.close()
785
786 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700787 print(file=sys.stderr)
788 print(out, file=sys.stderr)
789 print(err, file=sys.stderr)
790 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 return False
792 return True
Dave Borowitz67700e92012-10-23 15:00:54 -0700793
794class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -0700795 _ALPHA = 0.5
796
Dave Borowitz67700e92012-10-23 15:00:54 -0700797 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +0100798 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -0700799 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -0700800 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -0700801
802 def Get(self, project):
803 self._Load()
804 return self._times.get(project.name, _ONE_DAY_S)
805
806 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -0700807 self._Load()
808 name = project.name
809 old = self._times.get(name, t)
810 self._seen.add(name)
811 a = self._ALPHA
812 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -0700813
814 def _Load(self):
815 if self._times is None:
816 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100817 f = open(self._path)
Dave Borowitz67700e92012-10-23 15:00:54 -0700818 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100819 self._times = json.load(f)
820 finally:
821 f.close()
822 except (IOError, ValueError):
823 try:
824 os.remove(self._path)
825 except OSError:
826 pass
827 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -0700828
829 def Save(self):
830 if self._times is None:
831 return
Dave Borowitzd9478582012-10-23 16:35:39 -0700832
833 to_delete = []
834 for name in self._times:
835 if name not in self._seen:
836 to_delete.append(name)
837 for name in to_delete:
838 del self._times[name]
839
Dave Borowitz67700e92012-10-23 15:00:54 -0700840 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100841 f = open(self._path, 'w')
Dave Borowitz67700e92012-10-23 15:00:54 -0700842 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100843 json.dump(self._times, f, indent=2)
844 finally:
845 f.close()
846 except (IOError, TypeError):
847 try:
848 os.remove(self._path)
849 except OSError:
850 pass