blob: 16f1d189a360e105cbdffafbf5deaead3d12b591 [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
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070016from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import os
18import re
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070019import shutil
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070020import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import subprocess
22import sys
Shawn O. Pearcef6906872009-04-18 10:49:00 -070023import time
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import xmlrpclib
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025
Roy Lee18afd7f2010-05-09 04:32:08 +080026try:
27 import threading as _threading
28except ImportError:
29 import dummy_threading as _threading
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030
31from git_command import GIT
Nico Sallembien5732e472010-04-26 11:17:29 -070032from git_refs import R_HEADS
Shawn O. Pearcee756c412009-04-13 11:51:15 -070033from project import HEAD
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070034from project import Project
35from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080036from command import Command, MirrorSafeCommand
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037from error import RepoChangedException, GitError
38from project import R_HEADS
Shawn O. Pearce350cde42009-04-16 11:21:18 -070039from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070040from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080042class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080043 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044 common = True
45 helpSummary = "Update working tree to the latest revision"
46 helpUsage = """
47%prog [<project>...]
48"""
49 helpDescription = """
50The '%prog' command synchronizes local project directories
51with the remote repositories specified in the manifest. If a local
52project does not yet exist, it will clone a new local directory from
53the remote repository and set up tracking branches as specified in
54the manifest. If the local project already exists, '%prog'
55will update the remote branches and rebase any new local changes
56on top of the new remote changes.
57
58'%prog' will synchronize all projects listed at the command
59line. Projects can be specified either by name, or by a relative
60or absolute path to the project's local directory. If no projects
61are specified, '%prog' will synchronize all projects listed in
62the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070063
64The -d/--detach option can be used to switch specified projects
65back to the manifest revision. This option is especially helpful
66if the project is currently on a topic branch, but the manifest
67revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070068
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070069The -s/--smart-sync option can be used to sync to a known good
70build as specified by the manifest-server element in the current
71manifest.
72
Andrei Warkentin5df6de02010-07-02 17:58:31 -050073The -f/--force-broken option can be used to proceed with syncing
74other projects if a project sync fails.
75
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070076SSH Connections
77---------------
78
79If at least one project remote URL uses an SSH connection (ssh://,
80git+ssh://, or user@host:path syntax) repo will automatically
81enable the SSH ControlMaster option when connecting to that host.
82This feature permits other projects in the same '%prog' session to
83reuse the same SSH tunnel, saving connection setup overheads.
84
85To disable this behavior on UNIX platforms, set the GIT_SSH
86environment variable to 'ssh'. For example:
87
88 export GIT_SSH=ssh
89 %prog
90
91Compatibility
92~~~~~~~~~~~~~
93
94This feature is automatically disabled on Windows, due to the lack
95of UNIX domain socket support.
96
97This feature is not compatible with url.insteadof rewrites in the
98user's ~/.gitconfig. '%prog' is currently not able to perform the
99rewrite early enough to establish the ControlMaster tunnel.
100
101If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
102later is required to fix a server side protocol bug.
103
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700104"""
105
Nico Sallembien6623b212010-05-11 12:57:01 -0700106 def _Options(self, p, show_smart=True):
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500107 p.add_option('-f', '--force-broken',
108 dest='force_broken', action='store_true',
109 help="continue sync even if a project fails to sync")
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700110 p.add_option('-l','--local-only',
111 dest='local_only', action='store_true',
112 help="only update working tree, don't fetch")
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700113 p.add_option('-n','--network-only',
114 dest='network_only', action='store_true',
115 help="fetch only, don't update working tree")
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700116 p.add_option('-d','--detach',
117 dest='detach_head', action='store_true',
118 help='detach projects back to manifest revision')
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700119 p.add_option('-q','--quiet',
120 dest='quiet', action='store_true',
121 help='be more quiet')
Roy Lee18afd7f2010-05-09 04:32:08 +0800122 p.add_option('-j','--jobs',
123 dest='jobs', action='store', type='int',
124 help="number of projects to fetch simultaneously")
Nico Sallembien6623b212010-05-11 12:57:01 -0700125 if show_smart:
126 p.add_option('-s', '--smart-sync',
127 dest='smart_sync', action='store_true',
128 help='smart sync using manifest from a known good build')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700129
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700130 g = p.add_option_group('repo Version options')
131 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132 dest='no_repo_verify', action='store_true',
133 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700134 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800135 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700136 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700138 def _FetchHelper(self, opt, project, lock, fetched, pm, sem):
139 if not project.Sync_NetworkHalf(quiet=opt.quiet):
Roy Lee18afd7f2010-05-09 04:32:08 +0800140 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500141 if opt.force_broken:
142 print >>sys.stderr, 'warn: --force-broken, continuing to sync'
143 else:
144 sem.release()
145 sys.exit(1)
Roy Lee18afd7f2010-05-09 04:32:08 +0800146
147 lock.acquire()
148 fetched.add(project.gitdir)
149 pm.update()
150 lock.release()
151 sem.release()
152
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700153 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 fetched = set()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700155 pm = Progress('Fetching projects', len(projects))
Roy Lee18afd7f2010-05-09 04:32:08 +0800156
157 if self.jobs == 1:
158 for project in projects:
159 pm.update()
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700160 if project.Sync_NetworkHalf(quiet=opt.quiet):
Roy Lee18afd7f2010-05-09 04:32:08 +0800161 fetched.add(project.gitdir)
162 else:
163 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500164 if opt.force_broken:
165 print >>sys.stderr, 'warn: --force-broken, continuing to sync'
166 else:
167 sys.exit(1)
Roy Lee18afd7f2010-05-09 04:32:08 +0800168 else:
169 threads = set()
170 lock = _threading.Lock()
171 sem = _threading.Semaphore(self.jobs)
172 for project in projects:
173 sem.acquire()
174 t = _threading.Thread(target = self._FetchHelper,
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700175 args = (opt,
176 project,
177 lock,
178 fetched,
179 pm,
180 sem))
Roy Lee18afd7f2010-05-09 04:32:08 +0800181 threads.add(t)
182 t.start()
183
184 for t in threads:
185 t.join()
186
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700187 pm.end()
Shawn O. Pearce1875ddd2009-07-03 15:22:49 -0700188 for project in projects:
189 project.bare_git.gc('--auto')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190 return fetched
191
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700192 def UpdateProjectList(self):
193 new_project_paths = []
194 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700195 if project.relpath:
196 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700197 file_name = 'project.list'
198 file_path = os.path.join(self.manifest.repodir, file_name)
199 old_project_paths = []
200
201 if os.path.exists(file_path):
202 fd = open(file_path, 'r')
203 try:
204 old_project_paths = fd.read().split('\n')
205 finally:
206 fd.close()
207 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700208 if not path:
209 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700210 if path not in new_project_paths:
Anthonyf3fdf822009-09-26 13:38:52 -0400211 """If the path has already been deleted, we don't need to do it
212 """
213 if os.path.exists(self.manifest.topdir + '/' + path):
214 project = Project(
215 manifest = self.manifest,
216 name = path,
217 remote = RemoteSpec('origin'),
218 gitdir = os.path.join(self.manifest.topdir,
219 path, '.git'),
220 worktree = os.path.join(self.manifest.topdir, path),
221 relpath = path,
222 revisionExpr = 'HEAD',
223 revisionId = None)
224
225 if project.IsDirty():
226 print >>sys.stderr, 'error: Cannot remove project "%s": \
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700227uncommitted changes are present' % project.relpath
Anthonyf3fdf822009-09-26 13:38:52 -0400228 print >>sys.stderr, ' commit changes, then run sync again'
229 return -1
230 else:
231 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
232 shutil.rmtree(project.worktree)
233 # Try deleting parent subdirs if they are empty
234 dir = os.path.dirname(project.worktree)
235 while dir != self.manifest.topdir:
236 try:
237 os.rmdir(dir)
238 except OSError:
239 break
240 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700241
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700242 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700243 fd = open(file_path, 'w')
244 try:
245 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700246 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700247 finally:
248 fd.close()
249 return 0
250
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800252 if opt.jobs:
253 self.jobs = opt.jobs
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700254 if opt.network_only and opt.detach_head:
255 print >>sys.stderr, 'error: cannot combine -n and -d'
256 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700257 if opt.network_only and opt.local_only:
258 print >>sys.stderr, 'error: cannot combine -n and -l'
259 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700260
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700261 if opt.smart_sync:
262 if not self.manifest.manifest_server:
263 print >>sys.stderr, \
264 'error: cannot smart sync: no manifest server defined in manifest'
265 sys.exit(1)
266 try:
267 server = xmlrpclib.Server(self.manifest.manifest_server)
268 p = self.manifest.manifestProject
269 b = p.GetBranch(p.CurrentBranch)
270 branch = b.merge
Nico Sallembien5732e472010-04-26 11:17:29 -0700271 if branch.startswith(R_HEADS):
272 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700273
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800274 env = os.environ.copy()
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700275 if (env.has_key('TARGET_PRODUCT') and
276 env.has_key('TARGET_BUILD_VARIANT')):
277 target = '%s-%s' % (env['TARGET_PRODUCT'],
278 env['TARGET_BUILD_VARIANT'])
279 [success, manifest_str] = server.GetApprovedManifest(branch, target)
280 else:
281 [success, manifest_str] = server.GetApprovedManifest(branch)
282
283 if success:
284 manifest_name = "smart_sync_override.xml"
285 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
286 manifest_name)
287 try:
288 f = open(manifest_path, 'w')
289 try:
290 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700291 finally:
292 f.close()
293 except IOError:
294 print >>sys.stderr, 'error: cannot write manifest to %s' % \
295 manifest_path
296 sys.exit(1)
Nico Sallembien719965a2010-04-20 15:28:19 -0700297 self.manifest.Override(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700298 else:
299 print >>sys.stderr, 'error: %s' % manifest_str
300 sys.exit(1)
301 except socket.error:
302 print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
303 self.manifest.manifest_server)
304 sys.exit(1)
305
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700306 rp = self.manifest.repoProject
307 rp.PreSync()
308
309 mp = self.manifest.manifestProject
310 mp.PreSync()
311
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800312 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700313 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800314
Nico Sallembien9bb18162009-12-07 15:38:01 -0800315 if not opt.local_only:
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700316 mp.Sync_NetworkHalf(quiet=opt.quiet)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800317
318 if mp.HasChanges:
319 syncbuf = SyncBuffer(mp.config)
320 mp.Sync_LocalHalf(syncbuf)
321 if not syncbuf.Finish():
322 sys.exit(1)
323 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700324 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700325
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700326 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700327 to_fetch = []
328 now = time.time()
329 if (24 * 60 * 60) <= (now - rp.LastFetch):
330 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700331 to_fetch.extend(all)
332
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700333 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700334 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700335 if opt.network_only:
336 # bail out now; the rest touches the working tree
337 return
338
339 if mp.HasChanges:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700340 syncbuf = SyncBuffer(mp.config)
341 mp.Sync_LocalHalf(syncbuf)
342 if not syncbuf.Finish():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343 sys.exit(1)
Shawn O. Pearce87bda122009-07-03 16:37:30 -0700344 _ReloadManifest(self)
345 mp = self.manifest.manifestProject
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700346
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700347 all = self.GetProjects(args, missing_ok=True)
348 missing = []
349 for project in all:
350 if project.gitdir not in fetched:
351 missing.append(project)
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700352 self._Fetch(missing, opt)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700353
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700354 if self.manifest.IsMirror:
355 # bail out now, we have no working tree
356 return
357
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700358 if self.UpdateProjectList():
359 sys.exit(1)
360
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700361 syncbuf = SyncBuffer(mp.config,
362 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700363 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700364 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700365 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800366 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700367 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700368 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700369 print >>sys.stderr
370 if not syncbuf.Finish():
371 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372
Shawn O. Pearce87bda122009-07-03 16:37:30 -0700373def _ReloadManifest(cmd):
374 old = cmd.manifest
375 new = cmd.GetManifest(reparse=True)
376
377 if old.__class__ != new.__class__:
378 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
379 new.Upgrade_Local(old)
Shawn O. Pearce13f3da52010-12-07 10:31:19 -0800380 else:
381 if new.notice:
382 print new.notice
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700383
384def _PostRepoUpgrade(manifest):
385 for project in manifest.projects.values():
386 if project.Exists:
387 project.PostRepoUpgrade()
388
389def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
390 if rp.HasChanges:
391 print >>sys.stderr, 'info: A new version of repo is available'
392 print >>sys.stderr, ''
393 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700394 syncbuf = SyncBuffer(rp.config)
395 rp.Sync_LocalHalf(syncbuf)
396 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700397 sys.exit(1)
398 print >>sys.stderr, 'info: Restarting repo with latest version'
399 raise RepoChangedException(['--repo-upgraded'])
400 else:
401 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
402 else:
403 if verbose:
404 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
405
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700406def _VerifyTag(project):
407 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
408 if not os.path.exists(gpg_dir):
409 print >>sys.stderr,\
410"""warning: GnuPG was not available during last "repo init"
411warning: Cannot automatically authenticate repo."""
412 return True
413
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700414 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700415 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700416 except GitError:
417 cur = None
418
419 if not cur \
420 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700421 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700422 if rev.startswith(R_HEADS):
423 rev = rev[len(R_HEADS):]
424
425 print >>sys.stderr
426 print >>sys.stderr,\
427 "warning: project '%s' branch '%s' is not signed" \
428 % (project.name, rev)
429 return False
430
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800431 env = os.environ.copy()
432 env['GIT_DIR'] = project.gitdir.encode()
433 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700434
435 cmd = [GIT, 'tag', '-v', cur]
436 proc = subprocess.Popen(cmd,
437 stdout = subprocess.PIPE,
438 stderr = subprocess.PIPE,
439 env = env)
440 out = proc.stdout.read()
441 proc.stdout.close()
442
443 err = proc.stderr.read()
444 proc.stderr.close()
445
446 if proc.wait() != 0:
447 print >>sys.stderr
448 print >>sys.stderr, out
449 print >>sys.stderr, err
450 print >>sys.stderr
451 return False
452 return True