113540Sandrea.mondelli@ucf.edu#!/usr/bin/env python2.7 211922Sandreas.sandberg@arm.com# 312447Sandreas.sandberg@arm.com# Copyright (c) 2017-2018 Arm Limited 411922Sandreas.sandberg@arm.com# All rights reserved 511922Sandreas.sandberg@arm.com# 611922Sandreas.sandberg@arm.com# The license below extends only to copyright in the software and shall 711922Sandreas.sandberg@arm.com# not be construed as granting a license to any other intellectual 811922Sandreas.sandberg@arm.com# property including but not limited to intellectual property relating 911922Sandreas.sandberg@arm.com# to a hardware implementation of the functionality of the software 1011922Sandreas.sandberg@arm.com# licensed hereunder. You may use the software subject to the license 1111922Sandreas.sandberg@arm.com# terms below provided that you ensure that this notice is replicated 1211922Sandreas.sandberg@arm.com# unmodified and in its entirety in all distributions of the software, 1311922Sandreas.sandberg@arm.com# modified or unmodified, in source code or in binary form. 1411922Sandreas.sandberg@arm.com# 1511922Sandreas.sandberg@arm.com# Redistribution and use in source and binary forms, with or without 1611922Sandreas.sandberg@arm.com# modification, are permitted provided that the following conditions are 1711922Sandreas.sandberg@arm.com# met: redistributions of source code must retain the above copyright 1811922Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer; 1911922Sandreas.sandberg@arm.com# redistributions in binary form must reproduce the above copyright 2011922Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer in the 2111922Sandreas.sandberg@arm.com# documentation and/or other materials provided with the distribution; 2211922Sandreas.sandberg@arm.com# neither the name of the copyright holders nor the names of its 2311922Sandreas.sandberg@arm.com# contributors may be used to endorse or promote products derived from 2411922Sandreas.sandberg@arm.com# this software without specific prior written permission. 2511922Sandreas.sandberg@arm.com# 2611922Sandreas.sandberg@arm.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2711922Sandreas.sandberg@arm.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2811922Sandreas.sandberg@arm.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 2911922Sandreas.sandberg@arm.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 3011922Sandreas.sandberg@arm.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 3111922Sandreas.sandberg@arm.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 3211922Sandreas.sandberg@arm.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 3311922Sandreas.sandberg@arm.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 3411922Sandreas.sandberg@arm.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 3511922Sandreas.sandberg@arm.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 3611922Sandreas.sandberg@arm.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 3711922Sandreas.sandberg@arm.com# 3811922Sandreas.sandberg@arm.com# Authors: Andreas Sandberg 3911922Sandreas.sandberg@arm.com 4011922Sandreas.sandberg@arm.com 4111922Sandreas.sandberg@arm.comimport subprocess 4211922Sandreas.sandberg@arm.comimport re 4311922Sandreas.sandberg@arm.comfrom functools import wraps 4411922Sandreas.sandberg@arm.com 4511922Sandreas.sandberg@arm.comclass Commit(object): 4611922Sandreas.sandberg@arm.com _re_tag = re.compile(r"^((?:\w|-)+): (.*)$") 4711922Sandreas.sandberg@arm.com 4811922Sandreas.sandberg@arm.com def __init__(self, rev): 4911922Sandreas.sandberg@arm.com self.rev = rev 5011922Sandreas.sandberg@arm.com self._log = None 5111922Sandreas.sandberg@arm.com self._tags = None 5211922Sandreas.sandberg@arm.com 5311922Sandreas.sandberg@arm.com def _git(self, args): 5411922Sandreas.sandberg@arm.com return subprocess.check_output([ "git", ] + args) 5511922Sandreas.sandberg@arm.com 5611922Sandreas.sandberg@arm.com @property 5711922Sandreas.sandberg@arm.com def log(self): 5811922Sandreas.sandberg@arm.com """Log message belonging to a commit returned as a list with on line 5911922Sandreas.sandberg@arm.com per element. 6011922Sandreas.sandberg@arm.com 6111922Sandreas.sandberg@arm.com """ 6211922Sandreas.sandberg@arm.com if self._log is None: 6311922Sandreas.sandberg@arm.com self._log = self._git( 6411922Sandreas.sandberg@arm.com ["show", "--format=%B", "--no-patch", str(self.rev) ] 6511922Sandreas.sandberg@arm.com ).rstrip("\n").split("\n") 6611922Sandreas.sandberg@arm.com return self._log 6711922Sandreas.sandberg@arm.com 6811922Sandreas.sandberg@arm.com @property 6911922Sandreas.sandberg@arm.com def tags(self): 7011922Sandreas.sandberg@arm.com """Get all commit message tags in the current commit. 7111922Sandreas.sandberg@arm.com 7211922Sandreas.sandberg@arm.com Returns: { tag, [ value, ... ] } 7311922Sandreas.sandberg@arm.com 7411922Sandreas.sandberg@arm.com """ 7511922Sandreas.sandberg@arm.com if self._tags is None: 7611922Sandreas.sandberg@arm.com tags = {} 7711922Sandreas.sandberg@arm.com for l in self.log[1:]: 7811922Sandreas.sandberg@arm.com m = Commit._re_tag.match(l) 7911922Sandreas.sandberg@arm.com if m: 8011922Sandreas.sandberg@arm.com key, value = m.group(1), m.group(2) 8111922Sandreas.sandberg@arm.com try: 8211922Sandreas.sandberg@arm.com tags[key].append(value) 8311922Sandreas.sandberg@arm.com except KeyError: 8411922Sandreas.sandberg@arm.com tags[key] = [ value ] 8511922Sandreas.sandberg@arm.com self._tags = tags 8611922Sandreas.sandberg@arm.com 8711922Sandreas.sandberg@arm.com return self._tags 8811922Sandreas.sandberg@arm.com 8911922Sandreas.sandberg@arm.com @property 9011922Sandreas.sandberg@arm.com def change_id(self): 9111922Sandreas.sandberg@arm.com """Get the Change-Id tag from the commit 9211922Sandreas.sandberg@arm.com 9311922Sandreas.sandberg@arm.com Returns: A change ID or None if no change ID has been 9411922Sandreas.sandberg@arm.com specified. 9511922Sandreas.sandberg@arm.com 9611922Sandreas.sandberg@arm.com """ 9711922Sandreas.sandberg@arm.com try: 9811922Sandreas.sandberg@arm.com cids = self.tags["Change-Id"] 9911922Sandreas.sandberg@arm.com except KeyError: 10011922Sandreas.sandberg@arm.com return None 10111922Sandreas.sandberg@arm.com 10211922Sandreas.sandberg@arm.com assert len(cids) == 1 10311922Sandreas.sandberg@arm.com return cids[0] 10411922Sandreas.sandberg@arm.com 10511922Sandreas.sandberg@arm.com def __str__(self): 10611922Sandreas.sandberg@arm.com return "%s: %s" % (self.rev[0:8], self.log[0]) 10711922Sandreas.sandberg@arm.com 10812447Sandreas.sandberg@arm.comdef list_revs(branch, baseline=None, paths=[]): 10912256Sandreas.sandberg@arm.com """Get a generator that lists git revisions that exist in 'branch'. If 11012256Sandreas.sandberg@arm.com the optional parameter 'baseline' is specified, the generator 11112256Sandreas.sandberg@arm.com excludes commits that exist on that branch. 11211922Sandreas.sandberg@arm.com 11311922Sandreas.sandberg@arm.com Returns: Generator of Commit objects 11411922Sandreas.sandberg@arm.com 11511922Sandreas.sandberg@arm.com """ 11611922Sandreas.sandberg@arm.com 11712256Sandreas.sandberg@arm.com if baseline is not None: 11812256Sandreas.sandberg@arm.com query = "%s..%s" % (branch, baseline) 11912256Sandreas.sandberg@arm.com else: 12012256Sandreas.sandberg@arm.com query = str(branch) 12112256Sandreas.sandberg@arm.com 12212447Sandreas.sandberg@arm.com changes = subprocess.check_output( 12312447Sandreas.sandberg@arm.com [ "git", "rev-list", query, '--'] + paths 12412447Sandreas.sandberg@arm.com ) 12511922Sandreas.sandberg@arm.com 12611922Sandreas.sandberg@arm.com if changes == "": 12711922Sandreas.sandberg@arm.com return 12811922Sandreas.sandberg@arm.com 12911922Sandreas.sandberg@arm.com for rev in changes.rstrip("\n").split("\n"): 13011922Sandreas.sandberg@arm.com assert rev != "" 13111922Sandreas.sandberg@arm.com yield Commit(rev) 13211922Sandreas.sandberg@arm.com 13312447Sandreas.sandberg@arm.comdef list_changes(upstream, feature, paths=[]): 13412447Sandreas.sandberg@arm.com feature_revs = tuple(list_revs(upstream, feature, paths=paths)) 13512447Sandreas.sandberg@arm.com upstream_revs = tuple(list_revs(feature, upstream, paths=paths)) 13611922Sandreas.sandberg@arm.com 13711922Sandreas.sandberg@arm.com feature_cids = dict([ 13811922Sandreas.sandberg@arm.com (c.change_id, c) for c in feature_revs if c.change_id is not None ]) 13911922Sandreas.sandberg@arm.com upstream_cids = dict([ 14011922Sandreas.sandberg@arm.com (c.change_id, c) for c in upstream_revs if c.change_id is not None ]) 14111922Sandreas.sandberg@arm.com 14211922Sandreas.sandberg@arm.com incoming = filter( 14311922Sandreas.sandberg@arm.com lambda r: r.change_id and r.change_id not in feature_cids, 14411922Sandreas.sandberg@arm.com reversed(upstream_revs)) 14511922Sandreas.sandberg@arm.com outgoing = filter( 14611922Sandreas.sandberg@arm.com lambda r: r.change_id and r.change_id not in upstream_cids, 14711922Sandreas.sandberg@arm.com reversed(feature_revs)) 14811922Sandreas.sandberg@arm.com common = filter( 14911922Sandreas.sandberg@arm.com lambda r: r.change_id in upstream_cids, 15011922Sandreas.sandberg@arm.com reversed(feature_revs)) 15111922Sandreas.sandberg@arm.com upstream_unknown = filter( 15211922Sandreas.sandberg@arm.com lambda r: r.change_id is None, 15311922Sandreas.sandberg@arm.com reversed(upstream_revs)) 15411922Sandreas.sandberg@arm.com feature_unknown = filter( 15511922Sandreas.sandberg@arm.com lambda r: r.change_id is None, 15611922Sandreas.sandberg@arm.com reversed(feature_revs)) 15711922Sandreas.sandberg@arm.com 15811922Sandreas.sandberg@arm.com return incoming, outgoing, common, upstream_unknown, feature_unknown 15911922Sandreas.sandberg@arm.com 16011922Sandreas.sandberg@arm.comdef _main(): 16111922Sandreas.sandberg@arm.com import argparse 16211922Sandreas.sandberg@arm.com parser = argparse.ArgumentParser( 16311922Sandreas.sandberg@arm.com description="List incoming and outgoing changes in a feature branch") 16411922Sandreas.sandberg@arm.com 16511922Sandreas.sandberg@arm.com parser.add_argument("--upstream", "-u", type=str, default="origin/master", 16611922Sandreas.sandberg@arm.com help="Upstream branch for comparison. " \ 16711922Sandreas.sandberg@arm.com "Default: %(default)s") 16811922Sandreas.sandberg@arm.com parser.add_argument("--feature", "-f", type=str, default="HEAD", 16911922Sandreas.sandberg@arm.com help="Feature branch for comparison. " \ 17011922Sandreas.sandberg@arm.com "Default: %(default)s") 17111922Sandreas.sandberg@arm.com parser.add_argument("--show-unknown", action="store_true", 17211922Sandreas.sandberg@arm.com help="Print changes without Change-Id tags") 17311922Sandreas.sandberg@arm.com parser.add_argument("--show-common", action="store_true", 17411922Sandreas.sandberg@arm.com help="Print common changes") 17512256Sandreas.sandberg@arm.com parser.add_argument("--deep-search", action="store_true", 17612256Sandreas.sandberg@arm.com help="Use a deep search to find incorrectly " \ 17712256Sandreas.sandberg@arm.com "rebased changes") 17812447Sandreas.sandberg@arm.com parser.add_argument("paths", metavar="PATH", type=str, nargs="*", 17912447Sandreas.sandberg@arm.com help="Paths to list changes for") 18011922Sandreas.sandberg@arm.com 18111922Sandreas.sandberg@arm.com args = parser.parse_args() 18211922Sandreas.sandberg@arm.com 18311922Sandreas.sandberg@arm.com incoming, outgoing, common, upstream_unknown, feature_unknown = \ 18412447Sandreas.sandberg@arm.com list_changes(args.upstream, args.feature, paths=args.paths) 18511922Sandreas.sandberg@arm.com 18611922Sandreas.sandberg@arm.com if incoming: 18711922Sandreas.sandberg@arm.com print "Incoming changes:" 18811922Sandreas.sandberg@arm.com for rev in incoming: 18911922Sandreas.sandberg@arm.com print rev 19011922Sandreas.sandberg@arm.com print 19111922Sandreas.sandberg@arm.com 19211922Sandreas.sandberg@arm.com if args.show_unknown and upstream_unknown: 19311922Sandreas.sandberg@arm.com print "Upstream changes without change IDs:" 19411922Sandreas.sandberg@arm.com for rev in upstream_unknown: 19511922Sandreas.sandberg@arm.com print rev 19611922Sandreas.sandberg@arm.com print 19711922Sandreas.sandberg@arm.com 19811922Sandreas.sandberg@arm.com if outgoing: 19911922Sandreas.sandberg@arm.com print "Outgoing changes:" 20011922Sandreas.sandberg@arm.com for rev in outgoing: 20111922Sandreas.sandberg@arm.com print rev 20211922Sandreas.sandberg@arm.com print 20311922Sandreas.sandberg@arm.com 20411922Sandreas.sandberg@arm.com if args.show_common and common: 20511922Sandreas.sandberg@arm.com print "Common changes:" 20611922Sandreas.sandberg@arm.com for rev in common: 20711922Sandreas.sandberg@arm.com print rev 20811922Sandreas.sandberg@arm.com print 20911922Sandreas.sandberg@arm.com 21011922Sandreas.sandberg@arm.com if args.show_unknown and feature_unknown: 21111922Sandreas.sandberg@arm.com print "Outgoing changes without change IDs:" 21211922Sandreas.sandberg@arm.com for rev in feature_unknown: 21311922Sandreas.sandberg@arm.com print rev 21411922Sandreas.sandberg@arm.com 21512256Sandreas.sandberg@arm.com if args.deep_search: 21612256Sandreas.sandberg@arm.com print "Incorrectly rebased changes:" 21712447Sandreas.sandberg@arm.com all_upstream_revs = list_revs(args.upstream, paths=args.paths) 21812256Sandreas.sandberg@arm.com all_upstream_cids = dict([ 21912256Sandreas.sandberg@arm.com (c.change_id, c) for c in all_upstream_revs \ 22012256Sandreas.sandberg@arm.com if c.change_id is not None ]) 22112256Sandreas.sandberg@arm.com incorrect_outgoing = filter( 22212256Sandreas.sandberg@arm.com lambda r: r.change_id in all_upstream_cids, 22312256Sandreas.sandberg@arm.com outgoing) 22412256Sandreas.sandberg@arm.com for rev in incorrect_outgoing: 22512256Sandreas.sandberg@arm.com print rev 22612256Sandreas.sandberg@arm.com 22712256Sandreas.sandberg@arm.com 22811922Sandreas.sandberg@arm.com 22911922Sandreas.sandberg@arm.com 23011922Sandreas.sandberg@arm.comif __name__ == "__main__": 23111922Sandreas.sandberg@arm.com _main() 232