list_changes.py revision 11922:0b284e4322fc
1#!/usr/bin/env python2 2# 3# Copyright (c) 2017 ARM Limited 4# All rights reserved 5# 6# The license below extends only to copyright in the software and shall 7# not be construed as granting a license to any other intellectual 8# property including but not limited to intellectual property relating 9# to a hardware implementation of the functionality of the software 10# licensed hereunder. You may use the software subject to the license 11# terms below provided that you ensure that this notice is replicated 12# unmodified and in its entirety in all distributions of the software, 13# modified or unmodified, in source code or in binary form. 14# 15# Redistribution and use in source and binary forms, with or without 16# modification, are permitted provided that the following conditions are 17# met: redistributions of source code must retain the above copyright 18# notice, this list of conditions and the following disclaimer; 19# redistributions in binary form must reproduce the above copyright 20# notice, this list of conditions and the following disclaimer in the 21# documentation and/or other materials provided with the distribution; 22# neither the name of the copyright holders nor the names of its 23# contributors may be used to endorse or promote products derived from 24# this software without specific prior written permission. 25# 26# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37# 38# Authors: Andreas Sandberg 39 40 41import subprocess 42import re 43from functools import wraps 44 45class Commit(object): 46 _re_tag = re.compile(r"^((?:\w|-)+): (.*)$") 47 48 def __init__(self, rev): 49 self.rev = rev 50 self._log = None 51 self._tags = None 52 53 def _git(self, args): 54 return subprocess.check_output([ "git", ] + args) 55 56 @property 57 def log(self): 58 """Log message belonging to a commit returned as a list with on line 59 per element. 60 61 """ 62 if self._log is None: 63 self._log = self._git( 64 ["show", "--format=%B", "--no-patch", str(self.rev) ] 65 ).rstrip("\n").split("\n") 66 return self._log 67 68 @property 69 def tags(self): 70 """Get all commit message tags in the current commit. 71 72 Returns: { tag, [ value, ... ] } 73 74 """ 75 if self._tags is None: 76 tags = {} 77 for l in self.log[1:]: 78 m = Commit._re_tag.match(l) 79 if m: 80 key, value = m.group(1), m.group(2) 81 try: 82 tags[key].append(value) 83 except KeyError: 84 tags[key] = [ value ] 85 self._tags = tags 86 87 return self._tags 88 89 @property 90 def change_id(self): 91 """Get the Change-Id tag from the commit 92 93 Returns: A change ID or None if no change ID has been 94 specified. 95 96 """ 97 try: 98 cids = self.tags["Change-Id"] 99 except KeyError: 100 return None 101 102 assert len(cids) == 1 103 return cids[0] 104 105 def __str__(self): 106 return "%s: %s" % (self.rev[0:8], self.log[0]) 107 108def list_revs(upstream, branch): 109 """Get a generator that lists git revisions that exist in 'branch' but 110 not in 'upstream'. 111 112 Returns: Generator of Commit objects 113 114 """ 115 116 changes = subprocess.check_output( 117 [ "git", "rev-list", "%s..%s" % (upstream, branch) ]) 118 119 if changes == "": 120 return 121 122 for rev in changes.rstrip("\n").split("\n"): 123 assert rev != "" 124 yield Commit(rev) 125 126def list_changes(upstream, feature): 127 feature_revs = tuple(list_revs(upstream, feature)) 128 upstream_revs = tuple(list_revs(feature, upstream)) 129 130 feature_cids = dict([ 131 (c.change_id, c) for c in feature_revs if c.change_id is not None ]) 132 upstream_cids = dict([ 133 (c.change_id, c) for c in upstream_revs if c.change_id is not None ]) 134 135 incoming = filter( 136 lambda r: r.change_id and r.change_id not in feature_cids, 137 reversed(upstream_revs)) 138 outgoing = filter( 139 lambda r: r.change_id and r.change_id not in upstream_cids, 140 reversed(feature_revs)) 141 common = filter( 142 lambda r: r.change_id in upstream_cids, 143 reversed(feature_revs)) 144 upstream_unknown = filter( 145 lambda r: r.change_id is None, 146 reversed(upstream_revs)) 147 feature_unknown = filter( 148 lambda r: r.change_id is None, 149 reversed(feature_revs)) 150 151 return incoming, outgoing, common, upstream_unknown, feature_unknown 152 153def _main(): 154 import argparse 155 parser = argparse.ArgumentParser( 156 description="List incoming and outgoing changes in a feature branch") 157 158 parser.add_argument("--upstream", "-u", type=str, default="origin/master", 159 help="Upstream branch for comparison. " \ 160 "Default: %(default)s") 161 parser.add_argument("--feature", "-f", type=str, default="HEAD", 162 help="Feature branch for comparison. " \ 163 "Default: %(default)s") 164 parser.add_argument("--show-unknown", action="store_true", 165 help="Print changes without Change-Id tags") 166 parser.add_argument("--show-common", action="store_true", 167 help="Print common changes") 168 169 args = parser.parse_args() 170 171 incoming, outgoing, common, upstream_unknown, feature_unknown = \ 172 list_changes(args.upstream, args.feature) 173 174 if incoming: 175 print "Incoming changes:" 176 for rev in incoming: 177 print rev 178 print 179 180 if args.show_unknown and upstream_unknown: 181 print "Upstream changes without change IDs:" 182 for rev in upstream_unknown: 183 print rev 184 print 185 186 if outgoing: 187 print "Outgoing changes:" 188 for rev in outgoing: 189 print rev 190 print 191 192 if args.show_common and common: 193 print "Common changes:" 194 for rev in common: 195 print rev 196 print 197 198 if args.show_unknown and feature_unknown: 199 print "Outgoing changes without change IDs:" 200 for rev in feature_unknown: 201 print rev 202 203 204 205if __name__ == "__main__": 206 _main() 207