flurin-ticgit 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/ti +10 -0
- data/bin/ticgitweb +517 -0
- data/lib/ticgit/base.rb +308 -0
- data/lib/ticgit/cli.rb +338 -0
- data/lib/ticgit/comment.rb +17 -0
- data/lib/ticgit/ticket.rb +224 -0
- data/lib/ticgit.rb +28 -0
- metadata +88 -0
data/lib/ticgit/base.rb
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module TicGit
|
6
|
+
class NoRepoFound < StandardError;end
|
7
|
+
class Base
|
8
|
+
|
9
|
+
attr_reader :git, :logger
|
10
|
+
attr_reader :tic_working, :tic_index
|
11
|
+
attr_reader :tickets, :last_tickets, :current_ticket # saved in state
|
12
|
+
attr_reader :config
|
13
|
+
attr_reader :state, :config_file
|
14
|
+
|
15
|
+
def initialize(git_dir, opts = {})
|
16
|
+
@git = Git.open(find_repo(git_dir))
|
17
|
+
@logger = opts[:logger] || Logger.new(STDOUT)
|
18
|
+
|
19
|
+
proj = Ticket.clean_string(@git.dir.path)
|
20
|
+
|
21
|
+
@tic_dir = opts[:tic_dir] || '~/.ticgit'
|
22
|
+
@tic_working = opts[:working_directory] || File.expand_path(File.join(@tic_dir, proj, 'working'))
|
23
|
+
@tic_index = opts[:index_file] || File.expand_path(File.join(@tic_dir, proj, 'index'))
|
24
|
+
|
25
|
+
# load config file
|
26
|
+
@config_file = File.expand_path(File.join(@tic_dir, proj, 'config.yml'))
|
27
|
+
if File.exists?(config_file)
|
28
|
+
@config = YAML.load(File.read(config_file))
|
29
|
+
else
|
30
|
+
@config = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
@state = File.expand_path(File.join(@tic_dir, proj, 'state'))
|
34
|
+
|
35
|
+
if File.exists?(@state)
|
36
|
+
load_state
|
37
|
+
else
|
38
|
+
reset_ticgit
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_repo(dir)
|
43
|
+
full = File.expand_path(dir)
|
44
|
+
ENV["GIT_WORKING_DIR"] || loop do
|
45
|
+
return full if File.directory?(File.join(full, ".git"))
|
46
|
+
raise NoRepoFound if full == full=File.dirname(full)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def save_state
|
51
|
+
# marshal dump the internals
|
52
|
+
File.open(@state, 'w') { |f| Marshal.dump([@tickets, @last_tickets, @current_ticket], f) } rescue nil
|
53
|
+
# save config file
|
54
|
+
File.open(@config_file, 'w') { |f| f.write(config.to_yaml) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_state
|
58
|
+
# read in the internals
|
59
|
+
if(File.exists?(@state))
|
60
|
+
@tickets, @last_tickets, @current_ticket = File.open(@state) { |f| Marshal.load(f) } rescue nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# returns new Ticket
|
65
|
+
def ticket_new(title, options = {})
|
66
|
+
t = TicGit::Ticket.create(self, title, options)
|
67
|
+
reset_ticgit
|
68
|
+
TicGit::Ticket.open(self, t.ticket_name, @tickets[t.ticket_name])
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset_ticgit
|
72
|
+
load_tickets
|
73
|
+
save_state
|
74
|
+
end
|
75
|
+
|
76
|
+
# returns new Ticket
|
77
|
+
def ticket_comment(comment, ticket_id = nil)
|
78
|
+
if t = ticket_revparse(ticket_id)
|
79
|
+
ticket = TicGit::Ticket.open(self, t, @tickets[t])
|
80
|
+
ticket.add_comment(comment)
|
81
|
+
reset_ticgit
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# returns array of Tickets
|
86
|
+
def ticket_list(options = {})
|
87
|
+
ts = []
|
88
|
+
@last_tickets = []
|
89
|
+
@config['list_options'] ||= {}
|
90
|
+
|
91
|
+
@tickets.to_a.each do |name, t|
|
92
|
+
ts << TicGit::Ticket.open(self, name, t)
|
93
|
+
end
|
94
|
+
|
95
|
+
if name = options[:saved]
|
96
|
+
if c = config['list_options'][name]
|
97
|
+
options = c.merge(options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if options[:list]
|
102
|
+
# TODO : this is a hack and i need to fix it
|
103
|
+
config['list_options'].each do |name, opts|
|
104
|
+
puts name + "\t" + opts.inspect
|
105
|
+
end
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
# SORTING
|
110
|
+
if field = options[:order]
|
111
|
+
field, type = field.split('.')
|
112
|
+
field = "opened" if field == "date" || field.nil?
|
113
|
+
ts = sort_list_by_keys(ts,[field,:ticket_id])
|
114
|
+
ts = ts.reverse if type == 'desc'
|
115
|
+
else
|
116
|
+
# default list
|
117
|
+
ts = ts.sort { |a, b| a.opened <=> b.opened }
|
118
|
+
end
|
119
|
+
|
120
|
+
if options.size == 0
|
121
|
+
# default list
|
122
|
+
options[:state] = 'open'
|
123
|
+
end
|
124
|
+
|
125
|
+
# :tag, :state, :assigned
|
126
|
+
if t = options[:tag]
|
127
|
+
ts = ts.select { |tag| tag.tags.include?(t) }
|
128
|
+
end
|
129
|
+
if s = options[:state]
|
130
|
+
ts = ts.select { |tag| tag.state =~ /#{s}/ }
|
131
|
+
end
|
132
|
+
if a = options[:assigned]
|
133
|
+
ts = ts.select { |tag| tag.assigned =~ /#{a}/ }
|
134
|
+
end
|
135
|
+
|
136
|
+
if save = options[:save]
|
137
|
+
options.delete(:save)
|
138
|
+
@config['list_options'][save] = options
|
139
|
+
end
|
140
|
+
|
141
|
+
@last_tickets = ts.map { |t| t.ticket_name }
|
142
|
+
# :save
|
143
|
+
|
144
|
+
save_state
|
145
|
+
ts
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def sort_list_by_keys(list,keys)
|
150
|
+
return list if keys.find{|k| !list.first.respond_to?(k) }
|
151
|
+
list.sort do |a,b|
|
152
|
+
value,i = 0,0
|
153
|
+
while (keys.size > i && value == 0) do
|
154
|
+
value = a.send(keys[i]) <=> b.send(keys[i])
|
155
|
+
i += 1
|
156
|
+
end
|
157
|
+
value
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# returns single Ticket
|
162
|
+
def ticket_show(ticket_id = nil)
|
163
|
+
# ticket_id can be index of last_tickets, partial sha or nil => last ticket
|
164
|
+
if t = ticket_revparse(ticket_id)
|
165
|
+
return TicGit::Ticket.open(self, t, @tickets[t])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# returns recent ticgit activity
|
170
|
+
# uses the git logs for this
|
171
|
+
def ticket_recent(ticket_id = nil)
|
172
|
+
if ticket_id
|
173
|
+
t = ticket_revparse(ticket_id)
|
174
|
+
return git.log.object('ticgit').path(t)
|
175
|
+
else
|
176
|
+
return git.log.object('ticgit')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def ticket_revparse(ticket_id)
|
181
|
+
if ticket_id
|
182
|
+
if /^[0-9]*$/ =~ ticket_id
|
183
|
+
if t = @last_tickets[ticket_id.to_i - 1]
|
184
|
+
return t
|
185
|
+
end
|
186
|
+
else
|
187
|
+
# partial or full sha
|
188
|
+
if ch = @tickets.select { |name, t| t['files'].assoc('TICKET_ID')[1] =~ /^#{ticket_id}/ }
|
189
|
+
return ch.first[0]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
elsif(@current_ticket)
|
193
|
+
return @current_ticket
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def ticket_tag(tag, ticket_id = nil, options = {})
|
198
|
+
if t = ticket_revparse(ticket_id)
|
199
|
+
ticket = TicGit::Ticket.open(self, t, @tickets[t])
|
200
|
+
if options[:remove]
|
201
|
+
ticket.remove_tag(tag)
|
202
|
+
else
|
203
|
+
ticket.add_tag(tag)
|
204
|
+
end
|
205
|
+
reset_ticgit
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def ticket_change(new_state, ticket_id = nil)
|
210
|
+
if t = ticket_revparse(ticket_id)
|
211
|
+
if tic_states.include?(new_state)
|
212
|
+
ticket = TicGit::Ticket.open(self, t, @tickets[t])
|
213
|
+
ticket.change_state(new_state)
|
214
|
+
reset_ticgit
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def ticket_assign(new_assigned = nil, ticket_id = nil)
|
220
|
+
if t = ticket_revparse(ticket_id)
|
221
|
+
ticket = TicGit::Ticket.open(self, t, @tickets[t])
|
222
|
+
ticket.change_assigned(new_assigned)
|
223
|
+
reset_ticgit
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def ticket_checkout(ticket_id)
|
228
|
+
if t = ticket_revparse(ticket_id)
|
229
|
+
ticket = TicGit::Ticket.open(self, t, @tickets[t])
|
230
|
+
@current_ticket = ticket.ticket_name
|
231
|
+
save_state
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def comment_add(ticket_id, comment, options = {})
|
236
|
+
end
|
237
|
+
|
238
|
+
def comment_list(ticket_id)
|
239
|
+
end
|
240
|
+
|
241
|
+
def tic_states
|
242
|
+
['open', 'resolved', 'invalid', 'hold']
|
243
|
+
end
|
244
|
+
|
245
|
+
def load_tickets
|
246
|
+
@tickets = {}
|
247
|
+
|
248
|
+
bs = git.lib.branches_all.map { |b| b[0] }
|
249
|
+
init_ticgit_branch(bs.include?('ticgit')) if !(bs.include?('ticgit') && File.directory?(@tic_working))
|
250
|
+
|
251
|
+
tree = git.lib.full_tree('ticgit')
|
252
|
+
tree.each do |t|
|
253
|
+
data, file = t.split("\t")
|
254
|
+
mode, type, sha = data.split(" ")
|
255
|
+
tic = file.split('/')
|
256
|
+
if tic.size == 2 # directory depth
|
257
|
+
ticket, info = tic
|
258
|
+
@tickets[ticket] ||= { 'files' => [] }
|
259
|
+
@tickets[ticket]['files'] << [info, sha]
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def init_ticgit_branch(ticgit_branch = false)
|
265
|
+
@logger.info 'creating ticgit repo branch'
|
266
|
+
|
267
|
+
in_branch(ticgit_branch) do
|
268
|
+
new_file('.hold', 'hold')
|
269
|
+
if !ticgit_branch
|
270
|
+
git.add
|
271
|
+
git.commit('creating the ticgit branch')
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# temporarlily switches to ticgit branch for tic work
|
277
|
+
def in_branch(branch_exists = true)
|
278
|
+
needs_checkout = false
|
279
|
+
if !File.directory?(@tic_working)
|
280
|
+
FileUtils.mkdir_p(@tic_working)
|
281
|
+
needs_checkout = true
|
282
|
+
end
|
283
|
+
if !File.exists?('.hold')
|
284
|
+
needs_checkout = true
|
285
|
+
end
|
286
|
+
|
287
|
+
old_current = git.lib.branch_current
|
288
|
+
begin
|
289
|
+
git.lib.change_head_branch('ticgit')
|
290
|
+
git.with_index(@tic_index) do
|
291
|
+
git.with_working(@tic_working) do |wd|
|
292
|
+
git.lib.checkout('ticgit') if needs_checkout && branch_exists
|
293
|
+
yield wd
|
294
|
+
end
|
295
|
+
end
|
296
|
+
ensure
|
297
|
+
git.lib.change_head_branch(old_current)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def new_file(name, contents)
|
302
|
+
File.open(name, 'w') do |f|
|
303
|
+
f.puts contents
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
end
|
data/lib/ticgit/cli.rb
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
require 'ticgit'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
module TicGit
|
5
|
+
class CLI < Thor
|
6
|
+
attr_reader :tic
|
7
|
+
|
8
|
+
def initialize(opts = {}, *args)
|
9
|
+
@tic = TicGit.open('.', :keep_state => true)
|
10
|
+
$stdout.sync = true # so that Net::SSH prompts show up
|
11
|
+
rescue NoRepoFound
|
12
|
+
puts "No repo found"
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
# tic milestone
|
17
|
+
# tic milestone migration1 (list tickets)
|
18
|
+
# tic milestone -n migration1 3/4/08 (new milestone)
|
19
|
+
# tic milestone -a {1} (add ticket to milestone)
|
20
|
+
# tic milestone -d migration1 (delete)
|
21
|
+
desc "milestone [<name>]", %(Add a new milestone to this project)
|
22
|
+
method_options :new => :optional, :add => :optional, :delete => :optional
|
23
|
+
def milestone(name = nil)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "recent [<ticket-id>]", %(Recent ticgit activity)
|
28
|
+
def recent(ticket_id = nil)
|
29
|
+
tic.ticket_recent(ticket_id).each do |commit|
|
30
|
+
puts commit.sha[0, 7] + " " + commit.date.strftime("%m/%d %H:%M") + "\t" + commit.message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "tag [<ticket-id>] [<tag1,tag2...>]", %(Add or remove ticket tags)
|
35
|
+
method_options %w(--remove -d) => :boolean
|
36
|
+
def tag(*args)
|
37
|
+
puts 'remove' if options[:remove]
|
38
|
+
|
39
|
+
tid = args.size > 1 && args.shift
|
40
|
+
tags = args.first
|
41
|
+
|
42
|
+
if tags
|
43
|
+
tic.ticket_tag(tags, tid, options)
|
44
|
+
else
|
45
|
+
puts 'You need to at least specify one tag to add'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "comment [<ticket-id>]", %(Comment on a ticket)
|
50
|
+
method_options :message => :optional, :file => :optional
|
51
|
+
def comment(ticket_id = nil)
|
52
|
+
if options[:file]
|
53
|
+
raise ArgumentError, "Only 1 of -f/--file and -m/--message can be specified" if options[:message]
|
54
|
+
file = options[:file]
|
55
|
+
raise ArgumentError, "File #{file} doesn't exist" unless File.file?(file)
|
56
|
+
raise ArgumentError, "File #{file} must be <= 2048 bytes" unless File.size(file) <= 2048
|
57
|
+
tic.ticket_comment(File.read(file), ticket_id)
|
58
|
+
elsif m = options[:message]
|
59
|
+
tic.ticket_comment(m, ticket_id)
|
60
|
+
else
|
61
|
+
message, meta = get_editor_message
|
62
|
+
if message
|
63
|
+
tic.ticket_comment(message.join(''), ticket_id)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "checkout <ticket-id>", %(Checkout a ticket)
|
69
|
+
def checkout(ticket_id)
|
70
|
+
tic.ticket_checkout(ticket_id)
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "state [<ticket-id>] <state>", %(Change state of a ticket)
|
74
|
+
def state(id_or_state, state = nil)
|
75
|
+
if state.nil?
|
76
|
+
state = id_or_state
|
77
|
+
ticket_id = nil
|
78
|
+
else
|
79
|
+
ticket_id = id_or_state
|
80
|
+
end
|
81
|
+
|
82
|
+
if valid_state(state)
|
83
|
+
tic.ticket_change(state, ticket_id)
|
84
|
+
else
|
85
|
+
puts 'Invalid State - please choose from : ' + tic.tic_states.join(", ")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Assigns a ticket to someone
|
90
|
+
#
|
91
|
+
# Usage:
|
92
|
+
# ti assign (assign checked out ticket to current user)
|
93
|
+
# ti assign {1} (assign ticket to current user)
|
94
|
+
# ti assign -c {1} (assign ticket to current user and checkout the ticket)
|
95
|
+
# ti assign -u {name} (assign ticket to specified user)
|
96
|
+
desc "assign [<ticket-id>]", %(Assign ticket to user)
|
97
|
+
method_options :user => :optional, :checkout => :optional
|
98
|
+
def assign(ticket_id = nil)
|
99
|
+
tic.ticket_checkout(options[:checkout]) if options[:checkout]
|
100
|
+
tic.ticket_assign(options[:user], ticket_id)
|
101
|
+
end
|
102
|
+
|
103
|
+
# "-o ORDER", "--order ORDER", "Field to order by - one of : assigned,state,date"
|
104
|
+
# "-t TAG", "--tag TAG", "List only tickets with specific tag"
|
105
|
+
# "-s STATE", "--state STATE", "List only tickets in a specific state"
|
106
|
+
# "-a ASSIGNED", "--assigned ASSIGNED", "List only tickets assigned to someone"
|
107
|
+
# "-S SAVENAME", "--saveas SAVENAME", "Save this list as a saved name"
|
108
|
+
# "-l", "--list", "Show the saved queries"
|
109
|
+
desc "list [<saved-query>]", %(Show existing tickets)
|
110
|
+
method_options :order => :optional, :tag => :optional, :state => :optional,
|
111
|
+
:assigned => :optional, :list => :optional, %w(--save-as -S) => :optional
|
112
|
+
|
113
|
+
def list(saved_query = nil)
|
114
|
+
opts = options.dup
|
115
|
+
opts[:saved] = saved_query if saved_query
|
116
|
+
|
117
|
+
if tickets = tic.ticket_list(opts)
|
118
|
+
output_ticket_list(tickets)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
## SHOW TICKETS ##
|
123
|
+
|
124
|
+
desc 'show <ticket-id>', %(Show a single ticket)
|
125
|
+
def show(ticket_id = nil)
|
126
|
+
if t = @tic.ticket_show(ticket_id)
|
127
|
+
ticket_show(t)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
desc 'new', %(Create a new ticket)
|
132
|
+
method_options :title => :optional
|
133
|
+
def new
|
134
|
+
if title = options[:title]
|
135
|
+
ticket_show(@tic.ticket_new(title, options))
|
136
|
+
else
|
137
|
+
# interactive
|
138
|
+
message_file = Tempfile.new('ticgit_message').path
|
139
|
+
File.open(message_file, 'w') do |f|
|
140
|
+
f.puts "\n# ---"
|
141
|
+
f.puts "tags:"
|
142
|
+
f.puts "# The first line will be the title of the ticket,"
|
143
|
+
f.puts "# the rest will be the description. If you would like to add initial tags,"
|
144
|
+
f.puts "# put them on the 'tags:' line, comma delimited"
|
145
|
+
end
|
146
|
+
|
147
|
+
message, meta = get_editor_message(message_file)
|
148
|
+
if message
|
149
|
+
title, description, tags = parse_editor_message(message,meta)
|
150
|
+
if title and !title.empty?
|
151
|
+
ticket_show(@tic.ticket_new(title, :description => description, :tags => tags))
|
152
|
+
else
|
153
|
+
puts "You need to at least enter a title"
|
154
|
+
end
|
155
|
+
else
|
156
|
+
puts "It seems you wrote nothing"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
protected
|
162
|
+
|
163
|
+
def valid_state(state)
|
164
|
+
tic.tic_states.include?(state)
|
165
|
+
end
|
166
|
+
|
167
|
+
def ticket_show(t)
|
168
|
+
days_ago = ((Time.now - t.opened) / (60 * 60 * 24)).round.to_s
|
169
|
+
puts
|
170
|
+
puts just('Title', 10) + ': ' + t.title
|
171
|
+
puts just('TicId', 10) + ': ' + t.ticket_id
|
172
|
+
if t.description
|
173
|
+
desc = t.description.split("\n").map{|l| " "*12 + l.strip}.join("\n").lstrip
|
174
|
+
puts just('Descr.',10) + ': ' + desc
|
175
|
+
end
|
176
|
+
puts
|
177
|
+
puts just('Assigned', 10) + ': ' + t.assigned.to_s
|
178
|
+
puts just('Opened', 10) + ': ' + t.opened.to_s + ' (' + days_ago + ' days)'
|
179
|
+
puts just('State', 10) + ': ' + t.state.upcase
|
180
|
+
if !t.tags.empty?
|
181
|
+
puts just('Tags', 10) + ': ' + t.tags.join(', ')
|
182
|
+
end
|
183
|
+
puts
|
184
|
+
if !t.comments.empty?
|
185
|
+
puts 'Comments (' + t.comments.size.to_s + '):'
|
186
|
+
t.comments.reverse.each do |c|
|
187
|
+
puts ' * Added ' + c.added.strftime("%m/%d %H:%M") + ' by ' + c.user
|
188
|
+
|
189
|
+
wrapped = c.comment.split("\n").collect do |line|
|
190
|
+
line.length > 80 ? line.gsub(/(.{1,80})(\s+|$)/, "\\1\n").strip : line
|
191
|
+
end * "\n"
|
192
|
+
|
193
|
+
wrapped = wrapped.split("\n").map { |line| "\t" + line }
|
194
|
+
if wrapped.size > 6
|
195
|
+
puts wrapped[0, 6].join("\n")
|
196
|
+
puts "\t** more... **"
|
197
|
+
else
|
198
|
+
puts wrapped.join("\n")
|
199
|
+
end
|
200
|
+
puts
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
## NEW TICKETS ##
|
207
|
+
|
208
|
+
def parse_ticket_new
|
209
|
+
@options = {}
|
210
|
+
OptionParser.new do |opts|
|
211
|
+
opts.banner = "Usage: ti new [options]"
|
212
|
+
opts.on("-t TITLE", "--title TITLE", "Title to use for the name of the new ticket") do |v|
|
213
|
+
@options[:title] = v
|
214
|
+
end
|
215
|
+
end.parse!
|
216
|
+
end
|
217
|
+
|
218
|
+
def handle_ticket_new
|
219
|
+
parse_ticket_new
|
220
|
+
if(t = options[:title])
|
221
|
+
ticket_show(@tic.ticket_new(t, options))
|
222
|
+
else
|
223
|
+
# interactive
|
224
|
+
message_file = Tempfile.new('ticgit_message').path
|
225
|
+
File.open(message_file, 'w') do |f|
|
226
|
+
f.puts "\n# ---"
|
227
|
+
f.puts "tags:"
|
228
|
+
f.puts "# The first line will be the title of the ticket, "
|
229
|
+
f.puts "# the rest will be the description if you would like to add initial tags,"
|
230
|
+
f.puts "# put them on the 'tags:' line, comma delimited"
|
231
|
+
end
|
232
|
+
message,meta = get_editor_message(message_file)
|
233
|
+
if message
|
234
|
+
title,description,tags = parse_editor_message(message,meta)
|
235
|
+
if title != ""
|
236
|
+
ticket_show(@tic.ticket_new(title, :description => description, :tags => tags))
|
237
|
+
else
|
238
|
+
puts "You need to at least enter a title"
|
239
|
+
end
|
240
|
+
else
|
241
|
+
puts "It seems you wrote nothing"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def parse_editor_message(message,meta)
|
247
|
+
message.strip!
|
248
|
+
puts message.inspect
|
249
|
+
title,description = message.split(/\r?\n/,2).map{|t| t.strip }
|
250
|
+
tags = []
|
251
|
+
# Strip comments from meta block
|
252
|
+
meta.gsub!(/^\s*#.*$/,"")
|
253
|
+
meta.split("\n").each do |line|
|
254
|
+
if line[0, 5] == 'tags:'
|
255
|
+
tags = line.gsub('tags:', '')
|
256
|
+
tags = tags.split(',').map { |t| t.strip }
|
257
|
+
end
|
258
|
+
end
|
259
|
+
[title,description,tags]
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
def get_editor_message(message_file = nil)
|
264
|
+
message_file = Tempfile.new('ticgit_message').path if !message_file
|
265
|
+
|
266
|
+
editor = ENV["EDITOR"] || 'vim'
|
267
|
+
system("#{editor} #{message_file}");
|
268
|
+
message = File.read(message_file)
|
269
|
+
|
270
|
+
# We must have some content before the # --- line
|
271
|
+
message, meta = message.split("# ---")
|
272
|
+
if message =~ /[^\s]+/m
|
273
|
+
return [message,meta]
|
274
|
+
else
|
275
|
+
return [false,false]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def parse_editor_message(message, meta)
|
280
|
+
message.strip!
|
281
|
+
puts message.inspect
|
282
|
+
title, description = message.split(/\r?\n/,2).map { |t| t.strip }
|
283
|
+
tags = []
|
284
|
+
# Strip comments from meta block
|
285
|
+
meta.gsub!(/^\s*#.*$/, "")
|
286
|
+
meta.split("\n").each do |line|
|
287
|
+
if line[0, 5] == 'tags:'
|
288
|
+
tags = line.gsub('tags:', '')
|
289
|
+
tags = tags.split(',').map { |t| t.strip }
|
290
|
+
end
|
291
|
+
end
|
292
|
+
[title, description, tags]
|
293
|
+
end
|
294
|
+
|
295
|
+
def just(value, size, side = 'l')
|
296
|
+
value = value.to_s
|
297
|
+
if value.size > size
|
298
|
+
value = value[0, size]
|
299
|
+
end
|
300
|
+
if side == 'r'
|
301
|
+
return value.rjust(size)
|
302
|
+
else
|
303
|
+
return value.ljust(size)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def output_ticket_list(tickets)
|
308
|
+
counter = 0
|
309
|
+
|
310
|
+
puts
|
311
|
+
puts [' ', just('#', 4, 'r'),
|
312
|
+
just('TicId', 6),
|
313
|
+
just('Title', 25),
|
314
|
+
just('State', 5),
|
315
|
+
just('Date', 5),
|
316
|
+
just('Assgn', 8),
|
317
|
+
just('Tags', 20) ].join(" ")
|
318
|
+
|
319
|
+
a = []
|
320
|
+
80.times { a << '-'}
|
321
|
+
puts a.join('')
|
322
|
+
|
323
|
+
tickets.each do |t|
|
324
|
+
counter += 1
|
325
|
+
tic.current_ticket == t.ticket_name ? add = '*' : add = ' '
|
326
|
+
puts [add, just(counter, 4, 'r'),
|
327
|
+
t.ticket_id[0,6],
|
328
|
+
just(t.title, 25),
|
329
|
+
just(t.state, 5),
|
330
|
+
t.opened.strftime("%m/%d"),
|
331
|
+
just(t.assigned_name, 8),
|
332
|
+
just(t.tags.join(','), 20) ].join(" ")
|
333
|
+
end
|
334
|
+
puts
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|
338
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TicGit
|
2
|
+
class Comment
|
3
|
+
|
4
|
+
attr_reader :base, :user, :added, :comment
|
5
|
+
|
6
|
+
def initialize(base, file_name, sha)
|
7
|
+
@base = base
|
8
|
+
@comment = base.git.gblob(sha).contents rescue nil
|
9
|
+
|
10
|
+
type, date, user = file_name.split('_')
|
11
|
+
|
12
|
+
@added = Time.at(date.to_i)
|
13
|
+
@user = user
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|