blob: ceb81eaaabaf00c33b7f47e3ee3300e4f3a343f1 [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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import subprocess
21import sys
Shawn O. Pearcef6906872009-04-18 10:49:00 -070022import time
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023
24from git_command import GIT
Shawn O. Pearcee756c412009-04-13 11:51:15 -070025from project import HEAD
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070026from project import Project
27from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080028from command import Command, MirrorSafeCommand
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029from error import RepoChangedException, GitError
30from project import R_HEADS
Shawn O. Pearce350cde42009-04-16 11:21:18 -070031from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070032from progress import Progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070033
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080034class Sync(Command, MirrorSafeCommand):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035 common = True
36 helpSummary = "Update working tree to the latest revision"
37 helpUsage = """
38%prog [<project>...]
39"""
40 helpDescription = """
41The '%prog' command synchronizes local project directories
42with the remote repositories specified in the manifest. If a local
43project does not yet exist, it will clone a new local directory from
44the remote repository and set up tracking branches as specified in
45the manifest. If the local project already exists, '%prog'
46will update the remote branches and rebase any new local changes
47on top of the new remote changes.
48
49'%prog' will synchronize all projects listed at the command
50line. Projects can be specified either by name, or by a relative
51or absolute path to the project's local directory. If no projects
52are specified, '%prog' will synchronize all projects listed in
53the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070054
55The -d/--detach option can be used to switch specified projects
56back to the manifest revision. This option is especially helpful
57if the project is currently on a topic branch, but the manifest
58revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070059
60SSH Connections
61---------------
62
63If at least one project remote URL uses an SSH connection (ssh://,
64git+ssh://, or user@host:path syntax) repo will automatically
65enable the SSH ControlMaster option when connecting to that host.
66This feature permits other projects in the same '%prog' session to
67reuse the same SSH tunnel, saving connection setup overheads.
68
69To disable this behavior on UNIX platforms, set the GIT_SSH
70environment variable to 'ssh'. For example:
71
72 export GIT_SSH=ssh
73 %prog
74
75Compatibility
76~~~~~~~~~~~~~
77
78This feature is automatically disabled on Windows, due to the lack
79of UNIX domain socket support.
80
81This feature is not compatible with url.insteadof rewrites in the
82user's ~/.gitconfig. '%prog' is currently not able to perform the
83rewrite early enough to establish the ControlMaster tunnel.
84
85If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
86later is required to fix a server side protocol bug.
87
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070088"""
89
90 def _Options(self, p):
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -070091 p.add_option('-l','--local-only',
92 dest='local_only', action='store_true',
93 help="only update working tree, don't fetch")
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -070094 p.add_option('-n','--network-only',
95 dest='network_only', action='store_true',
96 help="fetch only, don't update working tree")
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070097 p.add_option('-d','--detach',
98 dest='detach_head', action='store_true',
99 help='detach projects back to manifest revision')
100
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700101 g = p.add_option_group('repo Version options')
102 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700103 dest='no_repo_verify', action='store_true',
104 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700105 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800106 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700107 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700109 def _Fetch(self, projects):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700110 fetched = set()
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700111 pm = Progress('Fetching projects', len(projects))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700112 for project in projects:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700113 pm.update()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114 if project.Sync_NetworkHalf():
115 fetched.add(project.gitdir)
116 else:
117 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
118 sys.exit(1)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700119 pm.end()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700120 return fetched
121
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700122 def UpdateProjectList(self):
123 new_project_paths = []
124 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700125 if project.relpath:
126 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700127 file_name = 'project.list'
128 file_path = os.path.join(self.manifest.repodir, file_name)
129 old_project_paths = []
130
131 if os.path.exists(file_path):
132 fd = open(file_path, 'r')
133 try:
134 old_project_paths = fd.read().split('\n')
135 finally:
136 fd.close()
137 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700138 if not path:
139 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700140 if path not in new_project_paths:
141 project = Project(
142 manifest = self.manifest,
143 name = path,
144 remote = RemoteSpec('origin'),
145 gitdir = os.path.join(self.manifest.topdir,
146 path, '.git'),
147 worktree = os.path.join(self.manifest.topdir, path),
148 relpath = path,
149 revisionExpr = 'HEAD',
150 revisionId = None)
151 if project.IsDirty():
152 print >>sys.stderr, 'error: Cannot remove project "%s": \
153uncommitted changes are present' % project.relpath
154 print >>sys.stderr, ' commit changes, then run sync again'
155 return -1
156 else:
157 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
158 shutil.rmtree(project.worktree)
Jaikumar Ganesh8135cdc2009-06-02 15:07:44 -0700159 # Try deleting parent subdirs if they are empty
160 dir = os.path.dirname(project.worktree)
161 while dir != self.manifest.topdir:
162 try:
163 os.rmdir(dir)
164 except OSError:
165 break
166 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700167
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700168 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700169 fd = open(file_path, 'w')
170 try:
171 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700172 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700173 finally:
174 fd.close()
175 return 0
176
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def Execute(self, opt, args):
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700178 if opt.network_only and opt.detach_head:
179 print >>sys.stderr, 'error: cannot combine -n and -d'
180 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700181 if opt.network_only and opt.local_only:
182 print >>sys.stderr, 'error: cannot combine -n and -l'
183 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700184
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185 rp = self.manifest.repoProject
186 rp.PreSync()
187
188 mp = self.manifest.manifestProject
189 mp.PreSync()
190
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800191 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700192 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800193
Nico Sallembien9bb18162009-12-07 15:38:01 -0800194 if not opt.local_only:
195 mp.Sync_NetworkHalf()
196
197 if mp.HasChanges:
198 syncbuf = SyncBuffer(mp.config)
199 mp.Sync_LocalHalf(syncbuf)
200 if not syncbuf.Finish():
201 sys.exit(1)
202 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700205 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700206 to_fetch = []
207 now = time.time()
208 if (24 * 60 * 60) <= (now - rp.LastFetch):
209 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700210 to_fetch.extend(all)
211
212 fetched = self._Fetch(to_fetch)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700213 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700214 if opt.network_only:
215 # bail out now; the rest touches the working tree
216 return
217
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700218 self.manifest._Unload()
219 all = self.GetProjects(args, missing_ok=True)
220 missing = []
221 for project in all:
222 if project.gitdir not in fetched:
223 missing.append(project)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700224 self._Fetch(missing)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700226 if self.manifest.IsMirror:
227 # bail out now, we have no working tree
228 return
229
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700230 if self.UpdateProjectList():
231 sys.exit(1)
232
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700233 syncbuf = SyncBuffer(mp.config,
234 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700235 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700237 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800238 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700239 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700240 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700241 print >>sys.stderr
242 if not syncbuf.Finish():
243 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700245def _PostRepoUpgrade(manifest):
246 for project in manifest.projects.values():
247 if project.Exists:
248 project.PostRepoUpgrade()
249
250def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
251 if rp.HasChanges:
252 print >>sys.stderr, 'info: A new version of repo is available'
253 print >>sys.stderr, ''
254 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700255 syncbuf = SyncBuffer(rp.config)
256 rp.Sync_LocalHalf(syncbuf)
257 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700258 sys.exit(1)
259 print >>sys.stderr, 'info: Restarting repo with latest version'
260 raise RepoChangedException(['--repo-upgraded'])
261 else:
262 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
263 else:
264 if verbose:
265 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
266
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267def _VerifyTag(project):
268 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
269 if not os.path.exists(gpg_dir):
270 print >>sys.stderr,\
271"""warning: GnuPG was not available during last "repo init"
272warning: Cannot automatically authenticate repo."""
273 return True
274
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700275 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700276 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700277 except GitError:
278 cur = None
279
280 if not cur \
281 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700282 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700283 if rev.startswith(R_HEADS):
284 rev = rev[len(R_HEADS):]
285
286 print >>sys.stderr
287 print >>sys.stderr,\
288 "warning: project '%s' branch '%s' is not signed" \
289 % (project.name, rev)
290 return False
291
292 env = dict(os.environ)
293 env['GIT_DIR'] = project.gitdir
294 env['GNUPGHOME'] = gpg_dir
295
296 cmd = [GIT, 'tag', '-v', cur]
297 proc = subprocess.Popen(cmd,
298 stdout = subprocess.PIPE,
299 stderr = subprocess.PIPE,
300 env = env)
301 out = proc.stdout.read()
302 proc.stdout.close()
303
304 err = proc.stderr.read()
305 proc.stderr.close()
306
307 if proc.wait() != 0:
308 print >>sys.stderr
309 print >>sys.stderr, out
310 print >>sys.stderr, err
311 print >>sys.stderr
312 return False
313 return True