blob: 36ef16db187124cfdc8dbd1fc1eabfa658a34d0a [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
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. Pearce0d2b61f2009-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
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700339 self.manifest._Unload()
340 all = self.GetProjects(args, missing_ok=True)
341 missing = []
342 for project in all:
343 if project.gitdir not in fetched:
344 missing.append(project)
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700345 self._Fetch(missing, opt)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700346
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700347 if self.manifest.IsMirror:
348 # bail out now, we have no working tree
349 return
350
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700351 if self.UpdateProjectList():
352 sys.exit(1)
353
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700354 syncbuf = SyncBuffer(mp.config,
355 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700356 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700358 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800359 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700360 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700361 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700362 print >>sys.stderr
363 if not syncbuf.Finish():
364 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700365
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700366 # If there's a notice that's supposed to print at the end of the sync, print
367 # it now...
368 if self.manifest.notice:
369 print self.manifest.notice
370
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700371def _PostRepoUpgrade(manifest):
372 for project in manifest.projects.values():
373 if project.Exists:
374 project.PostRepoUpgrade()
375
376def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
377 if rp.HasChanges:
378 print >>sys.stderr, 'info: A new version of repo is available'
379 print >>sys.stderr, ''
380 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700381 syncbuf = SyncBuffer(rp.config)
382 rp.Sync_LocalHalf(syncbuf)
383 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700384 sys.exit(1)
385 print >>sys.stderr, 'info: Restarting repo with latest version'
386 raise RepoChangedException(['--repo-upgraded'])
387 else:
388 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
389 else:
390 if verbose:
391 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
392
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700393def _VerifyTag(project):
394 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
395 if not os.path.exists(gpg_dir):
396 print >>sys.stderr,\
397"""warning: GnuPG was not available during last "repo init"
398warning: Cannot automatically authenticate repo."""
399 return True
400
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700401 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700402 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700403 except GitError:
404 cur = None
405
406 if not cur \
407 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700408 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700409 if rev.startswith(R_HEADS):
410 rev = rev[len(R_HEADS):]
411
412 print >>sys.stderr
413 print >>sys.stderr,\
414 "warning: project '%s' branch '%s' is not signed" \
415 % (project.name, rev)
416 return False
417
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800418 env = os.environ.copy()
419 env['GIT_DIR'] = project.gitdir.encode()
420 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700421
422 cmd = [GIT, 'tag', '-v', cur]
423 proc = subprocess.Popen(cmd,
424 stdout = subprocess.PIPE,
425 stderr = subprocess.PIPE,
426 env = env)
427 out = proc.stdout.read()
428 proc.stdout.close()
429
430 err = proc.stderr.read()
431 proc.stderr.close()
432
433 if proc.wait() != 0:
434 print >>sys.stderr
435 print >>sys.stderr, out
436 print >>sys.stderr, err
437 print >>sys.stderr
438 return False
439 return True