From 89e717d9481c0c69292a39f85599f5df8277b004 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 18 Apr 2009 15:04:41 -0700 Subject: [PATCH] Improve checkout performance for the common unmodified case Most projects will have their branch heads matching in all branches, so switching between them should be just a matter of updating the work tree's HEAD symref. This can be done in pure Python, saving quite a bit of time over forking 'git checkout'. Signed-off-by: Shawn O. Pearce --- project.py | 43 ++++++++++++++++++++++++++++++++----------- subcmds/checkout.py | 27 +++++++++++++++++++-------- subcmds/start.py | 5 +++-- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/project.py b/project.py index 10a9b25..029a80f 100644 --- a/project.py +++ b/project.py @@ -779,9 +779,8 @@ class Project(object): all = self.bare_ref.all if (R_HEADS + name) in all: - cmd = ['checkout', name, '--'] return GitCommand(self, - cmd, + ['checkout', name, '--'], capture_stdout = True, capture_stderr = True).Wait() == 0 @@ -815,9 +814,8 @@ class Project(object): branch.Save() return True - cmd = ['checkout', '-b', branch.name, rev] if GitCommand(self, - cmd, + ['checkout', '-b', branch.name, rev], capture_stdout = True, capture_stderr = True).Wait() == 0: branch.Save() @@ -827,16 +825,39 @@ class Project(object): def CheckoutBranch(self, name): """Checkout a local topic branch. """ + rev = R_HEADS + name + head = self.work_git.GetHead() + if head == rev: + # Already on the branch + # + return True - # Be sure the branch exists + all = self.bare_ref.all try: - tip_rev = self.bare_git.rev_parse(R_HEADS + name) - except GitError: - return False; + revid = all[rev] + except KeyError: + # Branch does not exist in this project + # + return False - # Do the checkout - cmd = ['checkout', name, '--'] - return GitCommand(self, cmd).Wait() == 0 + if head.startswith(R_HEADS): + try: + head = all[head] + except KeyError: + head = None + + if head == revid: + # Same revision; just update HEAD to point to the new + # target branch, but otherwise take no other action. + # + _lwrite(os.path.join(self.worktree, '.git', HEAD), + 'ref: %s%s\n' % (R_HEADS, name)) + return True + + return GitCommand(self, + ['checkout', name, '--'], + capture_stdout = True, + capture_stderr = True).Wait() == 0 def AbandonBranch(self, name): """Destroy a local topic branch. diff --git a/subcmds/checkout.py b/subcmds/checkout.py index 07644c9..4198acd 100644 --- a/subcmds/checkout.py +++ b/subcmds/checkout.py @@ -15,6 +15,7 @@ import sys from command import Command +from progress import Progress class Checkout(Command): common = True @@ -35,13 +36,23 @@ The command is equivalent to: if not args: self.Usage() - retValue = 0; + nb = args[0] + err = [] + all = self.GetProjects(args[1:]) - branch = args[0] - for project in self.GetProjects(args[1:]): - if not project.CheckoutBranch(branch): - retValue = 1; - print >>sys.stderr, "error: checking out branch '%s' in %s failed" % (branch, project.name) + pm = Progress('Checkout %s' % nb, len(all)) + for project in all: + pm.update() + if not project.CheckoutBranch(nb): + err.append(project) + pm.end() - if (retValue != 0): - sys.exit(retValue); + if err: + if len(err) == len(all): + print >>sys.stderr, 'error: no project has branch %s' % nb + else: + for p in err: + print >>sys.stderr,\ + "error: %s/: cannot checkout %s" \ + % (p.relpath, nb) + sys.exit(1) diff --git a/subcmds/start.py b/subcmds/start.py index 49bb0e1..8c74625 100644 --- a/subcmds/start.py +++ b/subcmds/start.py @@ -49,7 +49,8 @@ revision specified in the manifest. pm.end() if err: - err.sort() for p in err: - print >>sys.stderr, "error: cannot start in %s" % p.relpath + print >>sys.stderr,\ + "error: %s/: cannot start %s" \ + % (p.relpath, nb) sys.exit(1)