|
@@ -0,0 +1,198 @@
|
|
|
+#!/usr/bin/python
|
|
|
+#
|
|
|
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
|
|
+#
|
|
|
+# Permission to use, copy, modify, and/or distribute this software for any
|
|
|
+# purpose with or without fee is hereby granted, provided that the above
|
|
|
+# copyright notice and this permission notice appear in all copies.
|
|
|
+#
|
|
|
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
|
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
|
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
+# PERFORMANCE OF THIS SOFTWARE.
|
|
|
+
|
|
|
+#
|
|
|
+# This script lists obsolete (fully merged) branches. It is useful for periodic
|
|
|
+# maintenance of our GIT tree.
|
|
|
+#
|
|
|
+# It is good idea to use following command before running this script:
|
|
|
+#
|
|
|
+# git pull
|
|
|
+# git remote prune origin
|
|
|
+#
|
|
|
+# This script requires python 2.7 or 3.
|
|
|
+#
|
|
|
+# I have limited experience in Python. If things are done in a strange or
|
|
|
+# uncommon way, there are no obscure reasons to do it that way, just plain
|
|
|
+# lack of experience.
|
|
|
+#
|
|
|
+# tomek
|
|
|
+
|
|
|
+import string
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+from optparse import OptionParser
|
|
|
+
|
|
|
+class Branch:
|
|
|
+ MERGED = 1
|
|
|
+ NOTMERGED = 2
|
|
|
+ name = None
|
|
|
+ status = NOTMERGED
|
|
|
+ last_commit = None
|
|
|
+
|
|
|
+
|
|
|
+def branch_list_get(verbose):
|
|
|
+ """ Generates a list of available remote branches and
|
|
|
+ checks their status (merged/unmerged). A branch is merged
|
|
|
+ if all changes on that branch are also on master. """
|
|
|
+
|
|
|
+ # call git branch -r (list of remote branches)
|
|
|
+ txt_list = subprocess.check_output(["git", "branch", "-r"])
|
|
|
+
|
|
|
+ txt_list = txt_list.split(b"\n")
|
|
|
+
|
|
|
+ # we will store list of suitable branches here
|
|
|
+ out = []
|
|
|
+ for branch in txt_list:
|
|
|
+ # skip empty lines
|
|
|
+ if len(branch) == 0:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # skip branches that are aliases (something -> something_else)
|
|
|
+ if branch.find(b"->") != -1:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # don't complain about master
|
|
|
+ if branch == b"origin/master":
|
|
|
+ continue
|
|
|
+
|
|
|
+ branch_info = Branch()
|
|
|
+
|
|
|
+ # get branch name
|
|
|
+ branch_info.name = branch.strip(b" ")
|
|
|
+ branch_info.name = branch_info.name.decode("utf-8")
|
|
|
+
|
|
|
+ # check if branch is merged or not
|
|
|
+ if verbose:
|
|
|
+ print("Checking branch %s" % branch_info.name)
|
|
|
+
|
|
|
+ # get a diff with changes that are on that branch only
|
|
|
+ # i.e. all unmerged code.
|
|
|
+ cmd = ["git", "diff", "master..." + branch_info.name ]
|
|
|
+ diff = subprocess.check_output(cmd)
|
|
|
+ if len(diff) == 0:
|
|
|
+ # No diff? Then all changes from that branch are on master as well.
|
|
|
+ branch_info.status = Branch.MERGED
|
|
|
+
|
|
|
+ # let's get the last contributor with extra formatting
|
|
|
+ # see man git-log and search for PRETTY FORMATS.
|
|
|
+ # %ai = date, %ae = author e-mail, %an = author name
|
|
|
+ cmd = [ "git" , "log", "-n", "1", "--pretty=\"%ai,%ae,%an\"",
|
|
|
+ branch_info.name ]
|
|
|
+ offender = subprocess.check_output(cmd)
|
|
|
+ offender = offender.strip(b"\n\"")
|
|
|
+
|
|
|
+ # comment out this 2 lines to disable obfuscation
|
|
|
+ offender = offender.replace(b"@", b"(at)")
|
|
|
+ # Obfuscating a dot does not work too well for folks that use
|
|
|
+ # initials
|
|
|
+ #offender = offender.replace(b".", b"(dot)")
|
|
|
+
|
|
|
+ branch_info.last_commit = offender.decode("utf-8")
|
|
|
+
|
|
|
+ else:
|
|
|
+ # diff is not empty, so there is something to merge
|
|
|
+ branch_info.status = Branch.NOTMERGED
|
|
|
+
|
|
|
+ out.append(branch_info)
|
|
|
+ return out
|
|
|
+
|
|
|
+def branch_print(branches, csv, print_merged, print_notmerged, print_stats):
|
|
|
+ """ prints out list of branches with specified details (using
|
|
|
+ human-readable (or CSV) format. It is possible to specify,
|
|
|
+ which branches should be printed (merged, notmerged) and
|
|
|
+ also print out summary statistics """
|
|
|
+
|
|
|
+ # counters used for statistics
|
|
|
+ merged = 0
|
|
|
+ notmerged = 0
|
|
|
+
|
|
|
+ # compact list of merged/notmerged branches
|
|
|
+ merged_str = ""
|
|
|
+ notmerged_str = ""
|
|
|
+ for branch in branches:
|
|
|
+ if branch.status == Branch.MERGED:
|
|
|
+ merged = merged + 1
|
|
|
+ if not print_merged:
|
|
|
+ continue
|
|
|
+ if csv:
|
|
|
+ print("%s,merged,%s" % (branch.name, branch.last_commit) )
|
|
|
+ else:
|
|
|
+ merged_str = merged_str + " " + branch.name
|
|
|
+ else:
|
|
|
+ # NOT MERGED
|
|
|
+ notmerged = notmerged + 1
|
|
|
+ if not print_notmerged:
|
|
|
+ continue
|
|
|
+ if csv:
|
|
|
+ print("%s,notmerged,%s" % (branch.name, branch.last_commit) )
|
|
|
+ else:
|
|
|
+ notmerged_str = notmerged_str + " " + branch.name
|
|
|
+
|
|
|
+ if not csv:
|
|
|
+ if print_merged:
|
|
|
+ print("Merged branches : %s" % (merged_str))
|
|
|
+ if print_notmerged:
|
|
|
+ print("NOT merged branches: %s" % (notmerged_str))
|
|
|
+
|
|
|
+ if print_stats:
|
|
|
+ print("#----------")
|
|
|
+ print("#Merged : %d" % merged)
|
|
|
+ print("#Not merged: %d" % notmerged)
|
|
|
+
|
|
|
+
|
|
|
+def parse_args(args=sys.argv[1:], Parser=OptionParser):
|
|
|
+
|
|
|
+ parser = Parser(description="This script prints out merged and/or unmerged"
|
|
|
+ " branches of a GIT tree.")
|
|
|
+
|
|
|
+ parser.add_option("-c", "--csv", action="store_true",
|
|
|
+ default=False, help="generates CSV output")
|
|
|
+ parser.add_option("-u", "--unmerged", action="store_true",
|
|
|
+ default=False, help="lists unmerged branches")
|
|
|
+ parser.add_option("-m", "--skip-merged", action="store_true",
|
|
|
+ default=False, help="omits listing merged branches")
|
|
|
+ parser.add_option("-s", "--stats", action="store_true",
|
|
|
+ default=False, help="prints also statistics")
|
|
|
+
|
|
|
+ (options, args) = parser.parse_args(args)
|
|
|
+
|
|
|
+ if args:
|
|
|
+ parser.print_help()
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+ return options
|
|
|
+
|
|
|
+def main():
|
|
|
+ usage = """%prog
|
|
|
+ Lists all obsolete (fully merged into master) branches.
|
|
|
+ """
|
|
|
+
|
|
|
+ options = parse_args()
|
|
|
+ csv = options.csv
|
|
|
+ merged = not options.skip_merged
|
|
|
+ unmerged = options.unmerged
|
|
|
+ stats = options.stats
|
|
|
+
|
|
|
+ if csv:
|
|
|
+ print("branch name,status,date,last commit(mail),last commit(name)")
|
|
|
+
|
|
|
+ branch_list = branch_list_get(not csv)
|
|
|
+
|
|
|
+ branch_print(branch_list, csv, merged, unmerged, stats)
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ main()
|