jiragit 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +112 -0
- data/Rakefile +18 -0
- data/bin/jiragit +5 -0
- data/hooks/commit-msg +90 -0
- data/hooks/post-checkout +107 -0
- data/hooks/post-commit +51 -0
- data/hooks/prepare-commit-msg +144 -0
- data/lib/jiragit.rb +10 -0
- data/lib/jiragit/cli.rb +249 -0
- data/lib/jiragit/configuration.rb +51 -0
- data/lib/jiragit/git/branch.rb +29 -0
- data/lib/jiragit/git/commit_response.rb +23 -0
- data/lib/jiragit/git/repository.rb +222 -0
- data/lib/jiragit/jira_store.rb +48 -0
- data/lib/jiragit/logger.rb +32 -0
- data/lib/jiragit/tag.rb +30 -0
- data/lib/jiragit/vault.rb +113 -0
- data/lib/jiragit/version.rb +3 -0
- data/spec/branch_spec.rb +105 -0
- data/spec/cli_context.rb +13 -0
- data/spec/cli_spec.rb +149 -0
- data/spec/commit_message_spec.rb +82 -0
- data/spec/commit_spec.rb +81 -0
- data/spec/configuration_spec.rb +32 -0
- data/spec/git_editor.rb +40 -0
- data/spec/git_editor_spec.rb +59 -0
- data/spec/jira_store_spec.rb +57 -0
- data/spec/logger_spec.rb +37 -0
- data/spec/merge_spec.rb +85 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/tag_spec.rb +35 -0
- data/spec/test_repository_context.rb +17 -0
- data/spec/test_support.rb +81 -0
- data/spec/vault_spec.rb +100 -0
- metadata +124 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Jiragit
|
4
|
+
|
5
|
+
class JiraStore
|
6
|
+
|
7
|
+
def initialize(location = "#{Dir.home}/.jira_store")
|
8
|
+
self.location = location
|
9
|
+
end
|
10
|
+
|
11
|
+
def relate(params)
|
12
|
+
tags = extract_tags(params).compact
|
13
|
+
vault.relate(*tags)
|
14
|
+
vault.save
|
15
|
+
end
|
16
|
+
|
17
|
+
def relations(params)
|
18
|
+
jira, branch, commit = extract_tags(params).compact.first
|
19
|
+
tag = [jira, branch, commit].detect { |tag| !tag.nil? }
|
20
|
+
return Set.new unless tag
|
21
|
+
vault.load
|
22
|
+
vault.relations(tag)
|
23
|
+
end
|
24
|
+
|
25
|
+
def reload
|
26
|
+
vault.load
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :location
|
32
|
+
attr_reader :vault
|
33
|
+
|
34
|
+
def vault
|
35
|
+
@vault ||= Vault.new(location)
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_tags(params)
|
39
|
+
jira = branch = commit = nil
|
40
|
+
jira = Tag.new(jira: params[:jira]) if params.include?(:jira)
|
41
|
+
branch = Tag.new(branch: params[:branch]) if params.include?(:branch)
|
42
|
+
commit = Tag.new(commit: params[:commit]) if params.include?(:commit)
|
43
|
+
[jira, branch, commit]
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Jiragit
|
5
|
+
|
6
|
+
class Logger < ::Logger
|
7
|
+
|
8
|
+
attr_accessor :hook
|
9
|
+
|
10
|
+
def initialize(path = nil)
|
11
|
+
if path
|
12
|
+
@path = path
|
13
|
+
else
|
14
|
+
@path = ".git/jiragit/jiragit.log"
|
15
|
+
end
|
16
|
+
FileUtils.mkdir_p directory
|
17
|
+
super(@path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def format_message(severity, datetime, progname, msg)
|
21
|
+
"#{datetime} #{@hook}: #{msg}\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
def directory
|
25
|
+
dir = @path.gsub(/(^|\/)[^\/]*?$/,'')
|
26
|
+
dir = "." if dir == ''
|
27
|
+
dir
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/lib/jiragit/tag.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Jiragit
|
2
|
+
|
3
|
+
class Tag
|
4
|
+
|
5
|
+
attr_accessor :type, :label
|
6
|
+
|
7
|
+
def initialize(hashtag)
|
8
|
+
@type = hashtag.first.first.to_sym
|
9
|
+
@label = hashtag.first.last.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{type}: #{label}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash
|
21
|
+
to_s.hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def eql?(other)
|
25
|
+
other.to_s.hash == to_s.hash
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Jiragit
|
2
|
+
|
3
|
+
class Vault
|
4
|
+
|
5
|
+
def initialize(location)
|
6
|
+
self.location = location
|
7
|
+
load_or_create
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_or_create
|
11
|
+
self.vault = load || create
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
return false unless File.exists?(location)
|
16
|
+
self.vault = Marshal.load(File.read(location))
|
17
|
+
end
|
18
|
+
|
19
|
+
def create
|
20
|
+
self.vault = {}
|
21
|
+
save
|
22
|
+
vault
|
23
|
+
end
|
24
|
+
|
25
|
+
def save
|
26
|
+
File.open(location, 'w+') { |file| file.write Marshal.dump(vault) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(item)
|
30
|
+
vault[item] = vault[item]
|
31
|
+
end
|
32
|
+
|
33
|
+
def include?(item)
|
34
|
+
vault.keys.include?(item)
|
35
|
+
end
|
36
|
+
|
37
|
+
def relate(*items)
|
38
|
+
items.each do |item|
|
39
|
+
add(item) unless include?(item)
|
40
|
+
items.each do |related_item|
|
41
|
+
add(related_item) unless include?(related_item)
|
42
|
+
update_relation(item, related_item) unless item == related_item
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def relations(item)
|
48
|
+
vault[item] || Set.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def related?(*items)
|
52
|
+
items.inject(true) do |related, item|
|
53
|
+
related && items.inject(true) do |related, related_item|
|
54
|
+
related && related_items?(item, related_item)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def distally_related?(item, related_item)
|
60
|
+
!!relation_chain(item, related_item)
|
61
|
+
end
|
62
|
+
|
63
|
+
def relation_chain(item, related_item)
|
64
|
+
visited = Set.new
|
65
|
+
visited << item
|
66
|
+
to_visit = [ [ [item] , relations(item).to_a ] ]
|
67
|
+
loop do
|
68
|
+
break unless to_visit.any?
|
69
|
+
chain, items = to_visit.first
|
70
|
+
if items.any?
|
71
|
+
item = items.shift
|
72
|
+
if !visited.include?(item)
|
73
|
+
visited << item
|
74
|
+
relations = relations(item)
|
75
|
+
to_visit << [chain + [item], relations.to_a] if relations.any?
|
76
|
+
return chain + [item] if item == related_item
|
77
|
+
end
|
78
|
+
else
|
79
|
+
to_visit.shift
|
80
|
+
end
|
81
|
+
end
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
attr_accessor :vault
|
88
|
+
attr_accessor :location
|
89
|
+
|
90
|
+
def related_items?(item, related_item)
|
91
|
+
case
|
92
|
+
when item == related_item
|
93
|
+
true
|
94
|
+
when relations(item).include?(related_item)
|
95
|
+
true
|
96
|
+
else
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def update_relation(item, related_item)
|
102
|
+
relations = relations(item)
|
103
|
+
relations << related_item
|
104
|
+
vault[item] = relations if relations_empty?(item)
|
105
|
+
end
|
106
|
+
|
107
|
+
def relations_empty?(item)
|
108
|
+
self.include?(item) || vault[item].nil?
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
data/spec/branch_spec.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
require 'expect'
|
3
|
+
|
4
|
+
describe "Repository Branching Behaviors" do
|
5
|
+
|
6
|
+
include_context "Test Repository"
|
7
|
+
|
8
|
+
context "when checking out a new branch" do
|
9
|
+
|
10
|
+
it "asks for a jira number" do
|
11
|
+
checkout_a_new_branch('new_branch', 'PA-12345')
|
12
|
+
end
|
13
|
+
|
14
|
+
it "runs the post-checkout hook" do
|
15
|
+
checkout_a_new_branch('new_branch', 'PA-12345')
|
16
|
+
assert_log_contains(/post-checkout/)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "records a jira_branch relation" do
|
20
|
+
checkout_a_new_branch('new_branch', 'PA-12345')
|
21
|
+
assert_relation({jira: 'PA-12345'}, {branch: 'new_branch'})
|
22
|
+
end
|
23
|
+
|
24
|
+
it "records multiple jira_branch relations" do
|
25
|
+
checkout_a_new_branch('new_branch', 'PA-12345, PA-54321')
|
26
|
+
assert_relation({jira: 'PA-12345'}, {branch: 'new_branch'})
|
27
|
+
assert_relation({jira: 'PA-54321'}, {branch: 'new_branch'})
|
28
|
+
end
|
29
|
+
|
30
|
+
context "previous branch has associated jiras" do
|
31
|
+
|
32
|
+
before do
|
33
|
+
checkout_a_new_branch('parent_branch', 'PA-12345')
|
34
|
+
@repo.make_a_commit
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should provide and accept defaults" do
|
38
|
+
assert_relation({jira: 'PA-12345'}, {branch: 'parent_branch'})
|
39
|
+
checkout_a_new_branch_with_default('child_branch')
|
40
|
+
assert_relation({jira: 'PA-12345'}, {branch: 'child_branch'})
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when checking out an existing branch" do
|
48
|
+
|
49
|
+
context "without an associated jira" do
|
50
|
+
|
51
|
+
before do
|
52
|
+
checkout_a_new_branch('new_branch')
|
53
|
+
@repo.make_a_commit
|
54
|
+
checkout_an_existing_branch('master')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "asks for a jira number" do
|
58
|
+
checkout_an_existing_branch('new_branch', 'PA-12345')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "runs the post-checkout hook" do
|
62
|
+
checkout_an_existing_branch('new_branch', 'PA-12345')
|
63
|
+
assert_log_contains(/post-checkout/)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "has no prexisting jira_branch relation" do
|
67
|
+
assert_no_relation({jira: 'PA-12345'}, {branch: 'new_branch'})
|
68
|
+
end
|
69
|
+
|
70
|
+
it "records a jira_branch relation" do
|
71
|
+
checkout_an_existing_branch('new_branch', 'PA-12345')
|
72
|
+
assert_relation({jira: 'PA-12345'}, {branch: 'new_branch'})
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with an associated jira" do
|
78
|
+
|
79
|
+
before do
|
80
|
+
checkout_a_new_branch('new_branch', 'PA-12345')
|
81
|
+
@repo.make_a_commit
|
82
|
+
checkout_an_existing_branch('master')
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not ask for a jira number" do
|
86
|
+
@repo.checkout_branch('new_branch') do |output, input|
|
87
|
+
output.expect("What is the JIRA Number?", 5) do |message|
|
88
|
+
expect(message).to be nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "lists associated jira numbers" do
|
94
|
+
@repo.checkout_branch('new_branch') do |output, input|
|
95
|
+
output.expect("PA-12345", 5) do |message|
|
96
|
+
expect(message).to_not be nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
data/spec/cli_context.rb
ADDED
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require './spec/spec_helper'
|
2
|
+
|
3
|
+
module Jiragit
|
4
|
+
|
5
|
+
describe Cli do
|
6
|
+
|
7
|
+
include_context "CLI input/output"
|
8
|
+
|
9
|
+
it "should provide help with no command line options" do
|
10
|
+
Cli.new([])
|
11
|
+
expect($stdout.string).to match(/Commands/)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "in an empty repository" do
|
15
|
+
|
16
|
+
before do
|
17
|
+
@current_directory = Dir.pwd
|
18
|
+
@directory = "/tmp/test_directory"
|
19
|
+
Dir.mkdir(@directory) unless Dir.exists?(@directory)
|
20
|
+
Dir.chdir(@directory)
|
21
|
+
end
|
22
|
+
|
23
|
+
after do
|
24
|
+
Dir.chdir(@current_directory)
|
25
|
+
Dir.rmdir(@directory)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "provides an error message when calling commands that require a repository" do
|
29
|
+
Cli.new([:install])
|
30
|
+
expect($stderr.string).to match(/No valid Git repository/)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
context "in an empty repository" do
|
36
|
+
|
37
|
+
before do
|
38
|
+
@current_directory = Dir.pwd
|
39
|
+
repository = "test_repository"
|
40
|
+
Jiragit::Git::Repository.create(repository)
|
41
|
+
Dir.chdir repository
|
42
|
+
end
|
43
|
+
|
44
|
+
after do
|
45
|
+
Dir.chdir(@current_directory)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should not have hooks installed" do
|
49
|
+
cli = Cli.new([])
|
50
|
+
expect(Dir.exists?(".git")).to be true
|
51
|
+
expect(Dir.exists?(".git/hooks")).to be true
|
52
|
+
cli.send(:gem_hook_files).each do |hook|
|
53
|
+
expect(File.exists?(".git/hooks/#{hook}")).to be false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should install hooks" do
|
58
|
+
cli = Cli.new([:install])
|
59
|
+
cli.send(:gem_hook_files).each do |hook|
|
60
|
+
expect(File.exists?(".git/hooks/#{hook}")).to be true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should uninstalled hooks" do
|
65
|
+
cli = Cli.new([:install])
|
66
|
+
cli = Cli.new([:uninstall])
|
67
|
+
cli.send(:gem_hook_files).each do |hook|
|
68
|
+
expect(File.exists?(".git/hooks/#{hook}")).to be false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context "in a test repository" do
|
75
|
+
|
76
|
+
include_context "Test Repository"
|
77
|
+
|
78
|
+
context "in a new feature branch" do
|
79
|
+
|
80
|
+
before do
|
81
|
+
@repo.create('a_file', 'The quick fox jumped over the fence')
|
82
|
+
@repo.add('a_file')
|
83
|
+
@repo.commit('initial commit')
|
84
|
+
checkout_a_new_branch('feature_branch', 'PA-12345')
|
85
|
+
@repo.create('a_file', 'The brown fox jumped over the fence')
|
86
|
+
@repo.add('a_file')
|
87
|
+
@repo.commit('feature commit')
|
88
|
+
Dir.chdir @repository
|
89
|
+
end
|
90
|
+
|
91
|
+
after do
|
92
|
+
Dir.chdir '..'
|
93
|
+
end
|
94
|
+
|
95
|
+
it "browses to the current branch by default" do
|
96
|
+
expect_any_instance_of(Cli).to receive(:run) do |instance, arg|
|
97
|
+
expect(arg).to match(/open/)
|
98
|
+
expect(arg).to match(/feature_branch/)
|
99
|
+
end
|
100
|
+
cli = Cli.new([:browse])
|
101
|
+
end
|
102
|
+
|
103
|
+
it "browses to the current jira" do
|
104
|
+
expect_any_instance_of(Cli).to receive(:run) do |instance, arg|
|
105
|
+
expect(arg).to match(/open/)
|
106
|
+
expect(arg).to match(/PA-12345/)
|
107
|
+
end
|
108
|
+
cli = Cli.new([:browse, 'jira'])
|
109
|
+
end
|
110
|
+
|
111
|
+
it "browses to the current branch" do
|
112
|
+
expect_any_instance_of(Cli).to receive(:run) do |instance, arg|
|
113
|
+
expect(arg).to match(/open/)
|
114
|
+
expect(arg).to match(/feature_branch/)
|
115
|
+
end
|
116
|
+
cli = Cli.new([:browse, 'branch'])
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
context "in a new feature branch without a jira" do
|
122
|
+
|
123
|
+
before do
|
124
|
+
@repo.create('a_file', 'The quick fox jumped over the fence')
|
125
|
+
@repo.add('a_file')
|
126
|
+
@repo.commit('initial commit')
|
127
|
+
checkout_a_new_branch_with_default('feature_branch')
|
128
|
+
@repo.create('a_file', 'The brown fox jumped over the fence')
|
129
|
+
@repo.add('a_file')
|
130
|
+
@repo.commit('feature commit')
|
131
|
+
Dir.chdir @repository
|
132
|
+
end
|
133
|
+
|
134
|
+
after do
|
135
|
+
Dir.chdir '..'
|
136
|
+
end
|
137
|
+
|
138
|
+
it "does not browse to an unspecified jira" do
|
139
|
+
expect_any_instance_of(Cli).to_not receive(:run)
|
140
|
+
cli = Cli.new([:browse, 'jira'])
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|