diff options
-rw-r--r-- | color.py | 3 | ||||
-rw-r--r-- | project.py | 210 | ||||
-rw-r--r-- | subcmds/init.py | 6 | ||||
-rw-r--r-- | subcmds/sync.py | 18 |
4 files changed, 175 insertions, 62 deletions
@@ -100,6 +100,9 @@ class Coloring(object): | |||
100 | else: | 100 | else: |
101 | self._on = False | 101 | self._on = False |
102 | 102 | ||
103 | def redirect(self, out): | ||
104 | self._out = out | ||
105 | |||
103 | @property | 106 | @property |
104 | def is_on(self): | 107 | def is_on(self): |
105 | return self._on | 108 | return self._on |
@@ -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 | ||
41 | def _warn(fmt, *args): | ||
42 | msg = fmt % args | ||
43 | print >>sys.stderr, 'warn: %s' % msg | ||
44 | |||
45 | def _info(fmt, *args): | ||
46 | msg = fmt % args | ||
47 | print >>sys.stderr, 'info: %s' % msg | ||
48 | |||
49 | def not_rev(r): | 41 | def 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 | ||
1206 | class _PriorSyncFailedError(Exception): | ||
1207 | def __str__(self): | ||
1208 | return 'prior sync failed; rebase still in progress' | ||
1209 | |||
1210 | class _DirtyError(Exception): | ||
1211 | def __str__(self): | ||
1212 | return 'contains uncommitted changes' | ||
1213 | |||
1214 | class _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 | |||
1223 | class _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 | |||
1234 | class _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 | |||
1251 | class _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 | |||
1258 | class 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 | |||
1215 | class MetaProject(Project): | 1313 | class MetaProject(Project): |
1216 | """A special project housed under .repo. | 1314 | """A special project housed under .repo. |
1217 | """ | 1315 | """ |
diff --git a/subcmds/init.py b/subcmds/init.py index a32eaae0..103a78d6 100644 --- a/subcmds/init.py +++ b/subcmds/init.py | |||
@@ -20,6 +20,7 @@ from color import Coloring | |||
20 | from command import InteractiveCommand, MirrorSafeCommand | 20 | from command import InteractiveCommand, MirrorSafeCommand |
21 | from error import ManifestParseError | 21 | from error import ManifestParseError |
22 | from remote import Remote | 22 | from remote import Remote |
23 | from project import SyncBuffer | ||
23 | from git_command import git, MIN_GIT_VERSION | 24 | from git_command import git, MIN_GIT_VERSION |
24 | 25 | ||
25 | class Init(InteractiveCommand, MirrorSafeCommand): | 26 | class Init(InteractiveCommand, MirrorSafeCommand): |
@@ -129,7 +130,10 @@ default.xml will be used. | |||
129 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url | 130 | print >>sys.stderr, 'fatal: cannot obtain manifest %s' % r.url |
130 | sys.exit(1) | 131 | sys.exit(1) |
131 | 132 | ||
132 | m.Sync_LocalHalf() | 133 | syncbuf = SyncBuffer(m.config) |
134 | m.Sync_LocalHalf(syncbuf) | ||
135 | syncbuf.Finish() | ||
136 | |||
133 | if is_new or m.CurrentBranch is None: | 137 | if is_new or m.CurrentBranch is None: |
134 | if not m.StartBranch('default'): | 138 | if not m.StartBranch('default'): |
135 | print >>sys.stderr, 'fatal: cannot create default in manifest' | 139 | print >>sys.stderr, 'fatal: cannot create default in manifest' |
diff --git a/subcmds/sync.py b/subcmds/sync.py index f6eb2a08..ec5ada21 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -24,6 +24,7 @@ from project import HEAD | |||
24 | from command import Command, MirrorSafeCommand | 24 | from command import Command, MirrorSafeCommand |
25 | from error import RepoChangedException, GitError | 25 | from error import RepoChangedException, GitError |
26 | from project import R_HEADS | 26 | from project import R_HEADS |
27 | from project import SyncBuffer | ||
27 | from progress import Progress | 28 | from progress import Progress |
28 | 29 | ||
29 | class Sync(Command, MirrorSafeCommand): | 30 | class Sync(Command, MirrorSafeCommand): |
@@ -112,7 +113,9 @@ revision is temporarily needed. | |||
112 | return | 113 | return |
113 | 114 | ||
114 | if mp.HasChanges: | 115 | if mp.HasChanges: |
115 | if not mp.Sync_LocalHalf(): | 116 | syncbuf = SyncBuffer(mp.config) |
117 | mp.Sync_LocalHalf(syncbuf) | ||
118 | if not syncbuf.Finish(): | ||
116 | sys.exit(1) | 119 | sys.exit(1) |
117 | 120 | ||
118 | self.manifest._Unload() | 121 | self.manifest._Unload() |
@@ -123,14 +126,17 @@ revision is temporarily needed. | |||
123 | missing.append(project) | 126 | missing.append(project) |
124 | self._Fetch(*missing) | 127 | self._Fetch(*missing) |
125 | 128 | ||
129 | syncbuf = SyncBuffer(mp.config, | ||
130 | detach_head = opt.detach_head) | ||
126 | pm = Progress('Syncing work tree', len(all)) | 131 | pm = Progress('Syncing work tree', len(all)) |
127 | for project in all: | 132 | for project in all: |
128 | pm.update() | 133 | pm.update() |
129 | if project.worktree: | 134 | if project.worktree: |
130 | if not project.Sync_LocalHalf( | 135 | project.Sync_LocalHalf(syncbuf) |
131 | detach_head=opt.detach_head): | ||
132 | sys.exit(1) | ||
133 | pm.end() | 136 | pm.end() |
137 | print >>sys.stderr | ||
138 | if not syncbuf.Finish(): | ||
139 | sys.exit(1) | ||
134 | 140 | ||
135 | 141 | ||
136 | def _PostRepoUpgrade(manifest): | 142 | def _PostRepoUpgrade(manifest): |
@@ -143,7 +149,9 @@ def _PostRepoFetch(rp, no_repo_verify=False, verbose=False): | |||
143 | print >>sys.stderr, 'info: A new version of repo is available' | 149 | print >>sys.stderr, 'info: A new version of repo is available' |
144 | print >>sys.stderr, '' | 150 | print >>sys.stderr, '' |
145 | if no_repo_verify or _VerifyTag(rp): | 151 | if no_repo_verify or _VerifyTag(rp): |
146 | if not rp.Sync_LocalHalf(): | 152 | syncbuf = SyncBuffer(rp.config) |
153 | rp.Sync_LocalHalf(syncbuf) | ||
154 | if not syncbuf.Finish(): | ||
147 | sys.exit(1) | 155 | sys.exit(1) |
148 | print >>sys.stderr, 'info: Restarting repo with latest version' | 156 | print >>sys.stderr, 'info: Restarting repo with latest version' |
149 | raise RepoChangedException(['--repo-upgraded']) | 157 | raise RepoChangedException(['--repo-upgraded']) |