gitra 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +66 -0
- data/Rakefile +9 -0
- data/bin/gitra +36 -0
- data/lib/gitra.rb +4 -0
- data/lib/gitra/cli.rb +64 -0
- data/lib/gitra/git_patches.rb +27 -0
- data/lib/gitra/parser.rb +32 -0
- data/lib/gitra/tracker.rb +51 -0
- data/lib/gitra/version.rb +3 -0
- data/spec/support/git_helper.rb +71 -0
- data/spec/support/minitest_helper.rb +3 -0
- data/spec/tracker_spec.rb +80 -0
- metadata +136 -0
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
## Git repository analyser
|
2
|
+
|
3
|
+
(The too-soon version)
|
4
|
+
|
5
|
+
[![Build Status](https://travis-ci.org/sldblog/gitra.png?branch=master)](https://travis-ci.org/sldblog/gitra)
|
6
|
+
|
7
|
+
### Usage
|
8
|
+
|
9
|
+
```
|
10
|
+
Usage: gitra [options]
|
11
|
+
-a, --analyse [BRANCH] Analyze the repository in relation the the specified branch (defaults to master).
|
12
|
+
-l, --log SINCE_REV Shows the stories and bugs committed since the specified revision.
|
13
|
+
```
|
14
|
+
|
15
|
+
### Sample use case
|
16
|
+
|
17
|
+
Given the following git repository:
|
18
|
+
|
19
|
+
```
|
20
|
+
$ git log --oneline --graph --all --decorate
|
21
|
+
* 0fbdc16 (HEAD, master) master #3
|
22
|
+
* 08a0df3 Merge branch 'test'
|
23
|
+
|\
|
24
|
+
* | 23e7058 master #2
|
25
|
+
* | b4c12ca master #1
|
26
|
+
| | * 08e7339 (test) test #3
|
27
|
+
| |/
|
28
|
+
| * af75469 test #2
|
29
|
+
| * de14754 test #1
|
30
|
+
|/
|
31
|
+
* 6530d98 first
|
32
|
+
```
|
33
|
+
|
34
|
+
You can analyse the ahead/behind information using `gitra`.
|
35
|
+
|
36
|
+
By default, it analyses the active branch in the repository it is in (you have to be physically in the repo directory for now):
|
37
|
+
|
38
|
+
```
|
39
|
+
$ git branch
|
40
|
+
* master
|
41
|
+
test
|
42
|
+
```
|
43
|
+
|
44
|
+
```
|
45
|
+
$ gitra -a
|
46
|
+
---- Analyzing branches in relation to master ----
|
47
|
+
Analysing 2 branches: ..
|
48
|
+
master merged
|
49
|
+
test 1 ahead (unmerged), (but 2 behind)
|
50
|
+
```
|
51
|
+
|
52
|
+
You can specify any branches as part of the command line:
|
53
|
+
|
54
|
+
```
|
55
|
+
$ gitra -a test
|
56
|
+
---- Analyzing branches in relation to test ----
|
57
|
+
Analysing 2 branches: ..
|
58
|
+
test merged
|
59
|
+
master 2 ahead (unmerged), (but 1 behind)
|
60
|
+
```
|
61
|
+
|
62
|
+
As you can see, the reported information is in line compared to the repository graph displayed above:
|
63
|
+
- `master` is indeed ahead by 2 commits (`08a0df3`, `0fbdc16`), but missing 1 (`08e7339`), when viewed from `test` branch
|
64
|
+
- the other way around if viewed from the `master` branch
|
65
|
+
|
66
|
+
This kind of analysis really helps our current team to get on top of the current branching strategy we inherited.
|
data/Rakefile
ADDED
data/bin/gitra
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'gitra'
|
5
|
+
require 'optparse'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'term/ansicolor'
|
8
|
+
|
9
|
+
class String; include Term::ANSIColor; end
|
10
|
+
|
11
|
+
options = OpenStruct.new
|
12
|
+
parser = OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: gitra [options]"
|
14
|
+
|
15
|
+
opts.on("-a [BRANCH]", "--analyse [BRANCH]", "Analyze the repository in relation the the specified branch (defaults to master).") do |branch|
|
16
|
+
options.command = :analyze
|
17
|
+
options.parameter = branch || 'master'
|
18
|
+
end
|
19
|
+
opts.on("-l SINCE_REV", "--log SINCE_REV", "Shows the stories and bugs committed since the specified revision.") do |since_rev|
|
20
|
+
options.command = :history
|
21
|
+
options.parameter = since_rev
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
parser.parse!
|
27
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
28
|
+
$stderr.puts e.to_s.red
|
29
|
+
end
|
30
|
+
|
31
|
+
if options.command
|
32
|
+
cli = Gitra::CLI.new
|
33
|
+
cli.send(options.command, options.parameter)
|
34
|
+
else
|
35
|
+
puts parser
|
36
|
+
end
|
data/lib/gitra.rb
ADDED
data/lib/gitra/cli.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
class String
|
4
|
+
include Term::ANSIColor
|
5
|
+
end
|
6
|
+
|
7
|
+
module Gitra
|
8
|
+
|
9
|
+
class CLI
|
10
|
+
def initialize
|
11
|
+
@tracker = Tracker.new '.'
|
12
|
+
end
|
13
|
+
|
14
|
+
def analyze(reference_branch)
|
15
|
+
reference_branch ||= @tracker.current_branch
|
16
|
+
|
17
|
+
$stdout.puts "---- Analyzing branches in relation to #{reference_branch.yellow} ----"
|
18
|
+
branches = ([reference_branch] + @tracker.branches).uniq!
|
19
|
+
|
20
|
+
$stdout.print "Analysing #{branches.size} branches: "
|
21
|
+
branch_analysis = branches.map do |branch|
|
22
|
+
$stdout.print '.'
|
23
|
+
$stdout.flush
|
24
|
+
unmerged = @tracker.branch(branch).commits_since(reference_branch).collect { |c| c.sha }
|
25
|
+
behind = @tracker.branch(reference_branch).commits_since(branch).collect { |c| c.sha }
|
26
|
+
{:name => branch, :behind => behind.size, :unmerged => unmerged.size}
|
27
|
+
end
|
28
|
+
$stdout.puts
|
29
|
+
|
30
|
+
name_max_size = branch_analysis.map { |item| item[:name].size }.sort.last
|
31
|
+
branch_analysis.each do |item|
|
32
|
+
state = []
|
33
|
+
state << "#{item[:unmerged]} ahead (unmerged)".red if item[:unmerged] > 0
|
34
|
+
state << "(but #{item[:behind]} behind)" if item[:unmerged] > 0 and item[:behind] > 0
|
35
|
+
state << 'merged'.green if state.empty?
|
36
|
+
$stdout.puts "%#{name_max_size}s %s" % [item[:name], state.join(', ')]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def history(since_revision)
|
41
|
+
current = @tracker.current_branch
|
42
|
+
|
43
|
+
$stdout.puts "---- Analyzing commit history from #{since_revision.yellow} to #{current.yellow} ----"
|
44
|
+
commits = @tracker.branch(current).commits_since(since_revision)
|
45
|
+
|
46
|
+
$stdout.print "Analysing #{commits.size} commits: "
|
47
|
+
commit_parser = Gitra::Parser.new('.gitra-rules.yml')
|
48
|
+
commits.each do |commit|
|
49
|
+
$stdout.print '.'
|
50
|
+
$stdout.flush
|
51
|
+
commit_parser.use(commit)
|
52
|
+
end
|
53
|
+
$stdout.puts
|
54
|
+
|
55
|
+
commit_parser.result.each_pair do |type, ids|
|
56
|
+
ids.sort.each do |id, commits|
|
57
|
+
description = commits.map { |c| c.sha.slice(0..10) }.join(', ')
|
58
|
+
$stdout.puts "%28s - %s" % ["#{type.to_s} #{id.to_s}".yellow, description]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module Git
|
4
|
+
class Base
|
5
|
+
def merge_base(commit1, commit2)
|
6
|
+
self.lib.merge_base(commit1, commit2)
|
7
|
+
end
|
8
|
+
|
9
|
+
def log_ancestry(from, to)
|
10
|
+
self.lib.log_ancestry(from, to).map { |c| Git::Object::Commit.new(self, c['sha'], c) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Lib
|
15
|
+
def merge_base(commit1, commit2)
|
16
|
+
command('merge-base', [commit1, commit2])
|
17
|
+
end
|
18
|
+
|
19
|
+
def log_ancestry(from, to)
|
20
|
+
arr_opts = ['--pretty=raw']
|
21
|
+
arr_opts << "#{from.to_s}..#{to.to_s}"
|
22
|
+
arr_opts << '--ancestry-path'
|
23
|
+
full_log = command_lines('log', arr_opts, true)
|
24
|
+
process_commit_data(full_log)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/gitra/parser.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Gitra
|
4
|
+
class Parser
|
5
|
+
def initialize(rule_file)
|
6
|
+
rule_file ||= '.gitra-rules.yml'
|
7
|
+
@rules = YAML.load_file(rule_file)
|
8
|
+
|
9
|
+
@uses = {}
|
10
|
+
@rules.each_pair do |name, patterns|
|
11
|
+
@uses[name] = {}
|
12
|
+
patterns.map! { |p| Regexp.new p }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def use(commit)
|
17
|
+
@rules.each_pair do |name, patterns|
|
18
|
+
patterns.each do |pattern|
|
19
|
+
match = pattern.match commit.message
|
20
|
+
next unless match
|
21
|
+
|
22
|
+
id = match[1].to_i
|
23
|
+
(@uses[name][id] ||= []) << commit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def result
|
29
|
+
@uses
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module Gitra
|
4
|
+
|
5
|
+
class Tracker
|
6
|
+
def initialize(repository)
|
7
|
+
@git = Git.open(repository)
|
8
|
+
end
|
9
|
+
|
10
|
+
def current_branch
|
11
|
+
@git.current_branch
|
12
|
+
end
|
13
|
+
|
14
|
+
def branches
|
15
|
+
@git.branches.select do |branch|
|
16
|
+
name = branch.full.gsub(%r{^remotes/}, '')
|
17
|
+
next if name =~ / -> /
|
18
|
+
block_given? ? yield(name) : true
|
19
|
+
end.map { |branch| branch.full.gsub(%r{^remotes/}, '') }
|
20
|
+
end
|
21
|
+
|
22
|
+
def branch(branch)
|
23
|
+
TrackedBranch.new(@git, branch)
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(name, *args, &block)
|
27
|
+
raise NoMethodError, "method `#{name}' should be used as `<tracker>.branch(reference).#{name}(<args>)'" if TrackedBranch.method_defined? name
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
class TrackedBranch
|
35
|
+
def initialize(git, branch)
|
36
|
+
@git = git
|
37
|
+
@branch = branch.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def commits_since(reference, options = {:ancestry => true})
|
41
|
+
since = @git.object(reference.to_s)
|
42
|
+
base = @git.merge_base(@branch, since)
|
43
|
+
if options[:ancestry]
|
44
|
+
@git.log_ancestry(base, @branch).reverse
|
45
|
+
else
|
46
|
+
@git.log(2**16).between(base, @branch).to_a.reverse
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'git'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class GitHelper
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
path = File.join(Dir.tmpdir, "#{Time.now.to_i}_#{rand(1000)}")
|
8
|
+
FileUtils.mkdir_p path
|
9
|
+
Dir.chdir path do
|
10
|
+
@git = Git.init
|
11
|
+
@git.config('user.name', 'John Testable')
|
12
|
+
@git.config('user.email', 'john.testable@testing.it')
|
13
|
+
|
14
|
+
File.new(".gitignore", "w")
|
15
|
+
@git.add(".gitignore")
|
16
|
+
@git.commit "Initial commit."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def path
|
21
|
+
@git.dir.path
|
22
|
+
end
|
23
|
+
|
24
|
+
def cleanup
|
25
|
+
FileUtils.rm_rf(path, :secure => true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def commit_to(branch_sym, parent_sym = :master)
|
29
|
+
branch = branch_sym.to_s
|
30
|
+
parent = parent_sym.to_s
|
31
|
+
|
32
|
+
@git.checkout parent
|
33
|
+
@git.branch(branch).create
|
34
|
+
@git.checkout branch
|
35
|
+
file = File.new(File.join(path, "file_of_#{branch}"), "a+")
|
36
|
+
file.puts("additional line")
|
37
|
+
file.close
|
38
|
+
@git.add file.path
|
39
|
+
@git.commit "Update #{branch}"
|
40
|
+
@git.checkout 'master'
|
41
|
+
|
42
|
+
@git.object(branch).sha
|
43
|
+
end
|
44
|
+
|
45
|
+
def branch_off(branch_hash)
|
46
|
+
branch_hash.collect do |branch_sym, into_sym|
|
47
|
+
branch = branch_sym.to_s
|
48
|
+
into = into_sym.to_s
|
49
|
+
|
50
|
+
@git.checkout branch
|
51
|
+
@git.branch(into).create
|
52
|
+
@git.checkout 'master'
|
53
|
+
|
54
|
+
@git.object(into).sha
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge(merge_hash)
|
59
|
+
merge_hash.collect do |branch_sym, into_sym|
|
60
|
+
branch = branch_sym.to_s
|
61
|
+
into = into_sym.to_s
|
62
|
+
|
63
|
+
@git.checkout into
|
64
|
+
@git.merge branch, "Merge #{branch}"
|
65
|
+
@git.checkout 'master'
|
66
|
+
|
67
|
+
@git.object(into).sha
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'git'
|
3
|
+
|
4
|
+
describe Gitra::Tracker do
|
5
|
+
let(:git) { GitHelper.new }
|
6
|
+
let(:tracker) { Gitra::Tracker.new(git.path) }
|
7
|
+
|
8
|
+
after do
|
9
|
+
git.cleanup
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'Selects branches' do
|
13
|
+
it 'should list all branches without block' do
|
14
|
+
git.commit_to :b1
|
15
|
+
git.commit_to :b2
|
16
|
+
tracker.branches.sort.must_equal %w{master b1 b2}.sort
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should list matching branches with block' do
|
20
|
+
git.commit_to :b1
|
21
|
+
git.commit_to :b2
|
22
|
+
tracker.branches { |name| name =~ /^b/ }.sort.must_equal %w{b1 b2}.sort
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'Shows commits from merge base' do
|
27
|
+
it 'should contain all commits from branching point' do
|
28
|
+
git.commit_to :master
|
29
|
+
git.branch_off :master => :release
|
30
|
+
git.commit_to :release
|
31
|
+
after_branch_off_commits = [
|
32
|
+
git.commit_to(:master),
|
33
|
+
git.commit_to(:master)
|
34
|
+
]
|
35
|
+
tracker.branch(:master).commits_since(:release).map { |c| c.sha }.must_equal after_branch_off_commits
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should only contain commits with real ancestry' do
|
39
|
+
git.commit_to :master
|
40
|
+
git.branch_off :master => :release
|
41
|
+
2.times { git.commit_to :release }
|
42
|
+
2.times { git.commit_to :master }
|
43
|
+
|
44
|
+
only_on_master = []
|
45
|
+
only_on_master += git.merge(:release => :master)
|
46
|
+
only_on_master << git.commit_to(:master)
|
47
|
+
git.commit_to :release
|
48
|
+
|
49
|
+
tracker.branch(:master).commits_since(:release).map { |c| c.sha }.must_equal only_on_master
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should be able to show more than the default log limit (30)' do
|
53
|
+
git.commit_to :master
|
54
|
+
git.branch_off :master => :release
|
55
|
+
|
56
|
+
limit = 40
|
57
|
+
limit.times { git.commit_to :release }
|
58
|
+
tracker.branch(:release).commits_since(:master).size.must_equal limit
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should fail if "since" branch is not resolvable' do
|
62
|
+
proc { tracker.branch(:master).commits_since(:new_branch) }.must_raise Git::GitExecuteError
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should fail if "from" branch is not resolvable' do
|
66
|
+
proc { tracker.branch(:neverwhere).commits_since(:master) }.must_raise Git::GitExecuteError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'Shows unmerged commits between two branches' do
|
71
|
+
it 'should contain all unmerged commits' do
|
72
|
+
git.commit_to :master
|
73
|
+
git.branch_off :master => :release
|
74
|
+
missing_commit = git.commit_to :release
|
75
|
+
|
76
|
+
tracker.branch(:release).commits_since(:master).map { |c| c.sha }.must_equal [missing_commit]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- David Lantos
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-07-16 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: git
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: term-ansicolor
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: minitest
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
76
|
+
description: Analyze branches and continuity in a git repository.
|
77
|
+
email:
|
78
|
+
- david.lantos@gmail.com
|
79
|
+
executables:
|
80
|
+
- gitra
|
81
|
+
extensions: []
|
82
|
+
|
83
|
+
extra_rdoc_files: []
|
84
|
+
|
85
|
+
files:
|
86
|
+
- Rakefile
|
87
|
+
- bin/gitra
|
88
|
+
- lib/gitra/cli.rb
|
89
|
+
- lib/gitra/git_patches.rb
|
90
|
+
- lib/gitra/parser.rb
|
91
|
+
- lib/gitra/tracker.rb
|
92
|
+
- lib/gitra/version.rb
|
93
|
+
- lib/gitra.rb
|
94
|
+
- spec/support/git_helper.rb
|
95
|
+
- spec/support/minitest_helper.rb
|
96
|
+
- spec/tracker_spec.rb
|
97
|
+
- README.md
|
98
|
+
homepage: http://github.com/sldblog/gitra
|
99
|
+
licenses: []
|
100
|
+
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
hash: 57
|
112
|
+
segments:
|
113
|
+
- 1
|
114
|
+
- 8
|
115
|
+
- 7
|
116
|
+
version: 1.8.7
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 3
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
version: "0"
|
126
|
+
requirements: []
|
127
|
+
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 1.8.25
|
130
|
+
signing_key:
|
131
|
+
specification_version: 3
|
132
|
+
summary: Git Repository Analyzer
|
133
|
+
test_files:
|
134
|
+
- spec/support/git_helper.rb
|
135
|
+
- spec/support/minitest_helper.rb
|
136
|
+
- spec/tracker_spec.rb
|