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.
- checksums.yaml +7 -0
- data/bin/git-jira +199 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -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
|
data/bin/git-jira
ADDED
@@ -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:
|