1#!/usr/bin/env python 2# Copyright 2015 the V8 project authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6# for py2/py3 compatibility 7from __future__ import print_function 8 9import argparse 10import operator 11import os 12import re 13from sets import Set 14from subprocess import Popen, PIPE 15import sys 16 17def search_all_related_commits( 18 git_working_dir, start_hash, until, separator, verbose=False): 19 20 all_commits_raw = _find_commits_inbetween( 21 start_hash, until, git_working_dir, verbose) 22 if verbose: 23 print("All commits between <of> and <until>: " + all_commits_raw) 24 25 # Adding start hash too 26 all_commits = [start_hash] 27 all_commits.extend(all_commits_raw.splitlines()) 28 all_related_commits = {} 29 already_treated_commits = Set([]) 30 for commit in all_commits: 31 if commit in already_treated_commits: 32 continue 33 34 related_commits = _search_related_commits( 35 git_working_dir, commit, until, separator, verbose) 36 if len(related_commits) > 0: 37 all_related_commits[commit] = related_commits 38 already_treated_commits.update(related_commits) 39 40 already_treated_commits.update(commit) 41 42 return all_related_commits 43 44def _search_related_commits( 45 git_working_dir, start_hash, until, separator, verbose=False): 46 47 if separator: 48 commits_between = _find_commits_inbetween( 49 start_hash, separator, git_working_dir, verbose) 50 if commits_between == "": 51 return [] 52 53 # Extract commit position 54 original_message = git_execute( 55 git_working_dir, 56 ["show", "-s", "--format=%B", start_hash], 57 verbose) 58 title = original_message.splitlines()[0] 59 60 matches = re.search("(\{#)([0-9]*)(\})", original_message) 61 62 if not matches: 63 return [] 64 65 commit_position = matches.group(2) 66 if verbose: 67 print("1.) Commit position to look for: " + commit_position) 68 69 search_range = start_hash + ".." + until 70 71 def git_args(grep_pattern): 72 return [ 73 "log", 74 "--reverse", 75 "--grep=" + grep_pattern, 76 "--format=%H", 77 search_range, 78 ] 79 80 found_by_hash = git_execute( 81 git_working_dir, git_args(start_hash), verbose).strip() 82 83 if verbose: 84 print("2.) Found by hash: " + found_by_hash) 85 86 found_by_commit_pos = git_execute( 87 git_working_dir, git_args(commit_position), verbose).strip() 88 89 if verbose: 90 print("3.) Found by commit position: " + found_by_commit_pos) 91 92 # Replace brackets or else they are wrongly interpreted by --grep 93 title = title.replace("[", "\\[") 94 title = title.replace("]", "\\]") 95 96 found_by_title = git_execute( 97 git_working_dir, git_args(title), verbose).strip() 98 99 if verbose: 100 print("4.) Found by title: " + found_by_title) 101 102 hits = ( 103 _convert_to_array(found_by_hash) + 104 _convert_to_array(found_by_commit_pos) + 105 _convert_to_array(found_by_title)) 106 hits = _remove_duplicates(hits) 107 108 if separator: 109 for current_hit in hits: 110 commits_between = _find_commits_inbetween( 111 separator, current_hit, git_working_dir, verbose) 112 if commits_between != "": 113 return hits 114 return [] 115 116 return hits 117 118def _find_commits_inbetween(start_hash, end_hash, git_working_dir, verbose): 119 commits_between = git_execute( 120 git_working_dir, 121 ["rev-list", "--reverse", start_hash + ".." + end_hash], 122 verbose) 123 return commits_between.strip() 124 125def _convert_to_array(string_of_hashes): 126 return string_of_hashes.splitlines() 127 128def _remove_duplicates(array): 129 no_duplicates = [] 130 for current in array: 131 if not current in no_duplicates: 132 no_duplicates.append(current) 133 return no_duplicates 134 135def git_execute(working_dir, args, verbose=False): 136 command = ["git", "-C", working_dir] + args 137 if verbose: 138 print("Git working dir: " + working_dir) 139 print("Executing git command:" + str(command)) 140 p = Popen(args=command, stdin=PIPE, 141 stdout=PIPE, stderr=PIPE) 142 output, err = p.communicate() 143 rc = p.returncode 144 if rc != 0: 145 raise Exception(err) 146 if verbose: 147 print("Git return value: " + output) 148 return output 149 150def _pretty_print_entry(hash, git_dir, pre_text, verbose): 151 text_to_print = git_execute( 152 git_dir, 153 ["show", 154 "--quiet", 155 "--date=iso", 156 hash, 157 "--format=%ad # %H # %s"], 158 verbose) 159 return pre_text + text_to_print.strip() 160 161def main(options): 162 all_related_commits = search_all_related_commits( 163 options.git_dir, 164 options.of[0], 165 options.until[0], 166 options.separator, 167 options.verbose) 168 169 sort_key = lambda x: ( 170 git_execute( 171 options.git_dir, 172 ["show", "--quiet", "--date=iso", x, "--format=%ad"], 173 options.verbose)).strip() 174 175 high_level_commits = sorted(all_related_commits.keys(), key=sort_key) 176 177 for current_key in high_level_commits: 178 if options.prettyprint: 179 yield _pretty_print_entry( 180 current_key, 181 options.git_dir, 182 "+", 183 options.verbose) 184 else: 185 yield "+" + current_key 186 187 found_commits = all_related_commits[current_key] 188 for current_commit in found_commits: 189 if options.prettyprint: 190 yield _pretty_print_entry( 191 current_commit, 192 options.git_dir, 193 "| ", 194 options.verbose) 195 else: 196 yield "| " + current_commit 197 198if __name__ == "__main__": # pragma: no cover 199 parser = argparse.ArgumentParser( 200 "This tool analyzes the commit range between <of> and <until>. " 201 "It finds commits which belong together e.g. Implement/Revert pairs and " 202 "Implement/Port/Revert triples. All supplied hashes need to be " 203 "from the same branch e.g. master.") 204 parser.add_argument("-g", "--git-dir", required=False, default=".", 205 help="The path to your git working directory.") 206 parser.add_argument("--verbose", action="store_true", 207 help="Enables a very verbose output") 208 parser.add_argument("of", nargs=1, 209 help="Hash of the commit to be searched.") 210 parser.add_argument("until", nargs=1, 211 help="Commit when searching should stop") 212 parser.add_argument("--separator", required=False, 213 help="The script will only list related commits " 214 "which are separated by hash <--separator>.") 215 parser.add_argument("--prettyprint", action="store_true", 216 help="Pretty prints the output") 217 218 args = sys.argv[1:] 219 options = parser.parse_args(args) 220 for current_line in main(options): 221 print(current_line) 222