diff --git a/contrib/git-sl b/contrib/git-sl deleted file mode 100755 --- a/contrib/git-sl +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2004-present Facebook. All rights reserved. -# -# Emulate the output of smartlog.py atop git, instead of Mercurial. -# - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals -import re -from subprocess import Popen -import subprocess -import itertools -from time import time -import argparse - - -seconds_in_a_day = 60.0*60.0*24.0 - -class ColorOutput(object): - colors = { - 'black': 90, - 'red': 91, - 'green': 92, - 'yellow': 93, - 'blue': 94, - 'pink': 95, - 'cyan': 96, - 'under': 4, - 'none': 0 - } - str_pattern_color = '\33[%dm' - - @classmethod - def str(cls, color, text): - return (cls.str_pattern_color % cls.colors[color]) + str(text) + \ - (cls.str_pattern_color % cls.colors['none']) - -class GitRevision(object): - revisionmap = {} - root = None - current_branch = None - show_all = False - origin_timestamp_threshold = 0 - ignored_message = None - - # ref can be a hash or a branch_name - def __init__(self, ref, hash=None): - self.hash = hash if hash is not None else git_rev_parse(ref) - self.ref = [ref] - self.ancestors = [] - self.predecessors = [] - self.father = self - self.other_fathers = set() - self.children = [] - self.author = "AwesomeAuthor" - self.longref = "Awesome Description" - self.commit_timestamp = 0 - self.between_me_and_father = [] - self.evaluated = set() - self.real_father = False - self.me_head = False - self.small_hash = "" - self.get_my_info() - global origin_timestamp_threshold - self.use_me = (GitRevision.show_all or - self.commit_timestamp > GitRevision.origin_timestamp_threshold) - - @classmethod - def make_unique(cls, ref, hash=None): - hash = hash if hash is not None else git_rev_parse(ref) - if hash in cls.revisionmap: - cls.revisionmap[hash].ref += [ref] - else: - cls.revisionmap[hash] = cls(ref, hash=hash) - return cls.revisionmap[hash] - - @classmethod - def build_revmap(cls, reflist): - hashes = git_rev_parse_many(reflist) - for (hash, ref) in zip(hashes, reflist): - cls.make_unique(ref, hash=hash) - cls.remove_old_revs() - - @staticmethod - def set_hashes(revs): - return set([x.hash for x in revs]) - - @classmethod - def print_ignored_if_any(cls): - if cls.ignored_message is not None: - print(cls.ignored_message) - - @classmethod - def remove_old_revs(cls): - new_revisionmap = {} - ignored = [] - for hash, rev in cls.revisionmap.iteritems(): - if rev.use_me: - new_revisionmap[hash] = rev - else: - ignored.append( - "ignored: %s %s (%s) [%.1f days old] %s" % ( - rev.small_hash, - rev.author, - ", ".join(rev.ref), - (time() - rev.commit_timestamp)/seconds_in_a_day, - rev.longref) - ) - cls.revisionmap = new_revisionmap - cls.ignored_message = "\n".join(ignored) - - @classmethod - def get_rev(cls, rev_hash): - if rev_hash not in cls.revisionmap: - cls.make_unique(rev_hash) - return cls.revisionmap[rev_hash] - - @classmethod - def prepare(cls, to_be_prepared=None): - refs = cls.revisionmap.keys() - all_rev = cls.revisionmap.values() - will_prepare = to_be_prepared or all_rev - for rev in will_prepare: - for rev_s in all_rev: - rev.eval_rev(rev_s) - newrefs = set(cls.revisionmap.keys()) - set(refs) - if len(newrefs) > 0: - cls.prepare(to_be_prepared=[cls.get_rev(x) for x in newrefs]) - if to_be_prepared is None: - cls.find_parents_and_children() - head_hash = git_rev_parse("HEAD") - cls.get_rev(head_hash).me_head = True - - @classmethod - def find_parents_and_children(cls): - for rev in cls.revisionmap.values(): - rev.normalize() - for rev in cls.revisionmap.values(): - rev.find_my_children() - for rev in cls.revisionmap.values(): - if rev.father == rev: - cls.root = rev - for rev in cls.revisionmap.values(): - key=lambda x: time() if "master" in x.ref else x.newest_timestamp() - rev.children = sorted( - rev.children, - key=key - ) - - def newest_timestamp(self): - ts = self.commit_timestamp - for ch in self.children: - ts = max(ts, ch.commit_timestamp) - return ts - - def normalize(self): - self.ancestors = list( - dict([(x.hash, x) for x in self.ancestors]).values() - ) - self.predecessors = list( - dict([(x.hash, x) for x in self.predecessors]).values() - ) - - def find_my_children(self): - my_ancestors_hashes = GitRevision.set_hashes(self.ancestors) - my_predecessors_hashes = GitRevision.set_hashes(self.predecessors) - for child in self.predecessors: - - # / OTHER_A - # AN \ / - PARALLEL_A ----------\ - # AN - ME OTHER - # AN / \ - PARALLEL_B - CHILD --/ - # BR_A ------- BR_B -----/ \ OTHER_B - # - # if PARALLEL_B == {} => CHILD is child - # - # - # - # CHILD.predecessors = OTHER | OTHER_B - # CHILD.ancestors = AN | ME | PARALLEL_B | BR_A | BR_B - # ME.ancestors = AN | BR_A - # nodes := CHILD.ancestors - ME.ancestors - ME = PARALLEL_B | BR_B - # nodes & ME.predecessors = PARALLEL_B - - # all operations are NlogN with very low const - child_pred = GitRevision.set_hashes(child.predecessors) - child_anc = GitRevision.set_hashes(child.ancestors) - nodes = child_anc - my_ancestors_hashes - nodes = nodes - set([self.hash]) - parallel_b = nodes & my_predecessors_hashes - if len(parallel_b) == 0: - # if there is no one that is between me and my child, then - # it's a 'direct child' - self.children.append(child) - direct_fathers = set(get_parents(child.hash)) - fathers = child.other_fathers - if child.hash != child.father.hash: - fathers|= set([child.father.hash]) - fathers|= set([self.hash]) - real_fathers = fathers & direct_fathers - if len(real_fathers) > 0: - father_hash = real_fathers.pop() - child.other_fathers = fathers - set([father_hash]) - child.father = GitRevision.get_rev(father_hash) - child.real_father = True - else: - father_hash = fathers.pop() - child.other_fathers = fathers - set([father_hash]) - child.father = GitRevision.get_rev(father_hash) - child.real_father = False - - def is_same_hash(self, hash_str): - len_hash = min(len(self.hash), len(hash_str)) - if self.hash[:len_hash] == hash_str[:len_hash]: - return True - return False - - def eval_rev(self, revision): - if revision.hash == self.hash: - return - if revision.hash in self.evaluated: - return - self.evaluated.add(revision.hash) - revision.evaluated.add(self.hash) - ancestor = git_common_ancestor(self.hash, revision.hash) - if self.is_same_hash(ancestor): - self.predecessors.append(revision) - revision.ancestors.append(self) - elif revision.is_same_hash(ancestor): - self.ancestors.append(revision) - revision.predecessors.append(self) - else: - anc_rev = GitRevision.get_rev(ancestor) - anc_rev.eval_rev(self) - anc_rev.eval_rev(revision) - - def __str__(self): - return '\n'.join(reversed(list(self.tree()))) - - def tree(self): - yield " " if self.root != self else "|" - for line in self.twolines_ref(): - yield line - last = self.children[-1] if len(self.children) > 0 else None - for child in self.children: - if child.father is not self: - # this guy is a child of someone else too. let he print it - continue - prefix = '|' if child == last else '|/' - for line in child.tree(): - yield prefix + line - prefix = '' if child == last else '| ' - - def twolines_ref(self): - bullet = "@ " if self.me_head else "o " - text = self.small_hash + " " + self.author - if self.me_head: - lines = bullet + ColorOutput.str("green", text) - longref = ColorOutput.str("green", self.longref) - else: - lines = bullet + text - longref = self.longref - if self.ref != self.hash: - refv = [ - x + "*" if x == GitRevision.current_branch else x - for x in self.ref - ] - text = " (" + ", ".join(refv) + ")" - if self.me_head: - lines += ColorOutput.str("yellow", text) - else: - lines += ColorOutput.str("blue", text) - if self.real_father: - trace = "| " - else: - trace = ": " - if len(self.other_fathers) == 0: - return [trace + longref, lines] - else: - other = [GitRevision.get_rev(x).small_hash for x in - self.other_fathers] - other = ColorOutput.str("cyan", "Also son of: " + ", ".join(other)) - return [trace + other, trace + longref, lines] - - def get_my_info(self): - [name, longref, committime, small_hash] = get_info(self.hash) - self.author = name - self.longref = longref - self.commit_timestamp = int(committime) - self.small_hash = small_hash - -def run_cmd(cmd, swallow_error=False): - p = Popen(["git"] + cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - out, err = p.communicate() - if p.returncode != 0: - if swallow_error: - return '', '' - print( - "git %s returned code %d:\nstdout:\n%s\nstderr:\n%s" % - (cmd, p.returncode, out, err) - ) - exit(p.returncode) - return out, err - -def get_info(revref): - out, _ = run_cmd(["log", revref, "--format=%ae%x00%s%x00%ct%x00%h", "-n1"]) - result = out.split('\n')[0].split("\x00") - result[0] = result[0].split('@')[0] - return result - -def get_parents(rev): - out, _ = run_cmd(["log", "--pretty=%P", "-n1", rev]) - return out.strip().split() - -def git_rev_parse_many(refs): - out, _ = run_cmd(["rev-parse"] + refs) - return out.strip().split() - -def git_rev_parse(ref): - return git_rev_parse_many([ref])[0] - -# Git Branch (git branch) -def git_branch_names(): - out, _ = run_cmd(["branch"]) - reg = r"\*\s*(\w*)" - GitRevision.current_branch = re.search(reg, out).groups()[0] - return list(set(out.strip().split()) - set(["*"])) - -def git_common_ancestor(branch1, branch2): - out, _ = run_cmd(["merge-base", branch1, branch2], swallow_error=True) - return out.strip() - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Git Smart Log - prints a ' - 'tree representation of your branches') - parser.add_argument('-a', '--all', action='store_true', - help='By default it will only look into features not older than 2 weeks' - ', if this is not what you want and you don\'t care about waiting' - 'for 10 minutes, use this') - # defaults for - parser.add_argument('-t', '--time', metavar='FLOAT_TIME_IN_DAYS', - default=str(14), help='time in days to look for features.') - gitsl_args = parser.parse_args() - GitRevision.show_all = gitsl_args.all - GitRevision.origin_timestamp_threshold = (time() - - float(gitsl_args.time)*seconds_in_a_day) - GitRevision.build_revmap(git_branch_names() + ["HEAD"]) - GitRevision.prepare() - print(GitRevision.root) - GitRevision.print_ignored_if_any()