gitra 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|