git-jira 0.1.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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/bin/git-jira +199 -0
  3. metadata +46 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6aca75559bbe16627be69d2d9179d46bca592050
4
+ data.tar.gz: 2e7ffedc35bb5820718deb03e85996763262a185
5
+ SHA512:
6
+ metadata.gz: 3828ada68d80f931bf27650a3e1f559f6d95904c400299f49c56f4aba294113ff1645cbfc142535fbf35a9493602a7015bdb76f2d098f46533ac0d4047e799fc
7
+ data.tar.gz: 9fba0f88c01611c966ebb1b2a5ef11dcabc1faa9aa89458295156624d8131880ec1a4d79a62c88a192e94006a287f44eab55909ae525108fca5fa78389e5e84e
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env ruby
2
+ require 'yaml'
3
+ require 'tempfile'
4
+ class StoryNotFound < StandardError; end
5
+
6
+ begin
7
+ config = YAML.load_file(File.expand_path('~/.jira.d/config.yml'))
8
+ config['user'] ||= ENV['USER']
9
+ rescue
10
+ puts 'Could not load config file ~/.jira.d/config.yml'
11
+ puts 'Please make sure it is as described in these instructions:'
12
+ puts ' https://github.com/Netflix-Skunkworks/go-jira'
13
+ end
14
+
15
+ command = ARGV.shift
16
+
17
+ def commit_footer(story)
18
+ tag = case story['issuetype']
19
+ when 'Bug'
20
+ "[Fixes #{story['issue']}]"
21
+ else
22
+ "[Finishes #{story['issue']}]"
23
+ end
24
+ tag += ' -'
25
+ tag_indent = tag.length + 1
26
+ tag_line_length = tag.length
27
+
28
+ story['summary'].split(' ').each do |summary_word|
29
+ if (tag_line_length + summary_word.length) >= 72
30
+ tag += "\n#{' ' * tag_indent}#{summary_word}"
31
+ tag_line_length = tag_indent + summary_word.length + 1
32
+ else
33
+ tag += " #{summary_word}"
34
+ tag_line_length += summary_word.length + 1
35
+ end
36
+ end
37
+
38
+ tag
39
+ end
40
+
41
+ def load_story(story)
42
+ body = `jira view #{story} 2>&1`
43
+
44
+ if body =~ /404 Not Found/
45
+ raise StoryNotFound, "Story #{story} not found"
46
+ end
47
+
48
+ begin
49
+ YAML.load(body)
50
+ rescue Psych::SyntaxError
51
+ # go-jira's YAML is not quite equal to ruby's YAML - if the body of the
52
+ # description or comments fields has a " foo:" line, then ruby won't be
53
+ # able to parse it
54
+ # HACK: replace those fields with a double-quoted value.
55
+ body = body.each_line.map do |line|
56
+ # if the line contains colons or brackets or quotes, escape them
57
+ match = line.match(/^([a-z]+): .*(: |[<>'"\[\]])/)
58
+ if match
59
+ "#{match[1]}: #{line[(match[1].length + 2)..-1].strip.inspect}"
60
+ else
61
+ line
62
+ end
63
+ end.join("\n")
64
+
65
+ return YAML.load(body)
66
+ end
67
+ end
68
+
69
+ def story_branches
70
+ `git branch`
71
+ .strip.each_line.map(&:split).map(&:last)
72
+ .delete_if { |b| !matches_story?(b) }
73
+ .map(&:downcase)
74
+ end
75
+
76
+ def matches_story?(string)
77
+ string =~ /[a-z]{2,3}-[0-9]+/i
78
+ end
79
+
80
+ def current_branch
81
+ `git rev-parse --abbrev-ref HEAD`.strip
82
+ end
83
+
84
+ def confirm?
85
+ $stdout.write 'Go? [y/N]: '
86
+ exit unless $stdin.gets.chomp =~ /y/i
87
+ $stdout.puts
88
+ end
89
+
90
+ def pick_story_of_mine
91
+ my_stories = `jira ls -q 'assignee = currentUser() AND (status = "To Do" OR status = Backlog OR status = "In Progress")'`.strip.split("\n")
92
+ branches = story_branches
93
+ my_stories = my_stories
94
+ .map { |s| s.split(':', 2) }
95
+ .find_all { |s| !branches.include?(s[0].downcase) }
96
+ puts "which story to start?"
97
+ my_stories.each_with_index do |story, i|
98
+ puts "#{i}: #{story.join(' ')}"
99
+ end
100
+ my_stories[gets.strip.to_i][0]
101
+ end
102
+
103
+ def time_ago_in_words(distance_in_seconds)
104
+ distance_in_minutes = (distance_in_seconds / 60.0).round
105
+ distance_in_hours = (distance_in_minutes / 60.0).round
106
+ distance_in_days = (distance_in_hours / 24.0).floor
107
+
108
+ case distance_in_seconds
109
+ when 0..60
110
+ 'less than a minute ago'
111
+ when 60..2700
112
+ "about #{distance_in_minutes} minutes ago"
113
+ when 2700..86400
114
+ "about #{(distance_in_minutes / 60).round} hours ago"
115
+ else
116
+ "#{distance_in_days} days ago"
117
+ end
118
+ end
119
+
120
+ case command
121
+ when 'start'
122
+ story_id = ARGV.shift || pick_story_of_mine
123
+ story = load_story(story_id)
124
+ puts "Starting story #{story_id}:"
125
+ puts " summary: #{story['summary']}"
126
+ puts " reporter: #{story['reporter']}"
127
+ puts " created: #{time_ago_in_words(Time.now - story['created'])}"
128
+ puts
129
+
130
+ if story_branches.include?(story_id.downcase)
131
+ `git checkout #{story_id}`
132
+ else
133
+ `git checkout -b #{story_id} origin/master` unless current_branch == story_id
134
+ end
135
+
136
+ `jira start #{story_id}` unless story['status'] == 'In Progress'
137
+ `jira take #{story_id}` unless story['assignee'] == config['user']
138
+ when 'cleanup'
139
+ branches_to_remove = story_branches.find_all do |b|
140
+ begin
141
+ load_story(b)['status'] == 'Closed'
142
+ rescue StoryNotFound
143
+ # perhaps dangerous because of other branches in the format abc-123, but
144
+ # if a story has been deleted on JIRA then it won't be shown, and since we
145
+ # ask the user to confirm all removals, let's just assume that if the
146
+ # story doesn't exist on JIRA we should delete the branch
147
+ true
148
+ end
149
+ end
150
+
151
+ if branches_to_remove.none?
152
+ puts 'No branches to remove!'
153
+ exit
154
+ end
155
+
156
+ puts "Will remove branches: #{branches_to_remove.join(', ')}"
157
+ confirm?
158
+
159
+ `git checkout master` if branches_to_remove.include?(current_branch)
160
+ `git branch -D #{branches_to_remove.join(' ')}`
161
+ when 'ci'
162
+ unless matches_story?(current_branch)
163
+ puts "You don't appear to be on a story branch!"
164
+ exit 1
165
+ end
166
+
167
+ story = load_story(current_branch)
168
+
169
+ if story['description']
170
+ Tempfile.open('git-j-commit-msg') do |f|
171
+ f.puts story['summary']
172
+ f.puts
173
+ f.puts story['description'].strip.gsub("\n\n", "\n") # TODO: <- normalize helper
174
+ f.puts
175
+ f.puts commit_footer(story)
176
+ f.close
177
+
178
+ system("git commit -v -t #{f.path}")
179
+ end
180
+ else
181
+ puts 'Failed to load description for story!'
182
+ end
183
+ when 'ls'
184
+ branches = `git branch`.strip.each_line.map(&:split).map(&:last)
185
+ branches.delete_if { |b| !matches_story?(b) }
186
+ branches.map do |branch|
187
+ puts "#{branch.ljust(8)} - #{load_story(branch)['summary']}"
188
+ end
189
+ when 'show'
190
+ branch = current_branch
191
+ if story_branches.include?(branch)
192
+ puts load_story(branch).inspect
193
+ else
194
+ echo 'Could not parse story branch!'
195
+ end
196
+ else
197
+ puts 'unknown command!'
198
+ exit 1
199
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git-jira
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Dooner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: tom.dooner@brigade.com
15
+ executables:
16
+ - git-jira
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/git-jira
21
+ homepage: https://github.com/tdooner/git-jira
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib/git-jira.rb
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.4.5.1
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Workflow tools for integrating git and jira
45
+ test_files: []
46
+ has_rdoc: