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