blob: 1f4b137f3d837de0375aaf1b811dab3f62de6537 [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
30
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from 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
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070073SSH Connections
74---------------
75
76If at least one project remote URL uses an SSH connection (ssh://,
77git+ssh://, or user@host:path syntax) repo will automatically
78enable the SSH ControlMaster option when connecting to that host.
79This feature permits other projects in the same '%prog' session to
80reuse the same SSH tunnel, saving connection setup overheads.
81
82To disable this behavior on UNIX platforms, set the GIT_SSH
83environment variable to 'ssh'. For example:
84
85 export GIT_SSH=ssh
86 %prog
87
88Compatibility
89~~~~~~~~~~~~~
90
91This feature is automatically disabled on Windows, due to the lack
92of UNIX domain socket support.
93
94This feature is not compatible with url.insteadof rewrites in the
95user's ~/.gitconfig. '%prog' is currently not able to perform the
96rewrite early enough to establish the ControlMaster tunnel.
97
98If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
99later is required to fix a server side protocol bug.
100
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101"""
102
Nico Sallembien6623b212010-05-11 12:57:01 -0700103 def _Options(self, p, show_smart=True):
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700104 p.add_option('-l','--local-only',
105 dest='local_only', action='store_true',
106 help="only update working tree, don't fetch")
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700107 p.add_option('-n','--network-only',
108 dest='network_only', action='store_true',
109 help="fetch only, don't update working tree")
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700110 p.add_option('-d','--detach',
111 dest='detach_head', action='store_true',
112 help='detach projects back to manifest revision')
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700113 p.add_option('-q','--quiet',
114 dest='quiet', action='store_true',
115 help='be more quiet')
Roy Lee18afd7f2010-05-09 04:32:08 +0800116 p.add_option('-j','--jobs',
117 dest='jobs', action='store', type='int',
118 help="number of projects to fetch simultaneously")
Nico Sallembien6623b212010-05-11 12:57:01 -0700119 if show_smart:
120 p.add_option('-s', '--smart-sync',
121 dest='smart_sync', action='store_true',
122 help='smart sync using manifest from a known good build')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700123
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700124 g = p.add_option_group('repo Version options')
125 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700126 dest='no_repo_verify', action='store_true',
127 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700128 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800129 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700130 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700132 def _FetchHelper(self, opt, project, lock, fetched, pm, sem):
133 if not project.Sync_NetworkHalf(quiet=opt.quiet):
Roy Lee18afd7f2010-05-09 04:32:08 +0800134 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
135 sem.release()
136 sys.exit(1)
137
138 lock.acquire()
139 fetched.add(project.gitdir)
140 pm.update()
141 lock.release()
142 sem.release()
143
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700144 def _Fetch(self, projects, opt):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700145 fetched = set()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700146 pm = Progress('Fetching projects', len(projects))
Roy Lee18afd7f2010-05-09 04:32:08 +0800147
148 if self.jobs == 1:
149 for project in projects:
150 pm.update()
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700151 if project.Sync_NetworkHalf(quiet=opt.quiet):
Roy Lee18afd7f2010-05-09 04:32:08 +0800152 fetched.add(project.gitdir)
153 else:
154 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
155 sys.exit(1)
156 else:
157 threads = set()
158 lock = _threading.Lock()
159 sem = _threading.Semaphore(self.jobs)
160 for project in projects:
161 sem.acquire()
162 t = _threading.Thread(target = self._FetchHelper,
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700163 args = (opt,
164 project,
165 lock,
166 fetched,
167 pm,
168 sem))
Roy Lee18afd7f2010-05-09 04:32:08 +0800169 threads.add(t)
170 t.start()
171
172 for t in threads:
173 t.join()
174
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700175 pm.end()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 return fetched
177
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700178 def UpdateProjectList(self):
179 new_project_paths = []
180 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700181 if project.relpath:
182 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700183 file_name = 'project.list'
184 file_path = os.path.join(self.manifest.repodir, file_name)
185 old_project_paths = []
186
187 if os.path.exists(file_path):
188 fd = open(file_path, 'r')
189 try:
190 old_project_paths = fd.read().split('\n')
191 finally:
192 fd.close()
193 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700194 if not path:
195 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700196 if path not in new_project_paths:
Anthonyf3fdf822009-09-26 13:38:52 -0400197 """If the path has already been deleted, we don't need to do it
198 """
199 if os.path.exists(self.manifest.topdir + '/' + path):
200 project = Project(
201 manifest = self.manifest,
202 name = path,
203 remote = RemoteSpec('origin'),
204 gitdir = os.path.join(self.manifest.topdir,
205 path, '.git'),
206 worktree = os.path.join(self.manifest.topdir, path),
207 relpath = path,
208 revisionExpr = 'HEAD',
209 revisionId = None)
210
211 if project.IsDirty():
212 print >>sys.stderr, 'error: Cannot remove project "%s": \
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700213uncommitted changes are present' % project.relpath
Anthonyf3fdf822009-09-26 13:38:52 -0400214 print >>sys.stderr, ' commit changes, then run sync again'
215 return -1
216 else:
217 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
218 shutil.rmtree(project.worktree)
219 # Try deleting parent subdirs if they are empty
220 dir = os.path.dirname(project.worktree)
221 while dir != self.manifest.topdir:
222 try:
223 os.rmdir(dir)
224 except OSError:
225 break
226 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700227
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700228 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700229 fd = open(file_path, 'w')
230 try:
231 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700232 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700233 finally:
234 fd.close()
235 return 0
236
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800238 if opt.jobs:
239 self.jobs = opt.jobs
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700240 if opt.network_only and opt.detach_head:
241 print >>sys.stderr, 'error: cannot combine -n and -d'
242 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700243 if opt.network_only and opt.local_only:
244 print >>sys.stderr, 'error: cannot combine -n and -l'
245 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700246
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700247 if opt.smart_sync:
248 if not self.manifest.manifest_server:
249 print >>sys.stderr, \
250 'error: cannot smart sync: no manifest server defined in manifest'
251 sys.exit(1)
252 try:
253 server = xmlrpclib.Server(self.manifest.manifest_server)
254 p = self.manifest.manifestProject
255 b = p.GetBranch(p.CurrentBranch)
256 branch = b.merge
Nico Sallembien5732e472010-04-26 11:17:29 -0700257 if branch.startswith(R_HEADS):
258 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700259
260 env = dict(os.environ)
261 if (env.has_key('TARGET_PRODUCT') and
262 env.has_key('TARGET_BUILD_VARIANT')):
263 target = '%s-%s' % (env['TARGET_PRODUCT'],
264 env['TARGET_BUILD_VARIANT'])
265 [success, manifest_str] = server.GetApprovedManifest(branch, target)
266 else:
267 [success, manifest_str] = server.GetApprovedManifest(branch)
268
269 if success:
270 manifest_name = "smart_sync_override.xml"
271 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
272 manifest_name)
273 try:
274 f = open(manifest_path, 'w')
275 try:
276 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700277 finally:
278 f.close()
279 except IOError:
280 print >>sys.stderr, 'error: cannot write manifest to %s' % \
281 manifest_path
282 sys.exit(1)
Nico Sallembien719965a2010-04-20 15:28:19 -0700283 self.manifest.Override(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700284 else:
285 print >>sys.stderr, 'error: %s' % manifest_str
286 sys.exit(1)
287 except socket.error:
288 print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
289 self.manifest.manifest_server)
290 sys.exit(1)
291
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700292 rp = self.manifest.repoProject
293 rp.PreSync()
294
295 mp = self.manifest.manifestProject
296 mp.PreSync()
297
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800298 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700299 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800300
Nico Sallembien9bb18162009-12-07 15:38:01 -0800301 if not opt.local_only:
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700302 mp.Sync_NetworkHalf(quiet=opt.quiet)
Nico Sallembien9bb18162009-12-07 15:38:01 -0800303
304 if mp.HasChanges:
305 syncbuf = SyncBuffer(mp.config)
306 mp.Sync_LocalHalf(syncbuf)
307 if not syncbuf.Finish():
308 sys.exit(1)
309 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700310 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700311
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700312 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700313 to_fetch = []
314 now = time.time()
315 if (24 * 60 * 60) <= (now - rp.LastFetch):
316 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700317 to_fetch.extend(all)
318
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700319 fetched = self._Fetch(to_fetch, opt)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700320 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700321 if opt.network_only:
322 # bail out now; the rest touches the working tree
323 return
324
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700325 self.manifest._Unload()
326 all = self.GetProjects(args, missing_ok=True)
327 missing = []
328 for project in all:
329 if project.gitdir not in fetched:
330 missing.append(project)
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700331 self._Fetch(missing, opt)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700332
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700333 if self.manifest.IsMirror:
334 # bail out now, we have no working tree
335 return
336
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700337 if self.UpdateProjectList():
338 sys.exit(1)
339
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700340 syncbuf = SyncBuffer(mp.config,
341 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700342 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700344 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800345 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700346 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700347 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700348 print >>sys.stderr
349 if not syncbuf.Finish():
350 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700351
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700352def _PostRepoUpgrade(manifest):
353 for project in manifest.projects.values():
354 if project.Exists:
355 project.PostRepoUpgrade()
356
357def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
358 if rp.HasChanges:
359 print >>sys.stderr, 'info: A new version of repo is available'
360 print >>sys.stderr, ''
361 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700362 syncbuf = SyncBuffer(rp.config)
363 rp.Sync_LocalHalf(syncbuf)
364 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700365 sys.exit(1)
366 print >>sys.stderr, 'info: Restarting repo with latest version'
367 raise RepoChangedException(['--repo-upgraded'])
368 else:
369 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
370 else:
371 if verbose:
372 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700374def _VerifyTag(project):
375 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
376 if not os.path.exists(gpg_dir):
377 print >>sys.stderr,\
378"""warning: GnuPG was not available during last "repo init"
379warning: Cannot automatically authenticate repo."""
380 return True
381
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700382 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700383 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700384 except GitError:
385 cur = None
386
387 if not cur \
388 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700389 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700390 if rev.startswith(R_HEADS):
391 rev = rev[len(R_HEADS):]
392
393 print >>sys.stderr
394 print >>sys.stderr,\
395 "warning: project '%s' branch '%s' is not signed" \
396 % (project.name, rev)
397 return False
398
399 env = dict(os.environ)
400 env['GIT_DIR'] = project.gitdir
401 env['GNUPGHOME'] = gpg_dir
402
403 cmd = [GIT, 'tag', '-v', cur]
404 proc = subprocess.Popen(cmd,
405 stdout = subprocess.PIPE,
406 stderr = subprocess.PIPE,
407 env = env)
408 out = proc.stdout.read()
409 proc.stdout.close()
410
411 err = proc.stderr.read()
412 proc.stderr.close()
413
414 if proc.wait() != 0:
415 print >>sys.stderr
416 print >>sys.stderr, out
417 print >>sys.stderr, err
418 print >>sys.stderr
419 return False
420 return True