blob: ec333ae7096b850fe028f5ac04f47b0a97326af7 [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
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700122The --no-clone-bundle option disables any attempt to use
123$URL/clone.bundle to bootstrap a new Git repository from a
124resumeable bundle file on a content delivery network. This
125may be necessary if there are problems with the local Python
126HTTP client or proxy configuration, but the Git binary works.
127
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800128The --fetch-submodules option enables fetching Git submodules
129of a project from server.
130
David Pursehousef2fad612015-01-29 14:36:28 +0900131The -c/--current-branch option can be used to only fetch objects that
132are on the branch specified by a project's revision.
133
David Pursehouseb1553542014-09-04 21:28:09 +0900134The --optimized-fetch option can be used to only fetch projects that
135are fixed to a sha1 revision if the sha1 revision does not already
136exist locally.
137
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700138SSH Connections
139---------------
140
141If at least one project remote URL uses an SSH connection (ssh://,
142git+ssh://, or user@host:path syntax) repo will automatically
143enable the SSH ControlMaster option when connecting to that host.
144This feature permits other projects in the same '%prog' session to
145reuse the same SSH tunnel, saving connection setup overheads.
146
147To disable this behavior on UNIX platforms, set the GIT_SSH
148environment variable to 'ssh'. For example:
149
150 export GIT_SSH=ssh
151 %prog
152
153Compatibility
154~~~~~~~~~~~~~
155
156This feature is automatically disabled on Windows, due to the lack
157of UNIX domain socket support.
158
159This feature is not compatible with url.insteadof rewrites in the
160user's ~/.gitconfig. '%prog' is currently not able to perform the
161rewrite early enough to establish the ControlMaster tunnel.
162
163If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
164later is required to fix a server side protocol bug.
165
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166"""
167
Nico Sallembien6623b212010-05-11 12:57:01 -0700168 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000169 try:
170 self.jobs = self.manifest.default.sync_j
171 except ManifestParseError:
172 self.jobs = 1
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700173
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500174 p.add_option('-f', '--force-broken',
175 dest='force_broken', action='store_true',
176 help="continue sync even if a project fails to sync")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900177 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700178 dest='local_only', action='store_true',
179 help="only update working tree, don't fetch")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900180 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700181 dest='network_only', action='store_true',
182 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900183 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700184 dest='detach_head', action='store_true',
185 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900186 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700187 dest='current_branch_only', action='store_true',
188 help='fetch only current branch from server')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900189 p.add_option('-q', '--quiet',
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700190 dest='quiet', action='store_true',
191 help='be more quiet')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900192 p.add_option('-j', '--jobs',
Roy Lee18afd7f2010-05-09 04:32:08 +0800193 dest='jobs', action='store', type='int',
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700194 help="projects to fetch simultaneously (default %d)" % self.jobs)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500195 p.add_option('-m', '--manifest-name',
196 dest='manifest_name',
197 help='temporary manifest to use for this sync', metavar='NAME.xml')
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700198 p.add_option('--no-clone-bundle',
199 dest='no_clone_bundle', action='store_true',
200 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800201 p.add_option('-u', '--manifest-server-username', action='store',
202 dest='manifest_server_username',
203 help='username to authenticate with the manifest server')
204 p.add_option('-p', '--manifest-server-password', action='store',
205 dest='manifest_server_password',
206 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800207 p.add_option('--fetch-submodules',
208 dest='fetch_submodules', action='store_true',
209 help='fetch submodules from server')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700210 p.add_option('--no-tags',
211 dest='no_tags', action='store_true',
212 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900213 p.add_option('--optimized-fetch',
214 dest='optimized_fetch', action='store_true',
215 help='only fetch projects fixed to sha1 if revision does not exist locally')
Nico Sallembien6623b212010-05-11 12:57:01 -0700216 if show_smart:
217 p.add_option('-s', '--smart-sync',
218 dest='smart_sync', action='store_true',
219 help='smart sync using manifest from a known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200220 p.add_option('-t', '--smart-tag',
221 dest='smart_tag', action='store',
222 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700223
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700224 g = p.add_option_group('repo Version options')
225 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 dest='no_repo_verify', action='store_true',
227 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700228 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800229 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700230 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231
David James89ece422014-01-09 18:51:58 -0800232 def _FetchProjectList(self, opt, projects, *args, **kwargs):
David Pursehousec1b86a22012-11-14 11:36:51 +0900233 """Main function of the fetch threads when jobs are > 1.
Roy Lee18afd7f2010-05-09 04:32:08 +0800234
David James8d201162013-10-11 17:03:19 -0700235 Delegates most of the work to _FetchHelper.
236
237 Args:
238 opt: Program options returned from optparse. See _Options().
239 projects: Projects to fetch.
David James89ece422014-01-09 18:51:58 -0800240 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
David James8d201162013-10-11 17:03:19 -0700241 _FetchHelper docstring for details.
242 """
243 for project in projects:
David James89ece422014-01-09 18:51:58 -0800244 success = self._FetchHelper(opt, project, *args, **kwargs)
David James8d201162013-10-11 17:03:19 -0700245 if not success and not opt.force_broken:
246 break
247
248 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
249 """Fetch git objects for a single project.
250
David Pursehousec1b86a22012-11-14 11:36:51 +0900251 Args:
252 opt: Program options returned from optparse. See _Options().
253 project: Project object for the project to fetch.
254 lock: Lock for accessing objects that are shared amongst multiple
255 _FetchHelper() threads.
256 fetched: set object that we will add project.gitdir to when we're done
257 (with our lock held).
258 pm: Instance of a Project object. We will call pm.update() (with our
259 lock held).
260 sem: We'll release() this semaphore when we exit so that another thread
261 can be started up.
262 err_event: We'll set this event in the case of an error (after printing
263 out info about the error).
David James8d201162013-10-11 17:03:19 -0700264
265 Returns:
266 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900267 """
268 # We'll set to true once we've locked the lock.
269 did_lock = False
Doug Andersonfc06ced2011-03-16 15:49:18 -0700270
Chirayu Desaifef4ae72013-04-12 14:54:32 +0530271 if not opt.quiet:
272 print('Fetching project %s' % project.name)
273
David Pursehousec1b86a22012-11-14 11:36:51 +0900274 # Encapsulate everything in a try/except/finally so that:
275 # - We always set err_event in the case of an exception.
276 # - We always make sure we call sem.release().
277 # - We always make sure we unlock the lock if we locked it.
278 try:
Doug Andersonfc06ced2011-03-16 15:49:18 -0700279 try:
David Pursehousec1b86a22012-11-14 11:36:51 +0900280 start = time.time()
281 success = project.Sync_NetworkHalf(
282 quiet=opt.quiet,
283 current_branch_only=opt.current_branch_only,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700284 clone_bundle=not opt.no_clone_bundle,
David Pursehouseb1553542014-09-04 21:28:09 +0900285 no_tags=opt.no_tags, archive=self.manifest.IsArchive,
286 optimized_fetch=opt.optimized_fetch)
David Pursehousec1b86a22012-11-14 11:36:51 +0900287 self._fetch_times.Set(project, time.time() - start)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700288
David Pursehousec1b86a22012-11-14 11:36:51 +0900289 # Lock around all the rest of the code, since printing, updating a set
290 # and Progress.update() are not thread safe.
291 lock.acquire()
292 did_lock = True
Doug Andersonfc06ced2011-03-16 15:49:18 -0700293
David Pursehousec1b86a22012-11-14 11:36:51 +0900294 if not success:
295 print('error: Cannot fetch %s' % project.name, file=sys.stderr)
296 if opt.force_broken:
297 print('warn: --force-broken, continuing to sync',
298 file=sys.stderr)
299 else:
300 raise _FetchError()
Doug Andersonfc06ced2011-03-16 15:49:18 -0700301
David Pursehousec1b86a22012-11-14 11:36:51 +0900302 fetched.add(project.gitdir)
303 pm.update()
304 except _FetchError:
305 err_event.set()
306 except:
307 err_event.set()
308 raise
309 finally:
310 if did_lock:
311 lock.release()
312 sem.release()
Roy Lee18afd7f2010-05-09 04:32:08 +0800313
David James8d201162013-10-11 17:03:19 -0700314 return success
315
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700316 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317 fetched = set()
David James89ece422014-01-09 18:51:58 -0800318 lock = _threading.Lock()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700319 pm = Progress('Fetching projects', len(projects))
Roy Lee18afd7f2010-05-09 04:32:08 +0800320
David James89ece422014-01-09 18:51:58 -0800321 objdir_project_map = dict()
322 for project in projects:
323 objdir_project_map.setdefault(project.objdir, []).append(project)
David James8d201162013-10-11 17:03:19 -0700324
David James89ece422014-01-09 18:51:58 -0800325 threads = set()
326 sem = _threading.Semaphore(self.jobs)
327 err_event = _threading.Event()
328 for project_list in objdir_project_map.values():
329 # Check for any errors before running any more tasks.
330 # ...we'll let existing threads finish, though.
331 if err_event.isSet() and not opt.force_broken:
332 break
Doug Andersonfc06ced2011-03-16 15:49:18 -0700333
David James89ece422014-01-09 18:51:58 -0800334 sem.acquire()
335 kwargs = dict(opt=opt,
336 projects=project_list,
337 lock=lock,
338 fetched=fetched,
339 pm=pm,
340 sem=sem,
341 err_event=err_event)
342 if self.jobs > 1:
David James8d201162013-10-11 17:03:19 -0700343 t = _threading.Thread(target = self._FetchProjectList,
David James89ece422014-01-09 18:51:58 -0800344 kwargs = kwargs)
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200345 # Ensure that Ctrl-C will not freeze the repo process.
346 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800347 threads.add(t)
348 t.start()
David James89ece422014-01-09 18:51:58 -0800349 else:
350 self._FetchProjectList(**kwargs)
Roy Lee18afd7f2010-05-09 04:32:08 +0800351
David James89ece422014-01-09 18:51:58 -0800352 for t in threads:
353 t.join()
Roy Lee18afd7f2010-05-09 04:32:08 +0800354
David James89ece422014-01-09 18:51:58 -0800355 # If we saw an error, exit with code 1 so that other scripts can check.
356 if err_event.isSet():
357 print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
358 sys.exit(1)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700359
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700360 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700361 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700362
Julien Campergue335f5ef2013-10-16 11:02:35 +0200363 if not self.manifest.IsArchive:
364 self._GCProjects(projects)
365
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700366 return fetched
367
Dave Borowitz18857212012-10-23 17:02:59 -0700368 def _GCProjects(self, projects):
David James8d201162013-10-11 17:03:19 -0700369 gitdirs = {}
370 for project in projects:
371 gitdirs[project.gitdir] = project.bare_git
372
Dave Borowitze2152672012-10-31 12:24:38 -0700373 has_dash_c = git_require((1, 7, 2))
374 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700375 cpu_count = multiprocessing.cpu_count()
376 else:
377 cpu_count = 1
378 jobs = min(self.jobs, cpu_count)
379
380 if jobs < 2:
David James8d201162013-10-11 17:03:19 -0700381 for bare_git in gitdirs.values():
382 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700383 return
384
385 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
386
387 threads = set()
388 sem = _threading.Semaphore(jobs)
389 err_event = _threading.Event()
390
David James8d201162013-10-11 17:03:19 -0700391 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700392 try:
393 try:
David James8d201162013-10-11 17:03:19 -0700394 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700395 except GitError:
396 err_event.set()
397 except:
398 err_event.set()
399 raise
400 finally:
401 sem.release()
402
David James8d201162013-10-11 17:03:19 -0700403 for bare_git in gitdirs.values():
Dave Borowitz18857212012-10-23 17:02:59 -0700404 if err_event.isSet():
405 break
406 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700407 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700408 t.daemon = True
409 threads.add(t)
410 t.start()
411
412 for t in threads:
413 t.join()
414
415 if err_event.isSet():
Sarah Owenscecd1d82012-11-01 22:59:27 -0700416 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
Dave Borowitz18857212012-10-23 17:02:59 -0700417 sys.exit(1)
418
Tim Kilbourn07669002013-03-08 15:02:49 -0800419 def _ReloadManifest(self, manifest_name=None):
420 if manifest_name:
421 # Override calls _Unload already
422 self.manifest.Override(manifest_name)
423 else:
424 self.manifest._Unload()
425
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700426 def UpdateProjectList(self):
427 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700428 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700429 if project.relpath:
430 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700431 file_name = 'project.list'
432 file_path = os.path.join(self.manifest.repodir, file_name)
433 old_project_paths = []
434
435 if os.path.exists(file_path):
436 fd = open(file_path, 'r')
437 try:
438 old_project_paths = fd.read().split('\n')
439 finally:
440 fd.close()
441 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700442 if not path:
443 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700444 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900445 # If the path has already been deleted, we don't need to do it
Anthonyf3fdf822009-09-26 13:38:52 -0400446 if os.path.exists(self.manifest.topdir + '/' + path):
David James8d201162013-10-11 17:03:19 -0700447 gitdir = os.path.join(self.manifest.topdir, path, '.git')
David Pursehousec1b86a22012-11-14 11:36:51 +0900448 project = Project(
449 manifest = self.manifest,
450 name = path,
451 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700452 gitdir = gitdir,
453 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900454 worktree = os.path.join(self.manifest.topdir, path),
455 relpath = path,
456 revisionExpr = 'HEAD',
457 revisionId = None,
458 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400459
David Pursehousec1b86a22012-11-14 11:36:51 +0900460 if project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900461 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900462 'are present' % project.relpath, file=sys.stderr)
463 print(' commit changes, then run sync again',
464 file=sys.stderr)
465 return -1
466 else:
467 print('Deleting obsolete path %s' % project.worktree,
468 file=sys.stderr)
469 shutil.rmtree(project.worktree)
470 # Try deleting parent subdirs if they are empty
471 project_dir = os.path.dirname(project.worktree)
472 while project_dir != self.manifest.topdir:
473 try:
474 os.rmdir(project_dir)
475 except OSError:
476 break
477 project_dir = os.path.dirname(project_dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700478
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700479 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700480 fd = open(file_path, 'w')
481 try:
482 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700483 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700484 finally:
485 fd.close()
486 return 0
487
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700488 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800489 if opt.jobs:
490 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700491 if self.jobs > 1:
492 soft_limit, _ = _rlimit_nofile()
493 self.jobs = min(self.jobs, (soft_limit - 5) / 3)
494
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700495 if opt.network_only and opt.detach_head:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700496 print('error: cannot combine -n and -d', file=sys.stderr)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700497 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700498 if opt.network_only and opt.local_only:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700499 print('error: cannot combine -n and -l', file=sys.stderr)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700500 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500501 if opt.manifest_name and opt.smart_sync:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700502 print('error: cannot combine -m and -s', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500503 sys.exit(1)
504 if opt.manifest_name and opt.smart_tag:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700505 print('error: cannot combine -m and -t', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500506 sys.exit(1)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900507 if opt.manifest_server_username or opt.manifest_server_password:
508 if not (opt.smart_sync or opt.smart_tag):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700509 print('error: -u and -p may only be combined with -s or -t',
510 file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900511 sys.exit(1)
512 if None in [opt.manifest_server_username, opt.manifest_server_password]:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700513 print('error: both -u and -p must be given', file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900514 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500515
516 if opt.manifest_name:
517 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700518
Chirayu Desaia892b102013-06-11 14:18:46 +0530519 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900520 smart_sync_manifest_name = "smart_sync_override.xml"
521 smart_sync_manifest_path = os.path.join(
522 self.manifest.manifestProject.worktree, smart_sync_manifest_name)
Chirayu Desaia892b102013-06-11 14:18:46 +0530523
Victor Boivie08c880d2011-04-19 10:32:52 +0200524 if opt.smart_sync or opt.smart_tag:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700525 if not self.manifest.manifest_server:
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900526 print('error: cannot smart sync: no manifest server defined in '
Sarah Owenscecd1d82012-11-01 22:59:27 -0700527 'manifest', file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700528 sys.exit(1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900529
530 manifest_server = self.manifest.manifest_server
David Pursehousefb99c712013-09-25 11:09:34 +0900531 if not opt.quiet:
532 print('Using manifest server %s' % manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900533
David Pursehouse86d973d2012-08-24 10:21:02 +0900534 if not '@' in manifest_server:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900535 username = None
536 password = None
537 if opt.manifest_server_username and opt.manifest_server_password:
538 username = opt.manifest_server_username
539 password = opt.manifest_server_password
David Pursehouse86d973d2012-08-24 10:21:02 +0900540 else:
541 try:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900542 info = netrc.netrc()
543 except IOError:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700544 print('.netrc file does not exist or could not be opened',
545 file=sys.stderr)
David Pursehouse86d973d2012-08-24 10:21:02 +0900546 else:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900547 try:
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +0530548 parse_result = urllib.parse.urlparse(manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900549 if parse_result.hostname:
550 username, _account, password = \
551 info.authenticators(parse_result.hostname)
552 except TypeError:
553 # TypeError is raised when the given hostname is not present
554 # in the .netrc file.
Sarah Owenscecd1d82012-11-01 22:59:27 -0700555 print('No credentials found for %s in .netrc'
556 % parse_result.hostname, file=sys.stderr)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700557 except netrc.NetrcParseError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700558 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900559
560 if (username and password):
561 manifest_server = manifest_server.replace('://', '://%s:%s@' %
562 (username, password),
563 1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900564
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700565 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530566 server = xmlrpc.client.Server(manifest_server)
Victor Boivie08c880d2011-04-19 10:32:52 +0200567 if opt.smart_sync:
568 p = self.manifest.manifestProject
569 b = p.GetBranch(p.CurrentBranch)
570 branch = b.merge
571 if branch.startswith(R_HEADS):
572 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700573
Victor Boivie08c880d2011-04-19 10:32:52 +0200574 env = os.environ.copy()
Jeff Davidson5cf16602014-10-02 10:13:38 -0700575 if 'SYNC_TARGET' in env:
576 target = env['SYNC_TARGET']
577 [success, manifest_str] = server.GetApprovedManifest(branch, target)
578 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
Victor Boivie08c880d2011-04-19 10:32:52 +0200579 target = '%s-%s' % (env['TARGET_PRODUCT'],
580 env['TARGET_BUILD_VARIANT'])
581 [success, manifest_str] = server.GetApprovedManifest(branch, target)
582 else:
583 [success, manifest_str] = server.GetApprovedManifest(branch)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700584 else:
Victor Boivie08c880d2011-04-19 10:32:52 +0200585 assert(opt.smart_tag)
586 [success, manifest_str] = server.GetManifest(opt.smart_tag)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700587
588 if success:
David Pursehouse59b41742015-05-07 14:36:09 +0900589 manifest_name = smart_sync_manifest_name
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700590 try:
David Pursehouse59b41742015-05-07 14:36:09 +0900591 f = open(smart_sync_manifest_path, 'w')
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700592 try:
593 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700594 finally:
595 f.close()
David Pursehouse727cc3e2015-05-07 14:16:49 +0900596 except IOError as e:
597 print('error: cannot write manifest to %s:\n%s'
David Pursehouse59b41742015-05-07 14:36:09 +0900598 % (smart_sync_manifest_path, e),
Sarah Owenscecd1d82012-11-01 22:59:27 -0700599 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700600 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100601 self._ReloadManifest(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700602 else:
David Pursehouse351fe2c2013-09-25 17:54:26 +0900603 print('error: manifest server RPC call failed: %s' %
604 manifest_str, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700605 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530606 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700607 print('error: cannot connect to manifest server %s:\n%s'
608 % (self.manifest.manifest_server, e), file=sys.stderr)
David Pursehousebd489c42012-08-23 10:21:26 +0900609 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530610 except xmlrpc.client.ProtocolError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700611 print('error: cannot connect to manifest server %s:\n%d %s'
612 % (self.manifest.manifest_server, e.errcode, e.errmsg),
613 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700614 sys.exit(1)
David Pursehouse59b41742015-05-07 14:36:09 +0900615 else: # Not smart sync or smart tag mode
616 if os.path.isfile(smart_sync_manifest_path):
617 try:
618 os.remove(smart_sync_manifest_path)
619 except OSError as e:
620 print('error: failed to remove existing smart sync override manifest: %s' %
621 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700622
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700623 rp = self.manifest.repoProject
624 rp.PreSync()
625
626 mp = self.manifest.manifestProject
627 mp.PreSync()
628
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800629 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700630 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800631
Nico Sallembien9bb18162009-12-07 15:38:01 -0800632 if not opt.local_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700633 mp.Sync_NetworkHalf(quiet=opt.quiet,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700634 current_branch_only=opt.current_branch_only,
David Pursehouseb1553542014-09-04 21:28:09 +0900635 no_tags=opt.no_tags,
636 optimized_fetch=opt.optimized_fetch)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800637
638 if mp.HasChanges:
639 syncbuf = SyncBuffer(mp.config)
640 mp.Sync_LocalHalf(syncbuf)
641 if not syncbuf.Finish():
642 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100643 self._ReloadManifest(manifest_name)
Shawn O. Pearcec4657962011-09-26 09:08:01 -0700644 if opt.jobs is None:
645 self.jobs = self.manifest.default.sync_j
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800646 all_projects = self.GetProjects(args,
647 missing_ok=True,
648 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700649
Dave Borowitz67700e92012-10-23 15:00:54 -0700650 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700651 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700652 to_fetch = []
653 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700654 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700655 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900656 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700657 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700658
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800659 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700660 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700661 if opt.network_only:
662 # bail out now; the rest touches the working tree
663 return
664
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800665 # Iteratively fetch missing and/or nested unregistered submodules
666 previously_missing_set = set()
667 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100668 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800669 all_projects = self.GetProjects(args,
670 missing_ok=True,
671 submodules_ok=opt.fetch_submodules)
672 missing = []
673 for project in all_projects:
674 if project.gitdir not in fetched:
675 missing.append(project)
676 if not missing:
677 break
678 # Stop us from non-stopped fetching actually-missing repos: If set of
679 # missing repos has not been changed from last fetch, we break.
680 missing_set = set(p.name for p in missing)
681 if previously_missing_set == missing_set:
682 break
683 previously_missing_set = missing_set
684 fetched.update(self._Fetch(missing, opt))
685
Julien Campergue335f5ef2013-10-16 11:02:35 +0200686 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700687 # bail out now, we have no working tree
688 return
689
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700690 if self.UpdateProjectList():
691 sys.exit(1)
692
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700693 syncbuf = SyncBuffer(mp.config,
694 detach_head = opt.detach_head)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900695 pm = Progress('Syncing work tree', len(all_projects))
696 for project in all_projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700697 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800698 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700699 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700700 pm.end()
Sarah Owenscecd1d82012-11-01 22:59:27 -0700701 print(file=sys.stderr)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700702 if not syncbuf.Finish():
703 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700705 # If there's a notice that's supposed to print at the end of the sync, print
706 # it now...
707 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700708 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700709
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700710def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -0800711 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -0700712 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700713 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800714 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700715 if project.Exists:
716 project.PostRepoUpgrade()
717
718def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
719 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700720 print('info: A new version of repo is available', file=sys.stderr)
721 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700722 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700723 syncbuf = SyncBuffer(rp.config)
724 rp.Sync_LocalHalf(syncbuf)
725 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700726 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700727 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700728 raise RepoChangedException(['--repo-upgraded'])
729 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700730 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700731 else:
732 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700733 print('repo version %s is current' % rp.work_git.describe(HEAD),
734 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736def _VerifyTag(project):
737 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
738 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700739 print('warning: GnuPG was not available during last "repo init"\n'
740 'warning: Cannot automatically authenticate repo."""',
741 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 return True
743
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700745 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 except GitError:
747 cur = None
748
749 if not cur \
750 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700751 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 if rev.startswith(R_HEADS):
753 rev = rev[len(R_HEADS):]
754
Sarah Owenscecd1d82012-11-01 22:59:27 -0700755 print(file=sys.stderr)
756 print("warning: project '%s' branch '%s' is not signed"
757 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 return False
759
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800760 env = os.environ.copy()
761 env['GIT_DIR'] = project.gitdir.encode()
762 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763
764 cmd = [GIT, 'tag', '-v', cur]
765 proc = subprocess.Popen(cmd,
766 stdout = subprocess.PIPE,
767 stderr = subprocess.PIPE,
768 env = env)
769 out = proc.stdout.read()
770 proc.stdout.close()
771
772 err = proc.stderr.read()
773 proc.stderr.close()
774
775 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700776 print(file=sys.stderr)
777 print(out, file=sys.stderr)
778 print(err, file=sys.stderr)
779 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 return False
781 return True
Dave Borowitz67700e92012-10-23 15:00:54 -0700782
783class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -0700784 _ALPHA = 0.5
785
Dave Borowitz67700e92012-10-23 15:00:54 -0700786 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +0100787 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -0700788 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -0700789 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -0700790
791 def Get(self, project):
792 self._Load()
793 return self._times.get(project.name, _ONE_DAY_S)
794
795 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -0700796 self._Load()
797 name = project.name
798 old = self._times.get(name, t)
799 self._seen.add(name)
800 a = self._ALPHA
801 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -0700802
803 def _Load(self):
804 if self._times is None:
805 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100806 f = open(self._path)
Dave Borowitz67700e92012-10-23 15:00:54 -0700807 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100808 self._times = json.load(f)
809 finally:
810 f.close()
811 except (IOError, ValueError):
812 try:
813 os.remove(self._path)
814 except OSError:
815 pass
816 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -0700817
818 def Save(self):
819 if self._times is None:
820 return
Dave Borowitzd9478582012-10-23 16:35:39 -0700821
822 to_delete = []
823 for name in self._times:
824 if name not in self._seen:
825 to_delete.append(name)
826 for name in to_delete:
827 del self._times[name]
828
Dave Borowitz67700e92012-10-23 15:00:54 -0700829 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100830 f = open(self._path, 'w')
Dave Borowitz67700e92012-10-23 15:00:54 -0700831 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100832 json.dump(self._times, f, indent=2)
833 finally:
834 f.close()
835 except (IOError, TypeError):
836 try:
837 os.remove(self._path)
838 except OSError:
839 pass