blob: d89c2b8ce3d2d20ca1feba43fb9375a18dff567c [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()
Shawn O. Pearce1875ddd2009-07-03 15:22:49 -0700120 for project in projects:
121 project.bare_git.gc('--auto')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700122 return fetched
123
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700124 def UpdateProjectList(self):
125 new_project_paths = []
126 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700127 if project.relpath:
128 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700129 file_name = 'project.list'
130 file_path = os.path.join(self.manifest.repodir, file_name)
131 old_project_paths = []
132
133 if os.path.exists(file_path):
134 fd = open(file_path, 'r')
135 try:
136 old_project_paths = fd.read().split('\n')
137 finally:
138 fd.close()
139 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700140 if not path:
141 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700142 if path not in new_project_paths:
143 project = Project(
144 manifest = self.manifest,
145 name = path,
146 remote = RemoteSpec('origin'),
147 gitdir = os.path.join(self.manifest.topdir,
148 path, '.git'),
149 worktree = os.path.join(self.manifest.topdir, path),
150 relpath = path,
151 revisionExpr = 'HEAD',
152 revisionId = None)
153 if project.IsDirty():
154 print >>sys.stderr, 'error: Cannot remove project "%s": \
155uncommitted changes are present' % project.relpath
156 print >>sys.stderr, ' commit changes, then run sync again'
157 return -1
158 else:
159 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
160 shutil.rmtree(project.worktree)
Jaikumar Ganesh8135cdc2009-06-02 15:07:44 -0700161 # Try deleting parent subdirs if they are empty
162 dir = os.path.dirname(project.worktree)
163 while dir != self.manifest.topdir:
164 try:
165 os.rmdir(dir)
166 except OSError:
167 break
168 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700169
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700170 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700171 fd = open(file_path, 'w')
172 try:
173 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700174 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700175 finally:
176 fd.close()
177 return 0
178
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179 def Execute(self, opt, args):
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700180 if opt.network_only and opt.detach_head:
181 print >>sys.stderr, 'error: cannot combine -n and -d'
182 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700183 if opt.network_only and opt.local_only:
184 print >>sys.stderr, 'error: cannot combine -n and -l'
185 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700186
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700187 rp = self.manifest.repoProject
188 rp.PreSync()
189
190 mp = self.manifest.manifestProject
191 mp.PreSync()
192
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800193 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700194 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800195
Nico Sallembien9bb18162009-12-07 15:38:01 -0800196 if not opt.local_only:
197 mp.Sync_NetworkHalf()
198
199 if mp.HasChanges:
200 syncbuf = SyncBuffer(mp.config)
201 mp.Sync_LocalHalf(syncbuf)
202 if not syncbuf.Finish():
203 sys.exit(1)
204 self.manifest._Unload()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700207 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700208 to_fetch = []
209 now = time.time()
210 if (24 * 60 * 60) <= (now - rp.LastFetch):
211 to_fetch.append(rp)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700212 to_fetch.extend(all)
213
214 fetched = self._Fetch(to_fetch)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700215 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700216 if opt.network_only:
217 # bail out now; the rest touches the working tree
218 return
219
220 if mp.HasChanges:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700221 syncbuf = SyncBuffer(mp.config)
222 mp.Sync_LocalHalf(syncbuf)
223 if not syncbuf.Finish():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 sys.exit(1)
Shawn O. Pearce87bda122009-07-03 16:37:30 -0700225 _ReloadManifest(self)
226 mp = self.manifest.manifestProject
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700228 all = self.GetProjects(args, missing_ok=True)
229 missing = []
230 for project in all:
231 if project.gitdir not in fetched:
232 missing.append(project)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700233 self._Fetch(missing)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700235 if self.manifest.IsMirror:
236 # bail out now, we have no working tree
237 return
238
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700239 if self.UpdateProjectList():
240 sys.exit(1)
241
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700242 syncbuf = SyncBuffer(mp.config,
243 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700244 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700246 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800247 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700248 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700249 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700250 print >>sys.stderr
251 if not syncbuf.Finish():
252 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
Shawn O. Pearce87bda122009-07-03 16:37:30 -0700254def _ReloadManifest(cmd):
255 old = cmd.manifest
256 new = cmd.GetManifest(reparse=True)
257
258 if old.__class__ != new.__class__:
259 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
260 new.Upgrade_Local(old)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700261
262def _PostRepoUpgrade(manifest):
263 for project in manifest.projects.values():
264 if project.Exists:
265 project.PostRepoUpgrade()
266
267def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
268 if rp.HasChanges:
269 print >>sys.stderr, 'info: A new version of repo is available'
270 print >>sys.stderr, ''
271 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700272 syncbuf = SyncBuffer(rp.config)
273 rp.Sync_LocalHalf(syncbuf)
274 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700275 sys.exit(1)
276 print >>sys.stderr, 'info: Restarting repo with latest version'
277 raise RepoChangedException(['--repo-upgraded'])
278 else:
279 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
280 else:
281 if verbose:
282 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
283
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700284def _VerifyTag(project):
285 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
286 if not os.path.exists(gpg_dir):
287 print >>sys.stderr,\
288"""warning: GnuPG was not available during last "repo init"
289warning: Cannot automatically authenticate repo."""
290 return True
291
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700292 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700293 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700294 except GitError:
295 cur = None
296
297 if not cur \
298 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700299 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700300 if rev.startswith(R_HEADS):
301 rev = rev[len(R_HEADS):]
302
303 print >>sys.stderr
304 print >>sys.stderr,\
305 "warning: project '%s' branch '%s' is not signed" \
306 % (project.name, rev)
307 return False
308
309 env = dict(os.environ)
310 env['GIT_DIR'] = project.gitdir
311 env['GNUPGHOME'] = gpg_dir
312
313 cmd = [GIT, 'tag', '-v', cur]
314 proc = subprocess.Popen(cmd,
315 stdout = subprocess.PIPE,
316 stderr = subprocess.PIPE,
317 env = env)
318 out = proc.stdout.read()
319 proc.stdout.close()
320
321 err = proc.stderr.read()
322 proc.stderr.close()
323
324 if proc.wait() != 0:
325 print >>sys.stderr
326 print >>sys.stderr, out
327 print >>sys.stderr, err
328 print >>sys.stderr
329 return False
330 return True