summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xextras/git-branch-diff.py284
1 files changed, 284 insertions, 0 deletions
diff --git a/extras/git-branch-diff.py b/extras/git-branch-diff.py
new file mode 100755
index 00000000000..c8d74ec9f31
--- /dev/null
+++ b/extras/git-branch-diff.py
@@ -0,0 +1,284 @@
+#!/bin/env python
+
+"""
+ Copyright (c) 2016 Red Hat, Inc. <http://www.redhat.com>
+ This file is part of GlusterFS.
+
+ This file is licensed to you under your choice of the GNU Lesser
+ General Public License, version 3 or any later version (LGPLv3 or
+ later), or the GNU General Public License, version 2 (GPLv2), in all
+ cases as published by the Free Software Foundation.
+"""
+
+"""
+ ABOUT:
+ This script helps in visualizing backported and missed commits between two
+ different branches, tags or commit ranges. In the list of missed commits,
+ it will help you identify patches which are posted for reviews on gerrit server.
+
+ USAGE:
+ $ ./extras/git-branch-diff.py --help
+ usage: git-branch-diff.py [-h] [-s SOURCE] -t TARGET [-a AUTHOR] [-p PATH]
+ [-o OPTIONS]
+
+ git wrapper to diff local or remote branches/tags/commit-ranges
+
+ optional arguments:
+ -h, --help show this help message and exit
+ -s SOURCE, --source SOURCE
+ source pattern, it could be a branch, tag or a commit
+ range
+ -t TARGET, --target TARGET
+ target pattern, it could be a branch, tag or a commit
+ range
+ -a AUTHOR, --author AUTHOR
+ default: git config name/email, to provide multiple
+ specify comma separated values
+ -p PATH, --path PATH show source and target diff w.r.t given path, to
+ provide multiple specify space in between them
+ -o OPTIONS, --options OPTIONS
+ add other git options such as --after=<>, --before=<>
+ etc. experts use;
+
+ SAMPLE EXECUTIONS:
+ $ ./extras/git-branch-diff.py -t origin/release-3.8
+
+ $ ./extras/git-branch-diff.py -s local_branch -t origin/release-3.7
+
+ $ ./extras/git-branch-diff.py -s 4517bf8..e66add8 -t origin/release-3.7
+ $ ./extras/git-branch-diff.py -s HEAD..c4efd39 -t origin/release-3.7
+
+ $ ./extras/git-branch-diff.py -t v3.7.11 --author="author@redhat.com"
+ $ ./extras/git-branch-diff.py -t v3.7.11 --author="authorX, authorY, authorZ"
+
+ $ ./extras/git-branch-diff.py -t origin/release-3.8 --path="xlators/"
+ $ ./extras/git-branch-diff.py -t origin/release-3.8 --path="./xlators ./rpc"
+
+ $ ./extras/git-branch-diff.py -t origin/release-3.6 --author="*"
+ $ ./extras/git-branch-diff.py -t origin/release-3.6 --author="All"
+ $ ./extras/git-branch-diff.py -t origin/release-3.6 --author="Null"
+
+ $ ./extras/git-branch-diff.py -t v3.7.11 --options="--after=2015-03-01 \
+ --before=2016-01-30"
+
+ DECLARATION:
+ While backporting commit to another branch only subject of the patch may
+ remain unchanged, all others such as commit message, commit Id, change Id,
+ bug Id, may be changed. This script works by taking commit subject as the
+ key value for comparing two git branches, which can be local or remote.
+
+ Note: This script may ignore commits which have altered their commit subjects
+ while backporting patches. Also this script doesn't have any intelligence to
+ detect squashed commits.
+
+ AUTHOR:
+ Prasanna Kumar Kalever <prasanna.kalever@redhat.com>
+"""
+
+import os
+import sys
+import argparse
+import commands
+import subprocess
+import requests
+
+class GitBranchDiff:
+ def __init__ (self):
+ " color symbols"
+ self.tick = u'\033[1;32m[ \u2714 ]\033[0m'
+ self.cross = u'\033[1;31m[ \u2716 ]\033[0m'
+ self.green_set = u'\033[1;34m'
+ self.yello_set = u'\033[4;33m'
+ self.color_unset = '\033[0m'
+
+ self.parse_cmd_args()
+
+ " replace default values with actual values from command args"
+ self.g_author = self.argsdict['author']
+ self.s_pattern = self.argsdict['source']
+ self.t_pattern = self.argsdict['target']
+ self.r_path = self.argsdict['path']
+ self.options = ' '.join(self.argsdict['options'])
+
+ self.gerrit_server = "http://review.gluster.org"
+
+ def check_dir_exist (self, os_path):
+ " checks whether given path exist"
+ path_list = os_path.split()
+ for path in path_list:
+ if not os.path.exists(path):
+ raise argparse.ArgumentTypeError("'%s' path %s is not valid"
+ %(os_path, path))
+ return os_path
+
+ def check_pattern_exist (self):
+ " defend to check given branch[s] exit"
+ status_sbr, op = commands.getstatusoutput('git log ' +
+ self.s_pattern)
+ status_tbr, op = commands.getstatusoutput('git log ' +
+ self.t_pattern)
+ if status_sbr != 0:
+ print "Error: --source=" + self.s_pattern + " doesn't exit\n"
+ self.parser.print_help()
+ exit(status_sbr)
+ elif status_tbr != 0:
+ print "Error: --target=" + self.t_pattern + " doesn't exit\n"
+ self.parser.print_help()
+ exit(status_tbr)
+
+ def check_author_exist (self):
+ " defend to check given author exist, format incase of multiple"
+ contrib_list = ['', '*', 'all', 'All', 'ALL', 'null', 'Null', 'NULL']
+ if self.g_author in contrib_list:
+ self.g_author = ""
+ else:
+ ide_list = self.g_author.split(',')
+ for ide in ide_list:
+ cmd4 = 'git log ' + self.s_pattern + ' --author=' + ide
+ c_list = subprocess.check_output(cmd4, shell = True)
+ if len(c_list) is 0:
+ print "Error: --author=%s doesn't exit" %self.g_author
+ print "see '%s --help'" %__file__
+ exit(1)
+ if len(ide_list) > 1:
+ self.g_author = "\|".join(ide_list)
+
+ def connected_to_gerrit (self):
+ "check if gerrit server is reachable"
+ try:
+ r = requests.get(self.gerrit_server, timeout=3)
+ return True
+ except requests.Timeout as err:
+ " request timed out"
+ print "Warning: failed to get list of open review commits on " \
+ "gerrit.\n" \
+ "hint: Request timed out! gerrit server could possibly " \
+ "slow ...\n"
+ return False
+ except requests.RequestException as err:
+ " handle other errors"
+ print "Warning: failed to get list of open review commits on " \
+ "gerrit\n" \
+ "hint: check with internet connection ...\n"
+ return False
+
+ def parse_cmd_args (self):
+ " command line parser"
+ author = subprocess.check_output('git config user.email',
+ shell = True).rstrip('\n')
+ source = "remotes/origin/master"
+ options = [' --pretty=format:"%h %s" ']
+ path = subprocess.check_output('git rev-parse --show-toplevel',
+ shell = True).rstrip('\n')
+ self.parser = argparse.ArgumentParser(description = 'git wrapper to '
+ 'diff local or remote branches/'
+ 'tags/commit-ranges')
+ self.parser.add_argument('-s',
+ '--source',
+ help = 'source pattern, it could be a branch,'
+ ' tag or a commit range',
+ default = source,
+ dest = 'source')
+ self.parser.add_argument('-t',
+ '--target',
+ help = 'target pattern, it could be a branch,'
+ ' tag or a commit range',
+ required = True,
+ dest = 'target')
+ self.parser.add_argument('-a',
+ '--author',
+ help = 'default: git config name/email, '
+ 'to provide multiple specify comma'
+ ' seperated values',
+ default = author,
+ dest = 'author')
+ self.parser.add_argument('-p',
+ '--path',
+ type = self.check_dir_exist,
+ help = 'show source and target diff w.r.t '
+ 'given path, to provide multiple '
+ 'specify space in between them',
+ default = path,
+ dest = 'path')
+ self.parser.add_argument('-o',
+ '--options',
+ help = 'add other git options such as '
+ '--after=<>, --before=<> etc. '
+ 'experts use;',
+ default = options,
+ dest = 'options',
+ action='append')
+ self.argsdict = vars(self.parser.parse_args())
+
+ def print_output (self):
+ " display the result list"
+ print "\n------------------------------------------------------------\n"
+ print self.tick + " Successfully Backported changes:"
+ print ' {' + 'from: ' + self.s_pattern + \
+ ' to: '+ self.t_pattern + '}\n'
+ for key, value in self.s_dict.iteritems():
+ if value in self.t_dict.itervalues():
+ print "[%s%s%s] %s" %(self.yello_set,
+ key,
+ self.color_unset,
+ value)
+ print "\n------------------------------------------------------------\n"
+ print self.cross + " Missing patches in " + self.t_pattern + ':\n'
+ if self.connected_to_gerrit():
+ cmd3 = "git review -r origin -l"
+ review_list = subprocess.check_output(cmd3, shell = True).split('\n')
+ else:
+ review_list = []
+
+ for key, value in self.s_dict.iteritems():
+ if value not in self.t_dict.itervalues():
+ if any(value in s for s in review_list):
+ print "[%s%s%s] %s %s(under review)%s" %(self.yello_set,
+ key,
+ self.color_unset,
+ value,
+ self.green_set,
+ self.color_unset)
+ else:
+ print "[%s%s%s] %s" %(self.yello_set,
+ key,
+ self.color_unset,
+ value)
+ print "\n------------------------------------------------------------\n"
+
+ def main (self):
+ self.check_pattern_exist()
+ self.check_author_exist()
+
+ " actual git commands"
+ cmd1 = 'git log' + self.options + ' ' + self.s_pattern + \
+ ' --author=\'' + self.g_author + '\' ' + self.r_path
+
+ " could be backported by anybody so --author doesn't apply here"
+ cmd2 = 'git log' + self.options + ' ' + self.t_pattern + \
+ ' ' + self.r_path
+
+ s_list = subprocess.check_output(cmd1, shell = True).split('\n')
+ t_list = subprocess.check_output(cmd2, shell = True)
+
+ if len(t_list) is 0:
+ print "No commits in the target: %s" %self.t_pattern
+ print "see '%s --help'" %__file__
+ exit()
+ else:
+ t_list = t_list.split('\n')
+
+ self.s_dict = dict()
+ self.t_dict = dict()
+
+ for item in s_list:
+ self.s_dict.update(dict([item.split(' ', 1)]))
+ for item in t_list:
+ self.t_dict.update(dict([item.split(' ', 1)]))
+
+ self.print_output()
+
+
+if __name__ == '__main__':
+ run = GitBranchDiff()
+ run.main()