summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
Diffstat (limited to 'project.py')
-rw-r--r--project.py210
1 files changed, 154 insertions, 56 deletions
diff --git a/project.py b/project.py
index 33cb3444..6621f1cb 100644
--- a/project.py
+++ b/project.py
@@ -38,14 +38,6 @@ def _error(fmt, *args):
38 msg = fmt % args 38 msg = fmt % args
39 print >>sys.stderr, 'error: %s' % msg 39 print >>sys.stderr, 'error: %s' % msg
40 40
41def _warn(fmt, *args):
42 msg = fmt % args
43 print >>sys.stderr, 'warn: %s' % msg
44
45def _info(fmt, *args):
46 msg = fmt % args
47 print >>sys.stderr, 'info: %s' % msg
48
49def not_rev(r): 41def not_rev(r):
50 return '^' + r 42 return '^' + r
51 43
@@ -576,13 +568,9 @@ class Project(object):
576 for file in self.copyfiles: 568 for file in self.copyfiles:
577 file._Copy() 569 file._Copy()
578 570
579 def Sync_LocalHalf(self, detach_head=False): 571 def Sync_LocalHalf(self, syncbuf):
580 """Perform only the local IO portion of the sync process. 572 """Perform only the local IO portion of the sync process.
581 Network access is not required. 573 Network access is not required.
582
583 Return:
584 True: the sync was successful
585 False: the sync requires user input
586 """ 574 """
587 self._InitWorkTree() 575 self._InitWorkTree()
588 self.CleanPublishedCache() 576 self.CleanPublishedCache()
@@ -597,19 +585,25 @@ class Project(object):
597 585
598 branch = self.CurrentBranch 586 branch = self.CurrentBranch
599 587
600 if branch is None or detach_head: 588 if branch is None or syncbuf.detach_head:
601 # Currently on a detached HEAD. The user is assumed to 589 # Currently on a detached HEAD. The user is assumed to
602 # not have any local modifications worth worrying about. 590 # not have any local modifications worth worrying about.
603 # 591 #
592 if os.path.exists(os.path.join(self.worktree, '.dotest')) \
593 or os.path.exists(os.path.join(self.worktree, '.git', 'rebase-apply')):
594 syncbuf.fail(self, _PriorSyncFailedError())
595 return
596
604 lost = self._revlist(not_rev(rev), HEAD) 597 lost = self._revlist(not_rev(rev), HEAD)
605 if lost: 598 if lost:
606 _info("[%s] Discarding %d commits", self.name, len(lost)) 599 syncbuf.info(self, "discarding %d commits", len(lost))
607 try: 600 try:
608 self._Checkout(rev, quiet=True) 601 self._Checkout(rev, quiet=True)
609 except GitError: 602 except GitError, e:
610 return False 603 syncbuf.fail(self, e)
604 return
611 self._CopyFiles() 605 self._CopyFiles()
612 return True 606 return
613 607
614 branch = self.GetBranch(branch) 608 branch = self.GetBranch(branch)
615 merge = branch.LocalMerge 609 merge = branch.LocalMerge
@@ -618,16 +612,16 @@ class Project(object):
618 # The current branch has no tracking configuration. 612 # The current branch has no tracking configuration.
619 # Jump off it to a deatched HEAD. 613 # Jump off it to a deatched HEAD.
620 # 614 #
621 _info("[%s] Leaving %s" 615 syncbuf.info(self,
622 " (does not track any upstream)", 616 "leaving %s; does not track upstream",
623 self.name, 617 branch.name)
624 branch.name)
625 try: 618 try:
626 self._Checkout(rev, quiet=True) 619 self._Checkout(rev, quiet=True)
627 except GitError: 620 except GitError, e:
628 return False 621 syncbuf.fail(self, e)
622 return
629 self._CopyFiles() 623 self._CopyFiles()
630 return True 624 return
631 625
632 upstream_gain = self._revlist(not_rev(HEAD), rev) 626 upstream_gain = self._revlist(not_rev(HEAD), rev)
633 pub = self.WasPublished(branch.name) 627 pub = self.WasPublished(branch.name)
@@ -639,25 +633,24 @@ class Project(object):
639 # commits are not yet merged upstream. We do not want 633 # commits are not yet merged upstream. We do not want
640 # to rewrite the published commits so we punt. 634 # to rewrite the published commits so we punt.
641 # 635 #
642 _info("[%s] Branch %s is published," 636 syncbuf.info(self,
643 " but is now %d commits behind.", 637 "branch %s is published but is now %d commits behind",
644 self.name, branch.name, len(upstream_gain)) 638 branch.name,
645 _info("[%s] Consider merging or rebasing the" 639 len(upstream_gain))
646 " unpublished commits.", self.name) 640 syncbuf.info(self, "consider merging or rebasing the unpublished commits")
647 return True 641 return
648 elif upstream_gain: 642 elif upstream_gain:
649 # We can fast-forward safely. 643 # We can fast-forward safely.
650 # 644 #
651 try: 645 def _doff():
652 self._FastForward(rev) 646 self._FastForward(rev)
653 except GitError: 647 self._CopyFiles()
654 return False 648 syncbuf.later1(self, _doff)
655 self._CopyFiles() 649 return
656 return True
657 else: 650 else:
658 # Trivially no changes in the upstream. 651 # Trivially no changes in the upstream.
659 # 652 #
660 return True 653 return
661 654
662 if merge == rev: 655 if merge == rev:
663 try: 656 try:
@@ -672,8 +665,7 @@ class Project(object):
672 # and pray that the old upstream also wasn't in the habit 665 # and pray that the old upstream also wasn't in the habit
673 # of rebasing itself. 666 # of rebasing itself.
674 # 667 #
675 _info("[%s] Manifest switched from %s to %s", 668 syncbuf.info(self, "manifest switched %s...%s", merge, rev)
676 self.name, merge, rev)
677 old_merge = merge 669 old_merge = merge
678 670
679 if rev == old_merge: 671 if rev == old_merge:
@@ -684,19 +676,19 @@ class Project(object):
684 if not upstream_lost and not upstream_gain: 676 if not upstream_lost and not upstream_gain:
685 # Trivially no changes caused by the upstream. 677 # Trivially no changes caused by the upstream.
686 # 678 #
687 return True 679 return
688 680
689 if self.IsDirty(consider_untracked=False): 681 if self.IsDirty(consider_untracked=False):
690 _warn('[%s] commit (or discard) uncommitted changes' 682 syncbuf.fail(self, _DirtyError())
691 ' before sync', self.name) 683 return
692 return False
693 684
694 if upstream_lost: 685 if upstream_lost:
695 # Upstream rebased. Not everything in HEAD 686 # Upstream rebased. Not everything in HEAD
696 # may have been caused by the user. 687 # may have been caused by the user.
697 # 688 #
698 _info("[%s] Discarding %d commits removed from upstream", 689 syncbuf.info(self,
699 self.name, len(upstream_lost)) 690 "discarding %d commits removed from upstream",
691 len(upstream_lost))
700 692
701 branch.remote = rem 693 branch.remote = rem
702 branch.merge = self.revision 694 branch.merge = self.revision
@@ -704,23 +696,22 @@ class Project(object):
704 696
705 my_changes = self._revlist(not_rev(old_merge), HEAD) 697 my_changes = self._revlist(not_rev(old_merge), HEAD)
706 if my_changes: 698 if my_changes:
707 try: 699 def _dorebase():
708 self._Rebase(upstream = old_merge, onto = rev) 700 self._Rebase(upstream = old_merge, onto = rev)
709 except GitError: 701 self._CopyFiles()
710 return False 702 syncbuf.later2(self, _dorebase)
711 elif upstream_lost: 703 elif upstream_lost:
712 try: 704 try:
713 self._ResetHard(rev) 705 self._ResetHard(rev)
714 except GitError: 706 self._CopyFiles()
715 return False 707 except GitError, e:
708 syncbuf.fail(self, e)
709 return
716 else: 710 else:
717 try: 711 def _doff():
718 self._FastForward(rev) 712 self._FastForward(rev)
719 except GitError: 713 self._CopyFiles()
720 return False 714 syncbuf.later1(self, _doff)
721
722 self._CopyFiles()
723 return True
724 715
725 def AddCopyFile(self, src, dest, absdest): 716 def AddCopyFile(self, src, dest, absdest):
726 # dest should already be an absolute path, but src is project relative 717 # dest should already be an absolute path, but src is project relative
@@ -1212,6 +1203,113 @@ class Project(object):
1212 return runner 1203 return runner
1213 1204
1214 1205
1206class _PriorSyncFailedError(Exception):
1207 def __str__(self):
1208 return 'prior sync failed; rebase still in progress'
1209
1210class _DirtyError(Exception):
1211 def __str__(self):
1212 return 'contains uncommitted changes'
1213
1214class _InfoMessage(object):
1215 def __init__(self, project, text):
1216 self.project = project
1217 self.text = text
1218
1219 def Print(self, syncbuf):
1220 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
1221 syncbuf.out.nl()
1222
1223class _Failure(object):
1224 def __init__(self, project, why):
1225 self.project = project
1226 self.why = why
1227
1228 def Print(self, syncbuf):
1229 syncbuf.out.fail('error: %s/: %s',
1230 self.project.relpath,
1231 str(self.why))
1232 syncbuf.out.nl()
1233
1234class _Later(object):
1235 def __init__(self, project, action):
1236 self.project = project
1237 self.action = action
1238
1239 def Run(self, syncbuf):
1240 out = syncbuf.out
1241 out.project('project %s/', self.project.relpath)
1242 out.nl()
1243 try:
1244 self.action()
1245 out.nl()
1246 return True
1247 except GitError, e:
1248 out.nl()
1249 return False
1250
1251class _SyncColoring(Coloring):
1252 def __init__(self, config):
1253 Coloring.__init__(self, config, 'reposync')
1254 self.project = self.printer('header', attr = 'bold')
1255 self.info = self.printer('info')
1256 self.fail = self.printer('fail', fg='red')
1257
1258class SyncBuffer(object):
1259 def __init__(self, config, detach_head=False):
1260 self._messages = []
1261 self._failures = []
1262 self._later_queue1 = []
1263 self._later_queue2 = []
1264
1265 self.out = _SyncColoring(config)
1266 self.out.redirect(sys.stderr)
1267
1268 self.detach_head = detach_head
1269 self.clean = True
1270
1271 def info(self, project, fmt, *args):
1272 self._messages.append(_InfoMessage(project, fmt % args))
1273
1274 def fail(self, project, err=None):
1275 self._failures.append(_Failure(project, err))
1276 self.clean = False
1277
1278 def later1(self, project, what):
1279 self._later_queue1.append(_Later(project, what))
1280
1281 def later2(self, project, what):
1282 self._later_queue2.append(_Later(project, what))
1283
1284 def Finish(self):
1285 self._PrintMessages()
1286 self._RunLater()
1287 self._PrintMessages()
1288 return self.clean
1289
1290 def _RunLater(self):
1291 for q in ['_later_queue1', '_later_queue2']:
1292 if not self._RunQueue(q):
1293 return
1294
1295 def _RunQueue(self, queue):
1296 for m in getattr(self, queue):
1297 if not m.Run(self):
1298 self.clean = False
1299 return False
1300 setattr(self, queue, [])
1301 return True
1302
1303 def _PrintMessages(self):
1304 for m in self._messages:
1305 m.Print(self)
1306 for m in self._failures:
1307 m.Print(self)
1308
1309 self._messages = []
1310 self._failures = []
1311
1312
1215class MetaProject(Project): 1313class MetaProject(Project):
1216 """A special project housed under .repo. 1314 """A special project housed under .repo.
1217 """ 1315 """