blob: deff171a00a90365e2430c2ca3fb0434d35d4227 [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
Shawn O. Pearcee756c412009-04-13 11:51:15 -070027from project import HEAD
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070028from project import Project
29from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080030from command import Command, MirrorSafeCommand
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031from error import RepoChangedException, GitError
32from project import R_HEADS
Shawn O. Pearce350cde42009-04-16 11:21:18 -070033from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070034from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080036class Sync(Command, MirrorSafeCommand):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037 common = True
38 helpSummary = "Update working tree to the latest revision"
39 helpUsage = """
40%prog [<project>...]
41"""
42 helpDescription = """
43The '%prog' command synchronizes local project directories
44with the remote repositories specified in the manifest. If a local
45project does not yet exist, it will clone a new local directory from
46the remote repository and set up tracking branches as specified in
47the manifest. If the local project already exists, '%prog'
48will update the remote branches and rebase any new local changes
49on top of the new remote changes.
50
51'%prog' will synchronize all projects listed at the command
52line. Projects can be specified either by name, or by a relative
53or absolute path to the project's local directory. If no projects
54are specified, '%prog' will synchronize all projects listed in
55the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070056
57The -d/--detach option can be used to switch specified projects
58back to the manifest revision. This option is especially helpful
59if the project is currently on a topic branch, but the manifest
60revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070061
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070062The -s/--smart-sync option can be used to sync to a known good
63build as specified by the manifest-server element in the current
64manifest.
65
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070066SSH Connections
67---------------
68
69If at least one project remote URL uses an SSH connection (ssh://,
70git+ssh://, or user@host:path syntax) repo will automatically
71enable the SSH ControlMaster option when connecting to that host.
72This feature permits other projects in the same '%prog' session to
73reuse the same SSH tunnel, saving connection setup overheads.
74
75To disable this behavior on UNIX platforms, set the GIT_SSH
76environment variable to 'ssh'. For example:
77
78 export GIT_SSH=ssh
79 %prog
80
81Compatibility
82~~~~~~~~~~~~~
83
84This feature is automatically disabled on Windows, due to the lack
85of UNIX domain socket support.
86
87This feature is not compatible with url.insteadof rewrites in the
88user's ~/.gitconfig. '%prog' is currently not able to perform the
89rewrite early enough to establish the ControlMaster tunnel.
90
91If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
92later is required to fix a server side protocol bug.
93
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070094"""
95
96 def _Options(self, p):
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -070097 p.add_option('-l','--local-only',
98 dest='local_only', action='store_true',
99 help="only update working tree, don't fetch")
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700100 p.add_option('-n','--network-only',
101 dest='network_only', action='store_true',
102 help="fetch only, don't update working tree")
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700103 p.add_option('-d','--detach',
104 dest='detach_head', action='store_true',
105 help='detach projects back to manifest revision')
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700106 p.add_option('-s', '--smart-sync',
107 dest='smart_sync', action='store_true',
108 help='smart sync using manifest from a known good build')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700109
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700110 g = p.add_option_group('repo Version options')
111 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700112 dest='no_repo_verify', action='store_true',
113 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700114 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800115 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700116 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700118 def _Fetch(self, projects):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119 fetched = set()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700120 pm = Progress('Fetching projects', len(projects))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 for project in projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700122 pm.update()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 if project.Sync_NetworkHalf():
124 fetched.add(project.gitdir)
125 else:
126 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
127 sys.exit(1)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700128 pm.end()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129 return fetched
130
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700131 def UpdateProjectList(self):
132 new_project_paths = []
133 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700134 if project.relpath:
135 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700136 file_name = 'project.list'
137 file_path = os.path.join(self.manifest.repodir, file_name)
138 old_project_paths = []
139
140 if os.path.exists(file_path):
141 fd = open(file_path, 'r')
142 try:
143 old_project_paths = fd.read().split('\n')
144 finally:
145 fd.close()
146 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700147 if not path:
148 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700149 if path not in new_project_paths:
150 project = Project(
151 manifest = self.manifest,
152 name = path,
153 remote = RemoteSpec('origin'),
154 gitdir = os.path.join(self.manifest.topdir,
155 path, '.git'),
156 worktree = os.path.join(self.manifest.topdir, path),
157 relpath = path,
158 revisionExpr = 'HEAD',
159 revisionId = None)
160 if project.IsDirty():
161 print >>sys.stderr, 'error: Cannot remove project "%s": \
162uncommitted changes are present' % project.relpath
163 print >>sys.stderr, ' commit changes, then run sync again'
164 return -1
165 else:
166 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
167 shutil.rmtree(project.worktree)
Jaikumar Ganesh8135cdc2009-06-02 15:07:44 -0700168 # Try deleting parent subdirs if they are empty
169 dir = os.path.dirname(project.worktree)
170 while dir != self.manifest.topdir:
171 try:
172 os.rmdir(dir)
173 except OSError:
174 break
175 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700176
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700177 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700178 fd = open(file_path, 'w')
179 try:
180 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700181 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700182 finally:
183 fd.close()
184 return 0
185
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186 def Execute(self, opt, args):
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700187 if opt.network_only and opt.detach_head:
188 print >>sys.stderr, 'error: cannot combine -n and -d'
189 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700190 if opt.network_only and opt.local_only:
191 print >>sys.stderr, 'error: cannot combine -n and -l'
192 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700193
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700194 if opt.smart_sync:
195 if not self.manifest.manifest_server:
196 print >>sys.stderr, \
197 'error: cannot smart sync: no manifest server defined in manifest'
198 sys.exit(1)
199 try:
200 server = xmlrpclib.Server(self.manifest.manifest_server)
201 p = self.manifest.manifestProject
202 b = p.GetBranch(p.CurrentBranch)
203 branch = b.merge
204
205 env = dict(os.environ)
206 if (env.has_key('TARGET_PRODUCT') and
207 env.has_key('TARGET_BUILD_VARIANT')):
208 target = '%s-%s' % (env['TARGET_PRODUCT'],
209 env['TARGET_BUILD_VARIANT'])
210 [success, manifest_str] = server.GetApprovedManifest(branch, target)
211 else:
212 [success, manifest_str] = server.GetApprovedManifest(branch)
213
214 if success:
215 manifest_name = "smart_sync_override.xml"
216 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
217 manifest_name)
218 try:
219 f = open(manifest_path, 'w')
220 try:
221 f.write(manifest_str)
222 self.manifest.Override(manifest_name)
223 finally:
224 f.close()
225 except IOError:
226 print >>sys.stderr, 'error: cannot write manifest to %s' % \
227 manifest_path
228 sys.exit(1)
229 else:
230 print >>sys.stderr, 'error: %s' % manifest_str
231 sys.exit(1)
232 except socket.error:
233 print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
234 self.manifest.manifest_server)
235 sys.exit(1)
236
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 rp = self.manifest.repoProject
238 rp.PreSync()
239
240 mp = self.manifest.manifestProject
241 mp.PreSync()
242
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800243 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700244 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800245
Nico Sallembien9bb18162009-12-07 15:38:01 -0800246 if not opt.local_only:
247 mp.Sync_NetworkHalf()
248
249 if mp.HasChanges:
250 syncbuf = SyncBuffer(mp.config)
251 mp.Sync_LocalHalf(syncbuf)
252 if not syncbuf.Finish():
253 sys.exit(1)
254 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700257 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700258 to_fetch = []
259 now = time.time()
260 if (24 * 60 * 60) <= (now - rp.LastFetch):
261 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700262 to_fetch.extend(all)
263
264 fetched = self._Fetch(to_fetch)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700265 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700266 if opt.network_only:
267 # bail out now; the rest touches the working tree
268 return
269
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700270 self.manifest._Unload()
271 all = self.GetProjects(args, missing_ok=True)
272 missing = []
273 for project in all:
274 if project.gitdir not in fetched:
275 missing.append(project)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700276 self._Fetch(missing)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700277
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700278 if self.manifest.IsMirror:
279 # bail out now, we have no working tree
280 return
281
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700282 if self.UpdateProjectList():
283 sys.exit(1)
284
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700285 syncbuf = SyncBuffer(mp.config,
286 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700287 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700288 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700289 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800290 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700291 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700292 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700293 print >>sys.stderr
294 if not syncbuf.Finish():
295 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700297def _PostRepoUpgrade(manifest):
298 for project in manifest.projects.values():
299 if project.Exists:
300 project.PostRepoUpgrade()
301
302def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
303 if rp.HasChanges:
304 print >>sys.stderr, 'info: A new version of repo is available'
305 print >>sys.stderr, ''
306 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700307 syncbuf = SyncBuffer(rp.config)
308 rp.Sync_LocalHalf(syncbuf)
309 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700310 sys.exit(1)
311 print >>sys.stderr, 'info: Restarting repo with latest version'
312 raise RepoChangedException(['--repo-upgraded'])
313 else:
314 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
315 else:
316 if verbose:
317 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
318
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700319def _VerifyTag(project):
320 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
321 if not os.path.exists(gpg_dir):
322 print >>sys.stderr,\
323"""warning: GnuPG was not available during last "repo init"
324warning: Cannot automatically authenticate repo."""
325 return True
326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700327 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700328 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700329 except GitError:
330 cur = None
331
332 if not cur \
333 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700334 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335 if rev.startswith(R_HEADS):
336 rev = rev[len(R_HEADS):]
337
338 print >>sys.stderr
339 print >>sys.stderr,\
340 "warning: project '%s' branch '%s' is not signed" \
341 % (project.name, rev)
342 return False
343
344 env = dict(os.environ)
345 env['GIT_DIR'] = project.gitdir
346 env['GNUPGHOME'] = gpg_dir
347
348 cmd = [GIT, 'tag', '-v', cur]
349 proc = subprocess.Popen(cmd,
350 stdout = subprocess.PIPE,
351 stderr = subprocess.PIPE,
352 env = env)
353 out = proc.stdout.read()
354 proc.stdout.close()
355
356 err = proc.stderr.read()
357 proc.stderr.close()
358
359 if proc.wait() != 0:
360 print >>sys.stderr
361 print >>sys.stderr, out
362 print >>sys.stderr, err
363 print >>sys.stderr
364 return False
365 return True