blob: 6cac2e520dfd9cda772c9c0c2417a0237a2bf8ca [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')
Roy Lee18afd7f2010-05-09 04:32:08 +0800113 p.add_option('-j','--jobs',
114 dest='jobs', action='store', type='int',
115 help="number of projects to fetch simultaneously")
Nico Sallembien6623b212010-05-11 12:57:01 -0700116 if show_smart:
117 p.add_option('-s', '--smart-sync',
118 dest='smart_sync', action='store_true',
119 help='smart sync using manifest from a known good build')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700120
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700121 g = p.add_option_group('repo Version options')
122 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 dest='no_repo_verify', action='store_true',
124 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700125 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800126 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700127 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128
Roy Lee18afd7f2010-05-09 04:32:08 +0800129 def _FetchHelper(self, project, lock, fetched, pm, sem):
130 if not project.Sync_NetworkHalf():
131 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
132 sem.release()
133 sys.exit(1)
134
135 lock.acquire()
136 fetched.add(project.gitdir)
137 pm.update()
138 lock.release()
139 sem.release()
140
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700141 def _Fetch(self, projects):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142 fetched = set()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700143 pm = Progress('Fetching projects', len(projects))
Roy Lee18afd7f2010-05-09 04:32:08 +0800144
145 if self.jobs == 1:
146 for project in projects:
147 pm.update()
148 if project.Sync_NetworkHalf():
149 fetched.add(project.gitdir)
150 else:
151 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
152 sys.exit(1)
153 else:
154 threads = set()
155 lock = _threading.Lock()
156 sem = _threading.Semaphore(self.jobs)
157 for project in projects:
158 sem.acquire()
159 t = _threading.Thread(target = self._FetchHelper,
160 args = (project, lock, fetched, pm, sem))
161 threads.add(t)
162 t.start()
163
164 for t in threads:
165 t.join()
166
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700167 pm.end()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168 return fetched
169
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700170 def UpdateProjectList(self):
171 new_project_paths = []
172 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700173 if project.relpath:
174 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700175 file_name = 'project.list'
176 file_path = os.path.join(self.manifest.repodir, file_name)
177 old_project_paths = []
178
179 if os.path.exists(file_path):
180 fd = open(file_path, 'r')
181 try:
182 old_project_paths = fd.read().split('\n')
183 finally:
184 fd.close()
185 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700186 if not path:
187 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700188 if path not in new_project_paths:
Anthonyf3fdf822009-09-26 13:38:52 -0400189 """If the path has already been deleted, we don't need to do it
190 """
191 if os.path.exists(self.manifest.topdir + '/' + path):
192 project = Project(
193 manifest = self.manifest,
194 name = path,
195 remote = RemoteSpec('origin'),
196 gitdir = os.path.join(self.manifest.topdir,
197 path, '.git'),
198 worktree = os.path.join(self.manifest.topdir, path),
199 relpath = path,
200 revisionExpr = 'HEAD',
201 revisionId = None)
202
203 if project.IsDirty():
204 print >>sys.stderr, 'error: Cannot remove project "%s": \
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700205uncommitted changes are present' % project.relpath
Anthonyf3fdf822009-09-26 13:38:52 -0400206 print >>sys.stderr, ' commit changes, then run sync again'
207 return -1
208 else:
209 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
210 shutil.rmtree(project.worktree)
211 # Try deleting parent subdirs if they are empty
212 dir = os.path.dirname(project.worktree)
213 while dir != self.manifest.topdir:
214 try:
215 os.rmdir(dir)
216 except OSError:
217 break
218 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700219
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700220 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700221 fd = open(file_path, 'w')
222 try:
223 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700224 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700225 finally:
226 fd.close()
227 return 0
228
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800230 if opt.jobs:
231 self.jobs = opt.jobs
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700232 if opt.network_only and opt.detach_head:
233 print >>sys.stderr, 'error: cannot combine -n and -d'
234 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700235 if opt.network_only and opt.local_only:
236 print >>sys.stderr, 'error: cannot combine -n and -l'
237 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700238
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700239 if opt.smart_sync:
240 if not self.manifest.manifest_server:
241 print >>sys.stderr, \
242 'error: cannot smart sync: no manifest server defined in manifest'
243 sys.exit(1)
244 try:
245 server = xmlrpclib.Server(self.manifest.manifest_server)
246 p = self.manifest.manifestProject
247 b = p.GetBranch(p.CurrentBranch)
248 branch = b.merge
Nico Sallembien5732e472010-04-26 11:17:29 -0700249 if branch.startswith(R_HEADS):
250 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700251
252 env = dict(os.environ)
253 if (env.has_key('TARGET_PRODUCT') and
254 env.has_key('TARGET_BUILD_VARIANT')):
255 target = '%s-%s' % (env['TARGET_PRODUCT'],
256 env['TARGET_BUILD_VARIANT'])
257 [success, manifest_str] = server.GetApprovedManifest(branch, target)
258 else:
259 [success, manifest_str] = server.GetApprovedManifest(branch)
260
261 if success:
262 manifest_name = "smart_sync_override.xml"
263 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
264 manifest_name)
265 try:
266 f = open(manifest_path, 'w')
267 try:
268 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700269 finally:
270 f.close()
271 except IOError:
272 print >>sys.stderr, 'error: cannot write manifest to %s' % \
273 manifest_path
274 sys.exit(1)
Nico Sallembien719965a2010-04-20 15:28:19 -0700275 self.manifest.Override(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700276 else:
277 print >>sys.stderr, 'error: %s' % manifest_str
278 sys.exit(1)
279 except socket.error:
280 print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
281 self.manifest.manifest_server)
282 sys.exit(1)
283
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700284 rp = self.manifest.repoProject
285 rp.PreSync()
286
287 mp = self.manifest.manifestProject
288 mp.PreSync()
289
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800290 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700291 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800292
Nico Sallembien9bb18162009-12-07 15:38:01 -0800293 if not opt.local_only:
294 mp.Sync_NetworkHalf()
295
296 if mp.HasChanges:
297 syncbuf = SyncBuffer(mp.config)
298 mp.Sync_LocalHalf(syncbuf)
299 if not syncbuf.Finish():
300 sys.exit(1)
301 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700302 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700303
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700304 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700305 to_fetch = []
306 now = time.time()
307 if (24 * 60 * 60) <= (now - rp.LastFetch):
308 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700309 to_fetch.extend(all)
310
311 fetched = self._Fetch(to_fetch)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700312 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700313 if opt.network_only:
314 # bail out now; the rest touches the working tree
315 return
316
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700317 self.manifest._Unload()
318 all = self.GetProjects(args, missing_ok=True)
319 missing = []
320 for project in all:
321 if project.gitdir not in fetched:
322 missing.append(project)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700323 self._Fetch(missing)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700324
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700325 if self.manifest.IsMirror:
326 # bail out now, we have no working tree
327 return
328
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700329 if self.UpdateProjectList():
330 sys.exit(1)
331
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700332 syncbuf = SyncBuffer(mp.config,
333 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700334 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700336 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800337 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700338 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700339 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700340 print >>sys.stderr
341 if not syncbuf.Finish():
342 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700344def _PostRepoUpgrade(manifest):
345 for project in manifest.projects.values():
346 if project.Exists:
347 project.PostRepoUpgrade()
348
349def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
350 if rp.HasChanges:
351 print >>sys.stderr, 'info: A new version of repo is available'
352 print >>sys.stderr, ''
353 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700354 syncbuf = SyncBuffer(rp.config)
355 rp.Sync_LocalHalf(syncbuf)
356 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700357 sys.exit(1)
358 print >>sys.stderr, 'info: Restarting repo with latest version'
359 raise RepoChangedException(['--repo-upgraded'])
360 else:
361 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
362 else:
363 if verbose:
364 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
365
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700366def _VerifyTag(project):
367 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
368 if not os.path.exists(gpg_dir):
369 print >>sys.stderr,\
370"""warning: GnuPG was not available during last "repo init"
371warning: Cannot automatically authenticate repo."""
372 return True
373
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700374 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700375 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700376 except GitError:
377 cur = None
378
379 if not cur \
380 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700381 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700382 if rev.startswith(R_HEADS):
383 rev = rev[len(R_HEADS):]
384
385 print >>sys.stderr
386 print >>sys.stderr,\
387 "warning: project '%s' branch '%s' is not signed" \
388 % (project.name, rev)
389 return False
390
391 env = dict(os.environ)
392 env['GIT_DIR'] = project.gitdir
393 env['GNUPGHOME'] = gpg_dir
394
395 cmd = [GIT, 'tag', '-v', cur]
396 proc = subprocess.Popen(cmd,
397 stdout = subprocess.PIPE,
398 stderr = subprocess.PIPE,
399 env = env)
400 out = proc.stdout.read()
401 proc.stdout.close()
402
403 err = proc.stderr.read()
404 proc.stderr.close()
405
406 if proc.wait() != 0:
407 print >>sys.stderr
408 print >>sys.stderr, out
409 print >>sys.stderr, err
410 print >>sys.stderr
411 return False
412 return True