blob: e138be051f35132272fd20b316e2af15fbfe8d28 [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
David Pursehouse86d973d2012-08-24 10:21:02 +090017import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070018from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
Dave Borowitz67700e92012-10-23 15:00:54 -070020import pickle
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import 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
Conley Owensc9129d92012-10-01 16:12:28 -070061from main import WrapperModule
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070062from project import Project
63from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080064from command import Command, MirrorSafeCommand
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +000065from error import RepoChangedException, GitError, ManifestParseError
Shawn O. Pearce350cde42009-04-16 11:21:18 -070066from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070067from progress import Progress
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
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700131SSH Connections
132---------------
133
134If at least one project remote URL uses an SSH connection (ssh://,
135git+ssh://, or user@host:path syntax) repo will automatically
136enable the SSH ControlMaster option when connecting to that host.
137This feature permits other projects in the same '%prog' session to
138reuse the same SSH tunnel, saving connection setup overheads.
139
140To disable this behavior on UNIX platforms, set the GIT_SSH
141environment variable to 'ssh'. For example:
142
143 export GIT_SSH=ssh
144 %prog
145
146Compatibility
147~~~~~~~~~~~~~
148
149This feature is automatically disabled on Windows, due to the lack
150of UNIX domain socket support.
151
152This feature is not compatible with url.insteadof rewrites in the
153user's ~/.gitconfig. '%prog' is currently not able to perform the
154rewrite early enough to establish the ControlMaster tunnel.
155
156If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
157later is required to fix a server side protocol bug.
158
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159"""
160
Nico Sallembien6623b212010-05-11 12:57:01 -0700161 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000162 try:
163 self.jobs = self.manifest.default.sync_j
164 except ManifestParseError:
165 self.jobs = 1
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700166
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500167 p.add_option('-f', '--force-broken',
168 dest='force_broken', action='store_true',
169 help="continue sync even if a project fails to sync")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900170 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700171 dest='local_only', action='store_true',
172 help="only update working tree, don't fetch")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900173 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700174 dest='network_only', action='store_true',
175 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900176 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700177 dest='detach_head', action='store_true',
178 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900179 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700180 dest='current_branch_only', action='store_true',
181 help='fetch only current branch from server')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900182 p.add_option('-q', '--quiet',
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700183 dest='quiet', action='store_true',
184 help='be more quiet')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900185 p.add_option('-j', '--jobs',
Roy Lee18afd7f2010-05-09 04:32:08 +0800186 dest='jobs', action='store', type='int',
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700187 help="projects to fetch simultaneously (default %d)" % self.jobs)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500188 p.add_option('-m', '--manifest-name',
189 dest='manifest_name',
190 help='temporary manifest to use for this sync', metavar='NAME.xml')
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700191 p.add_option('--no-clone-bundle',
192 dest='no_clone_bundle', action='store_true',
193 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800194 p.add_option('-u', '--manifest-server-username', action='store',
195 dest='manifest_server_username',
196 help='username to authenticate with the manifest server')
197 p.add_option('-p', '--manifest-server-password', action='store',
198 dest='manifest_server_password',
199 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800200 p.add_option('--fetch-submodules',
201 dest='fetch_submodules', action='store_true',
202 help='fetch submodules from server')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700203 p.add_option('--no-tags',
204 dest='no_tags', action='store_true',
205 help="don't fetch tags")
Nico Sallembien6623b212010-05-11 12:57:01 -0700206 if show_smart:
207 p.add_option('-s', '--smart-sync',
208 dest='smart_sync', action='store_true',
209 help='smart sync using manifest from a known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200210 p.add_option('-t', '--smart-tag',
211 dest='smart_tag', action='store',
212 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700213
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700214 g = p.add_option_group('repo Version options')
215 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 dest='no_repo_verify', action='store_true',
217 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700218 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800219 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700220 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221
David James8d201162013-10-11 17:03:19 -0700222 def _FetchProjectList(self, opt, projects, *args):
David Pursehousec1b86a22012-11-14 11:36:51 +0900223 """Main function of the fetch threads when jobs are > 1.
Roy Lee18afd7f2010-05-09 04:32:08 +0800224
David James8d201162013-10-11 17:03:19 -0700225 Delegates most of the work to _FetchHelper.
226
227 Args:
228 opt: Program options returned from optparse. See _Options().
229 projects: Projects to fetch.
230 *args: Remaining arguments to pass to _FetchHelper. See the
231 _FetchHelper docstring for details.
232 """
233 for project in projects:
234 success = self._FetchHelper(opt, project, *args)
235 if not success and not opt.force_broken:
236 break
237
238 def _FetchHelper(self, opt, project, lock, fetched, pm, sem, err_event):
239 """Fetch git objects for a single project.
240
David Pursehousec1b86a22012-11-14 11:36:51 +0900241 Args:
242 opt: Program options returned from optparse. See _Options().
243 project: Project object for the project to fetch.
244 lock: Lock for accessing objects that are shared amongst multiple
245 _FetchHelper() threads.
246 fetched: set object that we will add project.gitdir to when we're done
247 (with our lock held).
248 pm: Instance of a Project object. We will call pm.update() (with our
249 lock held).
250 sem: We'll release() this semaphore when we exit so that another thread
251 can be started up.
252 err_event: We'll set this event in the case of an error (after printing
253 out info about the error).
David James8d201162013-10-11 17:03:19 -0700254
255 Returns:
256 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900257 """
258 # We'll set to true once we've locked the lock.
259 did_lock = False
Doug Andersonfc06ced2011-03-16 15:49:18 -0700260
Chirayu Desaifef4ae72013-04-12 14:54:32 +0530261 if not opt.quiet:
262 print('Fetching project %s' % project.name)
263
David Pursehousec1b86a22012-11-14 11:36:51 +0900264 # Encapsulate everything in a try/except/finally so that:
265 # - We always set err_event in the case of an exception.
266 # - We always make sure we call sem.release().
267 # - We always make sure we unlock the lock if we locked it.
268 try:
Doug Andersonfc06ced2011-03-16 15:49:18 -0700269 try:
David Pursehousec1b86a22012-11-14 11:36:51 +0900270 start = time.time()
271 success = project.Sync_NetworkHalf(
272 quiet=opt.quiet,
273 current_branch_only=opt.current_branch_only,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700274 clone_bundle=not opt.no_clone_bundle,
Julien Campergue335f5ef2013-10-16 11:02:35 +0200275 no_tags=opt.no_tags, archive=self.manifest.IsArchive)
David Pursehousec1b86a22012-11-14 11:36:51 +0900276 self._fetch_times.Set(project, time.time() - start)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700277
David Pursehousec1b86a22012-11-14 11:36:51 +0900278 # Lock around all the rest of the code, since printing, updating a set
279 # and Progress.update() are not thread safe.
280 lock.acquire()
281 did_lock = True
Doug Andersonfc06ced2011-03-16 15:49:18 -0700282
David Pursehousec1b86a22012-11-14 11:36:51 +0900283 if not success:
284 print('error: Cannot fetch %s' % project.name, file=sys.stderr)
285 if opt.force_broken:
286 print('warn: --force-broken, continuing to sync',
287 file=sys.stderr)
288 else:
289 raise _FetchError()
Doug Andersonfc06ced2011-03-16 15:49:18 -0700290
David Pursehousec1b86a22012-11-14 11:36:51 +0900291 fetched.add(project.gitdir)
292 pm.update()
293 except _FetchError:
294 err_event.set()
295 except:
296 err_event.set()
297 raise
298 finally:
299 if did_lock:
300 lock.release()
301 sem.release()
Roy Lee18afd7f2010-05-09 04:32:08 +0800302
David James8d201162013-10-11 17:03:19 -0700303 return success
304
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700305 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700306 fetched = set()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700307 pm = Progress('Fetching projects', len(projects))
Roy Lee18afd7f2010-05-09 04:32:08 +0800308
309 if self.jobs == 1:
310 for project in projects:
311 pm.update()
Chirayu Desaifef4ae72013-04-12 14:54:32 +0530312 if not opt.quiet:
313 print('Fetching project %s' % project.name)
Shawn O. Pearce5d0efdb2012-08-02 12:13:01 -0700314 if project.Sync_NetworkHalf(
315 quiet=opt.quiet,
316 current_branch_only=opt.current_branch_only,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700317 clone_bundle=not opt.no_clone_bundle,
Julien Campergue335f5ef2013-10-16 11:02:35 +0200318 no_tags=opt.no_tags,
319 archive=self.manifest.IsArchive):
Roy Lee18afd7f2010-05-09 04:32:08 +0800320 fetched.add(project.gitdir)
321 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700322 print('error: Cannot fetch %s' % project.name, file=sys.stderr)
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500323 if opt.force_broken:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700324 print('warn: --force-broken, continuing to sync', file=sys.stderr)
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500325 else:
326 sys.exit(1)
Roy Lee18afd7f2010-05-09 04:32:08 +0800327 else:
David James8d201162013-10-11 17:03:19 -0700328 objdir_project_map = dict()
329 for project in projects:
330 objdir_project_map.setdefault(project.objdir, []).append(project)
331
Roy Lee18afd7f2010-05-09 04:32:08 +0800332 threads = set()
333 lock = _threading.Lock()
334 sem = _threading.Semaphore(self.jobs)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700335 err_event = _threading.Event()
David James8d201162013-10-11 17:03:19 -0700336 for project_list in objdir_project_map.values():
Doug Andersonfc06ced2011-03-16 15:49:18 -0700337 # Check for any errors before starting any new threads.
338 # ...we'll let existing threads finish, though.
Daniel Sandler723c5dc2011-04-04 11:15:17 -0400339 if err_event.isSet():
Doug Andersonfc06ced2011-03-16 15:49:18 -0700340 break
341
Roy Lee18afd7f2010-05-09 04:32:08 +0800342 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700343 t = _threading.Thread(target = self._FetchProjectList,
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700344 args = (opt,
David James8d201162013-10-11 17:03:19 -0700345 project_list,
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700346 lock,
347 fetched,
348 pm,
Doug Andersonfc06ced2011-03-16 15:49:18 -0700349 sem,
350 err_event))
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200351 # Ensure that Ctrl-C will not freeze the repo process.
352 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800353 threads.add(t)
354 t.start()
355
356 for t in threads:
357 t.join()
358
Doug Andersonfc06ced2011-03-16 15:49:18 -0700359 # If we saw an error, exit with code 1 so that other scripts can check.
Daniel Sandler723c5dc2011-04-04 11:15:17 -0400360 if err_event.isSet():
Sarah Owenscecd1d82012-11-01 22:59:27 -0700361 print('\nerror: Exited sync due to fetch errors', file=sys.stderr)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700362 sys.exit(1)
363
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700364 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700365 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700366
Julien Campergue335f5ef2013-10-16 11:02:35 +0200367 if not self.manifest.IsArchive:
368 self._GCProjects(projects)
369
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700370 return fetched
371
Dave Borowitz18857212012-10-23 17:02:59 -0700372 def _GCProjects(self, projects):
David James8d201162013-10-11 17:03:19 -0700373 gitdirs = {}
374 for project in projects:
375 gitdirs[project.gitdir] = project.bare_git
376
Dave Borowitze2152672012-10-31 12:24:38 -0700377 has_dash_c = git_require((1, 7, 2))
378 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700379 cpu_count = multiprocessing.cpu_count()
380 else:
381 cpu_count = 1
382 jobs = min(self.jobs, cpu_count)
383
384 if jobs < 2:
David James8d201162013-10-11 17:03:19 -0700385 for bare_git in gitdirs.values():
386 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700387 return
388
389 config = {'pack.threads': cpu_count / jobs if cpu_count > jobs else 1}
390
391 threads = set()
392 sem = _threading.Semaphore(jobs)
393 err_event = _threading.Event()
394
David James8d201162013-10-11 17:03:19 -0700395 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700396 try:
397 try:
David James8d201162013-10-11 17:03:19 -0700398 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700399 except GitError:
400 err_event.set()
401 except:
402 err_event.set()
403 raise
404 finally:
405 sem.release()
406
David James8d201162013-10-11 17:03:19 -0700407 for bare_git in gitdirs.values():
Dave Borowitz18857212012-10-23 17:02:59 -0700408 if err_event.isSet():
409 break
410 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700411 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700412 t.daemon = True
413 threads.add(t)
414 t.start()
415
416 for t in threads:
417 t.join()
418
419 if err_event.isSet():
Sarah Owenscecd1d82012-11-01 22:59:27 -0700420 print('\nerror: Exited sync due to gc errors', file=sys.stderr)
Dave Borowitz18857212012-10-23 17:02:59 -0700421 sys.exit(1)
422
Tim Kilbourn07669002013-03-08 15:02:49 -0800423 def _ReloadManifest(self, manifest_name=None):
424 if manifest_name:
425 # Override calls _Unload already
426 self.manifest.Override(manifest_name)
427 else:
428 self.manifest._Unload()
429
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700430 def UpdateProjectList(self):
431 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700432 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700433 if project.relpath:
434 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700435 file_name = 'project.list'
436 file_path = os.path.join(self.manifest.repodir, file_name)
437 old_project_paths = []
438
439 if os.path.exists(file_path):
440 fd = open(file_path, 'r')
441 try:
442 old_project_paths = fd.read().split('\n')
443 finally:
444 fd.close()
445 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700446 if not path:
447 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700448 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900449 # If the path has already been deleted, we don't need to do it
Anthonyf3fdf822009-09-26 13:38:52 -0400450 if os.path.exists(self.manifest.topdir + '/' + path):
David James8d201162013-10-11 17:03:19 -0700451 gitdir = os.path.join(self.manifest.topdir, path, '.git')
David Pursehousec1b86a22012-11-14 11:36:51 +0900452 project = Project(
453 manifest = self.manifest,
454 name = path,
455 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700456 gitdir = gitdir,
457 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900458 worktree = os.path.join(self.manifest.topdir, path),
459 relpath = path,
460 revisionExpr = 'HEAD',
461 revisionId = None,
462 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400463
David Pursehousec1b86a22012-11-14 11:36:51 +0900464 if project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900465 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900466 'are present' % project.relpath, file=sys.stderr)
467 print(' commit changes, then run sync again',
468 file=sys.stderr)
469 return -1
470 else:
471 print('Deleting obsolete path %s' % project.worktree,
472 file=sys.stderr)
473 shutil.rmtree(project.worktree)
474 # Try deleting parent subdirs if they are empty
475 project_dir = os.path.dirname(project.worktree)
476 while project_dir != self.manifest.topdir:
477 try:
478 os.rmdir(project_dir)
479 except OSError:
480 break
481 project_dir = os.path.dirname(project_dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700482
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700483 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700484 fd = open(file_path, 'w')
485 try:
486 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700487 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700488 finally:
489 fd.close()
490 return 0
491
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700492 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800493 if opt.jobs:
494 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700495 if self.jobs > 1:
496 soft_limit, _ = _rlimit_nofile()
497 self.jobs = min(self.jobs, (soft_limit - 5) / 3)
498
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700499 if opt.network_only and opt.detach_head:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700500 print('error: cannot combine -n and -d', file=sys.stderr)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700501 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700502 if opt.network_only and opt.local_only:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700503 print('error: cannot combine -n and -l', file=sys.stderr)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700504 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500505 if opt.manifest_name and opt.smart_sync:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700506 print('error: cannot combine -m and -s', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500507 sys.exit(1)
508 if opt.manifest_name and opt.smart_tag:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700509 print('error: cannot combine -m and -t', file=sys.stderr)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500510 sys.exit(1)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900511 if opt.manifest_server_username or opt.manifest_server_password:
512 if not (opt.smart_sync or opt.smart_tag):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700513 print('error: -u and -p may only be combined with -s or -t',
514 file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900515 sys.exit(1)
516 if None in [opt.manifest_server_username, opt.manifest_server_password]:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700517 print('error: both -u and -p must be given', file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900518 sys.exit(1)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500519
520 if opt.manifest_name:
521 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700522
Chirayu Desaia892b102013-06-11 14:18:46 +0530523 manifest_name = opt.manifest_name
524
Victor Boivie08c880d2011-04-19 10:32:52 +0200525 if opt.smart_sync or opt.smart_tag:
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700526 if not self.manifest.manifest_server:
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900527 print('error: cannot smart sync: no manifest server defined in '
Sarah Owenscecd1d82012-11-01 22:59:27 -0700528 'manifest', file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700529 sys.exit(1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900530
531 manifest_server = self.manifest.manifest_server
David Pursehousefb99c712013-09-25 11:09:34 +0900532 if not opt.quiet:
533 print('Using manifest server %s' % manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900534
David Pursehouse86d973d2012-08-24 10:21:02 +0900535 if not '@' in manifest_server:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900536 username = None
537 password = None
538 if opt.manifest_server_username and opt.manifest_server_password:
539 username = opt.manifest_server_username
540 password = opt.manifest_server_password
David Pursehouse86d973d2012-08-24 10:21:02 +0900541 else:
542 try:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900543 info = netrc.netrc()
544 except IOError:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700545 print('.netrc file does not exist or could not be opened',
546 file=sys.stderr)
David Pursehouse86d973d2012-08-24 10:21:02 +0900547 else:
David Pursehousecf76b1b2012-09-14 10:31:42 +0900548 try:
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +0530549 parse_result = urllib.parse.urlparse(manifest_server)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900550 if parse_result.hostname:
551 username, _account, password = \
552 info.authenticators(parse_result.hostname)
553 except TypeError:
554 # TypeError is raised when the given hostname is not present
555 # in the .netrc file.
Sarah Owenscecd1d82012-11-01 22:59:27 -0700556 print('No credentials found for %s in .netrc'
557 % parse_result.hostname, file=sys.stderr)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700558 except netrc.NetrcParseError as e:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700559 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
David Pursehousecf76b1b2012-09-14 10:31:42 +0900560
561 if (username and password):
562 manifest_server = manifest_server.replace('://', '://%s:%s@' %
563 (username, password),
564 1)
David Pursehouse86d973d2012-08-24 10:21:02 +0900565
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700566 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530567 server = xmlrpc.client.Server(manifest_server)
Victor Boivie08c880d2011-04-19 10:32:52 +0200568 if opt.smart_sync:
569 p = self.manifest.manifestProject
570 b = p.GetBranch(p.CurrentBranch)
571 branch = b.merge
572 if branch.startswith(R_HEADS):
573 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700574
Victor Boivie08c880d2011-04-19 10:32:52 +0200575 env = os.environ.copy()
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530576 if 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
Victor Boivie08c880d2011-04-19 10:32:52 +0200577 target = '%s-%s' % (env['TARGET_PRODUCT'],
578 env['TARGET_BUILD_VARIANT'])
579 [success, manifest_str] = server.GetApprovedManifest(branch, target)
580 else:
581 [success, manifest_str] = server.GetApprovedManifest(branch)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700582 else:
Victor Boivie08c880d2011-04-19 10:32:52 +0200583 assert(opt.smart_tag)
584 [success, manifest_str] = server.GetManifest(opt.smart_tag)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700585
586 if success:
587 manifest_name = "smart_sync_override.xml"
588 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
589 manifest_name)
590 try:
591 f = open(manifest_path, 'w')
592 try:
593 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700594 finally:
595 f.close()
596 except IOError:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700597 print('error: cannot write manifest to %s' % manifest_path,
598 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,
627 no_tags=opt.no_tags)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800628
629 if mp.HasChanges:
630 syncbuf = SyncBuffer(mp.config)
631 mp.Sync_LocalHalf(syncbuf)
632 if not syncbuf.Finish():
633 sys.exit(1)
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100634 self._ReloadManifest(manifest_name)
Shawn O. Pearcec4657962011-09-26 09:08:01 -0700635 if opt.jobs is None:
636 self.jobs = self.manifest.default.sync_j
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800637 all_projects = self.GetProjects(args,
638 missing_ok=True,
639 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640
Dave Borowitz67700e92012-10-23 15:00:54 -0700641 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700642 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700643 to_fetch = []
644 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700645 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700646 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900647 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700648 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700649
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800650 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700651 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700652 if opt.network_only:
653 # bail out now; the rest touches the working tree
654 return
655
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800656 # Iteratively fetch missing and/or nested unregistered submodules
657 previously_missing_set = set()
658 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100659 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800660 all_projects = self.GetProjects(args,
661 missing_ok=True,
662 submodules_ok=opt.fetch_submodules)
663 missing = []
664 for project in all_projects:
665 if project.gitdir not in fetched:
666 missing.append(project)
667 if not missing:
668 break
669 # Stop us from non-stopped fetching actually-missing repos: If set of
670 # missing repos has not been changed from last fetch, we break.
671 missing_set = set(p.name for p in missing)
672 if previously_missing_set == missing_set:
673 break
674 previously_missing_set = missing_set
675 fetched.update(self._Fetch(missing, opt))
676
Julien Campergue335f5ef2013-10-16 11:02:35 +0200677 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700678 # bail out now, we have no working tree
679 return
680
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700681 if self.UpdateProjectList():
682 sys.exit(1)
683
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700684 syncbuf = SyncBuffer(mp.config,
685 detach_head = opt.detach_head)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900686 pm = Progress('Syncing work tree', len(all_projects))
687 for project in all_projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700688 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800689 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700690 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700691 pm.end()
Sarah Owenscecd1d82012-11-01 22:59:27 -0700692 print(file=sys.stderr)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700693 if not syncbuf.Finish():
694 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700696 # If there's a notice that's supposed to print at the end of the sync, print
697 # it now...
698 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700699 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700700
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700701def _PostRepoUpgrade(manifest, quiet=False):
Conley Owensc9129d92012-10-01 16:12:28 -0700702 wrapper = WrapperModule()
703 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700704 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800705 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700706 if project.Exists:
707 project.PostRepoUpgrade()
708
709def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
710 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700711 print('info: A new version of repo is available', file=sys.stderr)
712 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700713 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700714 syncbuf = SyncBuffer(rp.config)
715 rp.Sync_LocalHalf(syncbuf)
716 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700717 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700718 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700719 raise RepoChangedException(['--repo-upgraded'])
720 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700721 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700722 else:
723 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700724 print('repo version %s is current' % rp.work_git.describe(HEAD),
725 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700726
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727def _VerifyTag(project):
728 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
729 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700730 print('warning: GnuPG was not available during last "repo init"\n'
731 'warning: Cannot automatically authenticate repo."""',
732 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 return True
734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700736 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737 except GitError:
738 cur = None
739
740 if not cur \
741 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700742 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743 if rev.startswith(R_HEADS):
744 rev = rev[len(R_HEADS):]
745
Sarah Owenscecd1d82012-11-01 22:59:27 -0700746 print(file=sys.stderr)
747 print("warning: project '%s' branch '%s' is not signed"
748 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749 return False
750
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800751 env = os.environ.copy()
752 env['GIT_DIR'] = project.gitdir.encode()
753 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754
755 cmd = [GIT, 'tag', '-v', cur]
756 proc = subprocess.Popen(cmd,
757 stdout = subprocess.PIPE,
758 stderr = subprocess.PIPE,
759 env = env)
760 out = proc.stdout.read()
761 proc.stdout.close()
762
763 err = proc.stderr.read()
764 proc.stderr.close()
765
766 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700767 print(file=sys.stderr)
768 print(out, file=sys.stderr)
769 print(err, file=sys.stderr)
770 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771 return False
772 return True
Dave Borowitz67700e92012-10-23 15:00:54 -0700773
774class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -0700775 _ALPHA = 0.5
776
Dave Borowitz67700e92012-10-23 15:00:54 -0700777 def __init__(self, manifest):
778 self._path = os.path.join(manifest.repodir, '.repopickle_fetchtimes')
779 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -0700780 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -0700781
782 def Get(self, project):
783 self._Load()
784 return self._times.get(project.name, _ONE_DAY_S)
785
786 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -0700787 self._Load()
788 name = project.name
789 old = self._times.get(name, t)
790 self._seen.add(name)
791 a = self._ALPHA
792 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -0700793
794 def _Load(self):
795 if self._times is None:
796 try:
Chirayu Desai0eb35cb2013-11-19 18:46:29 +0530797 f = open(self._path, 'rb')
Dave Borowitz67700e92012-10-23 15:00:54 -0700798 except IOError:
799 self._times = {}
800 return self._times
801 try:
802 try:
803 self._times = pickle.load(f)
David Pursehouse1d947b32012-10-25 12:23:11 +0900804 except IOError:
Dave Borowitz67700e92012-10-23 15:00:54 -0700805 try:
806 os.remove(self._path)
807 except OSError:
808 pass
809 self._times = {}
810 finally:
811 f.close()
812 return self._times
813
814 def Save(self):
815 if self._times is None:
816 return
Dave Borowitzd9478582012-10-23 16:35:39 -0700817
818 to_delete = []
819 for name in self._times:
820 if name not in self._seen:
821 to_delete.append(name)
822 for name in to_delete:
823 del self._times[name]
824
Dave Borowitz67700e92012-10-23 15:00:54 -0700825 try:
826 f = open(self._path, 'wb')
827 try:
828 pickle.dump(self._times, f)
829 except (IOError, OSError, pickle.PickleError):
830 try:
831 os.remove(self._path)
832 except OSError:
833 pass
834 finally:
835 f.close()