jiragit 0.5.0

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.
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Jiragit
2
+ VERSION = "0.5.0"
3
+ end
@@ -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
@@ -0,0 +1,13 @@
1
+ RSpec.shared_context "CLI input/output" do
2
+
3
+ before do
4
+ $stdout = StringIO.new
5
+ $stderr = StringIO.new
6
+ end
7
+
8
+ after(:all) do
9
+ $stdout = STDOUT
10
+ $stderr = STDERR
11
+ end
12
+
13
+ end
@@ -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