Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions do_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,44 @@ def fail( msg , fd=os.sys.stdout ) :
os.chdir('../..')


os.chdir( 'tests/branchcount.git' )
repo = git_workflow_quality.Repository()
count = len(repo.branches)
if count == 5 :
ret += ok( "branch count working" )
else :
ret += fail( "counted %d branches" % count )
os.chdir('../..')


os.chdir( 'tests/complexnetwork.git' )
repo = git_workflow_quality.Repository()
count = len([ c for c in repo.values() if not c.child ])
if count == 2 :
ret += ok( "open branches properly found" )
else :
ret += fail( "found %d opened branches" % count )
#
count = repo.event_list()[0]['multimerged']
if count == 1 :
ret += ok( "1 multimerged found" )
else :
ret += fail( "%d multimerged found" % count )
#
count = repo.event_list()[0]['conflict']
if count == 1 :
ret += ok( "1 conflict found" )
else :
ret += fail( "%d conflict found" % count )
#
count = len(repo.branches)
if count == 8 :
ret += ok( "complex branches properly concatenated" )
else :
ret += fail( "complex concatenation produced %d branches" % count )
os.chdir('../..')


def test_event ( event ) :
os.chdir( 'tests/%s.git' % event )
repo = git_workflow_quality.Repository()
Expand Down
111 changes: 90 additions & 21 deletions git_workflow_quality/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ def add_child ( self , child ) :
self.child = child
elif child not in self.forks :
self.forks.append( child )
if not child.child and child.forks :
child.child = child.forks.pop(0)

def get_parents ( self , full=True ) :
parents = []
Expand Down Expand Up @@ -102,6 +104,9 @@ def merges ( self ) :
def stats ( self ) :
return len(self), len(self.commits()), len(self.merges())

def matches ( self , other ) :
return self.name.startswith( other.name ) or other.name.startswith( self.name )

def begin ( self ) :
begins = [ c for c in list.__iter__(self) if not c.parent ]
if not begins :
Expand All @@ -113,7 +118,7 @@ def end ( self ) :
ends = [ c for c in list.__iter__(self) if not c.child ]
if not ends :
ends = [ c for c in list.__iter__(self) if c.child.branch != self ]
if not ends :
if len(ends) != 1 :
# This just searches end on direct childs, and will not detect indirect merge-backs
ends = [ end for end in ends if not end.child or not end.child.branch.end().child or end.child.branch.end().child.branch != self ]
assert len(ends) == 1
Expand Down Expand Up @@ -378,12 +383,32 @@ def get_branches () :
fd.close()
return branches

class BranchList ( list ) :

def __init__ ( self ) :
self.names = []
list.__init__( self )

def get ( self , name ) :
match = [ b for b in self if b.name == name ]
if item.name in self.names :
assert len(match) == 1
return match[0]
assert not match
return None

def append ( self , item ) :
if item.name in self.names : assert False
self.names.append( item.name )
list.append( self , item )

class Repository ( dict ) :

def __init__ ( self ) :
def __init__ ( self , concatenate=True ) :

self.order = []
self.branches = []
self.branches = BranchList()
self.concatenated = concatenate

cmd = subprocess.Popen( ['git', 'log', '--all', '--format="%H \"%ae\" \"%ce\" %s"'] , stdout=subprocess.PIPE )
line = cmd.stdout.readline()
Expand All @@ -408,6 +433,9 @@ def __init__ ( self ) :

self.set_childs()

if self.concatenated :
self.concatenated = self.concatenation()

def set_params ( self , sha , line ) :
self[sha].author_date = int(line[0])
self[sha].committer_date = int(line[1])
Expand All @@ -419,8 +447,8 @@ def set_params ( self , sha , line ) :
raise Exception( "Octopus merges on %s from %s not handled" % ( self[sha].sha , ", ".join([c.sha for c in self[sha].parents]) ) )

def new_branch ( self , branchname , orphan=False ) :
match = [ branch for branch in self.branches if branch.name == branchname ]
if match :
if branchname in self.branches.names :
match = [ branch for branch in self.branches if branch.name == branchname ]
assert len(match) == 1
if match[0].is_primary() :
return match[0]
Expand All @@ -429,19 +457,34 @@ def new_branch ( self , branchname , orphan=False ) :
self.branches.append( Branch(branchname, orphan) )
return self.branches[-1]

def join_branch ( self , source , branch ) :
source.set_child( branch.begin() )
for commit in list(branch) :
source.branch.append( commit )
self.branches.remove(branch)
if len(source.branch.name) > len(branch.name) :
source.branch.name = branch.name

def events( self , details=False) :
output = ['']
event_list , msgs = self.event_list( details )

if msgs : output.extend( msgs )

output.append( "Branch events" )
if self.concatenated and self.base_events : output[-1] += " %+10d branches" % self.concatenated
output.append( " multitarget %4d" % event_list['multitarget'] )
if self.concatenated and self.base_events : output[-1] += " %4d" % self.base_events['multitarget']
output.append( " reutilized %4d" % event_list['reutilized'] )
if self.concatenated and self.base_events : output[-1] += " %4d" % self.base_events['reutilized']
output.append( " multimerged %4d" % event_list['multimerged'] )
if self.concatenated and self.base_events : output[-1] += " %4d" % self.base_events['multimerged']
output.append( " indirect %4d" % event_list['indirect'] )
if self.concatenated and self.base_events : output[-1] += " %4d" % self.base_events['indirect']
output.append( " multisource %4d" % event_list['multisource'] )
if self.concatenated and self.base_events : output[-1] += " %4d" % self.base_events['multisource']
output.append( " conflict %4d" % event_list['conflict'] )
if self.concatenated and self.base_events : output[-1] += " %4d" % self.base_events['conflict']
output.append( "" )

return "\n".join(output)
Expand Down Expand Up @@ -655,27 +698,53 @@ def set_childs ( self ) :
branch.ancestry( c )
self.order.reverse()

n , m = 0 , 0
m = 0
# Remove items within the loop is not safe
__branches = [ branch for branch in self.branches if len(branch) == 0 ]
for branch in __branches :
self.branches.remove(branch)
m += 1
if m :
print "ERROR : generated %d empty branches" % m

def concatenation ( self ) :

self.base_events , drop = self.event_list()

n = 0
# Remove items within the loop is not safe
__branches = [ branch for branch in self.branches ]
for branch in __branches :
if len(branch) == 0 :
self.branches.remove(branch)
m += 1

begin = branch.begin()
if not begin.parent : continue

source = begin.parent
if not source.forks and not begin.parents :
# This is in fact a plain commit with a single edge. As ancestry assigns childs, it cannot be detected there
self.join_branch( source , branch )
n += 1
continue
source = branch.begin().parent
if not source : continue
if ( source.child and source.child.branch == branch ) or \
branch.name.startswith( source.branch.name ) or source.branch.name.startswith( branch.name ) :
source.set_child( branch.begin() )
for commit in list(branch) :
source.branch.append( commit )
assert len(branch) == 0
self.branches.remove(branch)
source.branch.name = branch.name

if [ c for c in begin.get_childs() if c.branch == source.branch ] :
print "WARNING : %s merged back into %s" % ( begin.branch.pretty() , source.branch.pretty() )
continue

# Concatenation happens in two cases :
# incoming merge : a parent commit whose single child is the first commit on branch
# outgoing merge : the first commit has a single parent, and this is the only one of their childs with a single parent
candidates = [ c for c in begin.get_parents() if not c.forks ] + [ c.parent for c in begin.parent.get_childs() if not begin.parents and not c.parents ]
if len(candidates) != 1 : continue

source = candidates[0]
if source.branch.end() == source and branch.matches( source.branch ) :
self.join_branch( source , branch )
n += 1

if n :
print "WARNING : %d branches removed by concatenation with parents" % n
if m :
print "ERROR : generated %d empty branches" % m
else :
self.base_events = []

return n

3 changes: 2 additions & 1 deletion gwfqa
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ if __name__ == '__main__' :
parser = argparse.ArgumentParser(description='Extract simple analytics from git repository')
parser.add_argument('--detailed', action='store_true', help='Show per-branch statistics')
parser.add_argument('--graph', choices=['topo', 'date', 'backwards'], help='Generate gitgraphjs file')
parser.add_argument('--no-concatenate', dest='concatenate', action='store_false', help='Express branch relations in dot language')
parser.add_argument('--dot', help='Express branch relations in dot language')
args = parser.parse_args()

repo = git_workflow_quality.Repository()
repo = git_workflow_quality.Repository(args.concatenate)

print repo.report(args.detailed)

Expand Down
1 change: 1 addition & 0 deletions tests/branchcount.git/HEAD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ref: refs/heads/branch3
8 changes: 8 additions & 0 deletions tests/branchcount.git/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
x��Mj1 @�}
� A��A)]g�+hdO2e&\����3t���g��-�����g���N�-I���T�

Zʹ�1���v�5H�j�$�
U�J�sc_+���g��q�;��wi.��-�||᱾���.����| $�<�#!����p��n�z��?�FF{
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��I
1@Q�9E��T:��v�*�D[z����gp����u��Da�[)�Cn0�X��R���k�^��C��v�����A�L�f/���l0A��=U+1�G����w��.�K�+O,����c ��{��<NY�hk(��f�=���6�7�W��"wT?��G�
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions tests/branchcount.git/refs/heads/branch1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4bd1087aaf4d5b24fa17b6aaba80f8f4bc90502a
1 change: 1 addition & 0 deletions tests/branchcount.git/refs/heads/branch2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
84fd538a36d65b0cf3ab7f6c503af812fce9cb1a
1 change: 1 addition & 0 deletions tests/branchcount.git/refs/heads/branch3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
45a1efea7ca2d55fae24a8f62f29feeee4990313
1 change: 1 addition & 0 deletions tests/branchcount.git/refs/heads/branch4
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0bbb2de8671e2453f096d51cd4582aa197949835
1 change: 1 addition & 0 deletions tests/branchcount.git/refs/heads/master
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a6670da021af97b9421a1ae49cb1f9bd5b60a6ed
1 change: 1 addition & 0 deletions tests/complexnetwork.git/HEAD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ref: refs/heads/master
7 changes: 7 additions & 0 deletions tests/complexnetwork.git/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[core]
repositoryformatversion = 0
filemode = false
bare = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
1 change: 1 addition & 0 deletions tests/complexnetwork.git/description
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.
6 changes: 6 additions & 0 deletions tests/complexnetwork.git/info/exclude
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��Kj1 ��}
��c�/!�zY��)��z�����_�u�8�>FWr��RT�h]��}�PSk�|LSiC�l�u 8Ҋ�`��|��b֪�d��$\j4���Í߳v��eޟp�9�c��~�</����<"��1��:k�Q��q�qS:or'����H
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��MJCA�]�S�. Azz�A$kA�
����&�x|� �*꣪�x_׹�Л�b�' $�9�E���'�IjɁ�:�5ݺ)�G�C��Scd,�P�)r���O�-�lc����SE���2(}����+}���;-��~7��n���Ҽ<���9���# �4�8�G�_��M�Yͩ��s��x0���_��o��_?
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x��MJ1�a�}��f!HU*�T@ĵ x�JR���L�oz�/�|P�ض�|�M���&��� 3+ ��>���$����4ݻQϕ=��D3ZF[x�JL!V\b��w_�D�J�X��#*�� 위9x��_�f��k�f>d��w�|�~��7Y֧|l/=���<��FO���ŧwmg5�ɞ/��}L�̲��7��� ]l
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x+)JMU047a040031QH*J�K�0d�U���k���{?-zW'\R�E�CD�7˯��j�Ntޑ�[��1E�1a%����&��1쩸����W*����I)���r#Fl
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xK��OR02c���,�L�QH����,�2,.�1�� p
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x��MJ1�]���B�JU~Aĵ x�J�2��==�V<���|����}ۖ�b.w�P�9� ��� ���넠Z!v��� 17z=l���G��T�p�(;��L�'��O�BŖri�UJ5�p��zjɗ9b���þ�עþ�ʲ���}����|�xYdߞ� D>g��0��S�����Wg�u�U.���t2?w�Z-
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��Aj�0@Ѯu���hƒ%(%�r��hT��qP����g��Ã���-��Gof�B
:d����� �&�U�X�JY�]��:ĨT*K,�Y�Ԑ��g�LcF?��g��y-��*��?�����}��dY?u߾��!%NH���d?ܿ����tf@���G�
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
x��A
1 E]�� ҴӴ׮�B��:2�H������z�O�e�8�6���fJ�}h�B%�$}�0�K?go�\�Ѡ��űđ��ET1c�IHpL6�˄����V8�g�
g�Y���{��{�.<�;Y�`�~H݌���Z�i�l��K7��Cnh~�G�
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x���
!@��>�� ���"Z���8ZƘ�c��g����'�Rr�J�]o1r�g��v�sER B@k"�L��0Kl���Jg=�$�0�F7%�)��I�����6~�w��_i���Ə���s���!�r��h��<� %uL������Æ}ۋGM
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x��MJA �]�)��� I'��ׂ��ꪙ�g�i��� ^�Ճ��=xy[�iFz�[)��Ŵ��(".4�1�WW �F�8Rw�V���W�TQ<�8�^#g�,EG�`�J�Z�y�W�*��B0�<h���A-����~����T|�ly���|=�m�~=/6�Oy[^��Yb Q�{��ǩ����w諾 x�5_���z�i�7X�~ w?0#^8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��K
B!��Ʈ�̃�u}@D�Fm�疡^���m��l������D�)/U4ɯHƠ��.A�O\��)8$��qo.����cn3<��|���!�z�(���J��K�٬s:������s�qe:�
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��M
�0@a�9���L����kW^a2Mm�iK�����>��ɖ�\��jI $I��E�ч��d�F4$l��4��#��KZ+�1t5�hƈ~�Hl��5�6�iK�U���V�Ɵ9���2o/8?[ؗ���y^N�� `oL�]�GMZ�V�dm�/�b�U&�~��G�
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��K
1EQ�YE�I�����cGn����[�G�����^ށ��e�*��@LL?Ff��s0΋8���I2J2ݠv.�Vp.�9�c�3E�<t�( i�Q��!��(~�q+p��$�<s����-����Xx�Oy[.������6Z�V����_\��k-h��G�
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
x��K
1]�����̴ �ڕW�|tF�G����\��*^\�y��,�Zr%��w^�XE m`&GD�R�U�fӒ�
������x$O,B�4tS�%��A�����g�n:i���l���u�q��@�Z�2}{dD�h;Y���nB�%l~��Fj
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x+)JMU040a040031QH*J�K�0d�U���k���{?-zW'\R�E�1CD�7˯��j�Ntޑ�[��1�$7��$���W�ߚ���'ٚ����5B�g�*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x��M
�0�a�9E�������v���T#�)i�o�����PrNM�]�̒��#z?+�pd��1��� �3b���&Qi�ݴ�kC}��@:���7�bA�v/U^蝸�+-R����a]>�[��B�'9�q�Հr�@)ѵ�l��+��_l�]H�
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/branch1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f133084691e488fdf3dcd67f0dff4df2f55fb711
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/branch2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fb6e7570645235ebe94889f95783bc0fd1f5bec6
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/branch3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
35b0b2d786ccd201db37b83ca8f21f7d363499af
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/branch4
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0d58300606997e8eacf8504197c65982d0cbb8eb
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/branch5
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fe613ab22f00f45c5041697089c756fda875ac8e
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/branch6
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
74071922c169dd14300d31d67f2dfa3316c5dd09
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/branch7
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b20afdee9454ae329646b7b568b08c6065c90217
1 change: 1 addition & 0 deletions tests/complexnetwork.git/refs/heads/master
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f7f0860affbf5445ef0d8b5388cd9814a2063d57