gitra 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => [:test]
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'spec/support'
7
+ t.test_files = FileList['spec/**/*_spec.rb']
8
+ t.verbose = true
9
+ end
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
@@ -0,0 +1,4 @@
1
+ require 'gitra/tracker'
2
+ require 'gitra/cli'
3
+ require 'gitra/git_patches'
4
+ require 'gitra/parser'
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
@@ -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,3 @@
1
+ module Gitra
2
+ VERSION = '0.0.1'
3
+ 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,3 @@
1
+ require 'minitest/autorun'
2
+ require 'gitra'
3
+ require 'git_helper'
@@ -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