blob: 79ade3b60dfc0eb77fe926325813a019c36a8599 [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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700143
144class StatusColoring(Coloring):
145 def __init__(self, config):
146 Coloring.__init__(self, config, 'status')
147 self.project = self.printer('header', attr = 'bold')
148 self.branch = self.printer('header', attr = 'bold')
149 self.nobranch = self.printer('nobranch', fg = 'red')
150
151 self.added = self.printer('added', fg = 'green')
152 self.changed = self.printer('changed', fg = 'red')
153 self.untracked = self.printer('untracked', fg = 'red')
154
155
156class DiffColoring(Coloring):
157 def __init__(self, config):
158 Coloring.__init__(self, config, 'diff')
159 self.project = self.printer('header', attr = 'bold')
160
161
162class _CopyFile:
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800163 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164 self.src = src
165 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800166 self.abs_src = abssrc
167 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168
169 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800170 src = self.abs_src
171 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 # copy file if it does not exist or is out of date
173 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
174 try:
175 # remove existing file first, since it might be read-only
176 if os.path.exists(dest):
177 os.remove(dest)
178 shutil.copy(src, dest)
179 # make the file read-only
180 mode = os.stat(dest)[stat.ST_MODE]
181 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
182 os.chmod(dest, mode)
183 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700184 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185
186
187class Project(object):
188 def __init__(self,
189 manifest,
190 name,
191 remote,
192 gitdir,
193 worktree,
194 relpath,
195 revision):
196 self.manifest = manifest
197 self.name = name
198 self.remote = remote
199 self.gitdir = gitdir
200 self.worktree = worktree
201 self.relpath = relpath
202 self.revision = revision
203 self.snapshots = {}
204 self.extraRemotes = {}
205 self.copyfiles = []
206 self.config = GitConfig.ForRepository(
207 gitdir = self.gitdir,
208 defaults = self.manifest.globalConfig)
209
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800210 if self.worktree:
211 self.work_git = self._GitGetByExec(self, bare=False)
212 else:
213 self.work_git = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 self.bare_git = self._GitGetByExec(self, bare=True)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700215 self.bare_ref = GitRefs(gitdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
217 @property
218 def Exists(self):
219 return os.path.isdir(self.gitdir)
220
221 @property
222 def CurrentBranch(self):
223 """Obtain the name of the currently checked out branch.
224 The branch name omits the 'refs/heads/' prefix.
225 None is returned if the project is on a detached HEAD.
226 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700227 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 if b.startswith(R_HEADS):
229 return b[len(R_HEADS):]
230 return None
231
232 def IsDirty(self, consider_untracked=True):
233 """Is the working directory modified in some way?
234 """
235 self.work_git.update_index('-q',
236 '--unmerged',
237 '--ignore-missing',
238 '--refresh')
239 if self.work_git.DiffZ('diff-index','-M','--cached',HEAD):
240 return True
241 if self.work_git.DiffZ('diff-files'):
242 return True
243 if consider_untracked and self.work_git.LsOthers():
244 return True
245 return False
246
247 _userident_name = None
248 _userident_email = None
249
250 @property
251 def UserName(self):
252 """Obtain the user's personal name.
253 """
254 if self._userident_name is None:
255 self._LoadUserIdentity()
256 return self._userident_name
257
258 @property
259 def UserEmail(self):
260 """Obtain the user's email address. This is very likely
261 to be their Gerrit login.
262 """
263 if self._userident_email is None:
264 self._LoadUserIdentity()
265 return self._userident_email
266
267 def _LoadUserIdentity(self):
268 u = self.bare_git.var('GIT_COMMITTER_IDENT')
269 m = re.compile("^(.*) <([^>]*)> ").match(u)
270 if m:
271 self._userident_name = m.group(1)
272 self._userident_email = m.group(2)
273 else:
274 self._userident_name = ''
275 self._userident_email = ''
276
277 def GetRemote(self, name):
278 """Get the configuration for a single remote.
279 """
280 return self.config.GetRemote(name)
281
282 def GetBranch(self, name):
283 """Get the configuration for a single branch.
284 """
285 return self.config.GetBranch(name)
286
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700287 def GetBranches(self):
288 """Get all existing local branches.
289 """
290 current = self.CurrentBranch
Shawn O. Pearced237b692009-04-17 18:49:50 -0700291 all = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700292 heads = {}
293 pubd = {}
294
295 for name, id in all.iteritems():
296 if name.startswith(R_HEADS):
297 name = name[len(R_HEADS):]
298 b = self.GetBranch(name)
299 b.current = name == current
300 b.published = None
301 b.revision = id
302 heads[name] = b
303
304 for name, id in all.iteritems():
305 if name.startswith(R_PUB):
306 name = name[len(R_PUB):]
307 b = heads.get(name)
308 if b:
309 b.published = id
310
311 return heads
312
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700313
314## Status Display ##
315
316 def PrintWorkTreeStatus(self):
317 """Prints the status of the repository to stdout.
318 """
319 if not os.path.isdir(self.worktree):
320 print ''
321 print 'project %s/' % self.relpath
322 print ' missing (run "repo sync")'
323 return
324
325 self.work_git.update_index('-q',
326 '--unmerged',
327 '--ignore-missing',
328 '--refresh')
329 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
330 df = self.work_git.DiffZ('diff-files')
331 do = self.work_git.LsOthers()
332 if not di and not df and not do:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700333 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700334
335 out = StatusColoring(self.config)
336 out.project('project %-40s', self.relpath + '/')
337
338 branch = self.CurrentBranch
339 if branch is None:
340 out.nobranch('(*** NO BRANCH ***)')
341 else:
342 out.branch('branch %s', branch)
343 out.nl()
344
345 paths = list()
346 paths.extend(di.keys())
347 paths.extend(df.keys())
348 paths.extend(do)
349
350 paths = list(set(paths))
351 paths.sort()
352
353 for p in paths:
354 try: i = di[p]
355 except KeyError: i = None
356
357 try: f = df[p]
358 except KeyError: f = None
359
360 if i: i_status = i.status.upper()
361 else: i_status = '-'
362
363 if f: f_status = f.status.lower()
364 else: f_status = '-'
365
366 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800367 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700368 i.src_path, p, i.level)
369 else:
370 line = ' %s%s\t%s' % (i_status, f_status, p)
371
372 if i and not f:
373 out.added('%s', line)
374 elif (i and f) or (not i and f):
375 out.changed('%s', line)
376 elif not i and not f:
377 out.untracked('%s', line)
378 else:
379 out.write('%s', line)
380 out.nl()
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700381 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700382
383 def PrintWorkTreeDiff(self):
384 """Prints the status of the repository to stdout.
385 """
386 out = DiffColoring(self.config)
387 cmd = ['diff']
388 if out.is_on:
389 cmd.append('--color')
390 cmd.append(HEAD)
391 cmd.append('--')
392 p = GitCommand(self,
393 cmd,
394 capture_stdout = True,
395 capture_stderr = True)
396 has_diff = False
397 for line in p.process.stdout:
398 if not has_diff:
399 out.nl()
400 out.project('project %s/' % self.relpath)
401 out.nl()
402 has_diff = True
403 print line[:-1]
404 p.Wait()
405
406
407## Publish / Upload ##
408
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700409 def WasPublished(self, branch, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700410 """Was the branch published (uploaded) for code review?
411 If so, returns the SHA-1 hash of the last published
412 state for the branch.
413 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700414 key = R_PUB + branch
415 if all is None:
416 try:
417 return self.bare_git.rev_parse(key)
418 except GitError:
419 return None
420 else:
421 try:
422 return all[key]
423 except KeyError:
424 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700425
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700426 def CleanPublishedCache(self, all=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700427 """Prunes any stale published refs.
428 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700429 if all is None:
430 all = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700431 heads = set()
432 canrm = {}
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700433 for name, id in all.iteritems():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700434 if name.startswith(R_HEADS):
435 heads.add(name)
436 elif name.startswith(R_PUB):
437 canrm[name] = id
438
439 for name, id in canrm.iteritems():
440 n = name[len(R_PUB):]
441 if R_HEADS + n not in heads:
442 self.bare_git.DeleteRef(name, id)
443
444 def GetUploadableBranches(self):
445 """List any branches which can be uploaded for review.
446 """
447 heads = {}
448 pubed = {}
449
450 for name, id in self._allrefs.iteritems():
451 if name.startswith(R_HEADS):
452 heads[name[len(R_HEADS):]] = id
453 elif name.startswith(R_PUB):
454 pubed[name[len(R_PUB):]] = id
455
456 ready = []
457 for branch, id in heads.iteritems():
458 if branch in pubed and pubed[branch] == id:
459 continue
460
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800461 rb = self.GetUploadableBranch(branch)
462 if rb:
463 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700464 return ready
465
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800466 def GetUploadableBranch(self, branch_name):
467 """Get a single uploadable branch, or None.
468 """
469 branch = self.GetBranch(branch_name)
470 base = branch.LocalMerge
471 if branch.LocalMerge:
472 rb = ReviewableBranch(self, branch, base)
473 if rb.commits:
474 return rb
475 return None
476
Joe Onorato2896a792008-11-17 16:56:36 -0500477 def UploadForReview(self, branch=None, replace_changes=None, people=([],[])):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700478 """Uploads the named branch for code review.
479 """
480 if branch is None:
481 branch = self.CurrentBranch
482 if branch is None:
483 raise GitError('not currently on a branch')
484
485 branch = self.GetBranch(branch)
486 if not branch.LocalMerge:
487 raise GitError('branch %s does not track a remote' % branch.name)
488 if not branch.remote.review:
489 raise GitError('remote %s has no review url' % branch.remote.name)
490
491 dest_branch = branch.merge
492 if not dest_branch.startswith(R_HEADS):
493 dest_branch = R_HEADS + dest_branch
494
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800495 if not branch.remote.projectname:
496 branch.remote.projectname = self.name
497 branch.remote.Save()
498
Shawn O. Pearce370e3fa2009-01-26 10:55:39 -0800499 if branch.remote.ReviewProtocol == 'ssh':
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800500 if dest_branch.startswith(R_HEADS):
501 dest_branch = dest_branch[len(R_HEADS):]
502
503 rp = ['gerrit receive-pack']
504 for e in people[0]:
505 rp.append('--reviewer=%s' % sq(e))
506 for e in people[1]:
507 rp.append('--cc=%s' % sq(e))
508
509 cmd = ['push']
510 cmd.append('--receive-pack=%s' % " ".join(rp))
511 cmd.append(branch.remote.SshReviewUrl(self.UserEmail))
512 cmd.append('%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch))
513 if replace_changes:
514 for change_id,commit_id in replace_changes.iteritems():
515 cmd.append('%s:refs/changes/%s/new' % (commit_id, change_id))
516 if GitCommand(self, cmd, bare = True).Wait() != 0:
517 raise UploadError('Upload failed')
518
519 else:
520 raise UploadError('Unsupported protocol %s' \
521 % branch.remote.review)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522
523 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
524 self.bare_git.UpdateRef(R_PUB + branch.name,
525 R_HEADS + branch.name,
526 message = msg)
527
528
529## Sync ##
530
531 def Sync_NetworkHalf(self):
532 """Perform only the network IO portion of the sync process.
533 Local working directory/branch state is not affected.
534 """
535 if not self.Exists:
536 print >>sys.stderr
537 print >>sys.stderr, 'Initializing project %s ...' % self.name
538 self._InitGitDir()
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800539
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self._InitRemote()
541 for r in self.extraRemotes.values():
542 if not self._RemoteFetch(r.name):
543 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544 if not self._RemoteFetch():
545 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800546
547 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800548 self._InitMRef()
549 else:
550 self._InitMirrorHead()
551 try:
552 os.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
553 except OSError:
554 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800556
557 def PostRepoUpgrade(self):
558 self._InitHooks()
559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 def _CopyFiles(self):
561 for file in self.copyfiles:
562 file._Copy()
563
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700564 def Sync_LocalHalf(self, syncbuf):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565 """Perform only the local IO portion of the sync process.
566 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567 """
568 self._InitWorkTree()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700569 all = self.bare_ref.all
570 self.CleanPublishedCache(all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700571
572 rem = self.GetRemote(self.remote.name)
573 rev = rem.ToLocal(self.revision)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700574 if rev in all:
575 revid = all[rev]
576 elif IsId(rev):
577 revid = rev
578 else:
579 try:
580 revid = self.bare_git.rev_parse('--verify', '%s^0' % rev)
581 except GitError:
582 raise ManifestInvalidRevisionError(
583 'revision %s in %s not found' % (self.revision, self.name))
Shawn O. Pearce559b8462009-03-02 12:56:08 -0800584
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700585 head = self.work_git.GetHead()
586 if head.startswith(R_HEADS):
587 branch = head[len(R_HEADS):]
588 try:
589 head = all[head]
590 except KeyError:
591 head = None
592 else:
593 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700594
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700595 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700596 # Currently on a detached HEAD. The user is assumed to
597 # not have any local modifications worth worrying about.
598 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700599 if os.path.exists(os.path.join(self.worktree, '.dotest')) \
600 or os.path.exists(os.path.join(self.worktree, '.git', 'rebase-apply')):
601 syncbuf.fail(self, _PriorSyncFailedError())
602 return
603
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700604 if head == revid:
605 # No changes; don't do anything further.
606 #
607 return
608
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 lost = self._revlist(not_rev(rev), HEAD)
610 if lost:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700611 syncbuf.info(self, "discarding %d commits", len(lost))
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
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700620 if head == revid:
621 # No changes; don't do anything further.
622 #
623 return
624
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700625 branch = self.GetBranch(branch)
626 merge = branch.LocalMerge
627
628 if not merge:
629 # The current branch has no tracking configuration.
630 # Jump off it to a deatched HEAD.
631 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700632 syncbuf.info(self,
633 "leaving %s; does not track upstream",
634 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 try:
636 self._Checkout(rev, quiet=True)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700637 except GitError, e:
638 syncbuf.fail(self, e)
639 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640 self._CopyFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700641 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642
643 upstream_gain = self._revlist(not_rev(HEAD), rev)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700644 pub = self.WasPublished(branch.name, all)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645 if pub:
646 not_merged = self._revlist(not_rev(rev), pub)
647 if not_merged:
648 if upstream_gain:
649 # The user has published this branch and some of those
650 # commits are not yet merged upstream. We do not want
651 # to rewrite the published commits so we punt.
652 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700653 syncbuf.info(self,
654 "branch %s is published but is now %d commits behind",
655 branch.name,
656 len(upstream_gain))
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700657 return
Shawn O. Pearce23d77812008-10-30 11:06:57 -0700658 elif upstream_gain:
Shawn O. Pearcea54c5272008-10-30 11:03:00 -0700659 # We can fast-forward safely.
660 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700661 def _doff():
Shawn O. Pearcea54c5272008-10-30 11:03:00 -0700662 self._FastForward(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700663 self._CopyFiles()
664 syncbuf.later1(self, _doff)
665 return
Shawn O. Pearce23d77812008-10-30 11:06:57 -0700666 else:
667 # Trivially no changes in the upstream.
668 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700669 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700670
671 if merge == rev:
672 try:
673 old_merge = self.bare_git.rev_parse('%s@{1}' % merge)
674 except GitError:
675 old_merge = merge
Shawn O. Pearce07346002008-10-21 07:09:27 -0700676 if old_merge == '0000000000000000000000000000000000000000' \
677 or old_merge == '':
678 old_merge = merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679 else:
680 # The upstream switched on us. Time to cross our fingers
681 # and pray that the old upstream also wasn't in the habit
682 # of rebasing itself.
683 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700684 syncbuf.info(self, "manifest switched %s...%s", merge, rev)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685 old_merge = merge
686
687 if rev == old_merge:
688 upstream_lost = []
689 else:
690 upstream_lost = self._revlist(not_rev(rev), old_merge)
691
692 if not upstream_lost and not upstream_gain:
693 # Trivially no changes caused by the upstream.
694 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700695 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696
697 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700698 syncbuf.fail(self, _DirtyError())
699 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700
701 if upstream_lost:
702 # Upstream rebased. Not everything in HEAD
703 # may have been caused by the user.
704 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700705 syncbuf.info(self,
706 "discarding %d commits removed from upstream",
707 len(upstream_lost))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708
709 branch.remote = rem
710 branch.merge = self.revision
711 branch.Save()
712
713 my_changes = self._revlist(not_rev(old_merge), HEAD)
714 if my_changes:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700715 def _dorebase():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700716 self._Rebase(upstream = old_merge, onto = rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700717 self._CopyFiles()
718 syncbuf.later2(self, _dorebase)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 elif upstream_lost:
720 try:
721 self._ResetHard(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700722 self._CopyFiles()
723 except GitError, e:
724 syncbuf.fail(self, e)
725 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700726 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700727 def _doff():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728 self._FastForward(rev)
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700729 self._CopyFiles()
730 syncbuf.later1(self, _doff)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800732 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733 # dest should already be an absolute path, but src is project relative
734 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800735 abssrc = os.path.join(self.worktree, src)
736 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700738 def DownloadPatchSet(self, change_id, patch_id):
739 """Download a single patch set of a single change to FETCH_HEAD.
740 """
741 remote = self.GetRemote(self.remote.name)
742
743 cmd = ['fetch', remote.name]
744 cmd.append('refs/changes/%2.2d/%d/%d' \
745 % (change_id % 100, change_id, patch_id))
746 cmd.extend(map(lambda x: str(x), remote.fetch))
747 if GitCommand(self, cmd, bare=True).Wait() != 0:
748 return None
749 return DownloadedChange(self,
750 remote.ToLocal(self.revision),
751 change_id,
752 patch_id,
753 self.bare_git.rev_parse('FETCH_HEAD'))
754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755
756## Branch Management ##
757
758 def StartBranch(self, name):
759 """Create a new branch off the manifest's revision.
760 """
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700761 try:
762 self.bare_git.rev_parse(R_HEADS + name)
763 exists = True
764 except GitError:
765 exists = False;
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700767 if exists:
768 if name == self.CurrentBranch:
769 return True
770 else:
771 cmd = ['checkout', name, '--']
772 return GitCommand(self, cmd).Wait() == 0
773
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774 else:
Shawn O. Pearce0a389e92009-04-10 16:21:18 -0700775 branch = self.GetBranch(name)
776 branch.remote = self.GetRemote(self.remote.name)
777 branch.merge = self.revision
778
779 rev = branch.LocalMerge
780 cmd = ['checkout', '-b', branch.name, rev]
781 if GitCommand(self, cmd).Wait() == 0:
782 branch.Save()
783 return True
784 else:
785 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786
Wink Saville02d79452009-04-10 13:01:24 -0700787 def CheckoutBranch(self, name):
788 """Checkout a local topic branch.
789 """
790
791 # Be sure the branch exists
792 try:
793 tip_rev = self.bare_git.rev_parse(R_HEADS + name)
794 except GitError:
795 return False;
796
797 # Do the checkout
798 cmd = ['checkout', name, '--']
Shawn O. Pearce2675c3f2009-04-10 16:20:25 -0700799 return GitCommand(self, cmd).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -0700800
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -0800801 def AbandonBranch(self, name):
802 """Destroy a local topic branch.
803 """
804 try:
805 tip_rev = self.bare_git.rev_parse(R_HEADS + name)
806 except GitError:
807 return
808
809 if self.CurrentBranch == name:
810 self._Checkout(
811 self.GetRemote(self.remote.name).ToLocal(self.revision),
812 quiet=True)
813
814 cmd = ['branch', '-D', name]
815 GitCommand(self, cmd, capture_stdout=True).Wait()
816
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700817 def PruneHeads(self):
818 """Prune any topic branches already merged into upstream.
819 """
820 cb = self.CurrentBranch
821 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800822 left = self._allrefs
823 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824 if name.startswith(R_HEADS):
825 name = name[len(R_HEADS):]
826 if cb is None or name != cb:
827 kill.append(name)
828
829 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
830 if cb is not None \
831 and not self._revlist(HEAD + '...' + rev) \
832 and not self.IsDirty(consider_untracked = False):
833 self.work_git.DetachHead(HEAD)
834 kill.append(cb)
835
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700837 old = self.bare_git.GetHead()
838 if old is None:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 old = 'refs/heads/please_never_use_this_as_a_branch_name'
840
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 try:
842 self.bare_git.DetachHead(rev)
843
844 b = ['branch', '-d']
845 b.extend(kill)
846 b = GitCommand(self, b, bare=True,
847 capture_stdout=True,
848 capture_stderr=True)
849 b.Wait()
850 finally:
851 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800852 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800854 for branch in kill:
855 if (R_HEADS + branch) not in left:
856 self.CleanPublishedCache()
857 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858
859 if cb and cb not in kill:
860 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -0800861 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862
863 kept = []
864 for branch in kill:
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -0800865 if (R_HEADS + branch) in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700866 branch = self.GetBranch(branch)
867 base = branch.LocalMerge
868 if not base:
869 base = rev
870 kept.append(ReviewableBranch(self, branch, base))
871 return kept
872
873
874## Direct Git Commands ##
875
876 def _RemoteFetch(self, name=None):
877 if not name:
878 name = self.remote.name
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800879 cmd = ['fetch']
880 if not self.worktree:
881 cmd.append('--update-head-ok')
882 cmd.append(name)
883 return GitCommand(self, cmd, bare = True).Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700884
885 def _Checkout(self, rev, quiet=False):
886 cmd = ['checkout']
887 if quiet:
888 cmd.append('-q')
889 cmd.append(rev)
890 cmd.append('--')
891 if GitCommand(self, cmd).Wait() != 0:
892 if self._allrefs:
893 raise GitError('%s checkout %s ' % (self.name, rev))
894
895 def _ResetHard(self, rev, quiet=True):
896 cmd = ['reset', '--hard']
897 if quiet:
898 cmd.append('-q')
899 cmd.append(rev)
900 if GitCommand(self, cmd).Wait() != 0:
901 raise GitError('%s reset --hard %s ' % (self.name, rev))
902
903 def _Rebase(self, upstream, onto = None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -0700904 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 if onto is not None:
906 cmd.extend(['--onto', onto])
907 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -0700908 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909 raise GitError('%s rebase %s ' % (self.name, upstream))
910
911 def _FastForward(self, head):
912 cmd = ['merge', head]
913 if GitCommand(self, cmd).Wait() != 0:
914 raise GitError('%s merge %s ' % (self.name, head))
915
916 def _InitGitDir(self):
917 if not os.path.exists(self.gitdir):
918 os.makedirs(self.gitdir)
919 self.bare_git.init()
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -0800920
921 if self.manifest.IsMirror:
922 self.config.SetString('core.bare', 'true')
923 else:
924 self.config.SetString('core.bare', None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925
926 hooks = self._gitdir_path('hooks')
Shawn O. Pearcede646812008-10-29 14:38:12 -0700927 try:
928 to_rm = os.listdir(hooks)
929 except OSError:
930 to_rm = []
931 for old_hook in to_rm:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 os.remove(os.path.join(hooks, old_hook))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800933 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700934
935 m = self.manifest.manifestProject.config
936 for key in ['user.name', 'user.email']:
937 if m.Has(key, include_defaults = False):
938 self.config.SetString(key, m.GetString(key))
939
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800940 def _InitHooks(self):
941 hooks = self._gitdir_path('hooks')
942 if not os.path.exists(hooks):
943 os.makedirs(hooks)
944 for stock_hook in repo_hooks():
945 dst = os.path.join(hooks, os.path.basename(stock_hook))
946 try:
947 os.symlink(relpath(stock_hook, dst), dst)
948 except OSError, e:
949 if e.errno == errno.EEXIST:
950 pass
951 elif e.errno == errno.EPERM:
952 raise GitError('filesystem must support symlinks')
953 else:
954 raise
955
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956 def _InitRemote(self):
957 if self.remote.fetchUrl:
958 remote = self.GetRemote(self.remote.name)
959
960 url = self.remote.fetchUrl
961 while url.endswith('/'):
962 url = url[:-1]
963 url += '/%s.git' % self.name
964 remote.url = url
965 remote.review = self.remote.reviewUrl
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800966 if remote.projectname is None:
967 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800969 if self.worktree:
970 remote.ResetFetch(mirror=False)
971 else:
972 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700973 remote.Save()
974
975 for r in self.extraRemotes.values():
976 remote = self.GetRemote(r.name)
977 remote.url = r.fetchUrl
978 remote.review = r.reviewUrl
Shawn O. Pearceae6e0942008-11-06 10:25:35 -0800979 if r.projectName:
980 remote.projectname = r.projectName
981 elif remote.projectname is None:
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800982 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 remote.ResetFetch()
984 remote.Save()
985
986 def _InitMRef(self):
987 if self.manifest.branch:
988 msg = 'manifest set to %s' % self.revision
989 ref = R_M + self.manifest.branch
Shawn O. Pearce0f3dd232009-04-17 20:32:44 -0700990 cur = self.bare_ref.symref(ref)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991
992 if IsId(self.revision):
Shawn O. Pearce0f3dd232009-04-17 20:32:44 -0700993 if cur != '' or self.bare_ref.get(ref) != self.revision:
994 dst = self.revision + '^0'
995 self.bare_git.UpdateRef(ref, dst, message = msg, detach = True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700996 else:
997 remote = self.GetRemote(self.remote.name)
998 dst = remote.ToLocal(self.revision)
Shawn O. Pearce0f3dd232009-04-17 20:32:44 -0700999 if cur != dst:
1000 self.bare_git.symbolic_ref('-m', msg, ref, dst)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001002 def _InitMirrorHead(self):
1003 dst = self.GetRemote(self.remote.name).ToLocal(self.revision)
1004 msg = 'manifest set to %s' % self.revision
1005 self.bare_git.SetHead(dst, message=msg)
1006
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001007 def _InitWorkTree(self):
1008 dotgit = os.path.join(self.worktree, '.git')
1009 if not os.path.exists(dotgit):
1010 os.makedirs(dotgit)
1011
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012 for name in ['config',
1013 'description',
1014 'hooks',
1015 'info',
1016 'logs',
1017 'objects',
1018 'packed-refs',
1019 'refs',
1020 'rr-cache',
1021 'svn']:
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001022 try:
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001023 src = os.path.join(self.gitdir, name)
1024 dst = os.path.join(dotgit, name)
1025 os.symlink(relpath(src, dst), dst)
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -08001026 except OSError, e:
1027 if e.errno == errno.EPERM:
1028 raise GitError('filesystem must support symlinks')
1029 else:
1030 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001031
1032 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
1033 rev = self.bare_git.rev_parse('%s^0' % rev)
1034
1035 f = open(os.path.join(dotgit, HEAD), 'wb')
1036 f.write("%s\n" % rev)
1037 f.close()
1038
1039 cmd = ['read-tree', '--reset', '-u']
1040 cmd.append('-v')
1041 cmd.append('HEAD')
1042 if GitCommand(self, cmd).Wait() != 0:
1043 raise GitError("cannot initialize work tree")
1044
1045 def _gitdir_path(self, path):
1046 return os.path.join(self.gitdir, path)
1047
1048 def _revlist(self, *args):
1049 cmd = []
1050 cmd.extend(args)
1051 cmd.append('--')
1052 return self.work_git.rev_list(*args)
1053
1054 @property
1055 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07001056 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001057
1058 class _GitGetByExec(object):
1059 def __init__(self, project, bare):
1060 self._project = project
1061 self._bare = bare
1062
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 def LsOthers(self):
1064 p = GitCommand(self._project,
1065 ['ls-files',
1066 '-z',
1067 '--others',
1068 '--exclude-standard'],
1069 bare = False,
1070 capture_stdout = True,
1071 capture_stderr = True)
1072 if p.Wait() == 0:
1073 out = p.stdout
1074 if out:
1075 return out[:-1].split("\0")
1076 return []
1077
1078 def DiffZ(self, name, *args):
1079 cmd = [name]
1080 cmd.append('-z')
1081 cmd.extend(args)
1082 p = GitCommand(self._project,
1083 cmd,
1084 bare = False,
1085 capture_stdout = True,
1086 capture_stderr = True)
1087 try:
1088 out = p.process.stdout.read()
1089 r = {}
1090 if out:
1091 out = iter(out[:-1].split('\0'))
1092 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07001093 try:
1094 info = out.next()
1095 path = out.next()
1096 except StopIteration:
1097 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001098
1099 class _Info(object):
1100 def __init__(self, path, omode, nmode, oid, nid, state):
1101 self.path = path
1102 self.src_path = None
1103 self.old_mode = omode
1104 self.new_mode = nmode
1105 self.old_id = oid
1106 self.new_id = nid
1107
1108 if len(state) == 1:
1109 self.status = state
1110 self.level = None
1111 else:
1112 self.status = state[:1]
1113 self.level = state[1:]
1114 while self.level.startswith('0'):
1115 self.level = self.level[1:]
1116
1117 info = info[1:].split(' ')
1118 info =_Info(path, *info)
1119 if info.status in ('R', 'C'):
1120 info.src_path = info.path
1121 info.path = out.next()
1122 r[info.path] = info
1123 return r
1124 finally:
1125 p.Wait()
1126
1127 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001128 if self._bare:
1129 path = os.path.join(self._project.gitdir, HEAD)
1130 else:
1131 path = os.path.join(self._project.worktree, '.git', HEAD)
1132 line = open(path, 'r').read()
1133 if line.startswith('ref: '):
1134 return line[5:-1]
1135 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136
1137 def SetHead(self, ref, message=None):
1138 cmdv = []
1139 if message is not None:
1140 cmdv.extend(['-m', message])
1141 cmdv.append(HEAD)
1142 cmdv.append(ref)
1143 self.symbolic_ref(*cmdv)
1144
1145 def DetachHead(self, new, message=None):
1146 cmdv = ['--no-deref']
1147 if message is not None:
1148 cmdv.extend(['-m', message])
1149 cmdv.append(HEAD)
1150 cmdv.append(new)
1151 self.update_ref(*cmdv)
1152
1153 def UpdateRef(self, name, new, old=None,
1154 message=None,
1155 detach=False):
1156 cmdv = []
1157 if message is not None:
1158 cmdv.extend(['-m', message])
1159 if detach:
1160 cmdv.append('--no-deref')
1161 cmdv.append(name)
1162 cmdv.append(new)
1163 if old is not None:
1164 cmdv.append(old)
1165 self.update_ref(*cmdv)
1166
1167 def DeleteRef(self, name, old=None):
1168 if not old:
1169 old = self.rev_parse(name)
1170 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001171 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172
1173 def rev_list(self, *args):
1174 cmdv = ['rev-list']
1175 cmdv.extend(args)
1176 p = GitCommand(self._project,
1177 cmdv,
1178 bare = self._bare,
1179 capture_stdout = True,
1180 capture_stderr = True)
1181 r = []
1182 for line in p.process.stdout:
1183 r.append(line[:-1])
1184 if p.Wait() != 0:
1185 raise GitError('%s rev-list %s: %s' % (
1186 self._project.name,
1187 str(args),
1188 p.stderr))
1189 return r
1190
1191 def __getattr__(self, name):
1192 name = name.replace('_', '-')
1193 def runner(*args):
1194 cmdv = [name]
1195 cmdv.extend(args)
1196 p = GitCommand(self._project,
1197 cmdv,
1198 bare = self._bare,
1199 capture_stdout = True,
1200 capture_stderr = True)
1201 if p.Wait() != 0:
1202 raise GitError('%s %s: %s' % (
1203 self._project.name,
1204 name,
1205 p.stderr))
1206 r = p.stdout
1207 if r.endswith('\n') and r.index('\n') == len(r) - 1:
1208 return r[:-1]
1209 return r
1210 return runner
1211
1212
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001213class _PriorSyncFailedError(Exception):
1214 def __str__(self):
1215 return 'prior sync failed; rebase still in progress'
1216
1217class _DirtyError(Exception):
1218 def __str__(self):
1219 return 'contains uncommitted changes'
1220
1221class _InfoMessage(object):
1222 def __init__(self, project, text):
1223 self.project = project
1224 self.text = text
1225
1226 def Print(self, syncbuf):
1227 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1228 syncbuf.out.nl()
1229
1230class _Failure(object):
1231 def __init__(self, project, why):
1232 self.project = project
1233 self.why = why
1234
1235 def Print(self, syncbuf):
1236 syncbuf.out.fail('error: %s/: %s',
1237 self.project.relpath,
1238 str(self.why))
1239 syncbuf.out.nl()
1240
1241class _Later(object):
1242 def __init__(self, project, action):
1243 self.project = project
1244 self.action = action
1245
1246 def Run(self, syncbuf):
1247 out = syncbuf.out
1248 out.project('project %s/', self.project.relpath)
1249 out.nl()
1250 try:
1251 self.action()
1252 out.nl()
1253 return True
1254 except GitError, e:
1255 out.nl()
1256 return False
1257
1258class _SyncColoring(Coloring):
1259 def __init__(self, config):
1260 Coloring.__init__(self, config, 'reposync')
1261 self.project = self.printer('header', attr = 'bold')
1262 self.info = self.printer('info')
1263 self.fail = self.printer('fail', fg='red')
1264
1265class SyncBuffer(object):
1266 def __init__(self, config, detach_head=False):
1267 self._messages = []
1268 self._failures = []
1269 self._later_queue1 = []
1270 self._later_queue2 = []
1271
1272 self.out = _SyncColoring(config)
1273 self.out.redirect(sys.stderr)
1274
1275 self.detach_head = detach_head
1276 self.clean = True
1277
1278 def info(self, project, fmt, *args):
1279 self._messages.append(_InfoMessage(project, fmt % args))
1280
1281 def fail(self, project, err=None):
1282 self._failures.append(_Failure(project, err))
1283 self.clean = False
1284
1285 def later1(self, project, what):
1286 self._later_queue1.append(_Later(project, what))
1287
1288 def later2(self, project, what):
1289 self._later_queue2.append(_Later(project, what))
1290
1291 def Finish(self):
1292 self._PrintMessages()
1293 self._RunLater()
1294 self._PrintMessages()
1295 return self.clean
1296
1297 def _RunLater(self):
1298 for q in ['_later_queue1', '_later_queue2']:
1299 if not self._RunQueue(q):
1300 return
1301
1302 def _RunQueue(self, queue):
1303 for m in getattr(self, queue):
1304 if not m.Run(self):
1305 self.clean = False
1306 return False
1307 setattr(self, queue, [])
1308 return True
1309
1310 def _PrintMessages(self):
1311 for m in self._messages:
1312 m.Print(self)
1313 for m in self._failures:
1314 m.Print(self)
1315
1316 self._messages = []
1317 self._failures = []
1318
1319
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320class MetaProject(Project):
1321 """A special project housed under .repo.
1322 """
1323 def __init__(self, manifest, name, gitdir, worktree):
1324 repodir = manifest.repodir
1325 Project.__init__(self,
1326 manifest = manifest,
1327 name = name,
1328 gitdir = gitdir,
1329 worktree = worktree,
1330 remote = Remote('origin'),
1331 relpath = '.repo/%s' % name,
1332 revision = 'refs/heads/master')
1333
1334 def PreSync(self):
1335 if self.Exists:
1336 cb = self.CurrentBranch
1337 if cb:
1338 base = self.GetBranch(cb).merge
1339 if base:
1340 self.revision = base
1341
1342 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07001343 def LastFetch(self):
1344 try:
1345 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
1346 return os.path.getmtime(fh)
1347 except OSError:
1348 return 0
1349
1350 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001351 def HasChanges(self):
1352 """Has the remote received new commits not yet checked out?
1353 """
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001354 if not self.remote or not self.revision:
1355 return False
1356
1357 all = self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358 rev = self.GetRemote(self.remote.name).ToLocal(self.revision)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07001359 if rev in all:
1360 revid = all[rev]
1361 else:
1362 revid = rev
1363
1364 head = self.work_git.GetHead()
1365 if head.startswith(R_HEADS):
1366 try:
1367 head = all[head]
1368 except KeyError:
1369 head = None
1370
1371 if revid == head:
1372 return False
1373 elif self._revlist(not_rev(HEAD), rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 return True
1375 return False