blob: 12595cd7a1abc1b62a3c5618d15f0f9ad341f907 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080015import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070016import filecmp
17import os
18import re
19import shutil
20import stat
21import sys
22import urllib2
23
24from color import Coloring
25from git_command import GitCommand
26from git_config import GitConfig, IsId
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027from error import GitError, ImportError, UploadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080028from error import ManifestInvalidRevisionError
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029
Shawn O. Pearced237b692009-04-17 18:49:50 -070030from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070032def _lwrite(path, content):
33 lock = '%s.lock' % path
34
35 fd = open(lock, 'wb')
36 try:
37 fd.write(content)
38 finally:
39 fd.close()
40
41 try:
42 os.rename(lock, path)
43 except OSError:
44 os.remove(lock)
45 raise
46
Shawn O. Pearce48244782009-04-16 08:25:57 -070047def _error(fmt, *args):
48 msg = fmt % args
49 print >>sys.stderr, 'error: %s' % msg
50
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051def not_rev(r):
52 return '^' + r
53
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080054def sq(r):
55 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080056
57hook_list = None
58def repo_hooks():
59 global hook_list
60 if hook_list is None:
61 d = os.path.abspath(os.path.dirname(__file__))
62 d = os.path.join(d , 'hooks')
63 hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
64 return hook_list
65
66def relpath(dst, src):
67 src = os.path.dirname(src)
68 top = os.path.commonprefix([dst, src])
69 if top.endswith('/'):
70 top = top[:-1]
71 else:
72 top = os.path.dirname(top)
73
74 tmp = src
75 rel = ''
76 while top != tmp:
77 rel += '../'
78 tmp = os.path.dirname(tmp)
79 return rel + dst[len(top) + 1:]
80
81
Shawn O. Pearce632768b2008-10-23 11:58:52 -070082class DownloadedChange(object):
83 _commit_cache = None
84
85 def __init__(self, project, base, change_id, ps_id, commit):
86 self.project = project
87 self.base = base
88 self.change_id = change_id
89 self.ps_id = ps_id
90 self.commit = commit
91
92 @property
93 def commits(self):
94 if self._commit_cache is None:
95 self._commit_cache = self.project.bare_git.rev_list(
96 '--abbrev=8',
97 '--abbrev-commit',
98 '--pretty=oneline',
99 '--reverse',
100 '--date-order',
101 not_rev(self.base),
102 self.commit,
103 '--')
104 return self._commit_cache
105
106
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700107class ReviewableBranch(object):
108 _commit_cache = None
109
110 def __init__(self, project, branch, base):
111 self.project = project
112 self.branch = branch
113 self.base = base
114
115 @property
116 def name(self):
117 return self.branch.name
118
119 @property
120 def commits(self):
121 if self._commit_cache is None:
122 self._commit_cache = self.project.bare_git.rev_list(
123 '--abbrev=8',
124 '--abbrev-commit',
125 '--pretty=oneline',
126 '--reverse',
127 '--date-order',
128 not_rev(self.base),
129 R_HEADS + self.name,
130 '--')
131 return self._commit_cache
132
133 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800134 def unabbrev_commits(self):
135 r = dict()
136 for commit in self.project.bare_git.rev_list(
137 not_rev(self.base),
138 R_HEADS + self.name,
139 '--'):
140 r[commit[0:8]] = commit
141 return r
142
143 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700144 def date(self):
145 return self.project.bare_git.log(
146 '--pretty=format:%cd',
147 '-n', '1',
148 R_HEADS + self.name,
149 '--')
150
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700151 def UploadForReview(self, people, auto_topic=False):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800152 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700153 people,
154 auto_topic=auto_topic)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700155
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700156 def GetPublishedRefs(self):
157 refs = {}
158 output = self.project.bare_git.ls_remote(
159 self.branch.remote.SshReviewUrl(self.project.UserEmail),
160 'refs/changes/*')
161 for line in output.split('\n'):
162 try:
163 (sha, ref) = line.split()
164 refs[sha] = ref
165 except ValueError:
166 pass
167
168 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
170class StatusColoring(Coloring):
171 def __init__(self, config):
172 Coloring.__init__(self, config, 'status')
173 self.project = self.printer('header', attr = 'bold')
174 self.branch = self.printer('header', attr = 'bold')
175 self.nobranch = self.printer('nobranch', fg = 'red')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700176 self.important = self.printer('important', fg = 'red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177
178 self.added = self.printer('added', fg = 'green')
179 self.changed = self.printer('changed', fg = 'red')
180 self.untracked = self.printer('untracked', fg = 'red')
181
182
183class DiffColoring(Coloring):
184 def __init__(self, config):
185 Coloring.__init__(self, config, 'diff')
186 self.project = self.printer('header', attr = 'bold')
187
188
189class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800190 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191 self.src = src
192 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800193 self.abs_src = abssrc
194 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800197 src = self.abs_src
198 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 # copy file if it does not exist or is out of date
200 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
201 try:
202 # remove existing file first, since it might be read-only
203 if os.path.exists(dest):
204 os.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400205 else:
206 dir = os.path.dirname(dest)
207 if not os.path.isdir(dir):
208 os.makedirs(dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209 shutil.copy(src, dest)
210 # make the file read-only
211 mode = os.stat(dest)[stat.ST_MODE]
212 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
213 os.chmod(dest, mode)
214 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700215 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700217class RemoteSpec(object):
218 def __init__(self,
219 name,
220 url = None,
221 review = None):
222 self.name = name
223 self.url = url
224 self.review = review
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
226class Project(object):
227 def __init__(self,
228 manifest,
229 name,
230 remote,
231 gitdir,
232 worktree,
233 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700234 revisionExpr,
235 revisionId):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236 self.manifest = manifest
237 self.name = name
238 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800239 self.gitdir = gitdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800240 if worktree:
241 self.worktree = worktree.replace('\\', '/')
242 else:
243 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700245 self.revisionExpr = revisionExpr
246
247 if revisionId is None \
248 and revisionExpr \
249 and IsId(revisionExpr):
250 self.revisionId = revisionExpr
251 else:
252 self.revisionId = revisionId
253
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 self.copyfiles = []
256 self.config = GitConfig.ForRepository(
257 gitdir = self.gitdir,
258 defaults = self.manifest.globalConfig)
259
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800260 if self.worktree:
261 self.work_git = self._GitGetByExec(self, bare=False)
262 else:
263 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700265 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700266
267 @property
268 def Exists(self):
269 return os.path.isdir(self.gitdir)
270
271 @property
272 def CurrentBranch(self):
273 """Obtain the name of the currently checked out branch.
274 The branch name omits the 'refs/heads/' prefix.
275 None is returned if the project is on a detached HEAD.
276 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700277 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700278 if b.startswith(R_HEADS):
279 return b[len(R_HEADS):]
280 return None
281
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700282 def IsRebaseInProgress(self):
283 w = self.worktree
284 g = os.path.join(w, '.git')
285 return os.path.exists(os.path.join(g, 'rebase-apply')) \
286 or os.path.exists(os.path.join(g, 'rebase-merge')) \
287 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700289 def IsDirty(self, consider_untracked=True):
290 """Is the working directory modified in some way?
291 """
292 self.work_git.update_index('-q',
293 '--unmerged',
294 '--ignore-missing',
295 '--refresh')
296 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
297 return True
298 if self.work_git.DiffZ('diff-files'):
299 return True
300 if consider_untracked and self.work_git.LsOthers():
301 return True
302 return False
303
304 _userident_name = None
305 _userident_email = None
306
307 @property
308 def UserName(self):
309 """Obtain the user's personal name.
310 """
311 if self._userident_name is None:
312 self._LoadUserIdentity()
313 return self._userident_name
314
315 @property
316 def UserEmail(self):
317 """Obtain the user's email address. This is very likely
318 to be their Gerrit login.
319 """
320 if self._userident_email is None:
321 self._LoadUserIdentity()
322 return self._userident_email
323
324 def _LoadUserIdentity(self):
325 u = self.bare_git.var('GIT_COMMITTER_IDENT')
326 m = re.compile("^(.*) <([^>]*)> ").match(u)
327 if m:
328 self._userident_name = m.group(1)
329 self._userident_email = m.group(2)
330 else:
331 self._userident_name = ''
332 self._userident_email = ''
333
334 def GetRemote(self, name):
335 """Get the configuration for a single remote.
336 """
337 return self.config.GetRemote(name)
338
339 def GetBranch(self, name):
340 """Get the configuration for a single branch.
341 """
342 return self.config.GetBranch(name)
343
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700344 def GetBranches(self):
345 """Get all existing local branches.
346 """
347 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700348 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700349 heads = {}
350 pubd = {}
351
352 for name, id in all.iteritems():
353 if name.startswith(R_HEADS):
354 name = name[len(R_HEADS):]
355 b = self.GetBranch(name)
356 b.current = name == current
357 b.published = None
358 b.revision = id
359 heads[name] = b
360
361 for name, id in all.iteritems():
362 if name.startswith(R_PUB):
363 name = name[len(R_PUB):]
364 b = heads.get(name)
365 if b:
366 b.published = id
367
368 return heads
369
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700370
371## Status Display ##
372
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500373 def HasChanges(self):
374 """Returns true if there are uncommitted changes.
375 """
376 self.work_git.update_index('-q',
377 '--unmerged',
378 '--ignore-missing',
379 '--refresh')
380 if self.IsRebaseInProgress():
381 return True
382
383 if self.work_git.DiffZ('diff-index', '--cached', HEAD):
384 return True
385
386 if self.work_git.DiffZ('diff-files'):
387 return True
388
389 if self.work_git.LsOthers():
390 return True
391
392 return False
393
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700394 def PrintWorkTreeStatus(self):
395 """Prints the status of the repository to stdout.
396 """
397 if not os.path.isdir(self.worktree):
398 print ''
399 print 'project %s/' % self.relpath
400 print ' missing (run "repo sync")'
401 return
402
403 self.work_git.update_index('-q',
404 '--unmerged',
405 '--ignore-missing',
406 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700407 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700408 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
409 df = self.work_git.DiffZ('diff-files')
410 do = self.work_git.LsOthers()
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700411 if not rb and not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700412 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700413
414 out = StatusColoring(self.config)
415 out.project('project %-40s', self.relpath + '/')
416
417 branch = self.CurrentBranch
418 if branch is None:
419 out.nobranch('(*** NO BRANCH ***)')
420 else:
421 out.branch('branch %s', branch)
422 out.nl()
423
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700424 if rb:
425 out.important('prior sync failed; rebase still in progress')
426 out.nl()
427
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700428 paths = list()
429 paths.extend(di.keys())
430 paths.extend(df.keys())
431 paths.extend(do)
432
433 paths = list(set(paths))
434 paths.sort()
435
436 for p in paths:
437 try: i = di[p]
438 except KeyError: i = None
439
440 try: f = df[p]
441 except KeyError: f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200442
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700443 if i: i_status = i.status.upper()
444 else: i_status = '-'
445
446 if f: f_status = f.status.lower()
447 else: f_status = '-'
448
449 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800450 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700451 i.src_path, p, i.level)
452 else:
453 line = ' %s%s\t%s' % (i_status, f_status, p)
454
455 if i and not f:
456 out.added('%s', line)
457 elif (i and f) or (not i and f):
458 out.changed('%s', line)
459 elif not i and not f:
460 out.untracked('%s', line)
461 else:
462 out.write('%s', line)
463 out.nl()
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700464 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700465
466 def PrintWorkTreeDiff(self):
467 """Prints the status of the repository to stdout.
468 """
469 out = DiffColoring(self.config)
470 cmd = ['diff']
471 if out.is_on:
472 cmd.append('--color')
473 cmd.append(HEAD)
474 cmd.append('--')
475 p = GitCommand(self,
476 cmd,
477 capture_stdout = True,
478 capture_stderr = True)
479 has_diff = False
480 for line in p.process.stdout:
481 if not has_diff:
482 out.nl()
483 out.project('project %s/' % self.relpath)
484 out.nl()
485 has_diff = True
486 print line[:-1]
487 p.Wait()
488
489
490## Publish / Upload ##
491
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700492 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700493 """Was the branch published (uploaded) for code review?
494 If so, returns the SHA-1 hash of the last published
495 state for the branch.
496 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700497 key = R_PUB + branch
498 if all is None:
499 try:
500 return self.bare_git.rev_parse(key)
501 except GitError:
502 return None
503 else:
504 try:
505 return all[key]
506 except KeyError:
507 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700508
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700509 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700510 """Prunes any stale published refs.
511 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700512 if all is None:
513 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 heads = set()
515 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700516 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700517 if name.startswith(R_HEADS):
518 heads.add(name)
519 elif name.startswith(R_PUB):
520 canrm[name] = id
521
522 for name, id in canrm.iteritems():
523 n = name[len(R_PUB):]
524 if R_HEADS + n not in heads:
525 self.bare_git.DeleteRef(name, id)
526
527 def GetUploadableBranches(self):
528 """List any branches which can be uploaded for review.
529 """
530 heads = {}
531 pubed = {}
532
533 for name, id in self._allrefs.iteritems():
534 if name.startswith(R_HEADS):
535 heads[name[len(R_HEADS):]] = id
536 elif name.startswith(R_PUB):
537 pubed[name[len(R_PUB):]] = id
538
539 ready = []
540 for branch, id in heads.iteritems():
541 if branch in pubed and pubed[branch] == id:
542 continue
543
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800544 rb = self.GetUploadableBranch(branch)
545 if rb:
546 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700547 return ready
548
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800549 def GetUploadableBranch(self, branch_name):
550 """Get a single uploadable branch, or None.
551 """
552 branch = self.GetBranch(branch_name)
553 base = branch.LocalMerge
554 if branch.LocalMerge:
555 rb = ReviewableBranch(self, branch, base)
556 if rb.commits:
557 return rb
558 return None
559
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700560 def UploadForReview(self, branch=None,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700561 people=([],[]),
562 auto_topic=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700563 """Uploads the named branch for code review.
564 """
565 if branch is None:
566 branch = self.CurrentBranch
567 if branch is None:
568 raise GitError('not currently on a branch')
569
570 branch = self.GetBranch(branch)
571 if not branch.LocalMerge:
572 raise GitError('branch %s does not track a remote' % branch.name)
573 if not branch.remote.review:
574 raise GitError('remote %s has no review url' % branch.remote.name)
575
576 dest_branch = branch.merge
577 if not dest_branch.startswith(R_HEADS):
578 dest_branch = R_HEADS + dest_branch
579
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800580 if not branch.remote.projectname:
581 branch.remote.projectname = self.name
582 branch.remote.Save()
583
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800584 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800585 if dest_branch.startswith(R_HEADS):
586 dest_branch = dest_branch[len(R_HEADS):]
587
588 rp = ['gerrit receive-pack']
589 for e in people[0]:
590 rp.append('--reviewer=%s' % sq(e))
591 for e in people[1]:
592 rp.append('--cc=%s' % sq(e))
593
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700594 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
595 if auto_topic:
596 ref_spec = ref_spec + '/' + branch.name
597
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800598 cmd = ['push']
599 cmd.append('--receive-pack=%s' % " ".join(rp))
600 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700601 cmd.append(ref_spec)
602
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800603 if GitCommand(self, cmd, bare = True).Wait() != 0:
604 raise UploadError('Upload failed')
605
606 else:
607 raise UploadError('Unsupported protocol %s' \
608 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609
610 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
611 self.bare_git.UpdateRef(R_PUB + branch.name,
612 R_HEADS + branch.name,
613 message = msg)
614
615
616## Sync ##
617
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700618 def Sync_NetworkHalf(self, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619 """Perform only the network IO portion of the sync process.
620 Local working directory/branch state is not affected.
621 """
Shawn O. Pearce88443382010-10-08 10:02:09 +0200622 is_new = not self.Exists
623 if is_new:
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700624 if not quiet:
625 print >>sys.stderr
626 print >>sys.stderr, 'Initializing project %s ...' % self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800628
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629 self._InitRemote()
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700630 if not self._RemoteFetch(initial=is_new, quiet=quiet):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800632
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200633 #Check that the requested ref was found after fetch
634 #
635 try:
636 self.GetRevisionId()
637 except ManifestInvalidRevisionError:
638 # if the ref is a tag. We can try fetching
639 # the tag manually as a last resort
640 #
641 rev = self.revisionExpr
642 if rev.startswith(R_TAGS):
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700643 self._RemoteFetch(None, rev[len(R_TAGS):], quiet=quiet)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200644
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800645 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800646 self._InitMRef()
647 else:
648 self._InitMirrorHead()
649 try:
650 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
651 except OSError:
652 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800654
655 def PostRepoUpgrade(self):
656 self._InitHooks()
657
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700658 def _CopyFiles(self):
659 for file in self.copyfiles:
660 file._Copy()
661
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700662 def GetRevisionId(self, all=None):
663 if self.revisionId:
664 return self.revisionId
665
666 rem = self.GetRemote(self.remote.name)
667 rev = rem.ToLocal(self.revisionExpr)
668
669 if all is not None and rev in all:
670 return all[rev]
671
672 try:
673 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
674 except GitError:
675 raise ManifestInvalidRevisionError(
676 'revision %s in %s not found' % (self.revisionExpr,
677 self.name))
678
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700679 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680 """Perform only the local IO portion of the sync process.
681 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682 """
683 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700684 all = self.bare_ref.all
685 self.CleanPublishedCache(all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700687 revid = self.GetRevisionId(all)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700688 head = self.work_git.GetHead()
689 if head.startswith(R_HEADS):
690 branch = head[len(R_HEADS):]
691 try:
692 head = all[head]
693 except KeyError:
694 head = None
695 else:
696 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700698 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699 # Currently on a detached HEAD. The user is assumed to
700 # not have any local modifications worth worrying about.
701 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700702 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700703 syncbuf.fail(self, _PriorSyncFailedError())
704 return
705
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700706 if head == revid:
707 # No changes; don't do anything further.
708 #
709 return
710
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700711 lost = self._revlist(not_rev(revid), HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700713 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700714 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700715 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700716 except GitError, e:
717 syncbuf.fail(self, e)
718 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700720 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700722 if head == revid:
723 # No changes; don't do anything further.
724 #
725 return
726
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700727 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700729 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730 # The current branch has no tracking configuration.
731 # Jump off it to a deatched HEAD.
732 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700733 syncbuf.info(self,
734 "leaving %s; does not track upstream",
735 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700737 self._Checkout(revid, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700738 except GitError, e:
739 syncbuf.fail(self, e)
740 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700742 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700744 upstream_gain = self._revlist(not_rev(HEAD), revid)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700745 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700747 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748 if not_merged:
749 if upstream_gain:
750 # The user has published this branch and some of those
751 # commits are not yet merged upstream. We do not want
752 # to rewrite the published commits so we punt.
753 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -0500754 syncbuf.fail(self,
755 "branch %s is published (but not merged) and is now %d commits behind"
756 % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700757 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -0700758 elif pub == head:
759 # All published commits are merged, and thus we are a
760 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -0700761 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700762 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700763 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700764 self._CopyFiles()
765 syncbuf.later1(self, _doff)
766 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700768 # Examine the local commits not in the remote. Find the
769 # last one attributed to this user, if any.
770 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700771 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700772 last_mine = None
773 cnt_mine = 0
774 for commit in local_changes:
Shawn O. Pearceaa4982e2009-12-30 18:38:27 -0800775 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700776 if committer_email == self.UserEmail:
777 last_mine = commit_id
778 cnt_mine += 1
779
Shawn O. Pearceda88ff42009-06-03 11:09:12 -0700780 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700781 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700782
783 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700784 syncbuf.fail(self, _DirtyError())
785 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700787 # If the upstream switched on us, warn the user.
788 #
789 if branch.merge != self.revisionExpr:
790 if branch.merge and self.revisionExpr:
791 syncbuf.info(self,
792 'manifest switched %s...%s',
793 branch.merge,
794 self.revisionExpr)
795 elif branch.merge:
796 syncbuf.info(self,
797 'manifest no longer tracks %s',
798 branch.merge)
799
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700800 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700802 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700804 syncbuf.info(self,
805 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700806 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700808 branch.remote = self.GetRemote(self.remote.name)
809 branch.merge = self.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 branch.Save()
811
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700812 if cnt_mine > 0:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700813 def _dorebase():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700814 self._Rebase(upstream = '%s^1' % last_mine, onto = revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700815 self._CopyFiles()
816 syncbuf.later2(self, _dorebase)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -0700817 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700819 self._ResetHard(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700820 self._CopyFiles()
821 except GitError, e:
822 syncbuf.fail(self, e)
823 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700825 def _doff():
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700826 self._FastForward(revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700827 self._CopyFiles()
828 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800830 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700831 # dest should already be an absolute path, but src is project relative
832 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800833 abssrc = os.path.join(self.worktree, src)
834 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700836 def DownloadPatchSet(self, change_id, patch_id):
837 """Download a single patch set of a single change to FETCH_HEAD.
838 """
839 remote = self.GetRemote(self.remote.name)
840
841 cmd = ['fetch', remote.name]
842 cmd.append('refs/changes/%2.2d/%d/%d' \
843 % (change_id % 100, change_id, patch_id))
844 cmd.extend(map(lambda x: str(x), remote.fetch))
845 if GitCommand(self, cmd, bare=True).Wait() != 0:
846 return None
847 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700848 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700849 change_id,
850 patch_id,
851 self.bare_git.rev_parse('FETCH_HEAD'))
852
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853
854## Branch Management ##
855
856 def StartBranch(self, name):
857 """Create a new branch off the manifest's revision.
858 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700859 head = self.work_git.GetHead()
860 if head == (R_HEADS + name):
861 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700863 all = self.bare_ref.all
864 if (R_HEADS + name) in all:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700865 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -0700866 ['checkout', name, '--'],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -0700867 capture_stdout = True,
868 capture_stderr = True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700869
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700870 branch = self.GetBranch(name)
871 branch.remote = self.GetRemote(self.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700872 branch.merge = self.revisionExpr
873 revid = self.GetRevisionId(all)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700874
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700875 if head.startswith(R_HEADS):
876 try:
877 head = all[head]
878 except KeyError:
879 head = None
880
881 if revid and head and revid == head:
882 ref = os.path.join(self.gitdir, R_HEADS + name)
883 try:
884 os.makedirs(os.path.dirname(ref))
885 except OSError:
886 pass
887 _lwrite(ref, '%s\n' % revid)
888 _lwrite(os.path.join(self.worktree, '.git', HEAD),
889 'ref: %s%s\n' % (R_HEADS, name))
890 branch.Save()
891 return True
892
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700893 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700894 ['checkout', '-b', branch.name, revid],
Shawn O. Pearce0f0dfa32009-04-18 14:53:39 -0700895 capture_stdout = True,
896 capture_stderr = True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700897 branch.Save()
898 return True
899 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900
Wink Saville02d79452009-04-10 13:01:24 -0700901 def CheckoutBranch(self, name):
902 """Checkout a local topic branch.
903 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -0700904 rev = R_HEADS + name
905 head = self.work_git.GetHead()
906 if head == rev:
907 # Already on the branch
908 #
909 return True
Wink Saville02d79452009-04-10 13:01:24 -0700910
Shawn O. Pearce89e717d2009-04-18 15:04:41 -0700911 all = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -0700912 try:
Shawn O. Pearce89e717d2009-04-18 15:04:41 -0700913 revid = all[rev]
914 except KeyError:
915 # Branch does not exist in this project
916 #
917 return False
918
919 if head.startswith(R_HEADS):
920 try:
921 head = all[head]
922 except KeyError:
923 head = None
924
925 if head == revid:
926 # Same revision; just update HEAD to point to the new
927 # target branch, but otherwise take no other action.
928 #
929 _lwrite(os.path.join(self.worktree, '.git', HEAD),
930 'ref: %s%s\n' % (R_HEADS, name))
931 return True
Wink Saville02d79452009-04-10 13:01:24 -0700932
Shawn O. Pearce89e717d2009-04-18 15:04:41 -0700933 return GitCommand(self,
934 ['checkout', name, '--'],
935 capture_stdout = True,
936 capture_stderr = True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -0700937
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -0800938 def AbandonBranch(self, name):
939 """Destroy a local topic branch.
940 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -0700941 rev = R_HEADS + name
942 all = self.bare_ref.all
943 if rev not in all:
944 # Doesn't exist; assume already abandoned.
945 #
946 return True
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -0800947
Shawn O. Pearce552ac892009-04-18 15:15:24 -0700948 head = self.work_git.GetHead()
949 if head == rev:
950 # We can't destroy the branch while we are sitting
951 # on it. Switch to a detached HEAD.
952 #
953 head = all[head]
954
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700955 revid = self.GetRevisionId(all)
956 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -0700957 _lwrite(os.path.join(self.worktree, '.git', HEAD),
958 '%s\n' % revid)
959 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700960 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -0800961
Shawn O. Pearce552ac892009-04-18 15:15:24 -0700962 return GitCommand(self,
963 ['branch', '-D', name],
964 capture_stdout = True,
965 capture_stderr = True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -0800966
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 def PruneHeads(self):
968 """Prune any topic branches already merged into upstream.
969 """
970 cb = self.CurrentBranch
971 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800972 left = self._allrefs
973 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974 if name.startswith(R_HEADS):
975 name = name[len(R_HEADS):]
976 if cb is None or name != cb:
977 kill.append(name)
978
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700979 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980 if cb is not None \
981 and not self._revlist(HEAD + '...' + rev) \
982 and not self.IsDirty(consider_untracked = False):
983 self.work_git.DetachHead(HEAD)
984 kill.append(cb)
985
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700987 old = self.bare_git.GetHead()
988 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 old = 'refs/heads/please_never_use_this_as_a_branch_name'
990
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 try:
992 self.bare_git.DetachHead(rev)
993
994 b = ['branch', '-d']
995 b.extend(kill)
996 b = GitCommand(self, b, bare=True,
997 capture_stdout=True,
998 capture_stderr=True)
999 b.Wait()
1000 finally:
1001 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001002 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001004 for branch in kill:
1005 if (R_HEADS + branch) not in left:
1006 self.CleanPublishedCache()
1007 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008
1009 if cb and cb not in kill:
1010 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001011 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012
1013 kept = []
1014 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001015 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016 branch = self.GetBranch(branch)
1017 base = branch.LocalMerge
1018 if not base:
1019 base = rev
1020 kept.append(ReviewableBranch(self, branch, base))
1021 return kept
1022
1023
1024## Direct Git Commands ##
1025
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001026 def _RemoteFetch(self, name=None, tag=None,
1027 initial=False,
1028 quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001029 if not name:
1030 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07001031
1032 ssh_proxy = False
1033 if self.GetRemote(name).PreConnectFetch():
1034 ssh_proxy = True
1035
Shawn O. Pearce88443382010-10-08 10:02:09 +02001036 if initial:
1037 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1038 try:
1039 fd = open(alt, 'rb')
1040 try:
1041 ref_dir = fd.readline()
1042 if ref_dir and ref_dir.endswith('\n'):
1043 ref_dir = ref_dir[:-1]
1044 finally:
1045 fd.close()
1046 except IOError, e:
1047 ref_dir = None
1048
1049 if ref_dir and 'objects' == os.path.basename(ref_dir):
1050 ref_dir = os.path.dirname(ref_dir)
1051 packed_refs = os.path.join(self.gitdir, 'packed-refs')
1052 remote = self.GetRemote(name)
1053
1054 all = self.bare_ref.all
1055 ids = set(all.values())
1056 tmp = set()
1057
1058 for r, id in GitRefs(ref_dir).all.iteritems():
1059 if r not in all:
1060 if r.startswith(R_TAGS) or remote.WritesTo(r):
1061 all[r] = id
1062 ids.add(id)
1063 continue
1064
1065 if id in ids:
1066 continue
1067
1068 r = 'refs/_alt/%s' % id
1069 all[r] = id
1070 ids.add(id)
1071 tmp.add(r)
1072
1073 ref_names = list(all.keys())
1074 ref_names.sort()
1075
1076 tmp_packed = ''
1077 old_packed = ''
1078
1079 for r in ref_names:
1080 line = '%s %s\n' % (all[r], r)
1081 tmp_packed += line
1082 if r not in tmp:
1083 old_packed += line
1084
1085 _lwrite(packed_refs, tmp_packed)
1086
1087 else:
1088 ref_dir = None
1089
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001090 cmd = ['fetch']
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001091 if quiet:
1092 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001093 if not self.worktree:
1094 cmd.append('--update-head-ok')
1095 cmd.append(name)
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001096 if tag is not None:
1097 cmd.append('tag')
1098 cmd.append(tag)
Shawn O. Pearce88443382010-10-08 10:02:09 +02001099
1100 ok = GitCommand(self,
1101 cmd,
1102 bare = True,
1103 ssh_proxy = ssh_proxy).Wait() == 0
1104
1105 if initial:
1106 if ref_dir:
1107 if old_packed != '':
1108 _lwrite(packed_refs, old_packed)
1109 else:
1110 os.remove(packed_refs)
1111 self.bare_git.pack_refs('--all', '--prune')
1112
1113 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114
1115 def _Checkout(self, rev, quiet=False):
1116 cmd = ['checkout']
1117 if quiet:
1118 cmd.append('-q')
1119 cmd.append(rev)
1120 cmd.append('--')
1121 if GitCommand(self, cmd).Wait() != 0:
1122 if self._allrefs:
1123 raise GitError('%s checkout %s ' % (self.name, rev))
1124
1125 def _ResetHard(self, rev, quiet=True):
1126 cmd = ['reset', '--hard']
1127 if quiet:
1128 cmd.append('-q')
1129 cmd.append(rev)
1130 if GitCommand(self, cmd).Wait() != 0:
1131 raise GitError('%s reset --hard %s ' % (self.name, rev))
1132
1133 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001134 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 if onto is not None:
1136 cmd.extend(['--onto', onto])
1137 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07001138 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001139 raise GitError('%s rebase %s ' % (self.name, upstream))
1140
1141 def _FastForward(self, head):
1142 cmd = ['merge', head]
1143 if GitCommand(self, cmd).Wait() != 0:
1144 raise GitError('%s merge %s ' % (self.name, head))
1145
1146 def _InitGitDir(self):
1147 if not os.path.exists(self.gitdir):
1148 os.makedirs(self.gitdir)
1149 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001150
Shawn O. Pearce88443382010-10-08 10:02:09 +02001151 mp = self.manifest.manifestProject
1152 ref_dir = mp.config.GetString('repo.reference')
1153
1154 if ref_dir:
1155 mirror_git = os.path.join(ref_dir, self.name + '.git')
1156 repo_git = os.path.join(ref_dir, '.repo', 'projects',
1157 self.relpath + '.git')
1158
1159 if os.path.exists(mirror_git):
1160 ref_dir = mirror_git
1161
1162 elif os.path.exists(repo_git):
1163 ref_dir = repo_git
1164
1165 else:
1166 ref_dir = None
1167
1168 if ref_dir:
1169 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
1170 os.path.join(ref_dir, 'objects') + '\n')
1171
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08001172 if self.manifest.IsMirror:
1173 self.config.SetString('core.bare', 'true')
1174 else:
1175 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
1177 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -07001178 try:
1179 to_rm = os.listdir(hooks)
1180 except OSError:
1181 to_rm = []
1182 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001184 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001185
1186 m = self.manifest.manifestProject.config
1187 for key in ['user.name', 'user.email']:
1188 if m.Has(key, include_defaults = False):
1189 self.config.SetString(key, m.GetString(key))
1190
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001191 def _InitHooks(self):
1192 hooks = self._gitdir_path('hooks')
1193 if not os.path.exists(hooks):
1194 os.makedirs(hooks)
1195 for stock_hook in repo_hooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001196 name = os.path.basename(stock_hook)
1197
1198 if name in ('commit-msg') and not self.remote.review:
1199 # Don't install a Gerrit Code Review hook if this
1200 # project does not appear to use it for reviews.
1201 #
1202 continue
1203
1204 dst = os.path.join(hooks, name)
1205 if os.path.islink(dst):
1206 continue
1207 if os.path.exists(dst):
1208 if filecmp.cmp(stock_hook, dst, shallow=False):
1209 os.remove(dst)
1210 else:
1211 _error("%s: Not replacing %s hook", self.relpath, name)
1212 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001213 try:
1214 os.symlink(relpath(stock_hook, dst), dst)
1215 except OSError, e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07001216 if e.errno == errno.EPERM:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001217 raise GitError('filesystem must support symlinks')
1218 else:
1219 raise
1220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001222 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001224 remote.url = self.remote.url
1225 remote.review = self.remote.review
1226 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001228 if self.worktree:
1229 remote.ResetFetch(mirror=False)
1230 else:
1231 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001232 remote.Save()
1233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234 def _InitMRef(self):
1235 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001236 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001238 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07001239 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001240
1241 def _InitAnyMRef(self, ref):
1242 cur = self.bare_ref.symref(ref)
1243
1244 if self.revisionId:
1245 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
1246 msg = 'manifest set to %s' % self.revisionId
1247 dst = self.revisionId + '^0'
1248 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
1249 else:
1250 remote = self.GetRemote(self.remote.name)
1251 dst = remote.ToLocal(self.revisionExpr)
1252 if cur != dst:
1253 msg = 'manifest set to %s' % self.revisionExpr
1254 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001255
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256 def _InitWorkTree(self):
1257 dotgit = os.path.join(self.worktree, '.git')
1258 if not os.path.exists(dotgit):
1259 os.makedirs(dotgit)
1260
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261 for name in ['config',
1262 'description',
1263 'hooks',
1264 'info',
1265 'logs',
1266 'objects',
1267 'packed-refs',
1268 'refs',
1269 'rr-cache',
1270 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001271 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001272 src = os.path.join(self.gitdir, name)
1273 dst = os.path.join(dotgit, name)
Nico Sallembiend63060f2010-01-20 10:27:50 -08001274 if os.path.islink(dst) or not os.path.exists(dst):
1275 os.symlink(relpath(src, dst), dst)
1276 else:
1277 raise GitError('cannot overwrite a local work tree')
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001278 except OSError, e:
1279 if e.errno == errno.EPERM:
1280 raise GitError('filesystem must support symlinks')
1281 else:
1282 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001284 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285
1286 cmd = ['read-tree', '--reset', '-u']
1287 cmd.append('-v')
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001288 cmd.append(HEAD)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 if GitCommand(self, cmd).Wait() != 0:
1290 raise GitError("cannot initialize work tree")
Shawn O. Pearce93609662009-04-21 10:50:33 -07001291 self._CopyFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
1293 def _gitdir_path(self, path):
1294 return os.path.join(self.gitdir, path)
1295
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001296 def _revlist(self, *args, **kw):
1297 a = []
1298 a.extend(args)
1299 a.append('--')
1300 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301
1302 @property
1303 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001304 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305
1306 class _GitGetByExec(object):
1307 def __init__(self, project, bare):
1308 self._project = project
1309 self._bare = bare
1310
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 def LsOthers(self):
1312 p = GitCommand(self._project,
1313 ['ls-files',
1314 '-z',
1315 '--others',
1316 '--exclude-standard'],
1317 bare = False,
1318 capture_stdout = True,
1319 capture_stderr = True)
1320 if p.Wait() == 0:
1321 out = p.stdout
1322 if out:
1323 return out[:-1].split("\0")
1324 return []
1325
1326 def DiffZ(self, name, *args):
1327 cmd = [name]
1328 cmd.append('-z')
1329 cmd.extend(args)
1330 p = GitCommand(self._project,
1331 cmd,
1332 bare = False,
1333 capture_stdout = True,
1334 capture_stderr = True)
1335 try:
1336 out = p.process.stdout.read()
1337 r = {}
1338 if out:
1339 out = iter(out[:-1].split('\0'))
1340 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001341 try:
1342 info = out.next()
1343 path = out.next()
1344 except StopIteration:
1345 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346
1347 class _Info(object):
1348 def __init__(self, path, omode, nmode, oid, nid, state):
1349 self.path = path
1350 self.src_path = None
1351 self.old_mode = omode
1352 self.new_mode = nmode
1353 self.old_id = oid
1354 self.new_id = nid
1355
1356 if len(state) == 1:
1357 self.status = state
1358 self.level = None
1359 else:
1360 self.status = state[:1]
1361 self.level = state[1:]
1362 while self.level.startswith('0'):
1363 self.level = self.level[1:]
1364
1365 info = info[1:].split(' ')
1366 info =_Info(path, *info)
1367 if info.status in ('R', 'C'):
1368 info.src_path = info.path
1369 info.path = out.next()
1370 r[info.path] = info
1371 return r
1372 finally:
1373 p.Wait()
1374
1375 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001376 if self._bare:
1377 path = os.path.join(self._project.gitdir, HEAD)
1378 else:
1379 path = os.path.join(self._project.worktree, '.git', HEAD)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07001380 fd = open(path, 'rb')
1381 try:
1382 line = fd.read()
1383 finally:
1384 fd.close()
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001385 if line.startswith('ref: '):
1386 return line[5:-1]
1387 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001388
1389 def SetHead(self, ref, message=None):
1390 cmdv = []
1391 if message is not None:
1392 cmdv.extend(['-m', message])
1393 cmdv.append(HEAD)
1394 cmdv.append(ref)
1395 self.symbolic_ref(*cmdv)
1396
1397 def DetachHead(self, new, message=None):
1398 cmdv = ['--no-deref']
1399 if message is not None:
1400 cmdv.extend(['-m', message])
1401 cmdv.append(HEAD)
1402 cmdv.append(new)
1403 self.update_ref(*cmdv)
1404
1405 def UpdateRef(self, name, new, old=None,
1406 message=None,
1407 detach=False):
1408 cmdv = []
1409 if message is not None:
1410 cmdv.extend(['-m', message])
1411 if detach:
1412 cmdv.append('--no-deref')
1413 cmdv.append(name)
1414 cmdv.append(new)
1415 if old is not None:
1416 cmdv.append(old)
1417 self.update_ref(*cmdv)
1418
1419 def DeleteRef(self, name, old=None):
1420 if not old:
1421 old = self.rev_parse(name)
1422 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001423 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001425 def rev_list(self, *args, **kw):
1426 if 'format' in kw:
1427 cmdv = ['log', '--pretty=format:%s' % kw['format']]
1428 else:
1429 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430 cmdv.extend(args)
1431 p = GitCommand(self._project,
1432 cmdv,
1433 bare = self._bare,
1434 capture_stdout = True,
1435 capture_stderr = True)
1436 r = []
1437 for line in p.process.stdout:
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001438 if line[-1] == '\n':
1439 line = line[:-1]
1440 r.append(line)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001441 if p.Wait() != 0:
1442 raise GitError('%s rev-list %s: %s' % (
1443 self._project.name,
1444 str(args),
1445 p.stderr))
1446 return r
1447
1448 def __getattr__(self, name):
1449 name = name.replace('_', '-')
1450 def runner(*args):
1451 cmdv = [name]
1452 cmdv.extend(args)
1453 p = GitCommand(self._project,
1454 cmdv,
1455 bare = self._bare,
1456 capture_stdout = True,
1457 capture_stderr = True)
1458 if p.Wait() != 0:
1459 raise GitError('%s %s: %s' % (
1460 self._project.name,
1461 name,
1462 p.stderr))
1463 r = p.stdout
1464 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1465 return r[:-1]
1466 return r
1467 return runner
1468
1469
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001470class _PriorSyncFailedError(Exception):
1471 def __str__(self):
1472 return 'prior sync failed; rebase still in progress'
1473
1474class _DirtyError(Exception):
1475 def __str__(self):
1476 return 'contains uncommitted changes'
1477
1478class _InfoMessage(object):
1479 def __init__(self, project, text):
1480 self.project = project
1481 self.text = text
1482
1483 def Print(self, syncbuf):
1484 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1485 syncbuf.out.nl()
1486
1487class _Failure(object):
1488 def __init__(self, project, why):
1489 self.project = project
1490 self.why = why
1491
1492 def Print(self, syncbuf):
1493 syncbuf.out.fail('error: %s/: %s',
1494 self.project.relpath,
1495 str(self.why))
1496 syncbuf.out.nl()
1497
1498class _Later(object):
1499 def __init__(self, project, action):
1500 self.project = project
1501 self.action = action
1502
1503 def Run(self, syncbuf):
1504 out = syncbuf.out
1505 out.project('project %s/', self.project.relpath)
1506 out.nl()
1507 try:
1508 self.action()
1509 out.nl()
1510 return True
1511 except GitError, e:
1512 out.nl()
1513 return False
1514
1515class _SyncColoring(Coloring):
1516 def __init__(self, config):
1517 Coloring.__init__(self, config, 'reposync')
1518 self.project = self.printer('header', attr = 'bold')
1519 self.info = self.printer('info')
1520 self.fail = self.printer('fail', fg='red')
1521
1522class SyncBuffer(object):
1523 def __init__(self, config, detach_head=False):
1524 self._messages = []
1525 self._failures = []
1526 self._later_queue1 = []
1527 self._later_queue2 = []
1528
1529 self.out = _SyncColoring(config)
1530 self.out.redirect(sys.stderr)
1531
1532 self.detach_head = detach_head
1533 self.clean = True
1534
1535 def info(self, project, fmt, *args):
1536 self._messages.append(_InfoMessage(project, fmt % args))
1537
1538 def fail(self, project, err=None):
1539 self._failures.append(_Failure(project, err))
1540 self.clean = False
1541
1542 def later1(self, project, what):
1543 self._later_queue1.append(_Later(project, what))
1544
1545 def later2(self, project, what):
1546 self._later_queue2.append(_Later(project, what))
1547
1548 def Finish(self):
1549 self._PrintMessages()
1550 self._RunLater()
1551 self._PrintMessages()
1552 return self.clean
1553
1554 def _RunLater(self):
1555 for q in ['_later_queue1', '_later_queue2']:
1556 if not self._RunQueue(q):
1557 return
1558
1559 def _RunQueue(self, queue):
1560 for m in getattr(self, queue):
1561 if not m.Run(self):
1562 self.clean = False
1563 return False
1564 setattr(self, queue, [])
1565 return True
1566
1567 def _PrintMessages(self):
1568 for m in self._messages:
1569 m.Print(self)
1570 for m in self._failures:
1571 m.Print(self)
1572
1573 self._messages = []
1574 self._failures = []
1575
1576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577class MetaProject(Project):
1578 """A special project housed under .repo.
1579 """
1580 def __init__(self, manifest, name, gitdir, worktree):
1581 repodir = manifest.repodir
1582 Project.__init__(self,
1583 manifest = manifest,
1584 name = name,
1585 gitdir = gitdir,
1586 worktree = worktree,
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07001587 remote = RemoteSpec('origin'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001588 relpath = '.repo/%s' % name,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001589 revisionExpr = 'refs/heads/master',
1590 revisionId = None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001591
1592 def PreSync(self):
1593 if self.Exists:
1594 cb = self.CurrentBranch
1595 if cb:
1596 base = self.GetBranch(cb).merge
1597 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001598 self.revisionExpr = base
1599 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001600
1601 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07001602 def LastFetch(self):
1603 try:
1604 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
1605 return os.path.getmtime(fh)
1606 except OSError:
1607 return 0
1608
1609 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610 def HasChanges(self):
1611 """Has the remote received new commits not yet checked out?
1612 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001613 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001614 return False
1615
1616 all = self.bare_ref.all
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001617 revid = self.GetRevisionId(all)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001618 head = self.work_git.GetHead()
1619 if head.startswith(R_HEADS):
1620 try:
1621 head = all[head]
1622 except KeyError:
1623 head = None
1624
1625 if revid == head:
1626 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001627 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001628 return True
1629 return False