blob: 72a02034364603ec037f85ccc2e3bfd497a60a01 [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
Charles Hofer046fc912016-11-10 10:42:13 -060026import argparse
Charles Hoferac551212016-10-20 16:33:41 -050027import logging
28import os
29import re
30import sys
31import time
32import git
33
34###############################################################################
35# @brief Main function for the script
36#
37# @param i_args : Command line arguments
38###############################################################################
39def main(i_args):
Charles Hofer046fc912016-11-10 10:42:13 -060040 # Parse the arguments
41 l_args_obj = parse_arguments(i_args)
Charles Hoferac551212016-10-20 16:33:41 -050042
Charles Hofer046fc912016-11-10 10:42:13 -060043 # Print every commit
44 print 'Getting commits for ' + l_args_obj.repo_dir
45 print_commits(l_args_obj.repo_dir, l_args_obj.latest_commit,
46 l_args_obj.earliest_commit)
Charles Hoferac551212016-10-20 16:33:41 -050047
Charles Hofer046fc912016-11-10 10:42:13 -060048###############################################################################
49# @brief Parses the arguments from the command line
50#
51# @param i_args : The list of arguments from the command line, excluding the
52# name of the script
53#
54# @return An object representin the parsed arguments
55###############################################################################
56def parse_arguments(i_args):
57 l_parser = argparse.ArgumentParser(
58 description='Prints commit information from the given repo and all ' \
59 +'sub-repos specified with SRC_REV, starting from the ' \
60 +'most recent commit specified going back to the ' \
61 +'earliest commit specified.')
62 l_parser.add_argument(
63 'repo_dir',
64 help='The directory of the repo to get commit information for')
65 l_parser.add_argument(
66 'latest_commit',
67 help='A reference (branch name, HEAD, SHA, etc.) to the most ' \
68 +'recent commit to get information for')
69 l_parser.add_argument(
70 'earliest_commit',
71 help='A reference to the earliest commit to get information for')
72 return l_parser.parse_args(i_args)
Charles Hoferac551212016-10-20 16:33:41 -050073
74###############################################################################
75# @brief Prints all the commits from this repo and commits from
76# subrepos between the given references
77#
78# @param i_repo_path : The path to the repo to print commits for
79# @param i_begin_commit : A reference to the most recent commit. What
80# commit to start printing at
81# @param i_end_commit : A reference to the commit farthest in the
82# past. The commit to stop print at
83###############################################################################
84def print_commits(i_repo_path, i_begin_commit, i_end_commit, i_level=0):
85 try:
86 l_repo = git.Repo(i_repo_path)
87 except git.exc.InvalidGitRepositoryError:
88 logging.error(str(i_repo_path) + ' is not a valid git repository')
89 return
90
91 # Get commits between the beginning and end references
92 try:
93 l_commits = l_repo.iter_commits(rev=(i_begin_commit + '...'
94 + i_end_commit))
95 # Go through each commit
96 for l_commit in l_commits:
97 print_commit_info(i_repo_path, l_commit, i_level)
98 # Search the diffs for any bumps of submodule versions
99 l_diffs = l_commit.diff(str(l_commit.hexsha) + '~1')
100 for l_diff in l_diffs:
101 # If we have two files to compare with diff...
102 if l_diff.a_path and l_diff.b_path:
103 # ... get info about the change, log it...
104 l_subrepo_uri, l_subrepo_new_hash, l_subrepo_old_hash \
105 = get_bumped_repo(l_repo, str(l_commit.hexsha), i_repo_path + '/' + l_diff.b_path)
106 logging.debug('Found diff...')
107 logging.debug(' Subrepo URI: ' + str(l_subrepo_uri))
108 logging.debug(' Subrepo new hash: '
109 + str(l_subrepo_new_hash))
110 logging.debug(' Subrepo old hash: '
111 + str(l_subrepo_old_hash))
112 logging.debug(' Found in: ' + str(l_diff.a_path))
113 # ... and print the commits for the subrepo if this was a
114 # version bump
115 if (l_subrepo_new_hash
116 and l_subrepo_old_hash
117 and l_subrepo_uri
118 and l_subrepo_uri.startswith('git')):
119 logging.debug(' Bumped')
120 l_subrepo_path = l_subrepo_uri.split('/')[-1]
121 clone_or_update(l_subrepo_uri, l_subrepo_path)
122 print_commits(l_subrepo_path, l_subrepo_new_hash,
123 l_subrepo_old_hash, i_level=i_level+1)
124 except git.exc.GitCommandError:
125 logging.error(str(i_begin_commit) + ' and ' + str(i_end_commit)
126 + ' are invalid revisions')
127
128###############################################################################
129# @brief Gets the repo URI, the updated SHA, and the old SHA from a
130# given repo, commit SHA and file
131#
132# @param i_repo : The Repo object to get version bump information
133# from
134# @param i_file : The path to the file to search for version
135# bumps
136# @param i_hexsha : The hex hash for the commit to search for
137# version bumps
138#
139# @return Returns the repo URI, the updated SHA, and the old SHA in
140# a tuple in that order
141###############################################################################
142def get_bumped_repo(i_repo, i_hexsha, i_file):
143 # Checkout the old repo
144 i_repo.git.checkout(i_hexsha)
145 # Get the diff text
146 l_diff_text = i_repo.git.diff(i_hexsha, i_hexsha + '~1', '--', i_file)
147
148 # SRCREV sets the SHA for the version of the other repo to use when
149 # building openbmc. SHAs should be stored in the file in a format
150 # like SRCRV =? "<SHA>". Find both the new '+' and old '-' ones
151 l_old_hash = None
152 l_new_hash = None
153 l_old_hash_match = re.search('-SRCREV[+=? ]+"([a-f0-9]+)"', l_diff_text)
154 l_new_hash_match = re.search('\+SRCREV[+=? ]+"([a-f0-9]+)"', l_diff_text)
155 if l_old_hash_match:
156 l_old_hash = l_old_hash_match.group(1)
157 if l_new_hash_match:
158 l_new_hash = l_new_hash_match.group(1)
159
160 # Get the URI of the subrepo
161 l_uri = None
162 if os.path.isfile(i_file):
163 l_changed_file = open(i_file, 'r')
164 for l_line in l_changed_file:
165 # URIs should be stored in a format similar to
166 # SRC_URI ?= "git://github.com/<path to repo>"
167 l_uri_match = re.search('_URI[+=? ]+"([-a-zA-Z0-9/:\.]+)"', l_line)
168 if l_uri_match:
169 l_uri = l_uri_match.group(1)
170 break
171
172 # Go back to master
173 i_repo.git.checkout('master')
174 return l_uri, l_new_hash, l_old_hash
175
176###############################################################################
177# @brief Updates the repo under the given path or clones it from the
178# uri if it doesn't yet exist
179#
180# @param i_uri : The URI to the remote repo to clone
181# @param i_path : The file path to where the repo currently exists or
182# where it will be created
183###############################################################################
184def clone_or_update(i_uri, i_path):
185 # If the repo exists, just update it
186 if os.path.isdir(i_path):
187 l_repo = git.Repo(i_path)
188 l_repo.remotes[0].pull()
189
190 # If it doesn't exist, clone it
191 else:
192 os.mkdir(i_path)
193 l_repo = git.Repo.init(i_path)
194 origin = l_repo.create_remote('origin', i_uri)
195 origin.fetch()
196 l_repo.create_head('master', origin.refs.master) \
197 .set_tracking_branch(origin.refs.master)
198 origin.pull()
199
200###############################################################################
201# @brief Prints information for a given commit to the command line
202#
203# @param i_repo_path : The file path to the repo
204# @param i_commit : The commit object to print infor for
205# @param i_level : What subrepo level is this
206###############################################################################
207def print_commit_info(i_repo_path, i_commit, i_level):
208 # Use these to print console text with colors. RED + <text to print> + ENDC
209 # will print the text in red
210 RED = '\033[31m'
211 BLUE = '\033[94m'
212 ENDC = '\033[0m'
213
214 # Take just the first seven digits in the commit hash
215 l_name_rev = i_commit.name_rev
216 l_hash, l_name = l_name_rev.split()
217 l_name_rev = l_hash[0:7] + l_name
218
219 # Print out the line describing this commit
220 print (' ' * i_level) + RED + i_repo_path + ENDC + ' ' + BLUE \
221 + l_name_rev + ENDC + ' ' + re.sub('\s+', ' ', i_commit.summary)
222
Charles Hoferac551212016-10-20 16:33:41 -0500223# Only run main if run as a script
224if __name__ == '__main__':
225 main(sys.argv[1:])
226