blob: 6e4a235ab35ff9fcdd839bb0df2d82eb29a54bc6 [file] [log] [blame]
Charles Hoferac551212016-10-20 16:33:41 -05001#!/usr/bin/python
2
3##
4# Copyright c 2016 IBM Corporation
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17##
18
19###############################################################################
20# @file commit-tracker
21# @brief Prints out all commits on the master branch of the specified
22# repository, as well as all commits on linked submodule
23# repositories
24###############################################################################
25
26import logging
27import os
28import re
29import sys
30import time
31import git
32
33###############################################################################
34# @brief Main function for the script
35#
36# @param i_args : Command line arguments
37###############################################################################
38def main(i_args):
39 if 3 != len(i_args):
40 print usage()
41 exit(1)
42
43 l_repo_path = i_args[0]
44 l_begin_commit = i_args[1]
45 l_end_commit = i_args[2]
46
47 print 'Getting commits for ' + l_repo_path
48 print_commits(l_repo_path, l_begin_commit, l_end_commit)
49
50###############################################################################
51# @brief Prints all the commits from this repo and commits from
52# subrepos between the given references
53#
54# @param i_repo_path : The path to the repo to print commits for
55# @param i_begin_commit : A reference to the most recent commit. What
56# commit to start printing at
57# @param i_end_commit : A reference to the commit farthest in the
58# past. The commit to stop print at
59###############################################################################
60def print_commits(i_repo_path, i_begin_commit, i_end_commit, i_level=0):
61 try:
62 l_repo = git.Repo(i_repo_path)
63 except git.exc.InvalidGitRepositoryError:
64 logging.error(str(i_repo_path) + ' is not a valid git repository')
65 return
66
67 # Get commits between the beginning and end references
68 try:
69 l_commits = l_repo.iter_commits(rev=(i_begin_commit + '...'
70 + i_end_commit))
71 # Go through each commit
72 for l_commit in l_commits:
73 print_commit_info(i_repo_path, l_commit, i_level)
74 # Search the diffs for any bumps of submodule versions
75 l_diffs = l_commit.diff(str(l_commit.hexsha) + '~1')
76 for l_diff in l_diffs:
77 # If we have two files to compare with diff...
78 if l_diff.a_path and l_diff.b_path:
79 # ... get info about the change, log it...
80 l_subrepo_uri, l_subrepo_new_hash, l_subrepo_old_hash \
81 = get_bumped_repo(l_repo, str(l_commit.hexsha), i_repo_path + '/' + l_diff.b_path)
82 logging.debug('Found diff...')
83 logging.debug(' Subrepo URI: ' + str(l_subrepo_uri))
84 logging.debug(' Subrepo new hash: '
85 + str(l_subrepo_new_hash))
86 logging.debug(' Subrepo old hash: '
87 + str(l_subrepo_old_hash))
88 logging.debug(' Found in: ' + str(l_diff.a_path))
89 # ... and print the commits for the subrepo if this was a
90 # version bump
91 if (l_subrepo_new_hash
92 and l_subrepo_old_hash
93 and l_subrepo_uri
94 and l_subrepo_uri.startswith('git')):
95 logging.debug(' Bumped')
96 l_subrepo_path = l_subrepo_uri.split('/')[-1]
97 clone_or_update(l_subrepo_uri, l_subrepo_path)
98 print_commits(l_subrepo_path, l_subrepo_new_hash,
99 l_subrepo_old_hash, i_level=i_level+1)
100 except git.exc.GitCommandError:
101 logging.error(str(i_begin_commit) + ' and ' + str(i_end_commit)
102 + ' are invalid revisions')
103
104###############################################################################
105# @brief Gets the repo URI, the updated SHA, and the old SHA from a
106# given repo, commit SHA and file
107#
108# @param i_repo : The Repo object to get version bump information
109# from
110# @param i_file : The path to the file to search for version
111# bumps
112# @param i_hexsha : The hex hash for the commit to search for
113# version bumps
114#
115# @return Returns the repo URI, the updated SHA, and the old SHA in
116# a tuple in that order
117###############################################################################
118def get_bumped_repo(i_repo, i_hexsha, i_file):
119 # Checkout the old repo
120 i_repo.git.checkout(i_hexsha)
121 # Get the diff text
122 l_diff_text = i_repo.git.diff(i_hexsha, i_hexsha + '~1', '--', i_file)
123
124 # SRCREV sets the SHA for the version of the other repo to use when
125 # building openbmc. SHAs should be stored in the file in a format
126 # like SRCRV =? "<SHA>". Find both the new '+' and old '-' ones
127 l_old_hash = None
128 l_new_hash = None
129 l_old_hash_match = re.search('-SRCREV[+=? ]+"([a-f0-9]+)"', l_diff_text)
130 l_new_hash_match = re.search('\+SRCREV[+=? ]+"([a-f0-9]+)"', l_diff_text)
131 if l_old_hash_match:
132 l_old_hash = l_old_hash_match.group(1)
133 if l_new_hash_match:
134 l_new_hash = l_new_hash_match.group(1)
135
136 # Get the URI of the subrepo
137 l_uri = None
138 if os.path.isfile(i_file):
139 l_changed_file = open(i_file, 'r')
140 for l_line in l_changed_file:
141 # URIs should be stored in a format similar to
142 # SRC_URI ?= "git://github.com/<path to repo>"
143 l_uri_match = re.search('_URI[+=? ]+"([-a-zA-Z0-9/:\.]+)"', l_line)
144 if l_uri_match:
145 l_uri = l_uri_match.group(1)
146 break
147
148 # Go back to master
149 i_repo.git.checkout('master')
150 return l_uri, l_new_hash, l_old_hash
151
152###############################################################################
153# @brief Updates the repo under the given path or clones it from the
154# uri if it doesn't yet exist
155#
156# @param i_uri : The URI to the remote repo to clone
157# @param i_path : The file path to where the repo currently exists or
158# where it will be created
159###############################################################################
160def clone_or_update(i_uri, i_path):
161 # If the repo exists, just update it
162 if os.path.isdir(i_path):
163 l_repo = git.Repo(i_path)
164 l_repo.remotes[0].pull()
165
166 # If it doesn't exist, clone it
167 else:
168 os.mkdir(i_path)
169 l_repo = git.Repo.init(i_path)
170 origin = l_repo.create_remote('origin', i_uri)
171 origin.fetch()
172 l_repo.create_head('master', origin.refs.master) \
173 .set_tracking_branch(origin.refs.master)
174 origin.pull()
175
176###############################################################################
177# @brief Prints information for a given commit to the command line
178#
179# @param i_repo_path : The file path to the repo
180# @param i_commit : The commit object to print infor for
181# @param i_level : What subrepo level is this
182###############################################################################
183def print_commit_info(i_repo_path, i_commit, i_level):
184 # Use these to print console text with colors. RED + <text to print> + ENDC
185 # will print the text in red
186 RED = '\033[31m'
187 BLUE = '\033[94m'
188 ENDC = '\033[0m'
189
190 # Take just the first seven digits in the commit hash
191 l_name_rev = i_commit.name_rev
192 l_hash, l_name = l_name_rev.split()
193 l_name_rev = l_hash[0:7] + l_name
194
195 # Print out the line describing this commit
196 print (' ' * i_level) + RED + i_repo_path + ENDC + ' ' + BLUE \
197 + l_name_rev + ENDC + ' ' + re.sub('\s+', ' ', i_commit.summary)
198
199###############################################################################
200# @brief Prints out the usage for this script
201###############################################################################
202def usage():
203 print 'Usage: commit-tracker <repo_dir> <begin_rev> <end_rev>\n' \
204 + ' Prints commit information from the given repo and all\n' \
205 + ' sub-repos specified with SRC_REV, starting from the most \n' \
206 + ' recent commit specified going back to the earliest commit \n' \
207 + ' specified.\n' \
208 + ' repo_name - The directory of the repo to get commit\n' \
209 + ' information for\n' \
210 + ' begin_rev - A reference (branch name, HEAD, SHA, etc.) to\n' \
211 + ' the most recent commit to get information for\n' \
212 + ' end_rev - A reference to the earliest commit to get\n' \
213 + ' information for'
214
215# Only run main if run as a script
216if __name__ == '__main__':
217 main(sys.argv[1:])
218