blob: 311379cac081a2c3fca90bf45822f38b1799ae06 [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 -070029from remote import Remote
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030
31HEAD = 'HEAD'
32R_HEADS = 'refs/heads/'
33R_TAGS = 'refs/tags/'
34R_PUB = 'refs/published/'
35R_M = 'refs/remotes/m/'
36
Shawn O. Pearce48244782009-04-16 08:25:57 -070037def _error(fmt, *args):
38 msg = fmt % args
39 print >>sys.stderr, 'error: %s' % msg
40
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041def not_rev(r):
42 return '^' + r
43
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080044def sq(r):
45 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080046
47hook_list = None
48def repo_hooks():
49 global hook_list
50 if hook_list is None:
51 d = os.path.abspath(os.path.dirname(__file__))
52 d = os.path.join(d , 'hooks')
53 hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
54 return hook_list
55
56def relpath(dst, src):
57 src = os.path.dirname(src)
58 top = os.path.commonprefix([dst, src])
59 if top.endswith('/'):
60 top = top[:-1]
61 else:
62 top = os.path.dirname(top)
63
64 tmp = src
65 rel = ''
66 while top != tmp:
67 rel += '../'
68 tmp = os.path.dirname(tmp)
69 return rel + dst[len(top) + 1:]
70
71
Shawn O. Pearce632768b2008-10-23 11:58:52 -070072class DownloadedChange(object):
73 _commit_cache = None
74
75 def __init__(self, project, base, change_id, ps_id, commit):
76 self.project = project
77 self.base = base
78 self.change_id = change_id
79 self.ps_id = ps_id
80 self.commit = commit
81
82 @property
83 def commits(self):
84 if self._commit_cache is None:
85 self._commit_cache = self.project.bare_git.rev_list(
86 '--abbrev=8',
87 '--abbrev-commit',
88 '--pretty=oneline',
89 '--reverse',
90 '--date-order',
91 not_rev(self.base),
92 self.commit,
93 '--')
94 return self._commit_cache
95
96
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070097class ReviewableBranch(object):
98 _commit_cache = None
99
100 def __init__(self, project, branch, base):
101 self.project = project
102 self.branch = branch
103 self.base = base
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800104 self.replace_changes = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700105
106 @property
107 def name(self):
108 return self.branch.name
109
110 @property
111 def commits(self):
112 if self._commit_cache is None:
113 self._commit_cache = self.project.bare_git.rev_list(
114 '--abbrev=8',
115 '--abbrev-commit',
116 '--pretty=oneline',
117 '--reverse',
118 '--date-order',
119 not_rev(self.base),
120 R_HEADS + self.name,
121 '--')
122 return self._commit_cache
123
124 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800125 def unabbrev_commits(self):
126 r = dict()
127 for commit in self.project.bare_git.rev_list(
128 not_rev(self.base),
129 R_HEADS + self.name,
130 '--'):
131 r[commit[0:8]] = commit
132 return r
133
134 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135 def date(self):
136 return self.project.bare_git.log(
137 '--pretty=format:%cd',
138 '-n', '1',
139 R_HEADS + self.name,
140 '--')
141
Joe Onorato2896a792008-11-17 16:56:36 -0500142 def UploadForReview(self, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800143 self.project.UploadForReview(self.name,
Joe Onorato2896a792008-11-17 16:56:36 -0500144 self.replace_changes,
145 people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146
147 @property
148 def tip_url(self):
149 me = self.project.GetBranch(self.name)
150 commit = self.project.bare_git.rev_parse(R_HEADS + self.name)
151 return 'http://%s/r/%s' % (me.remote.review, commit[0:12])
152
Shawn O. Pearce0758d2f2008-10-22 13:13:40 -0700153 @property
154 def owner_email(self):
155 return self.project.UserEmail
156
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157
158class StatusColoring(Coloring):
159 def __init__(self, config):
160 Coloring.__init__(self, config, 'status')
161 self.project = self.printer('header', attr = 'bold')
162 self.branch = self.printer('header', attr = 'bold')
163 self.nobranch = self.printer('nobranch', fg = 'red')
164
165 self.added = self.printer('added', fg = 'green')
166 self.changed = self.printer('changed', fg = 'red')
167 self.untracked = self.printer('untracked', fg = 'red')
168
169
170class DiffColoring(Coloring):
171 def __init__(self, config):
172 Coloring.__init__(self, config, 'diff')
173 self.project = self.printer('header', attr = 'bold')
174
175
176class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800177 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 self.src = src
179 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800180 self.abs_src = abssrc
181 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
183 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800184 src = self.abs_src
185 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186 # copy file if it does not exist or is out of date
187 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
188 try:
189 # remove existing file first, since it might be read-only
190 if os.path.exists(dest):
191 os.remove(dest)
192 shutil.copy(src, dest)
193 # make the file read-only
194 mode = os.stat(dest)[stat.ST_MODE]
195 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
196 os.chmod(dest, mode)
197 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700198 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199
200
201class Project(object):
202 def __init__(self,
203 manifest,
204 name,
205 remote,
206 gitdir,
207 worktree,
208 relpath,
209 revision):
210 self.manifest = manifest
211 self.name = name
212 self.remote = remote
213 self.gitdir = gitdir
214 self.worktree = worktree
215 self.relpath = relpath
216 self.revision = revision
217 self.snapshots = {}
218 self.extraRemotes = {}
219 self.copyfiles = []
220 self.config = GitConfig.ForRepository(
221 gitdir = self.gitdir,
222 defaults = self.manifest.globalConfig)
223
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800224 if self.worktree:
225 self.work_git = self._GitGetByExec(self, bare=False)
226 else:
227 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 self.bare_git = self._GitGetByExec(self, bare=True)
229
230 @property
231 def Exists(self):
232 return os.path.isdir(self.gitdir)
233
234 @property
235 def CurrentBranch(self):
236 """Obtain the name of the currently checked out branch.
237 The branch name omits the 'refs/heads/' prefix.
238 None is returned if the project is on a detached HEAD.
239 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700240 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 if b.startswith(R_HEADS):
242 return b[len(R_HEADS):]
243 return None
244
245 def IsDirty(self, consider_untracked=True):
246 """Is the working directory modified in some way?
247 """
248 self.work_git.update_index('-q',
249 '--unmerged',
250 '--ignore-missing',
251 '--refresh')
252 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
253 return True
254 if self.work_git.DiffZ('diff-files'):
255 return True
256 if consider_untracked and self.work_git.LsOthers():
257 return True
258 return False
259
260 _userident_name = None
261 _userident_email = None
262
263 @property
264 def UserName(self):
265 """Obtain the user's personal name.
266 """
267 if self._userident_name is None:
268 self._LoadUserIdentity()
269 return self._userident_name
270
271 @property
272 def UserEmail(self):
273 """Obtain the user's email address. This is very likely
274 to be their Gerrit login.
275 """
276 if self._userident_email is None:
277 self._LoadUserIdentity()
278 return self._userident_email
279
280 def _LoadUserIdentity(self):
281 u = self.bare_git.var('GIT_COMMITTER_IDENT')
282 m = re.compile("^(.*) <([^>]*)> ").match(u)
283 if m:
284 self._userident_name = m.group(1)
285 self._userident_email = m.group(2)
286 else:
287 self._userident_name = ''
288 self._userident_email = ''
289
290 def GetRemote(self, name):
291 """Get the configuration for a single remote.
292 """
293 return self.config.GetRemote(name)
294
295 def GetBranch(self, name):
296 """Get the configuration for a single branch.
297 """
298 return self.config.GetBranch(name)
299
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700300 def GetBranches(self):
301 """Get all existing local branches.
302 """
303 current = self.CurrentBranch
304 all = self.bare_git.ListRefs()
305 heads = {}
306 pubd = {}
307
308 for name, id in all.iteritems():
309 if name.startswith(R_HEADS):
310 name = name[len(R_HEADS):]
311 b = self.GetBranch(name)
312 b.current = name == current
313 b.published = None
314 b.revision = id
315 heads[name] = b
316
317 for name, id in all.iteritems():
318 if name.startswith(R_PUB):
319 name = name[len(R_PUB):]
320 b = heads.get(name)
321 if b:
322 b.published = id
323
324 return heads
325
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700326
327## Status Display ##
328
329 def PrintWorkTreeStatus(self):
330 """Prints the status of the repository to stdout.
331 """
332 if not os.path.isdir(self.worktree):
333 print ''
334 print 'project %s/' % self.relpath
335 print ' missing (run "repo sync")'
336 return
337
338 self.work_git.update_index('-q',
339 '--unmerged',
340 '--ignore-missing',
341 '--refresh')
342 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
343 df = self.work_git.DiffZ('diff-files')
344 do = self.work_git.LsOthers()
345 if not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700346 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700347
348 out = StatusColoring(self.config)
349 out.project('project %-40s', self.relpath + '/')
350
351 branch = self.CurrentBranch
352 if branch is None:
353 out.nobranch('(*** NO BRANCH ***)')
354 else:
355 out.branch('branch %s', branch)
356 out.nl()
357
358 paths = list()
359 paths.extend(di.keys())
360 paths.extend(df.keys())
361 paths.extend(do)
362
363 paths = list(set(paths))
364 paths.sort()
365
366 for p in paths:
367 try: i = di[p]
368 except KeyError: i = None
369
370 try: f = df[p]
371 except KeyError: f = None
372
373 if i: i_status = i.status.upper()
374 else: i_status = '-'
375
376 if f: f_status = f.status.lower()
377 else: f_status = '-'
378
379 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800380 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700381 i.src_path, p, i.level)
382 else:
383 line = ' %s%s\t%s' % (i_status, f_status, p)
384
385 if i and not f:
386 out.added('%s', line)
387 elif (i and f) or (not i and f):
388 out.changed('%s', line)
389 elif not i and not f:
390 out.untracked('%s', line)
391 else:
392 out.write('%s', line)
393 out.nl()
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700394 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700395
396 def PrintWorkTreeDiff(self):
397 """Prints the status of the repository to stdout.
398 """
399 out = DiffColoring(self.config)
400 cmd = ['diff']
401 if out.is_on:
402 cmd.append('--color')
403 cmd.append(HEAD)
404 cmd.append('--')
405 p = GitCommand(self,
406 cmd,
407 capture_stdout = True,
408 capture_stderr = True)
409 has_diff = False
410 for line in p.process.stdout:
411 if not has_diff:
412 out.nl()
413 out.project('project %s/' % self.relpath)
414 out.nl()
415 has_diff = True
416 print line[:-1]
417 p.Wait()
418
419
420## Publish / Upload ##
421
422 def WasPublished(self, branch):
423 """Was the branch published (uploaded) for code review?
424 If so, returns the SHA-1 hash of the last published
425 state for the branch.
426 """
427 try:
428 return self.bare_git.rev_parse(R_PUB + branch)
429 except GitError:
430 return None
431
432 def CleanPublishedCache(self):
433 """Prunes any stale published refs.
434 """
435 heads = set()
436 canrm = {}
437 for name, id in self._allrefs.iteritems():
438 if name.startswith(R_HEADS):
439 heads.add(name)
440 elif name.startswith(R_PUB):
441 canrm[name] = id
442
443 for name, id in canrm.iteritems():
444 n = name[len(R_PUB):]
445 if R_HEADS + n not in heads:
446 self.bare_git.DeleteRef(name, id)
447
448 def GetUploadableBranches(self):
449 """List any branches which can be uploaded for review.
450 """
451 heads = {}
452 pubed = {}
453
454 for name, id in self._allrefs.iteritems():
455 if name.startswith(R_HEADS):
456 heads[name[len(R_HEADS):]] = id
457 elif name.startswith(R_PUB):
458 pubed[name[len(R_PUB):]] = id
459
460 ready = []
461 for branch, id in heads.iteritems():
462 if branch in pubed and pubed[branch] == id:
463 continue
464
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800465 rb = self.GetUploadableBranch(branch)
466 if rb:
467 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700468 return ready
469
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800470 def GetUploadableBranch(self, branch_name):
471 """Get a single uploadable branch, or None.
472 """
473 branch = self.GetBranch(branch_name)
474 base = branch.LocalMerge
475 if branch.LocalMerge:
476 rb = ReviewableBranch(self, branch, base)
477 if rb.commits:
478 return rb
479 return None
480
Joe Onorato2896a792008-11-17 16:56:36 -0500481 def UploadForReview(self, branch=None, replace_changes=None, people=([],[])):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700482 """Uploads the named branch for code review.
483 """
484 if branch is None:
485 branch = self.CurrentBranch
486 if branch is None:
487 raise GitError('not currently on a branch')
488
489 branch = self.GetBranch(branch)
490 if not branch.LocalMerge:
491 raise GitError('branch %s does not track a remote' % branch.name)
492 if not branch.remote.review:
493 raise GitError('remote %s has no review url' % branch.remote.name)
494
495 dest_branch = branch.merge
496 if not dest_branch.startswith(R_HEADS):
497 dest_branch = R_HEADS + dest_branch
498
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800499 if not branch.remote.projectname:
500 branch.remote.projectname = self.name
501 branch.remote.Save()
502
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800503 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800504 if dest_branch.startswith(R_HEADS):
505 dest_branch = dest_branch[len(R_HEADS):]
506
507 rp = ['gerrit receive-pack']
508 for e in people[0]:
509 rp.append('--reviewer=%s' % sq(e))
510 for e in people[1]:
511 rp.append('--cc=%s' % sq(e))
512
513 cmd = ['push']
514 cmd.append('--receive-pack=%s' % " ".join(rp))
515 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
516 cmd.append('%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch))
517 if replace_changes:
518 for change_id,commit_id in replace_changes.iteritems():
519 cmd.append('%s:refs/changes/%s/new' % (commit_id, change_id))
520 if GitCommand(self, cmd, bare = True).Wait() != 0:
521 raise UploadError('Upload failed')
522
523 else:
524 raise UploadError('Unsupported protocol %s' \
525 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526
527 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
528 self.bare_git.UpdateRef(R_PUB + branch.name,
529 R_HEADS + branch.name,
530 message = msg)
531
532
533## Sync ##
534
535 def Sync_NetworkHalf(self):
536 """Perform only the network IO portion of the sync process.
537 Local working directory/branch state is not affected.
538 """
539 if not self.Exists:
540 print >>sys.stderr
541 print >>sys.stderr, 'Initializing project %s ...' % self.name
542 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800543
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544 self._InitRemote()
545 for r in self.extraRemotes.values():
546 if not self._RemoteFetch(r.name):
547 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548 if not self._RemoteFetch():
549 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800550
551 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800552 self._InitMRef()
553 else:
554 self._InitMirrorHead()
555 try:
556 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
557 except OSError:
558 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800560
561 def PostRepoUpgrade(self):
562 self._InitHooks()
563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 def _CopyFiles(self):
565 for file in self.copyfiles:
566 file._Copy()
567
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700568 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700569 """Perform only the local IO portion of the sync process.
570 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700571 """
572 self._InitWorkTree()
573 self.CleanPublishedCache()
574
575 rem = self.GetRemote(self.remote.name)
576 rev = rem.ToLocal(self.revision)
Shawn O. Pearce559b8462009-03-02 12:56:08 -0800577 try:
578 self.bare_git.rev_parse('--verify', '%s^0' % rev)
579 except GitError:
580 raise ManifestInvalidRevisionError(
581 'revision %s in %s not found' % (self.revision, self.name))
582
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 branch = self.CurrentBranch
584
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700585 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700586 # Currently on a detached HEAD. The user is assumed to
587 # not have any local modifications worth worrying about.
588 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700589 if os.path.exists(os.path.join(self.worktree, '.dotest')) \
590 or os.path.exists(os.path.join(self.worktree, '.git', 'rebase-apply')):
591 syncbuf.fail(self, _PriorSyncFailedError())
592 return
593
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700594 lost = self._revlist(not_rev(rev), HEAD)
595 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700596 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597 try:
598 self._Checkout(rev, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700599 except GitError, e:
600 syncbuf.fail(self, e)
601 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700603 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700604
605 branch = self.GetBranch(branch)
606 merge = branch.LocalMerge
607
608 if not merge:
609 # The current branch has no tracking configuration.
610 # Jump off it to a deatched HEAD.
611 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700612 syncbuf.info(self,
613 "leaving %s; does not track upstream",
614 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700615 try:
616 self._Checkout(rev, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700617 except GitError, e:
618 syncbuf.fail(self, e)
619 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700620 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700621 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700622
623 upstream_gain = self._revlist(not_rev(HEAD), rev)
624 pub = self.WasPublished(branch.name)
625 if pub:
626 not_merged = self._revlist(not_rev(rev), pub)
627 if not_merged:
628 if upstream_gain:
629 # The user has published this branch and some of those
630 # commits are not yet merged upstream. We do not want
631 # to rewrite the published commits so we punt.
632 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700633 syncbuf.info(self,
634 "branch %s is published but is now %d commits behind",
635 branch.name,
636 len(upstream_gain))
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700637 return
Shawn O. Pearce23d77812008-10-30 11:06:57 -0700638 elif upstream_gain:
Shawn O. Pearcea54c5272008-10-30 11:03:00 -0700639 # We can fast-forward safely.
640 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700641 def _doff():
Shawn O. Pearcea54c5272008-10-30 11:03:00 -0700642 self._FastForward(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700643 self._CopyFiles()
644 syncbuf.later1(self, _doff)
645 return
Shawn O. Pearce23d77812008-10-30 11:06:57 -0700646 else:
647 # Trivially no changes in the upstream.
648 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700649 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700650
651 if merge == rev:
652 try:
653 old_merge = self.bare_git.rev_parse('%s@{1}' % merge)
654 except GitError:
655 old_merge = merge
Shawn O. Pearce07346002008-10-21 07:09:27 -0700656 if old_merge == '0000000000000000000000000000000000000000' \
657 or old_merge == '':
658 old_merge = merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659 else:
660 # The upstream switched on us. Time to cross our fingers
661 # and pray that the old upstream also wasn't in the habit
662 # of rebasing itself.
663 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700664 syncbuf.info(self, "manifest switched %s...%s", merge, rev)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700665 old_merge = merge
666
667 if rev == old_merge:
668 upstream_lost = []
669 else:
670 upstream_lost = self._revlist(not_rev(rev), old_merge)
671
672 if not upstream_lost and not upstream_gain:
673 # Trivially no changes caused by the upstream.
674 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700675 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700676
677 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700678 syncbuf.fail(self, _DirtyError())
679 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680
681 if upstream_lost:
682 # Upstream rebased. Not everything in HEAD
683 # may have been caused by the user.
684 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700685 syncbuf.info(self,
686 "discarding %d commits removed from upstream",
687 len(upstream_lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688
689 branch.remote = rem
690 branch.merge = self.revision
691 branch.Save()
692
693 my_changes = self._revlist(not_rev(old_merge), HEAD)
694 if my_changes:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700695 def _dorebase():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 self._Rebase(upstream = old_merge, onto = rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700697 self._CopyFiles()
698 syncbuf.later2(self, _dorebase)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699 elif upstream_lost:
700 try:
701 self._ResetHard(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700702 self._CopyFiles()
703 except GitError, e:
704 syncbuf.fail(self, e)
705 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700707 def _doff():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708 self._FastForward(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700709 self._CopyFiles()
710 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700711
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800712 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713 # dest should already be an absolute path, but src is project relative
714 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800715 abssrc = os.path.join(self.worktree, src)
716 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700718 def DownloadPatchSet(self, change_id, patch_id):
719 """Download a single patch set of a single change to FETCH_HEAD.
720 """
721 remote = self.GetRemote(self.remote.name)
722
723 cmd = ['fetch', remote.name]
724 cmd.append('refs/changes/%2.2d/%d/%d' \
725 % (change_id % 100, change_id, patch_id))
726 cmd.extend(map(lambda x: str(x), remote.fetch))
727 if GitCommand(self, cmd, bare=True).Wait() != 0:
728 return None
729 return DownloadedChange(self,
730 remote.ToLocal(self.revision),
731 change_id,
732 patch_id,
733 self.bare_git.rev_parse('FETCH_HEAD'))
734
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735
736## Branch Management ##
737
738 def StartBranch(self, name):
739 """Create a new branch off the manifest's revision.
740 """
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700741 try:
742 self.bare_git.rev_parse(R_HEADS + name)
743 exists = True
744 except GitError:
745 exists = False;
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700747 if exists:
748 if name == self.CurrentBranch:
749 return True
750 else:
751 cmd = ['checkout', name, '--']
752 return GitCommand(self, cmd).Wait() == 0
753
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754 else:
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700755 branch = self.GetBranch(name)
756 branch.remote = self.GetRemote(self.remote.name)
757 branch.merge = self.revision
758
759 rev = branch.LocalMerge
760 cmd = ['checkout', '-b', branch.name, rev]
761 if GitCommand(self, cmd).Wait() == 0:
762 branch.Save()
763 return True
764 else:
765 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766
Wink Saville02d79452009-04-10 13:01:24 -0700767 def CheckoutBranch(self, name):
768 """Checkout a local topic branch.
769 """
770
771 # Be sure the branch exists
772 try:
773 tip_rev = self.bare_git.rev_parse(R_HEADS + name)
774 except GitError:
775 return False;
776
777 # Do the checkout
778 cmd = ['checkout', name, '--']
Shawn O. Pearce2675c3f2009-04-10 16:20:25 -0700779 return GitCommand(self, cmd).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -0700780
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -0800781 def AbandonBranch(self, name):
782 """Destroy a local topic branch.
783 """
784 try:
785 tip_rev = self.bare_git.rev_parse(R_HEADS + name)
786 except GitError:
787 return
788
789 if self.CurrentBranch == name:
790 self._Checkout(
791 self.GetRemote(self.remote.name).ToLocal(self.revision),
792 quiet=True)
793
794 cmd = ['branch', '-D', name]
795 GitCommand(self, cmd, capture_stdout=True).Wait()
796
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797 def PruneHeads(self):
798 """Prune any topic branches already merged into upstream.
799 """
800 cb = self.CurrentBranch
801 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800802 left = self._allrefs
803 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 if name.startswith(R_HEADS):
805 name = name[len(R_HEADS):]
806 if cb is None or name != cb:
807 kill.append(name)
808
809 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
810 if cb is not None \
811 and not self._revlist(HEAD + '...' + rev) \
812 and not self.IsDirty(consider_untracked = False):
813 self.work_git.DetachHead(HEAD)
814 kill.append(cb)
815
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700817 old = self.bare_git.GetHead()
818 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819 old = 'refs/heads/please_never_use_this_as_a_branch_name'
820
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821 try:
822 self.bare_git.DetachHead(rev)
823
824 b = ['branch', '-d']
825 b.extend(kill)
826 b = GitCommand(self, b, bare=True,
827 capture_stdout=True,
828 capture_stderr=True)
829 b.Wait()
830 finally:
831 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800832 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800834 for branch in kill:
835 if (R_HEADS + branch) not in left:
836 self.CleanPublishedCache()
837 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838
839 if cb and cb not in kill:
840 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -0800841 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842
843 kept = []
844 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800845 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 branch = self.GetBranch(branch)
847 base = branch.LocalMerge
848 if not base:
849 base = rev
850 kept.append(ReviewableBranch(self, branch, base))
851 return kept
852
853
854## Direct Git Commands ##
855
856 def _RemoteFetch(self, name=None):
857 if not name:
858 name = self.remote.name
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800859 cmd = ['fetch']
860 if not self.worktree:
861 cmd.append('--update-head-ok')
862 cmd.append(name)
863 return GitCommand(self, cmd, bare = True).Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864
865 def _Checkout(self, rev, quiet=False):
866 cmd = ['checkout']
867 if quiet:
868 cmd.append('-q')
869 cmd.append(rev)
870 cmd.append('--')
871 if GitCommand(self, cmd).Wait() != 0:
872 if self._allrefs:
873 raise GitError('%s checkout %s ' % (self.name, rev))
874
875 def _ResetHard(self, rev, quiet=True):
876 cmd = ['reset', '--hard']
877 if quiet:
878 cmd.append('-q')
879 cmd.append(rev)
880 if GitCommand(self, cmd).Wait() != 0:
881 raise GitError('%s reset --hard %s ' % (self.name, rev))
882
883 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -0700884 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700885 if onto is not None:
886 cmd.extend(['--onto', onto])
887 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -0700888 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 raise GitError('%s rebase %s ' % (self.name, upstream))
890
891 def _FastForward(self, head):
892 cmd = ['merge', head]
893 if GitCommand(self, cmd).Wait() != 0:
894 raise GitError('%s merge %s ' % (self.name, head))
895
896 def _InitGitDir(self):
897 if not os.path.exists(self.gitdir):
898 os.makedirs(self.gitdir)
899 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -0800900
901 if self.manifest.IsMirror:
902 self.config.SetString('core.bare', 'true')
903 else:
904 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905
906 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -0700907 try:
908 to_rm = os.listdir(hooks)
909 except OSError:
910 to_rm = []
911 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800913 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914
915 m = self.manifest.manifestProject.config
916 for key in ['user.name', 'user.email']:
917 if m.Has(key, include_defaults = False):
918 self.config.SetString(key, m.GetString(key))
919
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800920 def _InitHooks(self):
921 hooks = self._gitdir_path('hooks')
922 if not os.path.exists(hooks):
923 os.makedirs(hooks)
924 for stock_hook in repo_hooks():
925 dst = os.path.join(hooks, os.path.basename(stock_hook))
926 try:
927 os.symlink(relpath(stock_hook, dst), dst)
928 except OSError, e:
929 if e.errno == errno.EEXIST:
930 pass
931 elif e.errno == errno.EPERM:
932 raise GitError('filesystem must support symlinks')
933 else:
934 raise
935
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936 def _InitRemote(self):
937 if self.remote.fetchUrl:
938 remote = self.GetRemote(self.remote.name)
939
940 url = self.remote.fetchUrl
941 while url.endswith('/'):
942 url = url[:-1]
943 url += '/%s.git' % self.name
944 remote.url = url
945 remote.review = self.remote.reviewUrl
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800946 if remote.projectname is None:
947 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800949 if self.worktree:
950 remote.ResetFetch(mirror=False)
951 else:
952 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 remote.Save()
954
955 for r in self.extraRemotes.values():
956 remote = self.GetRemote(r.name)
957 remote.url = r.fetchUrl
958 remote.review = r.reviewUrl
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800959 if r.projectName:
960 remote.projectname = r.projectName
961 elif remote.projectname is None:
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800962 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 remote.ResetFetch()
964 remote.Save()
965
966 def _InitMRef(self):
967 if self.manifest.branch:
968 msg = 'manifest set to %s' % self.revision
969 ref = R_M + self.manifest.branch
970
971 if IsId(self.revision):
Marcelo E. Magallon21f73852008-12-31 04:44:37 +0000972 dst = self.revision + '^0'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
974 else:
975 remote = self.GetRemote(self.remote.name)
976 dst = remote.ToLocal(self.revision)
977 self.bare_git.symbolic_ref('-m', msg, ref, dst)
978
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800979 def _InitMirrorHead(self):
980 dst = self.GetRemote(self.remote.name).ToLocal(self.revision)
981 msg = 'manifest set to %s' % self.revision
982 self.bare_git.SetHead(dst, message=msg)
983
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 def _InitWorkTree(self):
985 dotgit = os.path.join(self.worktree, '.git')
986 if not os.path.exists(dotgit):
987 os.makedirs(dotgit)
988
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 for name in ['config',
990 'description',
991 'hooks',
992 'info',
993 'logs',
994 'objects',
995 'packed-refs',
996 'refs',
997 'rr-cache',
998 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -0800999 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001000 src = os.path.join(self.gitdir, name)
1001 dst = os.path.join(dotgit, name)
1002 os.symlink(relpath(src, dst), dst)
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001003 except OSError, e:
1004 if e.errno == errno.EPERM:
1005 raise GitError('filesystem must support symlinks')
1006 else:
1007 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001008
1009 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
1010 rev = self.bare_git.rev_parse('%s^0' % rev)
1011
1012 f = open(os.path.join(dotgit, HEAD), 'wb')
1013 f.write("%s\n" % rev)
1014 f.close()
1015
1016 cmd = ['read-tree', '--reset', '-u']
1017 cmd.append('-v')
1018 cmd.append('HEAD')
1019 if GitCommand(self, cmd).Wait() != 0:
1020 raise GitError("cannot initialize work tree")
1021
1022 def _gitdir_path(self, path):
1023 return os.path.join(self.gitdir, path)
1024
1025 def _revlist(self, *args):
1026 cmd = []
1027 cmd.extend(args)
1028 cmd.append('--')
1029 return self.work_git.rev_list(*args)
1030
1031 @property
1032 def _allrefs(self):
1033 return self.bare_git.ListRefs()
1034
1035 class _GitGetByExec(object):
1036 def __init__(self, project, bare):
1037 self._project = project
1038 self._bare = bare
1039
1040 def ListRefs(self, *args):
1041 cmdv = ['for-each-ref', '--format=%(objectname) %(refname)']
1042 cmdv.extend(args)
1043 p = GitCommand(self._project,
1044 cmdv,
1045 bare = self._bare,
1046 capture_stdout = True,
1047 capture_stderr = True)
1048 r = {}
1049 for line in p.process.stdout:
1050 id, name = line[:-1].split(' ', 2)
1051 r[name] = id
1052 if p.Wait() != 0:
1053 raise GitError('%s for-each-ref %s: %s' % (
1054 self._project.name,
1055 str(args),
1056 p.stderr))
1057 return r
1058
1059 def LsOthers(self):
1060 p = GitCommand(self._project,
1061 ['ls-files',
1062 '-z',
1063 '--others',
1064 '--exclude-standard'],
1065 bare = False,
1066 capture_stdout = True,
1067 capture_stderr = True)
1068 if p.Wait() == 0:
1069 out = p.stdout
1070 if out:
1071 return out[:-1].split("\0")
1072 return []
1073
1074 def DiffZ(self, name, *args):
1075 cmd = [name]
1076 cmd.append('-z')
1077 cmd.extend(args)
1078 p = GitCommand(self._project,
1079 cmd,
1080 bare = False,
1081 capture_stdout = True,
1082 capture_stderr = True)
1083 try:
1084 out = p.process.stdout.read()
1085 r = {}
1086 if out:
1087 out = iter(out[:-1].split('\0'))
1088 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001089 try:
1090 info = out.next()
1091 path = out.next()
1092 except StopIteration:
1093 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001094
1095 class _Info(object):
1096 def __init__(self, path, omode, nmode, oid, nid, state):
1097 self.path = path
1098 self.src_path = None
1099 self.old_mode = omode
1100 self.new_mode = nmode
1101 self.old_id = oid
1102 self.new_id = nid
1103
1104 if len(state) == 1:
1105 self.status = state
1106 self.level = None
1107 else:
1108 self.status = state[:1]
1109 self.level = state[1:]
1110 while self.level.startswith('0'):
1111 self.level = self.level[1:]
1112
1113 info = info[1:].split(' ')
1114 info =_Info(path, *info)
1115 if info.status in ('R', 'C'):
1116 info.src_path = info.path
1117 info.path = out.next()
1118 r[info.path] = info
1119 return r
1120 finally:
1121 p.Wait()
1122
1123 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001124 if self._bare:
1125 path = os.path.join(self._project.gitdir, HEAD)
1126 else:
1127 path = os.path.join(self._project.worktree, '.git', HEAD)
1128 line = open(path, 'r').read()
1129 if line.startswith('ref: '):
1130 return line[5:-1]
1131 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001132
1133 def SetHead(self, ref, message=None):
1134 cmdv = []
1135 if message is not None:
1136 cmdv.extend(['-m', message])
1137 cmdv.append(HEAD)
1138 cmdv.append(ref)
1139 self.symbolic_ref(*cmdv)
1140
1141 def DetachHead(self, new, message=None):
1142 cmdv = ['--no-deref']
1143 if message is not None:
1144 cmdv.extend(['-m', message])
1145 cmdv.append(HEAD)
1146 cmdv.append(new)
1147 self.update_ref(*cmdv)
1148
1149 def UpdateRef(self, name, new, old=None,
1150 message=None,
1151 detach=False):
1152 cmdv = []
1153 if message is not None:
1154 cmdv.extend(['-m', message])
1155 if detach:
1156 cmdv.append('--no-deref')
1157 cmdv.append(name)
1158 cmdv.append(new)
1159 if old is not None:
1160 cmdv.append(old)
1161 self.update_ref(*cmdv)
1162
1163 def DeleteRef(self, name, old=None):
1164 if not old:
1165 old = self.rev_parse(name)
1166 self.update_ref('-d', name, old)
1167
1168 def rev_list(self, *args):
1169 cmdv = ['rev-list']
1170 cmdv.extend(args)
1171 p = GitCommand(self._project,
1172 cmdv,
1173 bare = self._bare,
1174 capture_stdout = True,
1175 capture_stderr = True)
1176 r = []
1177 for line in p.process.stdout:
1178 r.append(line[:-1])
1179 if p.Wait() != 0:
1180 raise GitError('%s rev-list %s: %s' % (
1181 self._project.name,
1182 str(args),
1183 p.stderr))
1184 return r
1185
1186 def __getattr__(self, name):
1187 name = name.replace('_', '-')
1188 def runner(*args):
1189 cmdv = [name]
1190 cmdv.extend(args)
1191 p = GitCommand(self._project,
1192 cmdv,
1193 bare = self._bare,
1194 capture_stdout = True,
1195 capture_stderr = True)
1196 if p.Wait() != 0:
1197 raise GitError('%s %s: %s' % (
1198 self._project.name,
1199 name,
1200 p.stderr))
1201 r = p.stdout
1202 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1203 return r[:-1]
1204 return r
1205 return runner
1206
1207
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001208class _PriorSyncFailedError(Exception):
1209 def __str__(self):
1210 return 'prior sync failed; rebase still in progress'
1211
1212class _DirtyError(Exception):
1213 def __str__(self):
1214 return 'contains uncommitted changes'
1215
1216class _InfoMessage(object):
1217 def __init__(self, project, text):
1218 self.project = project
1219 self.text = text
1220
1221 def Print(self, syncbuf):
1222 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1223 syncbuf.out.nl()
1224
1225class _Failure(object):
1226 def __init__(self, project, why):
1227 self.project = project
1228 self.why = why
1229
1230 def Print(self, syncbuf):
1231 syncbuf.out.fail('error: %s/: %s',
1232 self.project.relpath,
1233 str(self.why))
1234 syncbuf.out.nl()
1235
1236class _Later(object):
1237 def __init__(self, project, action):
1238 self.project = project
1239 self.action = action
1240
1241 def Run(self, syncbuf):
1242 out = syncbuf.out
1243 out.project('project %s/', self.project.relpath)
1244 out.nl()
1245 try:
1246 self.action()
1247 out.nl()
1248 return True
1249 except GitError, e:
1250 out.nl()
1251 return False
1252
1253class _SyncColoring(Coloring):
1254 def __init__(self, config):
1255 Coloring.__init__(self, config, 'reposync')
1256 self.project = self.printer('header', attr = 'bold')
1257 self.info = self.printer('info')
1258 self.fail = self.printer('fail', fg='red')
1259
1260class SyncBuffer(object):
1261 def __init__(self, config, detach_head=False):
1262 self._messages = []
1263 self._failures = []
1264 self._later_queue1 = []
1265 self._later_queue2 = []
1266
1267 self.out = _SyncColoring(config)
1268 self.out.redirect(sys.stderr)
1269
1270 self.detach_head = detach_head
1271 self.clean = True
1272
1273 def info(self, project, fmt, *args):
1274 self._messages.append(_InfoMessage(project, fmt % args))
1275
1276 def fail(self, project, err=None):
1277 self._failures.append(_Failure(project, err))
1278 self.clean = False
1279
1280 def later1(self, project, what):
1281 self._later_queue1.append(_Later(project, what))
1282
1283 def later2(self, project, what):
1284 self._later_queue2.append(_Later(project, what))
1285
1286 def Finish(self):
1287 self._PrintMessages()
1288 self._RunLater()
1289 self._PrintMessages()
1290 return self.clean
1291
1292 def _RunLater(self):
1293 for q in ['_later_queue1', '_later_queue2']:
1294 if not self._RunQueue(q):
1295 return
1296
1297 def _RunQueue(self, queue):
1298 for m in getattr(self, queue):
1299 if not m.Run(self):
1300 self.clean = False
1301 return False
1302 setattr(self, queue, [])
1303 return True
1304
1305 def _PrintMessages(self):
1306 for m in self._messages:
1307 m.Print(self)
1308 for m in self._failures:
1309 m.Print(self)
1310
1311 self._messages = []
1312 self._failures = []
1313
1314
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315class MetaProject(Project):
1316 """A special project housed under .repo.
1317 """
1318 def __init__(self, manifest, name, gitdir, worktree):
1319 repodir = manifest.repodir
1320 Project.__init__(self,
1321 manifest = manifest,
1322 name = name,
1323 gitdir = gitdir,
1324 worktree = worktree,
1325 remote = Remote('origin'),
1326 relpath = '.repo/%s' % name,
1327 revision = 'refs/heads/master')
1328
1329 def PreSync(self):
1330 if self.Exists:
1331 cb = self.CurrentBranch
1332 if cb:
1333 base = self.GetBranch(cb).merge
1334 if base:
1335 self.revision = base
1336
1337 @property
1338 def HasChanges(self):
1339 """Has the remote received new commits not yet checked out?
1340 """
1341 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
1342 if self._revlist(not_rev(HEAD), rev):
1343 return True
1344 return False