blob: 5fc834d095ac1789cb94071b1cdd9ff9d5dcc194 [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()
114
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115 if project.Sync_NetworkHalf():
116 fetched.add(project.gitdir)
117 else:
118 print >>sys.stderr, 'error: Cannot fetch %s' % project.name
119 sys.exit(1)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700120 pm.end()
Shawn O. Pearce1875ddd2009-07-03 15:22:49 -0700121 for project in projects:
122 project.bare_git.gc('--auto')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 return fetched
124
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700125 def UpdateProjectList(self):
126 new_project_paths = []
127 for project in self.manifest.projects.values():
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700128 if project.relpath:
129 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700130 file_name = 'project.list'
131 file_path = os.path.join(self.manifest.repodir, file_name)
132 old_project_paths = []
133
134 if os.path.exists(file_path):
135 fd = open(file_path, 'r')
136 try:
137 old_project_paths = fd.read().split('\n')
138 finally:
139 fd.close()
140 for path in old_project_paths:
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700141 if not path:
142 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700143 if path not in new_project_paths:
144 project = Project(
145 manifest = self.manifest,
146 name = path,
147 remote = RemoteSpec('origin'),
148 gitdir = os.path.join(self.manifest.topdir,
149 path, '.git'),
150 worktree = os.path.join(self.manifest.topdir, path),
151 relpath = path,
152 revisionExpr = 'HEAD',
153 revisionId = None)
154 if project.IsDirty():
155 print >>sys.stderr, 'error: Cannot remove project "%s": \
156uncommitted changes are present' % project.relpath
157 print >>sys.stderr, ' commit changes, then run sync again'
158 return -1
159 else:
160 print >>sys.stderr, 'Deleting obsolete path %s' % project.worktree
161 shutil.rmtree(project.worktree)
Jaikumar Ganesh8135cdc2009-06-02 15:07:44 -0700162 # Try deleting parent subdirs if they are empty
163 dir = os.path.dirname(project.worktree)
164 while dir != self.manifest.topdir:
165 try:
166 os.rmdir(dir)
167 except OSError:
168 break
169 dir = os.path.dirname(dir)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700170
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700171 new_project_paths.sort()
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700172 fd = open(file_path, 'w')
173 try:
174 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700175 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700176 finally:
177 fd.close()
178 return 0
179
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700180 def Execute(self, opt, args):
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700181 if opt.network_only and opt.detach_head:
182 print >>sys.stderr, 'error: cannot combine -n and -d'
183 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700184 if opt.network_only and opt.local_only:
185 print >>sys.stderr, 'error: cannot combine -n and -l'
186 sys.exit(1)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700187
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188 rp = self.manifest.repoProject
189 rp.PreSync()
190
191 mp = self.manifest.manifestProject
192 mp.PreSync()
193
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800194 if opt.repo_upgraded:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700195 _PostRepoUpgrade(self.manifest)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800196
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197 all = self.GetProjects(args, missing_ok=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700199 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700200 to_fetch = []
201 now = time.time()
202 if (24 * 60 * 60) <= (now - rp.LastFetch):
203 to_fetch.append(rp)
204 to_fetch.append(mp)
205 to_fetch.extend(all)
206
207 fetched = self._Fetch(to_fetch)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700208 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700209 if opt.network_only:
210 # bail out now; the rest touches the working tree
211 return
212
213 if mp.HasChanges:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700214 syncbuf = SyncBuffer(mp.config)
215 mp.Sync_LocalHalf(syncbuf)
216 if not syncbuf.Finish():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 sys.exit(1)
Shawn O. Pearce87bda122009-07-03 16:37:30 -0700218 _ReloadManifest(self)
219 mp = self.manifest.manifestProject
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700221 all = self.GetProjects(args, missing_ok=True)
222 missing = []
223 for project in all:
224 if project.gitdir not in fetched:
225 missing.append(project)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700226 self._Fetch(missing)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700228 if self.manifest.IsMirror:
229 # bail out now, we have no working tree
230 return
231
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700232 if self.UpdateProjectList():
233 sys.exit(1)
234
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700235 syncbuf = SyncBuffer(mp.config,
236 detach_head = opt.detach_head)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700237 pm = Progress('Syncing work tree', len(all))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238 for project in all:
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700239 pm.update()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800240 if project.worktree:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700241 project.Sync_LocalHalf(syncbuf)
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700242 pm.end()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700243 print >>sys.stderr
244 if not syncbuf.Finish():
245 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246
Shawn O. Pearce87bda122009-07-03 16:37:30 -0700247def _ReloadManifest(cmd):
248 old = cmd.manifest
249 new = cmd.GetManifest(reparse=True)
250
251 if old.__class__ != new.__class__:
252 print >>sys.stderr, 'NOTICE: manifest format has changed ***'
253 new.Upgrade_Local(old)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700254
255def _PostRepoUpgrade(manifest):
256 for project in manifest.projects.values():
257 if project.Exists:
258 project.PostRepoUpgrade()
259
260def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
261 if rp.HasChanges:
262 print >>sys.stderr, 'info: A new version of repo is available'
263 print >>sys.stderr, ''
264 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700265 syncbuf = SyncBuffer(rp.config)
266 rp.Sync_LocalHalf(syncbuf)
267 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700268 sys.exit(1)
269 print >>sys.stderr, 'info: Restarting repo with latest version'
270 raise RepoChangedException(['--repo-upgraded'])
271 else:
272 print >>sys.stderr, 'warning: Skipped upgrade to unverified version'
273 else:
274 if verbose:
275 print >>sys.stderr, 'repo version %s is current' % rp.work_git.describe(HEAD)
276
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700277def _VerifyTag(project):
278 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
279 if not os.path.exists(gpg_dir):
280 print >>sys.stderr,\
281"""warning: GnuPG was not available during last "repo init"
282warning: Cannot automatically authenticate repo."""
283 return True
284
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700285 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700286 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700287 except GitError:
288 cur = None
289
290 if not cur \
291 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700292 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293 if rev.startswith(R_HEADS):
294 rev = rev[len(R_HEADS):]
295
296 print >>sys.stderr
297 print >>sys.stderr,\
298 "warning: project '%s' branch '%s' is not signed" \
299 % (project.name, rev)
300 return False
301
302 env = dict(os.environ)
303 env['GIT_DIR'] = project.gitdir
304 env['GNUPGHOME'] = gpg_dir
305
306 cmd = [GIT, 'tag', '-v', cur]
307 proc = subprocess.Popen(cmd,
308 stdout = subprocess.PIPE,
309 stderr = subprocess.PIPE,
310 env = env)
311 out = proc.stdout.read()
312 proc.stdout.close()
313
314 err = proc.stderr.read()
315 proc.stderr.close()
316
317 if proc.wait() != 0:
318 print >>sys.stderr
319 print >>sys.stderr, out
320 print >>sys.stderr, err
321 print >>sys.stderr
322 return False
323 return True