blob: 086f0d77bf35a73f6bd673b9d34fc8549c8a6277 [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
Shawn O. Pearced237b692009-04-17 18:49:50 -070031from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
Shawn O. Pearce48244782009-04-16 08:25:57 -070033def _error(fmt, *args):
34 msg = fmt % args
35 print >>sys.stderr, 'error: %s' % msg
36
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037def not_rev(r):
38 return '^' + r
39
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080040def sq(r):
41 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080042
43hook_list = None
44def repo_hooks():
45 global hook_list
46 if hook_list is None:
47 d = os.path.abspath(os.path.dirname(__file__))
48 d = os.path.join(d , 'hooks')
49 hook_list = map(lambda x: os.path.join(d, x), os.listdir(d))
50 return hook_list
51
52def relpath(dst, src):
53 src = os.path.dirname(src)
54 top = os.path.commonprefix([dst, src])
55 if top.endswith('/'):
56 top = top[:-1]
57 else:
58 top = os.path.dirname(top)
59
60 tmp = src
61 rel = ''
62 while top != tmp:
63 rel += '../'
64 tmp = os.path.dirname(tmp)
65 return rel + dst[len(top) + 1:]
66
67
Shawn O. Pearce632768b2008-10-23 11:58:52 -070068class DownloadedChange(object):
69 _commit_cache = None
70
71 def __init__(self, project, base, change_id, ps_id, commit):
72 self.project = project
73 self.base = base
74 self.change_id = change_id
75 self.ps_id = ps_id
76 self.commit = commit
77
78 @property
79 def commits(self):
80 if self._commit_cache is None:
81 self._commit_cache = self.project.bare_git.rev_list(
82 '--abbrev=8',
83 '--abbrev-commit',
84 '--pretty=oneline',
85 '--reverse',
86 '--date-order',
87 not_rev(self.base),
88 self.commit,
89 '--')
90 return self._commit_cache
91
92
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093class ReviewableBranch(object):
94 _commit_cache = None
95
96 def __init__(self, project, branch, base):
97 self.project = project
98 self.branch = branch
99 self.base = base
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800100 self.replace_changes = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101
102 @property
103 def name(self):
104 return self.branch.name
105
106 @property
107 def commits(self):
108 if self._commit_cache is None:
109 self._commit_cache = self.project.bare_git.rev_list(
110 '--abbrev=8',
111 '--abbrev-commit',
112 '--pretty=oneline',
113 '--reverse',
114 '--date-order',
115 not_rev(self.base),
116 R_HEADS + self.name,
117 '--')
118 return self._commit_cache
119
120 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800121 def unabbrev_commits(self):
122 r = dict()
123 for commit in self.project.bare_git.rev_list(
124 not_rev(self.base),
125 R_HEADS + self.name,
126 '--'):
127 r[commit[0:8]] = commit
128 return r
129
130 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131 def date(self):
132 return self.project.bare_git.log(
133 '--pretty=format:%cd',
134 '-n', '1',
135 R_HEADS + self.name,
136 '--')
137
Joe Onorato2896a792008-11-17 16:56:36 -0500138 def UploadForReview(self, people):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800139 self.project.UploadForReview(self.name,
Joe Onorato2896a792008-11-17 16:56:36 -0500140 self.replace_changes,
141 people)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142
143 @property
144 def tip_url(self):
145 me = self.project.GetBranch(self.name)
146 commit = self.project.bare_git.rev_parse(R_HEADS + self.name)
147 return 'http://%s/r/%s' % (me.remote.review, commit[0:12])
148
Shawn O. Pearce0758d2f2008-10-22 13:13:40 -0700149 @property
150 def owner_email(self):
151 return self.project.UserEmail
152
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700153
154class StatusColoring(Coloring):
155 def __init__(self, config):
156 Coloring.__init__(self, config, 'status')
157 self.project = self.printer('header', attr = 'bold')
158 self.branch = self.printer('header', attr = 'bold')
159 self.nobranch = self.printer('nobranch', fg = 'red')
160
161 self.added = self.printer('added', fg = 'green')
162 self.changed = self.printer('changed', fg = 'red')
163 self.untracked = self.printer('untracked', fg = 'red')
164
165
166class DiffColoring(Coloring):
167 def __init__(self, config):
168 Coloring.__init__(self, config, 'diff')
169 self.project = self.printer('header', attr = 'bold')
170
171
172class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800173 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174 self.src = src
175 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800176 self.abs_src = abssrc
177 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178
179 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800180 src = self.abs_src
181 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182 # copy file if it does not exist or is out of date
183 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
184 try:
185 # remove existing file first, since it might be read-only
186 if os.path.exists(dest):
187 os.remove(dest)
188 shutil.copy(src, dest)
189 # make the file read-only
190 mode = os.stat(dest)[stat.ST_MODE]
191 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
192 os.chmod(dest, mode)
193 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700194 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196
197class Project(object):
198 def __init__(self,
199 manifest,
200 name,
201 remote,
202 gitdir,
203 worktree,
204 relpath,
205 revision):
206 self.manifest = manifest
207 self.name = name
208 self.remote = remote
209 self.gitdir = gitdir
210 self.worktree = worktree
211 self.relpath = relpath
212 self.revision = revision
213 self.snapshots = {}
214 self.extraRemotes = {}
215 self.copyfiles = []
216 self.config = GitConfig.ForRepository(
217 gitdir = self.gitdir,
218 defaults = self.manifest.globalConfig)
219
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800220 if self.worktree:
221 self.work_git = self._GitGetByExec(self, bare=False)
222 else:
223 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700225 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226
227 @property
228 def Exists(self):
229 return os.path.isdir(self.gitdir)
230
231 @property
232 def CurrentBranch(self):
233 """Obtain the name of the currently checked out branch.
234 The branch name omits the 'refs/heads/' prefix.
235 None is returned if the project is on a detached HEAD.
236 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700237 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238 if b.startswith(R_HEADS):
239 return b[len(R_HEADS):]
240 return None
241
242 def IsDirty(self, consider_untracked=True):
243 """Is the working directory modified in some way?
244 """
245 self.work_git.update_index('-q',
246 '--unmerged',
247 '--ignore-missing',
248 '--refresh')
249 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
250 return True
251 if self.work_git.DiffZ('diff-files'):
252 return True
253 if consider_untracked and self.work_git.LsOthers():
254 return True
255 return False
256
257 _userident_name = None
258 _userident_email = None
259
260 @property
261 def UserName(self):
262 """Obtain the user's personal name.
263 """
264 if self._userident_name is None:
265 self._LoadUserIdentity()
266 return self._userident_name
267
268 @property
269 def UserEmail(self):
270 """Obtain the user's email address. This is very likely
271 to be their Gerrit login.
272 """
273 if self._userident_email is None:
274 self._LoadUserIdentity()
275 return self._userident_email
276
277 def _LoadUserIdentity(self):
278 u = self.bare_git.var('GIT_COMMITTER_IDENT')
279 m = re.compile("^(.*) <([^>]*)> ").match(u)
280 if m:
281 self._userident_name = m.group(1)
282 self._userident_email = m.group(2)
283 else:
284 self._userident_name = ''
285 self._userident_email = ''
286
287 def GetRemote(self, name):
288 """Get the configuration for a single remote.
289 """
290 return self.config.GetRemote(name)
291
292 def GetBranch(self, name):
293 """Get the configuration for a single branch.
294 """
295 return self.config.GetBranch(name)
296
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700297 def GetBranches(self):
298 """Get all existing local branches.
299 """
300 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700301 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700302 heads = {}
303 pubd = {}
304
305 for name, id in all.iteritems():
306 if name.startswith(R_HEADS):
307 name = name[len(R_HEADS):]
308 b = self.GetBranch(name)
309 b.current = name == current
310 b.published = None
311 b.revision = id
312 heads[name] = b
313
314 for name, id in all.iteritems():
315 if name.startswith(R_PUB):
316 name = name[len(R_PUB):]
317 b = heads.get(name)
318 if b:
319 b.published = id
320
321 return heads
322
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700323
324## Status Display ##
325
326 def PrintWorkTreeStatus(self):
327 """Prints the status of the repository to stdout.
328 """
329 if not os.path.isdir(self.worktree):
330 print ''
331 print 'project %s/' % self.relpath
332 print ' missing (run "repo sync")'
333 return
334
335 self.work_git.update_index('-q',
336 '--unmerged',
337 '--ignore-missing',
338 '--refresh')
339 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
340 df = self.work_git.DiffZ('diff-files')
341 do = self.work_git.LsOthers()
342 if not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700343 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700344
345 out = StatusColoring(self.config)
346 out.project('project %-40s', self.relpath + '/')
347
348 branch = self.CurrentBranch
349 if branch is None:
350 out.nobranch('(*** NO BRANCH ***)')
351 else:
352 out.branch('branch %s', branch)
353 out.nl()
354
355 paths = list()
356 paths.extend(di.keys())
357 paths.extend(df.keys())
358 paths.extend(do)
359
360 paths = list(set(paths))
361 paths.sort()
362
363 for p in paths:
364 try: i = di[p]
365 except KeyError: i = None
366
367 try: f = df[p]
368 except KeyError: f = None
369
370 if i: i_status = i.status.upper()
371 else: i_status = '-'
372
373 if f: f_status = f.status.lower()
374 else: f_status = '-'
375
376 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800377 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700378 i.src_path, p, i.level)
379 else:
380 line = ' %s%s\t%s' % (i_status, f_status, p)
381
382 if i and not f:
383 out.added('%s', line)
384 elif (i and f) or (not i and f):
385 out.changed('%s', line)
386 elif not i and not f:
387 out.untracked('%s', line)
388 else:
389 out.write('%s', line)
390 out.nl()
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700391 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700392
393 def PrintWorkTreeDiff(self):
394 """Prints the status of the repository to stdout.
395 """
396 out = DiffColoring(self.config)
397 cmd = ['diff']
398 if out.is_on:
399 cmd.append('--color')
400 cmd.append(HEAD)
401 cmd.append('--')
402 p = GitCommand(self,
403 cmd,
404 capture_stdout = True,
405 capture_stderr = True)
406 has_diff = False
407 for line in p.process.stdout:
408 if not has_diff:
409 out.nl()
410 out.project('project %s/' % self.relpath)
411 out.nl()
412 has_diff = True
413 print line[:-1]
414 p.Wait()
415
416
417## Publish / Upload ##
418
419 def WasPublished(self, branch):
420 """Was the branch published (uploaded) for code review?
421 If so, returns the SHA-1 hash of the last published
422 state for the branch.
423 """
424 try:
425 return self.bare_git.rev_parse(R_PUB + branch)
426 except GitError:
427 return None
428
429 def CleanPublishedCache(self):
430 """Prunes any stale published refs.
431 """
432 heads = set()
433 canrm = {}
434 for name, id in self._allrefs.iteritems():
435 if name.startswith(R_HEADS):
436 heads.add(name)
437 elif name.startswith(R_PUB):
438 canrm[name] = id
439
440 for name, id in canrm.iteritems():
441 n = name[len(R_PUB):]
442 if R_HEADS + n not in heads:
443 self.bare_git.DeleteRef(name, id)
444
445 def GetUploadableBranches(self):
446 """List any branches which can be uploaded for review.
447 """
448 heads = {}
449 pubed = {}
450
451 for name, id in self._allrefs.iteritems():
452 if name.startswith(R_HEADS):
453 heads[name[len(R_HEADS):]] = id
454 elif name.startswith(R_PUB):
455 pubed[name[len(R_PUB):]] = id
456
457 ready = []
458 for branch, id in heads.iteritems():
459 if branch in pubed and pubed[branch] == id:
460 continue
461
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800462 rb = self.GetUploadableBranch(branch)
463 if rb:
464 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700465 return ready
466
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800467 def GetUploadableBranch(self, branch_name):
468 """Get a single uploadable branch, or None.
469 """
470 branch = self.GetBranch(branch_name)
471 base = branch.LocalMerge
472 if branch.LocalMerge:
473 rb = ReviewableBranch(self, branch, base)
474 if rb.commits:
475 return rb
476 return None
477
Joe Onorato2896a792008-11-17 16:56:36 -0500478 def UploadForReview(self, branch=None, replace_changes=None, people=([],[])):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700479 """Uploads the named branch for code review.
480 """
481 if branch is None:
482 branch = self.CurrentBranch
483 if branch is None:
484 raise GitError('not currently on a branch')
485
486 branch = self.GetBranch(branch)
487 if not branch.LocalMerge:
488 raise GitError('branch %s does not track a remote' % branch.name)
489 if not branch.remote.review:
490 raise GitError('remote %s has no review url' % branch.remote.name)
491
492 dest_branch = branch.merge
493 if not dest_branch.startswith(R_HEADS):
494 dest_branch = R_HEADS + dest_branch
495
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800496 if not branch.remote.projectname:
497 branch.remote.projectname = self.name
498 branch.remote.Save()
499
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800500 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800501 if dest_branch.startswith(R_HEADS):
502 dest_branch = dest_branch[len(R_HEADS):]
503
504 rp = ['gerrit receive-pack']
505 for e in people[0]:
506 rp.append('--reviewer=%s' % sq(e))
507 for e in people[1]:
508 rp.append('--cc=%s' % sq(e))
509
510 cmd = ['push']
511 cmd.append('--receive-pack=%s' % " ".join(rp))
512 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
513 cmd.append('%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch))
514 if replace_changes:
515 for change_id,commit_id in replace_changes.iteritems():
516 cmd.append('%s:refs/changes/%s/new' % (commit_id, change_id))
517 if GitCommand(self, cmd, bare = True).Wait() != 0:
518 raise UploadError('Upload failed')
519
520 else:
521 raise UploadError('Unsupported protocol %s' \
522 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523
524 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
525 self.bare_git.UpdateRef(R_PUB + branch.name,
526 R_HEADS + branch.name,
527 message = msg)
528
529
530## Sync ##
531
532 def Sync_NetworkHalf(self):
533 """Perform only the network IO portion of the sync process.
534 Local working directory/branch state is not affected.
535 """
536 if not self.Exists:
537 print >>sys.stderr
538 print >>sys.stderr, 'Initializing project %s ...' % self.name
539 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800540
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700541 self._InitRemote()
542 for r in self.extraRemotes.values():
543 if not self._RemoteFetch(r.name):
544 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545 if not self._RemoteFetch():
546 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800547
548 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800549 self._InitMRef()
550 else:
551 self._InitMirrorHead()
552 try:
553 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
554 except OSError:
555 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700556 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800557
558 def PostRepoUpgrade(self):
559 self._InitHooks()
560
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700561 def _CopyFiles(self):
562 for file in self.copyfiles:
563 file._Copy()
564
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700565 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566 """Perform only the local IO portion of the sync process.
567 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700568 """
569 self._InitWorkTree()
570 self.CleanPublishedCache()
571
572 rem = self.GetRemote(self.remote.name)
573 rev = rem.ToLocal(self.revision)
Shawn O. Pearce559b8462009-03-02 12:56:08 -0800574 try:
575 self.bare_git.rev_parse('--verify', '%s^0' % rev)
576 except GitError:
577 raise ManifestInvalidRevisionError(
578 'revision %s in %s not found' % (self.revision, self.name))
579
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700580 branch = self.CurrentBranch
581
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700582 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 # Currently on a detached HEAD. The user is assumed to
584 # not have any local modifications worth worrying about.
585 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700586 if os.path.exists(os.path.join(self.worktree, '.dotest')) \
587 or os.path.exists(os.path.join(self.worktree, '.git', 'rebase-apply')):
588 syncbuf.fail(self, _PriorSyncFailedError())
589 return
590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 lost = self._revlist(not_rev(rev), HEAD)
592 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700593 syncbuf.info(self, "discarding %d commits", len(lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700594 try:
595 self._Checkout(rev, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700596 except GitError, e:
597 syncbuf.fail(self, e)
598 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700600 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601
602 branch = self.GetBranch(branch)
603 merge = branch.LocalMerge
604
605 if not merge:
606 # The current branch has no tracking configuration.
607 # Jump off it to a deatched HEAD.
608 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700609 syncbuf.info(self,
610 "leaving %s; does not track upstream",
611 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700612 try:
613 self._Checkout(rev, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700614 except GitError, e:
615 syncbuf.fail(self, e)
616 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700617 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700618 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619
620 upstream_gain = self._revlist(not_rev(HEAD), rev)
621 pub = self.WasPublished(branch.name)
622 if pub:
623 not_merged = self._revlist(not_rev(rev), pub)
624 if not_merged:
625 if upstream_gain:
626 # The user has published this branch and some of those
627 # commits are not yet merged upstream. We do not want
628 # to rewrite the published commits so we punt.
629 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700630 syncbuf.info(self,
631 "branch %s is published but is now %d commits behind",
632 branch.name,
633 len(upstream_gain))
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700634 return
Shawn O. Pearce23d77812008-10-30 11:06:57 -0700635 elif upstream_gain:
Shawn O. Pearcea54c5272008-10-30 11:03:00 -0700636 # We can fast-forward safely.
637 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700638 def _doff():
Shawn O. Pearcea54c5272008-10-30 11:03:00 -0700639 self._FastForward(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700640 self._CopyFiles()
641 syncbuf.later1(self, _doff)
642 return
Shawn O. Pearce23d77812008-10-30 11:06:57 -0700643 else:
644 # Trivially no changes in the upstream.
645 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700646 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647
648 if merge == rev:
649 try:
650 old_merge = self.bare_git.rev_parse('%s@{1}' % merge)
651 except GitError:
652 old_merge = merge
Shawn O. Pearce07346002008-10-21 07:09:27 -0700653 if old_merge == '0000000000000000000000000000000000000000' \
654 or old_merge == '':
655 old_merge = merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656 else:
657 # The upstream switched on us. Time to cross our fingers
658 # and pray that the old upstream also wasn't in the habit
659 # of rebasing itself.
660 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700661 syncbuf.info(self, "manifest switched %s...%s", merge, rev)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662 old_merge = merge
663
664 if rev == old_merge:
665 upstream_lost = []
666 else:
667 upstream_lost = self._revlist(not_rev(rev), old_merge)
668
669 if not upstream_lost and not upstream_gain:
670 # Trivially no changes caused by the upstream.
671 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700672 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700673
674 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700675 syncbuf.fail(self, _DirtyError())
676 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677
678 if upstream_lost:
679 # Upstream rebased. Not everything in HEAD
680 # may have been caused by the user.
681 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700682 syncbuf.info(self,
683 "discarding %d commits removed from upstream",
684 len(upstream_lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685
686 branch.remote = rem
687 branch.merge = self.revision
688 branch.Save()
689
690 my_changes = self._revlist(not_rev(old_merge), HEAD)
691 if my_changes:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700692 def _dorebase():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693 self._Rebase(upstream = old_merge, onto = rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700694 self._CopyFiles()
695 syncbuf.later2(self, _dorebase)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 elif upstream_lost:
697 try:
698 self._ResetHard(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700699 self._CopyFiles()
700 except GitError, e:
701 syncbuf.fail(self, e)
702 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700703 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700704 def _doff():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 self._FastForward(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700706 self._CopyFiles()
707 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800709 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700710 # dest should already be an absolute path, but src is project relative
711 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800712 abssrc = os.path.join(self.worktree, src)
713 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700714
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700715 def DownloadPatchSet(self, change_id, patch_id):
716 """Download a single patch set of a single change to FETCH_HEAD.
717 """
718 remote = self.GetRemote(self.remote.name)
719
720 cmd = ['fetch', remote.name]
721 cmd.append('refs/changes/%2.2d/%d/%d' \
722 % (change_id % 100, change_id, patch_id))
723 cmd.extend(map(lambda x: str(x), remote.fetch))
724 if GitCommand(self, cmd, bare=True).Wait() != 0:
725 return None
726 return DownloadedChange(self,
727 remote.ToLocal(self.revision),
728 change_id,
729 patch_id,
730 self.bare_git.rev_parse('FETCH_HEAD'))
731
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
733## Branch Management ##
734
735 def StartBranch(self, name):
736 """Create a new branch off the manifest's revision.
737 """
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700738 try:
739 self.bare_git.rev_parse(R_HEADS + name)
740 exists = True
741 except GitError:
742 exists = False;
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700744 if exists:
745 if name == self.CurrentBranch:
746 return True
747 else:
748 cmd = ['checkout', name, '--']
749 return GitCommand(self, cmd).Wait() == 0
750
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 else:
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700752 branch = self.GetBranch(name)
753 branch.remote = self.GetRemote(self.remote.name)
754 branch.merge = self.revision
755
756 rev = branch.LocalMerge
757 cmd = ['checkout', '-b', branch.name, rev]
758 if GitCommand(self, cmd).Wait() == 0:
759 branch.Save()
760 return True
761 else:
762 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763
Wink Saville02d79452009-04-10 13:01:24 -0700764 def CheckoutBranch(self, name):
765 """Checkout a local topic branch.
766 """
767
768 # Be sure the branch exists
769 try:
770 tip_rev = self.bare_git.rev_parse(R_HEADS + name)
771 except GitError:
772 return False;
773
774 # Do the checkout
775 cmd = ['checkout', name, '--']
Shawn O. Pearce2675c3f2009-04-10 16:20:25 -0700776 return GitCommand(self, cmd).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -0700777
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -0800778 def AbandonBranch(self, name):
779 """Destroy a local topic branch.
780 """
781 try:
782 tip_rev = self.bare_git.rev_parse(R_HEADS + name)
783 except GitError:
784 return
785
786 if self.CurrentBranch == name:
787 self._Checkout(
788 self.GetRemote(self.remote.name).ToLocal(self.revision),
789 quiet=True)
790
791 cmd = ['branch', '-D', name]
792 GitCommand(self, cmd, capture_stdout=True).Wait()
793
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794 def PruneHeads(self):
795 """Prune any topic branches already merged into upstream.
796 """
797 cb = self.CurrentBranch
798 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800799 left = self._allrefs
800 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801 if name.startswith(R_HEADS):
802 name = name[len(R_HEADS):]
803 if cb is None or name != cb:
804 kill.append(name)
805
806 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
807 if cb is not None \
808 and not self._revlist(HEAD + '...' + rev) \
809 and not self.IsDirty(consider_untracked = False):
810 self.work_git.DetachHead(HEAD)
811 kill.append(cb)
812
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700814 old = self.bare_git.GetHead()
815 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816 old = 'refs/heads/please_never_use_this_as_a_branch_name'
817
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700818 try:
819 self.bare_git.DetachHead(rev)
820
821 b = ['branch', '-d']
822 b.extend(kill)
823 b = GitCommand(self, b, bare=True,
824 capture_stdout=True,
825 capture_stderr=True)
826 b.Wait()
827 finally:
828 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800829 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800831 for branch in kill:
832 if (R_HEADS + branch) not in left:
833 self.CleanPublishedCache()
834 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835
836 if cb and cb not in kill:
837 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -0800838 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839
840 kept = []
841 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800842 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843 branch = self.GetBranch(branch)
844 base = branch.LocalMerge
845 if not base:
846 base = rev
847 kept.append(ReviewableBranch(self, branch, base))
848 return kept
849
850
851## Direct Git Commands ##
852
853 def _RemoteFetch(self, name=None):
854 if not name:
855 name = self.remote.name
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800856 cmd = ['fetch']
857 if not self.worktree:
858 cmd.append('--update-head-ok')
859 cmd.append(name)
860 return GitCommand(self, cmd, bare = True).Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700861
862 def _Checkout(self, rev, quiet=False):
863 cmd = ['checkout']
864 if quiet:
865 cmd.append('-q')
866 cmd.append(rev)
867 cmd.append('--')
868 if GitCommand(self, cmd).Wait() != 0:
869 if self._allrefs:
870 raise GitError('%s checkout %s ' % (self.name, rev))
871
872 def _ResetHard(self, rev, quiet=True):
873 cmd = ['reset', '--hard']
874 if quiet:
875 cmd.append('-q')
876 cmd.append(rev)
877 if GitCommand(self, cmd).Wait() != 0:
878 raise GitError('%s reset --hard %s ' % (self.name, rev))
879
880 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -0700881 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882 if onto is not None:
883 cmd.extend(['--onto', onto])
884 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -0700885 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886 raise GitError('%s rebase %s ' % (self.name, upstream))
887
888 def _FastForward(self, head):
889 cmd = ['merge', head]
890 if GitCommand(self, cmd).Wait() != 0:
891 raise GitError('%s merge %s ' % (self.name, head))
892
893 def _InitGitDir(self):
894 if not os.path.exists(self.gitdir):
895 os.makedirs(self.gitdir)
896 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -0800897
898 if self.manifest.IsMirror:
899 self.config.SetString('core.bare', 'true')
900 else:
901 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902
903 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -0700904 try:
905 to_rm = os.listdir(hooks)
906 except OSError:
907 to_rm = []
908 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800910 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911
912 m = self.manifest.manifestProject.config
913 for key in ['user.name', 'user.email']:
914 if m.Has(key, include_defaults = False):
915 self.config.SetString(key, m.GetString(key))
916
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800917 def _InitHooks(self):
918 hooks = self._gitdir_path('hooks')
919 if not os.path.exists(hooks):
920 os.makedirs(hooks)
921 for stock_hook in repo_hooks():
922 dst = os.path.join(hooks, os.path.basename(stock_hook))
923 try:
924 os.symlink(relpath(stock_hook, dst), dst)
925 except OSError, e:
926 if e.errno == errno.EEXIST:
927 pass
928 elif e.errno == errno.EPERM:
929 raise GitError('filesystem must support symlinks')
930 else:
931 raise
932
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933 def _InitRemote(self):
934 if self.remote.fetchUrl:
935 remote = self.GetRemote(self.remote.name)
936
937 url = self.remote.fetchUrl
938 while url.endswith('/'):
939 url = url[:-1]
940 url += '/%s.git' % self.name
941 remote.url = url
942 remote.review = self.remote.reviewUrl
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800943 if remote.projectname is None:
944 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700945
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800946 if self.worktree:
947 remote.ResetFetch(mirror=False)
948 else:
949 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700950 remote.Save()
951
952 for r in self.extraRemotes.values():
953 remote = self.GetRemote(r.name)
954 remote.url = r.fetchUrl
955 remote.review = r.reviewUrl
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800956 if r.projectName:
957 remote.projectname = r.projectName
958 elif remote.projectname is None:
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800959 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700960 remote.ResetFetch()
961 remote.Save()
962
963 def _InitMRef(self):
964 if self.manifest.branch:
965 msg = 'manifest set to %s' % self.revision
966 ref = R_M + self.manifest.branch
967
968 if IsId(self.revision):
Marcelo E. Magallon21f73852008-12-31 04:44:37 +0000969 dst = self.revision + '^0'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
971 else:
972 remote = self.GetRemote(self.remote.name)
973 dst = remote.ToLocal(self.revision)
974 self.bare_git.symbolic_ref('-m', msg, ref, dst)
975
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800976 def _InitMirrorHead(self):
977 dst = self.GetRemote(self.remote.name).ToLocal(self.revision)
978 msg = 'manifest set to %s' % self.revision
979 self.bare_git.SetHead(dst, message=msg)
980
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 def _InitWorkTree(self):
982 dotgit = os.path.join(self.worktree, '.git')
983 if not os.path.exists(dotgit):
984 os.makedirs(dotgit)
985
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 for name in ['config',
987 'description',
988 'hooks',
989 'info',
990 'logs',
991 'objects',
992 'packed-refs',
993 'refs',
994 'rr-cache',
995 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -0800996 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800997 src = os.path.join(self.gitdir, name)
998 dst = os.path.join(dotgit, name)
999 os.symlink(relpath(src, dst), dst)
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001000 except OSError, e:
1001 if e.errno == errno.EPERM:
1002 raise GitError('filesystem must support symlinks')
1003 else:
1004 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001005
1006 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
1007 rev = self.bare_git.rev_parse('%s^0' % rev)
1008
1009 f = open(os.path.join(dotgit, HEAD), 'wb')
1010 f.write("%s\n" % rev)
1011 f.close()
1012
1013 cmd = ['read-tree', '--reset', '-u']
1014 cmd.append('-v')
1015 cmd.append('HEAD')
1016 if GitCommand(self, cmd).Wait() != 0:
1017 raise GitError("cannot initialize work tree")
1018
1019 def _gitdir_path(self, path):
1020 return os.path.join(self.gitdir, path)
1021
1022 def _revlist(self, *args):
1023 cmd = []
1024 cmd.extend(args)
1025 cmd.append('--')
1026 return self.work_git.rev_list(*args)
1027
1028 @property
1029 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001030 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031
1032 class _GitGetByExec(object):
1033 def __init__(self, project, bare):
1034 self._project = project
1035 self._bare = bare
1036
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037 def LsOthers(self):
1038 p = GitCommand(self._project,
1039 ['ls-files',
1040 '-z',
1041 '--others',
1042 '--exclude-standard'],
1043 bare = False,
1044 capture_stdout = True,
1045 capture_stderr = True)
1046 if p.Wait() == 0:
1047 out = p.stdout
1048 if out:
1049 return out[:-1].split("\0")
1050 return []
1051
1052 def DiffZ(self, name, *args):
1053 cmd = [name]
1054 cmd.append('-z')
1055 cmd.extend(args)
1056 p = GitCommand(self._project,
1057 cmd,
1058 bare = False,
1059 capture_stdout = True,
1060 capture_stderr = True)
1061 try:
1062 out = p.process.stdout.read()
1063 r = {}
1064 if out:
1065 out = iter(out[:-1].split('\0'))
1066 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001067 try:
1068 info = out.next()
1069 path = out.next()
1070 except StopIteration:
1071 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001072
1073 class _Info(object):
1074 def __init__(self, path, omode, nmode, oid, nid, state):
1075 self.path = path
1076 self.src_path = None
1077 self.old_mode = omode
1078 self.new_mode = nmode
1079 self.old_id = oid
1080 self.new_id = nid
1081
1082 if len(state) == 1:
1083 self.status = state
1084 self.level = None
1085 else:
1086 self.status = state[:1]
1087 self.level = state[1:]
1088 while self.level.startswith('0'):
1089 self.level = self.level[1:]
1090
1091 info = info[1:].split(' ')
1092 info =_Info(path, *info)
1093 if info.status in ('R', 'C'):
1094 info.src_path = info.path
1095 info.path = out.next()
1096 r[info.path] = info
1097 return r
1098 finally:
1099 p.Wait()
1100
1101 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001102 if self._bare:
1103 path = os.path.join(self._project.gitdir, HEAD)
1104 else:
1105 path = os.path.join(self._project.worktree, '.git', HEAD)
1106 line = open(path, 'r').read()
1107 if line.startswith('ref: '):
1108 return line[5:-1]
1109 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110
1111 def SetHead(self, ref, message=None):
1112 cmdv = []
1113 if message is not None:
1114 cmdv.extend(['-m', message])
1115 cmdv.append(HEAD)
1116 cmdv.append(ref)
1117 self.symbolic_ref(*cmdv)
1118
1119 def DetachHead(self, new, message=None):
1120 cmdv = ['--no-deref']
1121 if message is not None:
1122 cmdv.extend(['-m', message])
1123 cmdv.append(HEAD)
1124 cmdv.append(new)
1125 self.update_ref(*cmdv)
1126
1127 def UpdateRef(self, name, new, old=None,
1128 message=None,
1129 detach=False):
1130 cmdv = []
1131 if message is not None:
1132 cmdv.extend(['-m', message])
1133 if detach:
1134 cmdv.append('--no-deref')
1135 cmdv.append(name)
1136 cmdv.append(new)
1137 if old is not None:
1138 cmdv.append(old)
1139 self.update_ref(*cmdv)
1140
1141 def DeleteRef(self, name, old=None):
1142 if not old:
1143 old = self.rev_parse(name)
1144 self.update_ref('-d', name, old)
1145
1146 def rev_list(self, *args):
1147 cmdv = ['rev-list']
1148 cmdv.extend(args)
1149 p = GitCommand(self._project,
1150 cmdv,
1151 bare = self._bare,
1152 capture_stdout = True,
1153 capture_stderr = True)
1154 r = []
1155 for line in p.process.stdout:
1156 r.append(line[:-1])
1157 if p.Wait() != 0:
1158 raise GitError('%s rev-list %s: %s' % (
1159 self._project.name,
1160 str(args),
1161 p.stderr))
1162 return r
1163
1164 def __getattr__(self, name):
1165 name = name.replace('_', '-')
1166 def runner(*args):
1167 cmdv = [name]
1168 cmdv.extend(args)
1169 p = GitCommand(self._project,
1170 cmdv,
1171 bare = self._bare,
1172 capture_stdout = True,
1173 capture_stderr = True)
1174 if p.Wait() != 0:
1175 raise GitError('%s %s: %s' % (
1176 self._project.name,
1177 name,
1178 p.stderr))
1179 r = p.stdout
1180 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1181 return r[:-1]
1182 return r
1183 return runner
1184
1185
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001186class _PriorSyncFailedError(Exception):
1187 def __str__(self):
1188 return 'prior sync failed; rebase still in progress'
1189
1190class _DirtyError(Exception):
1191 def __str__(self):
1192 return 'contains uncommitted changes'
1193
1194class _InfoMessage(object):
1195 def __init__(self, project, text):
1196 self.project = project
1197 self.text = text
1198
1199 def Print(self, syncbuf):
1200 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1201 syncbuf.out.nl()
1202
1203class _Failure(object):
1204 def __init__(self, project, why):
1205 self.project = project
1206 self.why = why
1207
1208 def Print(self, syncbuf):
1209 syncbuf.out.fail('error: %s/: %s',
1210 self.project.relpath,
1211 str(self.why))
1212 syncbuf.out.nl()
1213
1214class _Later(object):
1215 def __init__(self, project, action):
1216 self.project = project
1217 self.action = action
1218
1219 def Run(self, syncbuf):
1220 out = syncbuf.out
1221 out.project('project %s/', self.project.relpath)
1222 out.nl()
1223 try:
1224 self.action()
1225 out.nl()
1226 return True
1227 except GitError, e:
1228 out.nl()
1229 return False
1230
1231class _SyncColoring(Coloring):
1232 def __init__(self, config):
1233 Coloring.__init__(self, config, 'reposync')
1234 self.project = self.printer('header', attr = 'bold')
1235 self.info = self.printer('info')
1236 self.fail = self.printer('fail', fg='red')
1237
1238class SyncBuffer(object):
1239 def __init__(self, config, detach_head=False):
1240 self._messages = []
1241 self._failures = []
1242 self._later_queue1 = []
1243 self._later_queue2 = []
1244
1245 self.out = _SyncColoring(config)
1246 self.out.redirect(sys.stderr)
1247
1248 self.detach_head = detach_head
1249 self.clean = True
1250
1251 def info(self, project, fmt, *args):
1252 self._messages.append(_InfoMessage(project, fmt % args))
1253
1254 def fail(self, project, err=None):
1255 self._failures.append(_Failure(project, err))
1256 self.clean = False
1257
1258 def later1(self, project, what):
1259 self._later_queue1.append(_Later(project, what))
1260
1261 def later2(self, project, what):
1262 self._later_queue2.append(_Later(project, what))
1263
1264 def Finish(self):
1265 self._PrintMessages()
1266 self._RunLater()
1267 self._PrintMessages()
1268 return self.clean
1269
1270 def _RunLater(self):
1271 for q in ['_later_queue1', '_later_queue2']:
1272 if not self._RunQueue(q):
1273 return
1274
1275 def _RunQueue(self, queue):
1276 for m in getattr(self, queue):
1277 if not m.Run(self):
1278 self.clean = False
1279 return False
1280 setattr(self, queue, [])
1281 return True
1282
1283 def _PrintMessages(self):
1284 for m in self._messages:
1285 m.Print(self)
1286 for m in self._failures:
1287 m.Print(self)
1288
1289 self._messages = []
1290 self._failures = []
1291
1292
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293class MetaProject(Project):
1294 """A special project housed under .repo.
1295 """
1296 def __init__(self, manifest, name, gitdir, worktree):
1297 repodir = manifest.repodir
1298 Project.__init__(self,
1299 manifest = manifest,
1300 name = name,
1301 gitdir = gitdir,
1302 worktree = worktree,
1303 remote = Remote('origin'),
1304 relpath = '.repo/%s' % name,
1305 revision = 'refs/heads/master')
1306
1307 def PreSync(self):
1308 if self.Exists:
1309 cb = self.CurrentBranch
1310 if cb:
1311 base = self.GetBranch(cb).merge
1312 if base:
1313 self.revision = base
1314
1315 @property
1316 def HasChanges(self):
1317 """Has the remote received new commits not yet checked out?
1318 """
1319 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
1320 if self._revlist(not_rev(HEAD), rev):
1321 return True
1322 return False