123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- #!/usr/bin/python
- #
- # Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
- #
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- #
- # 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()
|