jiragit 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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