blob: c9ca8897c0638b1490ae6aae8524f2e93cbeffae [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
520
Victor Boivie08c880d2011-04-19 10:32:52 +0200521 if opt.smart_sync or opt.smart_tag:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700522 if not self.manifest.manifest_server:
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900523 print('error: cannot smart sync: no manifest server defined in '
Sarah Owenscecd1d82012-11-01 22:59:27 -0700524 'manifest', file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700525 sys.exit(1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900526
527 manifest_server = self.manifest.manifest_server
David Pursehousefb99c712013-09-25 11:09:34 +0900528 if not opt.quiet:
529 print('Using manifest server %s' % manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900530
David Pursehouse86d973d2012-08-24 10:21:02 +0900531 if not '@' in manifest_server:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900532 username = None
533 password = None
534 if opt.manifest_server_username and opt.manifest_server_password:
535 username = opt.manifest_server_username
536 password = opt.manifest_server_password
David Pursehouse86d973d2012-08-24 10:21:02 +0900537 else:
538 try:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900539 info = netrc.netrc()
540 except IOError:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700541 print('.netrc file does not exist or could not be opened',
542 file=sys.stderr)
David Pursehouse86d973d2012-08-24 10:21:02 +0900543 else:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900544 try:
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +0530545 parse_result = urllib.parse.urlparse(manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900546 if parse_result.hostname:
547 username, _account, password = \
548 info.authenticators(parse_result.hostname)
549 except TypeError:
550 # TypeError is raised when the given hostname is not present
551 # in the .netrc file.
Sarah Owenscecd1d82012-11-01 22:59:27 -0700552 print('No credentials found for %s in .netrc'
553 % parse_result.hostname, file=sys.stderr)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700554 except netrc.NetrcParseError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700555 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900556
557 if (username and password):
558 manifest_server = manifest_server.replace('://', '://%s:%s@' %
559 (username, password),
560 1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900561
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700562 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530563 server = xmlrpc.client.Server(manifest_server)
Victor Boivie08c880d2011-04-19 10:32:52 +0200564 if opt.smart_sync:
565 p = self.manifest.manifestProject
566 b = p.GetBranch(p.CurrentBranch)
567 branch = b.merge
568 if branch.startswith(R_HEADS):
569 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700570
Victor Boivie08c880d2011-04-19 10:32:52 +0200571 env = os.environ.copy()
Jeff Davidson5cf16602014-10-02 10:13:38 -0700572 if 'SYNC_TARGET' in env:
573 target = env['SYNC_TARGET']
574 [success, manifest_str] = server.GetApprovedManifest(branch, target)
575 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
Victor Boivie08c880d2011-04-19 10:32:52 +0200576 target = '%s-%s' % (env['TARGET_PRODUCT'],
577 env['TARGET_BUILD_VARIANT'])
578 [success, manifest_str] = server.GetApprovedManifest(branch, target)
579 else:
580 [success, manifest_str] = server.GetApprovedManifest(branch)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700581 else:
Victor Boivie08c880d2011-04-19 10:32:52 +0200582 assert(opt.smart_tag)
583 [success, manifest_str] = server.GetManifest(opt.smart_tag)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700584
585 if success:
586 manifest_name = "smart_sync_override.xml"
587 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
588 manifest_name)
589 try:
590 f = open(manifest_path, 'w')
591 try:
592 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700593 finally:
594 f.close()
David Pursehouse727cc3e2015-05-07 14:16:49 +0900595 except IOError as e:
596 print('error: cannot write manifest to %s:\n%s'
597 % (manifest_path, e),
Sarah Owenscecd1d82012-11-01 22:59:27 -0700598 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700599 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100600 self._ReloadManifest(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700601 else:
David Pursehouse351fe2c2013-09-25 17:54:26 +0900602 print('error: manifest server RPC call failed: %s' %
603 manifest_str, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700604 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530605 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700606 print('error: cannot connect to manifest server %s:\n%s'
607 % (self.manifest.manifest_server, e), file=sys.stderr)
David Pursehousebd489c42012-08-23 10:21:26 +0900608 sys.exit(1)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530609 except xmlrpc.client.ProtocolError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700610 print('error: cannot connect to manifest server %s:\n%d %s'
611 % (self.manifest.manifest_server, e.errcode, e.errmsg),
612 file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700613 sys.exit(1)
614
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700615 rp = self.manifest.repoProject
616 rp.PreSync()
617
618 mp = self.manifest.manifestProject
619 mp.PreSync()
620
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800621 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700622 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800623
Nico Sallembien9bb18162009-12-07 15:38:01 -0800624 if not opt.local_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700625 mp.Sync_NetworkHalf(quiet=opt.quiet,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700626 current_branch_only=opt.current_branch_only,
David Pursehouseb1553542014-09-04 21:28:09 +0900627 no_tags=opt.no_tags,
628 optimized_fetch=opt.optimized_fetch)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800629
630 if mp.HasChanges:
631 syncbuf = SyncBuffer(mp.config)
632 mp.Sync_LocalHalf(syncbuf)
633 if not syncbuf.Finish():
634 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100635 self._ReloadManifest(manifest_name)
Shawn O. Pearcec4657962011-09-26 09:08:01 -0700636 if opt.jobs is None:
637 self.jobs = self.manifest.default.sync_j
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800638 all_projects = self.GetProjects(args,
639 missing_ok=True,
640 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641
Dave Borowitz67700e92012-10-23 15:00:54 -0700642 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700643 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700644 to_fetch = []
645 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700646 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700647 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900648 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700649 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700650
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800651 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700652 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700653 if opt.network_only:
654 # bail out now; the rest touches the working tree
655 return
656
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800657 # Iteratively fetch missing and/or nested unregistered submodules
658 previously_missing_set = set()
659 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100660 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800661 all_projects = self.GetProjects(args,
662 missing_ok=True,
663 submodules_ok=opt.fetch_submodules)
664 missing = []
665 for project in all_projects:
666 if project.gitdir not in fetched:
667 missing.append(project)
668 if not missing:
669 break
670 # Stop us from non-stopped fetching actually-missing repos: If set of
671 # missing repos has not been changed from last fetch, we break.
672 missing_set = set(p.name for p in missing)
673 if previously_missing_set == missing_set:
674 break
675 previously_missing_set = missing_set
676 fetched.update(self._Fetch(missing, opt))
677
Julien Campergue335f5ef2013-10-16 11:02:35 +0200678 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700679 # bail out now, we have no working tree
680 return
681
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700682 if self.UpdateProjectList():
683 sys.exit(1)
684
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700685 syncbuf = SyncBuffer(mp.config,
686 detach_head = opt.detach_head)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900687 pm = Progress('Syncing work tree', len(all_projects))
688 for project in all_projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700689 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800690 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700691 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700692 pm.end()
Sarah Owenscecd1d82012-11-01 22:59:27 -0700693 print(file=sys.stderr)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700694 if not syncbuf.Finish():
695 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700697 # If there's a notice that's supposed to print at the end of the sync, print
698 # it now...
699 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700700 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700701
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700702def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -0800703 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -0700704 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700705 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800706 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700707 if project.Exists:
708 project.PostRepoUpgrade()
709
710def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
711 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700712 print('info: A new version of repo is available', file=sys.stderr)
713 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700714 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700715 syncbuf = SyncBuffer(rp.config)
716 rp.Sync_LocalHalf(syncbuf)
717 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700718 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700719 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700720 raise RepoChangedException(['--repo-upgraded'])
721 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700722 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700723 else:
724 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700725 print('repo version %s is current' % rp.work_git.describe(HEAD),
726 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728def _VerifyTag(project):
729 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
730 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700731 print('warning: GnuPG was not available during last "repo init"\n'
732 'warning: Cannot automatically authenticate repo."""',
733 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734 return True
735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700737 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738 except GitError:
739 cur = None
740
741 if not cur \
742 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700743 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 if rev.startswith(R_HEADS):
745 rev = rev[len(R_HEADS):]
746
Sarah Owenscecd1d82012-11-01 22:59:27 -0700747 print(file=sys.stderr)
748 print("warning: project '%s' branch '%s' is not signed"
749 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750 return False
751
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800752 env = os.environ.copy()
753 env['GIT_DIR'] = project.gitdir.encode()
754 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755
756 cmd = [GIT, 'tag', '-v', cur]
757 proc = subprocess.Popen(cmd,
758 stdout = subprocess.PIPE,
759 stderr = subprocess.PIPE,
760 env = env)
761 out = proc.stdout.read()
762 proc.stdout.close()
763
764 err = proc.stderr.read()
765 proc.stderr.close()
766
767 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700768 print(file=sys.stderr)
769 print(out, file=sys.stderr)
770 print(err, file=sys.stderr)
771 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772 return False
773 return True
Dave Borowitz67700e92012-10-23 15:00:54 -0700774
775class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -0700776 _ALPHA = 0.5
777
Dave Borowitz67700e92012-10-23 15:00:54 -0700778 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +0100779 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -0700780 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -0700781 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -0700782
783 def Get(self, project):
784 self._Load()
785 return self._times.get(project.name, _ONE_DAY_S)
786
787 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -0700788 self._Load()
789 name = project.name
790 old = self._times.get(name, t)
791 self._seen.add(name)
792 a = self._ALPHA
793 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -0700794
795 def _Load(self):
796 if self._times is None:
797 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100798 f = open(self._path)
Dave Borowitz67700e92012-10-23 15:00:54 -0700799 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100800 self._times = json.load(f)
801 finally:
802 f.close()
803 except (IOError, ValueError):
804 try:
805 os.remove(self._path)
806 except OSError:
807 pass
808 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -0700809
810 def Save(self):
811 if self._times is None:
812 return
Dave Borowitzd9478582012-10-23 16:35:39 -0700813
814 to_delete = []
815 for name in self._times:
816 if name not in self._seen:
817 to_delete.append(name)
818 for name in to_delete:
819 del self._times[name]
820
Dave Borowitz67700e92012-10-23 15:00:54 -0700821 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100822 f = open(self._path, 'w')
Dave Borowitz67700e92012-10-23 15:00:54 -0700823 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100824 json.dump(self._times, f, indent=2)
825 finally:
826 f.close()
827 except (IOError, TypeError):
828 try:
829 os.remove(self._path)
830 except OSError:
831 pass