blob: 9b8a6122690c4ae9c2240ca8599267e7675b8e65 [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
26from git_command import GIT
Nico Sallembien5732e472010-04-26 11:17:29 -070027from git_refs import R_HEADS
Shawn O. Pearcee756c412009-04-13 11:51:15 -070028from project import HEAD
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070029from project import Project
30from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080031from command import Command, MirrorSafeCommand
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032from error import RepoChangedException, GitError
33from project import R_HEADS
Shawn O. Pearce350cde42009-04-16 11:21:18 -070034from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070035from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080037class Sync(Command, MirrorSafeCommand):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070038 common = True
39 helpSummary = "Update working tree to the latest revision"
40 helpUsage = """
41%prog [<project>...]
42"""
43 helpDescription = """
44The '%prog' command synchronizes local project directories
45with the remote repositories specified in the manifest. If a local
46project does not yet exist, it will clone a new local directory from
47the remote repository and set up tracking branches as specified in
48the manifest. If the local project already exists, '%prog'
49will update the remote branches and rebase any new local changes
50on top of the new remote changes.
51
52'%prog' will synchronize all projects listed at the command
53line. Projects can be specified either by name, or by a relative
54or absolute path to the project's local directory. If no projects
55are specified, '%prog' will synchronize all projects listed in
56the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070057
58The -d/--detach option can be used to switch specified projects
59back to the manifest revision. This option is especially helpful
60if the project is currently on a topic branch, but the manifest
61revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070062
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070063The -s/--smart-sync option can be used to sync to a known good
64build as specified by the manifest-server element in the current
65manifest.
66
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070067SSH Connections
68---------------
69
70If at least one project remote URL uses an SSH connection (ssh://,
71git+ssh://, or user@host:path syntax) repo will automatically
72enable the SSH ControlMaster option when connecting to that host.
73This feature permits other projects in the same '%prog' session to
74reuse the same SSH tunnel, saving connection setup overheads.
75
76To disable this behavior on UNIX platforms, set the GIT_SSH
77environment variable to 'ssh'. For example:
78
79 export GIT_SSH=ssh
80 %prog
81
82Compatibility
83~~~~~~~~~~~~~
84
85This feature is automatically disabled on Windows, due to the lack
86of UNIX domain socket support.
87
88This feature is not compatible with url.insteadof rewrites in the
89user's ~/.gitconfig. '%prog' is currently not able to perform the
90rewrite early enough to establish the ControlMaster tunnel.
91
92If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
93later is required to fix a server side protocol bug.
94
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095"""
96
Nico Sallembien6623b212010-05-11 12:57:01 -070097 def _Options(self, p, show_smart=True):
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -070098 p.add_option('-l','--local-only',
99 dest='local_only', action='store_true',
100 help="only update working tree, don't fetch")
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700101 p.add_option('-n','--network-only',
102 dest='network_only', action='store_true',
103 help="fetch only, don't update working tree")
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700104 p.add_option('-d','--detach',
105 dest='detach_head', action='store_true',
106 help='detach projects back to manifest revision')
Nico Sallembien6623b212010-05-11 12:57:01 -0700107 if show_smart:
108 p.add_option('-s', '--smart-sync',
109 dest='smart_sync', action='store_true',
110 help='smart sync using manifest from a known good build')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700111
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700112 g = p.add_option_group('repo Version options')
113 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114 dest='no_repo_verify', action='store_true',
115 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700116 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800117 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700118 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700120 def _Fetch(self, projects):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 fetched = set()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700122 pm = Progress('Fetching projects', len(projects))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 for project in projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700124 pm.update()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700125 if project.Sync_NetworkHalf():
126 fetched.add(project.gitdir)
127 else:
128 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
129 sys.exit(1)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700130 pm.end()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131 return fetched
132
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700133 def UpdateProjectList(self):
134 new_project_paths = []
135 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700136 if project.relpath:
137 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700138 file_name = 'project.list'
139 file_path = os.path.join(self.manifest.repodir, file_name)
140 old_project_paths = []
141
142 if os.path.exists(file_path):
143 fd = open(file_path, 'r')
144 try:
145 old_project_paths = fd.read().split('\n')
146 finally:
147 fd.close()
148 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700149 if not path:
150 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700151 if path not in new_project_paths:
Anthonyf3fdf822009-09-26 13:38:52 -0400152 """If the path has already been deleted, we don't need to do it
153 """
154 if os.path.exists(self.manifest.topdir + '/' + path):
155 project = Project(
156 manifest = self.manifest,
157 name = path,
158 remote = RemoteSpec('origin'),
159 gitdir = os.path.join(self.manifest.topdir,
160 path, '.git'),
161 worktree = os.path.join(self.manifest.topdir, path),
162 relpath = path,
163 revisionExpr = 'HEAD',
164 revisionId = None)
165
166 if project.IsDirty():
167 print >>sys.stderr, 'error: Cannot remove project "%s": \
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700168uncommitted changes are present' % project.relpath
Anthonyf3fdf822009-09-26 13:38:52 -0400169 print >>sys.stderr, ' commit changes, then run sync again'
170 return -1
171 else:
172 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
173 shutil.rmtree(project.worktree)
174 # Try deleting parent subdirs if they are empty
175 dir = os.path.dirname(project.worktree)
176 while dir != self.manifest.topdir:
177 try:
178 os.rmdir(dir)
179 except OSError:
180 break
181 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700182
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700183 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700184 fd = open(file_path, 'w')
185 try:
186 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700187 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700188 finally:
189 fd.close()
190 return 0
191
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192 def Execute(self, opt, args):
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700193 if opt.network_only and opt.detach_head:
194 print >>sys.stderr, 'error: cannot combine -n and -d'
195 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700196 if opt.network_only and opt.local_only:
197 print >>sys.stderr, 'error: cannot combine -n and -l'
198 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700199
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700200 if opt.smart_sync:
201 if not self.manifest.manifest_server:
202 print >>sys.stderr, \
203 'error: cannot smart sync: no manifest server defined in manifest'
204 sys.exit(1)
205 try:
206 server = xmlrpclib.Server(self.manifest.manifest_server)
207 p = self.manifest.manifestProject
208 b = p.GetBranch(p.CurrentBranch)
209 branch = b.merge
Nico Sallembien5732e472010-04-26 11:17:29 -0700210 if branch.startswith(R_HEADS):
211 branch = branch[len(R_HEADS):]
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700212
213 env = dict(os.environ)
214 if (env.has_key('TARGET_PRODUCT') and
215 env.has_key('TARGET_BUILD_VARIANT')):
216 target = '%s-%s' % (env['TARGET_PRODUCT'],
217 env['TARGET_BUILD_VARIANT'])
218 [success, manifest_str] = server.GetApprovedManifest(branch, target)
219 else:
220 [success, manifest_str] = server.GetApprovedManifest(branch)
221
222 if success:
223 manifest_name = "smart_sync_override.xml"
224 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
225 manifest_name)
226 try:
227 f = open(manifest_path, 'w')
228 try:
229 f.write(manifest_str)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700230 finally:
231 f.close()
232 except IOError:
233 print >>sys.stderr, 'error: cannot write manifest to %s' % \
234 manifest_path
235 sys.exit(1)
Nico Sallembien719965a2010-04-20 15:28:19 -0700236 self.manifest.Override(manifest_name)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700237 else:
238 print >>sys.stderr, 'error: %s' % manifest_str
239 sys.exit(1)
240 except socket.error:
241 print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
242 self.manifest.manifest_server)
243 sys.exit(1)
244
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 rp = self.manifest.repoProject
246 rp.PreSync()
247
248 mp = self.manifest.manifestProject
249 mp.PreSync()
250
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800251 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700252 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800253
Nico Sallembien9bb18162009-12-07 15:38:01 -0800254 if not opt.local_only:
255 mp.Sync_NetworkHalf()
256
257 if mp.HasChanges:
258 syncbuf = SyncBuffer(mp.config)
259 mp.Sync_LocalHalf(syncbuf)
260 if not syncbuf.Finish():
261 sys.exit(1)
262 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700265 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700266 to_fetch = []
267 now = time.time()
268 if (24 * 60 * 60) <= (now - rp.LastFetch):
269 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700270 to_fetch.extend(all)
271
272 fetched = self._Fetch(to_fetch)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700273 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700274 if opt.network_only:
275 # bail out now; the rest touches the working tree
276 return
277
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700278 self.manifest._Unload()
279 all = self.GetProjects(args, missing_ok=True)
280 missing = []
281 for project in all:
282 if project.gitdir not in fetched:
283 missing.append(project)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700284 self._Fetch(missing)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700285
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700286 if self.manifest.IsMirror:
287 # bail out now, we have no working tree
288 return
289
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700290 if self.UpdateProjectList():
291 sys.exit(1)
292
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700293 syncbuf = SyncBuffer(mp.config,
294 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700295 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700297 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800298 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700299 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700300 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700301 print >>sys.stderr
302 if not syncbuf.Finish():
303 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700304
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700305def _PostRepoUpgrade(manifest):
306 for project in manifest.projects.values():
307 if project.Exists:
308 project.PostRepoUpgrade()
309
310def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
311 if rp.HasChanges:
312 print >>sys.stderr, 'info: A new version of repo is available'
313 print >>sys.stderr, ''
314 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700315 syncbuf = SyncBuffer(rp.config)
316 rp.Sync_LocalHalf(syncbuf)
317 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700318 sys.exit(1)
319 print >>sys.stderr, 'info: Restarting repo with latest version'
320 raise RepoChangedException(['--repo-upgraded'])
321 else:
322 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
323 else:
324 if verbose:
325 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700327def _VerifyTag(project):
328 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
329 if not os.path.exists(gpg_dir):
330 print >>sys.stderr,\
331"""warning: GnuPG was not available during last "repo init"
332warning: Cannot automatically authenticate repo."""
333 return True
334
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700336 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700337 except GitError:
338 cur = None
339
340 if not cur \
341 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700342 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700343 if rev.startswith(R_HEADS):
344 rev = rev[len(R_HEADS):]
345
346 print >>sys.stderr
347 print >>sys.stderr,\
348 "warning: project '%s' branch '%s' is not signed" \
349 % (project.name, rev)
350 return False
351
352 env = dict(os.environ)
353 env['GIT_DIR'] = project.gitdir
354 env['GNUPGHOME'] = gpg_dir
355
356 cmd = [GIT, 'tag', '-v', cur]
357 proc = subprocess.Popen(cmd,
358 stdout = subprocess.PIPE,
359 stderr = subprocess.PIPE,
360 env = env)
361 out = proc.stdout.read()
362 proc.stdout.close()
363
364 err = proc.stderr.read()
365 proc.stderr.close()
366
367 if proc.wait() != 0:
368 print >>sys.stderr
369 print >>sys.stderr, out
370 print >>sys.stderr, err
371 print >>sys.stderr
372 return False
373 return True