| #!/usr/bin/env python |
| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Display active git branches and code changes in a ChromiumOS workspace.""" |
| |
| import optparse |
| import os |
| import re |
| import subprocess |
| |
| def RunCommand(path, command): |
| """Run a command in a given directory, return stdout.""" |
| |
| return subprocess.Popen(command, |
| cwd=path, |
| stdout=subprocess.PIPE).communicate()[0].rstrip() |
| |
| # |
| # Taken with slight modification from gclient_utils.py in the depot_tools |
| # project. |
| # |
| def FindFileUpwards(filename, path): |
| """Search upwards from the a directory to find a file.""" |
| |
| path = os.path.realpath(path) |
| while True: |
| file_path = os.path.join(path, filename) |
| if os.path.exists(file_path): |
| return file_path |
| (new_path, _) = os.path.split(path) |
| if new_path == path: |
| return None |
| path = new_path |
| |
| |
| def ShowName(relative_name, color): |
| """Display the directory name.""" |
| |
| if color: |
| print('\033[44m\033[37m%s\033[0m' % relative_name) |
| else: |
| print relative_name |
| |
| |
| def GetBranches(full_name, color): |
| """Return a list of branch descriptions.""" |
| |
| command = ['git', 'branch', '-vv'] |
| |
| if color: |
| command.append('--color') |
| |
| branches = RunCommand(full_name, command).splitlines() |
| |
| if re.search(r"\(no branch\)", branches[0]) and len(branches) == 1: |
| return [] |
| |
| return branches |
| |
| def GetStatus(full_name, color): |
| """Return a list of files that have modifications.""" |
| |
| command = ['git', 'status', '-s'] |
| |
| return RunCommand(full_name, command).splitlines() |
| |
| |
| def GetHistory(full_name, color, author, days): |
| """Return a list of oneline log messages. |
| |
| The messages are for the author going back a specified number of days. |
| """ |
| |
| command = ['git', 'log', |
| '--author=' + author, |
| '--after=' + '-' + str(days) + 'days', |
| '--pretty=oneline', |
| 'm/master'] |
| |
| return RunCommand(full_name, command).splitlines() |
| |
| |
| def ShowDir(full_name, color, logs, author, days): |
| """Display active work in a single git repository.""" |
| |
| branches = GetBranches(full_name, color) |
| status = GetStatus(full_name, color) |
| |
| if logs: |
| history = GetHistory(full_name, color, author, days) |
| else: |
| history = [] |
| |
| if branches or status or history: |
| # We want to use the full path for testing, but we want to use the |
| # relative path for display. |
| ShowName(os.path.relpath(full_name), color) |
| |
| if branches: print '\n'.join(branches) |
| if status: print '\n'.join(status) |
| if history: print '\n'.join(history) |
| |
| if branches or status or history: |
| print "" |
| |
| |
| def FindRoot(): |
| """Returns the repo root.""" |
| |
| repo_file = '.repo' |
| repo_path = FindFileUpwards(repo_file, os.getcwd()) |
| |
| if repo_path is None: |
| raise Exception('Failed to find %s.' % repo_file) |
| |
| return os.path.dirname(repo_path) |
| |
| |
| def main(): |
| parser = optparse.OptionParser(usage = 'usage: %prog [options]\n') |
| |
| parser.add_option('-l', '--logs', default=False, |
| help='Show the last few days of your commits in short ' |
| 'form.', |
| action='store_true', |
| dest='logs') |
| |
| parser.add_option('-d', '--days', default=8, |
| help='Set the number of days of history to show.', |
| type='int', |
| dest='days') |
| |
| parser.add_option('-a', '--author', default=os.environ['USER'], |
| help='Set the author to filter for.', |
| type='string', |
| dest='author') |
| |
| options, arguments = parser.parse_args() |
| |
| if arguments: |
| parser.print_usage() |
| return 1 |
| |
| color = os.isatty(1) |
| root = FindRoot() |
| repos = RunCommand(root, ['repo', 'forall', '-c', 'pwd']).splitlines() |
| |
| for full in repos: |
| ShowDir(full, color, options.logs, options.author, options.days) |
| |
| |
| if __name__ == '__main__': |
| main() |