blob: 15bf5e4bcc66ed8eaefd671116b6941787522a1a [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (c) 2013 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.
"""
A script to diff master branch and factory branch. It shows the commits that
only in master branch or only in factory branch.
By default, these repos are diff'ed:
platform/factory
platform/chromeos-hwid
If a board is specified, the private overlay repo for that board is also
included. In this case, if branch is not specified, this script tries to find
the latest factory-<board>-*.B branch.
"""
import argparse
from collections import namedtuple
import glob
import logging
import os
import re
import sys
import factory_common # pylint: disable=W0611
from cros.factory.utils.process_utils import CheckOutput
DiffEntry = namedtuple('DiffEntry',
['left_right', 'hash', 'author', 'subject'])
COLOR_RESET = '\033[0m'
COLOR_YELLOW = '\033[33m'
COLOR_CYAN = '\033[36m'
COLOR_GREEN = '\033[1;32m'
CHERRY_PICK = 'CHERRY-PICK: '
PRIVATE_OVERLAY_LIST = ['src/private-overlays/overlay-%s-private',
'src/private-overlays/overlay-variant-*-%s-private']
REPO_LIST = ['src/platform/factory', 'src/platform/chromeos-hwid']
def GetFullRepoPath(repo_path):
"""Returns the full path of the given repo."""
return os.path.join(os.path.expanduser('~'), 'trunk', repo_path)
def FindGitPrefix(repo_path):
"""Gets Git prefix, either 'cros' or 'cros-internal'."""
os.chdir(GetFullRepoPath(repo_path))
branch_list = CheckOutput(['git', 'branch', '-av'])
for line in reversed(branch_list.split('\n')):
match = re.search('remotes\/([^/]*)/master', line)
if match and match.group(1) != 'm':
return match.group(1)
return None
def GetBranch(board):
"""Gets latest factory branch for a board."""
if not board:
return None
os.chdir(GetFullRepoPath('src/platform/factory'))
branch_list = CheckOutput(['git', 'branch', '-av'])
for line in reversed(branch_list.split('\n')):
match = re.search('remotes\/cros\/(factory-%s-\d+.B)' % board, line)
if match:
return match.group(1)
return None
def GetPrivateOverlay(board):
"""Gets the path to private overlay."""
for pattern in PRIVATE_OVERLAY_LIST:
path = glob.glob(GetFullRepoPath(pattern % board))
if path:
if len(path) > 1:
logging.warning('Found more than one private overlays:\n%s',
'\n'.join(path))
return path[0]
return None
def GetDiffList(diff):
"""Generates a list of DiffEntry from Git output.
Args:
diff: The output from Git log command.
Returns:
A list of DiffEntry. Each DiffEntry corresponds to a commit that
is in one branch.
"""
ret = []
for line in diff.split('\n'):
match = re.match('([<>]) ([0-9a-f]+) \(([^\)]+)\) (.*)', line)
if match:
if match.group(4) == 'Marking set of ebuilds as stable':
continue
ret.append(DiffEntry(*match.groups()))
return ret
def RemoveCherryPickPrefix(subject):
return (subject[len(CHERRY_PICK):]
if subject.startswith(CHERRY_PICK)
else subject)
def RemoveCherryPick(diff_list):
diffed = {'<': set(), '>': set()}
for entry in diff_list:
subject = RemoveCherryPickPrefix(entry.subject)
diffed[entry.left_right].add((entry.author, subject))
# If a commit shows up in both branches, it is cherry-picked.
# (Even though Git doesn't know they are the same.)
cherrypicked = diffed['<'] & diffed['>']
return [entry for entry in diff_list
if (entry.author, RemoveCherryPickPrefix(entry.subject))
not in cherrypicked]
def DiffRepo(repo_path, branch, author, branch_only):
print '%s*** Diff %s ***%s' % (COLOR_GREEN, repo_path, COLOR_RESET)
os.chdir(GetFullRepoPath(repo_path))
prefix = FindGitPrefix(repo_path)
cmd = ['git', 'log', '--cherry-pick', '--oneline', '--left-right',
'--pretty=format:%m %h (%an) %s',
'%s/master...%s/%s' % (prefix, prefix, branch)]
if author:
cmd += ['--author', author]
diff = CheckOutput(cmd)
diff_list = GetDiffList(diff)
diff_list = RemoveCherryPick(diff_list)
# To make [Master] stands out, we only show [------] for commits that
# are in factory branch.
for entry in diff_list:
if branch_only and entry.left_right == '<':
continue
print '%s%s %s%s %s %s(%s)%s' % (COLOR_YELLOW,
'[Master]' if entry.left_right == '<'
else '[------]',
entry.hash,
COLOR_RESET,
entry.subject,
COLOR_CYAN,
entry.author,
COLOR_RESET)
print ''
def main():
parser = argparse.ArgumentParser(
description=('List the commits that are only in one of master '
'and factory branch.'))
parser.add_argument('--branch', '-r', default=None,
help='name of the factory branch')
parser.add_argument('--board', '-b', default=None,
help='board name')
parser.add_argument('--author', '-a', default=None,
help='Limit the output to this author only')
parser.add_argument('--factory_only', '-o', action='store_true',
help='Only show commits on factory branch')
args = parser.parse_args()
if not args.branch:
args.branch = GetBranch(args.board)
if not args.branch:
logging.error('Cannot determine factory branch name. '
'Specify --branch to continue.')
sys.exit(1)
if args.board:
REPO_LIST.append(GetPrivateOverlay(args.board))
for repo in REPO_LIST:
DiffRepo(repo, args.branch, args.author, args.factory_only)
sys.exit(0)
if __name__ == '__main__':
main()