blob: 02dd82dffce8aea1196c4243bf2854d99d2c81af [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:
Anthonyf3fdf822009-09-26 13:38:52 -0400150 """If the path has already been deleted, we don't need to do it
151 """
152 if os.path.exists(self.manifest.topdir + '/' + path):
153 project = Project(
154 manifest = self.manifest,
155 name = path,
156 remote = RemoteSpec('origin'),
157 gitdir = os.path.join(self.manifest.topdir,
158 path, '.git'),
159 worktree = os.path.join(self.manifest.topdir, path),
160 relpath = path,
161 revisionExpr = 'HEAD',
162 revisionId = None)
163
164 if project.IsDirty():
165 print >>sys.stderr, 'error: Cannot remove project "%s": \
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700166uncommitted changes are present' % project.relpath
Anthonyf3fdf822009-09-26 13:38:52 -0400167 print >>sys.stderr, ' commit changes, then run sync again'
168 return -1
169 else:
170 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
171 shutil.rmtree(project.worktree)
172 # Try deleting parent subdirs if they are empty
173 dir = os.path.dirname(project.worktree)
174 while dir != self.manifest.topdir:
175 try:
176 os.rmdir(dir)
177 except OSError:
178 break
179 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700180
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700181 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700182 fd = open(file_path, 'w')
183 try:
184 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700185 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700186 finally:
187 fd.close()
188 return 0
189
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190 def Execute(self, opt, args):
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700191 if opt.network_only and opt.detach_head:
192 print >>sys.stderr, 'error: cannot combine -n and -d'
193 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700194 if opt.network_only and opt.local_only:
195 print >>sys.stderr, 'error: cannot combine -n and -l'
196 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700197
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700198 if opt.smart_sync:
199 if not self.manifest.manifest_server:
200 print >>sys.stderr, \
201 'error: cannot smart sync: no manifest server defined in manifest'
202 sys.exit(1)
203 try:
204 server = xmlrpclib.Server(self.manifest.manifest_server)
205 p = self.manifest.manifestProject
206 b = p.GetBranch(p.CurrentBranch)
207 branch = b.merge
208
209 env = dict(os.environ)
210 if (env.has_key('TARGET_PRODUCT') and
211 env.has_key('TARGET_BUILD_VARIANT')):
212 target = '%s-%s' % (env['TARGET_PRODUCT'],
213 env['TARGET_BUILD_VARIANT'])
214 [success, manifest_str] = server.GetApprovedManifest(branch, target)
215 else:
216 [success, manifest_str] = server.GetApprovedManifest(branch)
217
218 if success:
219 manifest_name = "smart_sync_override.xml"
220 manifest_path = os.path.join(self.manifest.manifestProject.worktree,
221 manifest_name)
222 try:
223 f = open(manifest_path, 'w')
224 try:
225 f.write(manifest_str)
226 self.manifest.Override(manifest_name)
227 finally:
228 f.close()
229 except IOError:
230 print >>sys.stderr, 'error: cannot write manifest to %s' % \
231 manifest_path
232 sys.exit(1)
233 else:
234 print >>sys.stderr, 'error: %s' % manifest_str
235 sys.exit(1)
236 except socket.error:
237 print >>sys.stderr, 'error: cannot connect to manifest server %s' % (
238 self.manifest.manifest_server)
239 sys.exit(1)
240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 rp = self.manifest.repoProject
242 rp.PreSync()
243
244 mp = self.manifest.manifestProject
245 mp.PreSync()
246
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800247 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700248 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800249
Nico Sallembien9bb18162009-12-07 15:38:01 -0800250 if not opt.local_only:
251 mp.Sync_NetworkHalf()
252
253 if mp.HasChanges:
254 syncbuf = SyncBuffer(mp.config)
255 mp.Sync_LocalHalf(syncbuf)
256 if not syncbuf.Finish():
257 sys.exit(1)
258 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700261 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700262 to_fetch = []
263 now = time.time()
264 if (24 * 60 * 60) <= (now - rp.LastFetch):
265 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700266 to_fetch.extend(all)
267
268 fetched = self._Fetch(to_fetch)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700269 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700270 if opt.network_only:
271 # bail out now; the rest touches the working tree
272 return
273
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700274 self.manifest._Unload()
275 all = self.GetProjects(args, missing_ok=True)
276 missing = []
277 for project in all:
278 if project.gitdir not in fetched:
279 missing.append(project)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700280 self._Fetch(missing)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700281
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700282 if self.manifest.IsMirror:
283 # bail out now, we have no working tree
284 return
285
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700286 if self.UpdateProjectList():
287 sys.exit(1)
288
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700289 syncbuf = SyncBuffer(mp.config,
290 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700291 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700292 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700293 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800294 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700295 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700296 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700297 print >>sys.stderr
298 if not syncbuf.Finish():
299 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700300
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700301def _PostRepoUpgrade(manifest):
302 for project in manifest.projects.values():
303 if project.Exists:
304 project.PostRepoUpgrade()
305
306def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
307 if rp.HasChanges:
308 print >>sys.stderr, 'info: A new version of repo is available'
309 print >>sys.stderr, ''
310 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700311 syncbuf = SyncBuffer(rp.config)
312 rp.Sync_LocalHalf(syncbuf)
313 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700314 sys.exit(1)
315 print >>sys.stderr, 'info: Restarting repo with latest version'
316 raise RepoChangedException(['--repo-upgraded'])
317 else:
318 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
319 else:
320 if verbose:
321 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
322
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700323def _VerifyTag(project):
324 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
325 if not os.path.exists(gpg_dir):
326 print >>sys.stderr,\
327"""warning: GnuPG was not available during last "repo init"
328warning: Cannot automatically authenticate repo."""
329 return True
330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700332 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700333 except GitError:
334 cur = None
335
336 if not cur \
337 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700338 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339 if rev.startswith(R_HEADS):
340 rev = rev[len(R_HEADS):]
341
342 print >>sys.stderr
343 print >>sys.stderr,\
344 "warning: project '%s' branch '%s' is not signed" \
345 % (project.name, rev)
346 return False
347
348 env = dict(os.environ)
349 env['GIT_DIR'] = project.gitdir
350 env['GNUPGHOME'] = gpg_dir
351
352 cmd = [GIT, 'tag', '-v', cur]
353 proc = subprocess.Popen(cmd,
354 stdout = subprocess.PIPE,
355 stderr = subprocess.PIPE,
356 env = env)
357 out = proc.stdout.read()
358 proc.stdout.close()
359
360 err = proc.stderr.read()
361 proc.stderr.close()
362
363 if proc.wait() != 0:
364 print >>sys.stderr
365 print >>sys.stderr, out
366 print >>sys.stderr, err
367 print >>sys.stderr
368 return False
369 return True