flurin-ticgit 0.3.7
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.
- 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/bin/ti
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This is a command line client that does all the actual tic commands
|
4
|
+
#
|
5
|
+
# authors: Scott Chacon <schacon@gmail.com>
|
6
|
+
# Mislav Marohnić <mislav.marohnic@gmail.com>
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + "/../lib/ticgit"
|
9
|
+
|
10
|
+
TicGit::CLI::start()
|
data/bin/ticgitweb
ADDED
@@ -0,0 +1,517 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# starts a sinatra based web server that provides an interface to
|
4
|
+
# your ticgit tickets
|
5
|
+
#
|
6
|
+
# some of the sinatra code borrowed from sr's git-wiki
|
7
|
+
#
|
8
|
+
# author: Flurin Egger
|
9
|
+
# original author : Scott Chacon (schacon@gmail.com)
|
10
|
+
#
|
11
|
+
|
12
|
+
def require_dependency_with_check(dependency)
|
13
|
+
begin
|
14
|
+
require dependency
|
15
|
+
rescue LoadError => e
|
16
|
+
puts "You need to install #{dependency} before we can proceed"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require_dependency_with_check 'rubygems'
|
21
|
+
|
22
|
+
# We need a sinatra version bigger than 0.3.1
|
23
|
+
gem 'sinatra', "~> 0.3.1"
|
24
|
+
|
25
|
+
%w(sinatra git ticgit haml sass).each do |dependency|
|
26
|
+
require_dependency_with_check dependency
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
configure do
|
31
|
+
enable :sessions
|
32
|
+
end
|
33
|
+
use_in_file_templates!
|
34
|
+
|
35
|
+
# !! TODO : if ARGV[1] is a path to a git repo, use that
|
36
|
+
# otherwise, look in ~/.ticgit
|
37
|
+
|
38
|
+
$ticgit = TicGit.open('.')
|
39
|
+
|
40
|
+
# Always load saved searches. (used in navigation)
|
41
|
+
before do
|
42
|
+
@saved = $ticgit.config['list_options'].keys rescue []
|
43
|
+
end
|
44
|
+
|
45
|
+
# Stylesheets
|
46
|
+
get('/_stylesheet.css') do
|
47
|
+
header("Content-Type" => "text/css;charset=utf-8")
|
48
|
+
sass :stylesheet_all
|
49
|
+
end
|
50
|
+
get('/_print.css') do
|
51
|
+
header("Content-Type" => "text/css;charset=utf-8")
|
52
|
+
sass :stylesheet_print
|
53
|
+
end
|
54
|
+
|
55
|
+
# ticket list view
|
56
|
+
get '/' do
|
57
|
+
@tickets = $ticgit.ticket_list(:order => 'date.desc')
|
58
|
+
store_list_url!
|
59
|
+
haml :list, :locals => {:title => "All tickets"}
|
60
|
+
end
|
61
|
+
|
62
|
+
get '/fs/:state' do
|
63
|
+
@tickets = $ticgit.ticket_list(:state => params[:state], :order => 'date.desc')
|
64
|
+
store_list_url!
|
65
|
+
haml :list, :locals => {:title => "#{params[:state].to_s.capitalize} tickets"}
|
66
|
+
end
|
67
|
+
|
68
|
+
get '/tag/:tag' do
|
69
|
+
@tickets = $ticgit.ticket_list(:tag => params[:tag], :order => 'date.desc')
|
70
|
+
store_list_url!
|
71
|
+
haml :list, :locals => {:title => "All tickets with tag '#{params[:tag]}'"}
|
72
|
+
end
|
73
|
+
|
74
|
+
get '/sv/:saved_view' do
|
75
|
+
@tickets = $ticgit.ticket_list(:saved => params[:saved_view])
|
76
|
+
store_list_url!
|
77
|
+
haml :list, :locals => {:title => "All tickets in view '#{params[:saved_view]}'"}
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# new ticket
|
82
|
+
get '/tickets/new' do
|
83
|
+
haml :new, :locals => {:title => "Create new ticket"}
|
84
|
+
end
|
85
|
+
|
86
|
+
# create ticket
|
87
|
+
post '/tickets' do
|
88
|
+
title = params[:title].to_s.strip
|
89
|
+
if title.size > 1
|
90
|
+
tags = params[:tags].split(',').map { |t| t.strip } rescue nil
|
91
|
+
t = $ticgit.ticket_new(title, {:description => params[:description].strip, :tags => tags})
|
92
|
+
if params[:addmore]
|
93
|
+
redirect '/tickets/new?addmore=true'
|
94
|
+
else
|
95
|
+
redirect session[:recent_list_url] || "/"
|
96
|
+
end
|
97
|
+
else
|
98
|
+
redirect '/tickets/new'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# show ticket
|
103
|
+
get '/tickets/:ticket' do
|
104
|
+
@ticket = $ticgit.ticket_show(params[:ticket])
|
105
|
+
haml :show, :locals => {:title => "Ticket #{@ticket.ticket_id}"}
|
106
|
+
end
|
107
|
+
|
108
|
+
# update ticket
|
109
|
+
put '/tickets/:ticket' do
|
110
|
+
@ticket = $ticgit.ticket_show(params[:ticket])
|
111
|
+
orig_state = nil
|
112
|
+
new_state = nil
|
113
|
+
new_tags = nil
|
114
|
+
if params[:state] && $ticgit.tic_states.include?(params[:state]) && @ticket.state != params[:state]
|
115
|
+
orig_state = @ticket.state
|
116
|
+
new_state = params[:state]
|
117
|
+
@ticket.change_state(params[:state])
|
118
|
+
end
|
119
|
+
if params[:tags]
|
120
|
+
current_tags = @ticket.tags
|
121
|
+
posted_tags = params[:tags].split(",").map{|s| s.strip }
|
122
|
+
tags_to_remove = current_tags - posted_tags
|
123
|
+
tags_to_add = posted_tags - current_tags
|
124
|
+
tags_to_remove.each{|t| @ticket.remove_tag(t) }
|
125
|
+
tags_to_add.each{|t| @ticket.add_tag(t) }
|
126
|
+
new_tags = posted_tags if tags_to_remove.any? || tags_to_add.any?
|
127
|
+
end
|
128
|
+
|
129
|
+
if params[:comment] && params[:comment].strip != ""
|
130
|
+
comment = ""
|
131
|
+
comment = "Set state from #{orig_state} to #{new_state}\n\n" if new_state
|
132
|
+
@ticket.add_comment(comment + params[:comment])
|
133
|
+
end
|
134
|
+
|
135
|
+
$ticgit.reset_ticgit
|
136
|
+
|
137
|
+
if ["resolved","invalid"].include?(params[:state]) && session[:recent_list_url]
|
138
|
+
# If the ticket is resolved or invalid return to the last listview
|
139
|
+
redirect session[:recent_list_url]
|
140
|
+
else
|
141
|
+
redirect "/tickets/#{@ticket.ticket_id}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
## Helper methods
|
147
|
+
|
148
|
+
#
|
149
|
+
def store_list_url!
|
150
|
+
session[:recent_list_url] = request.fullpath
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
__END__
|
155
|
+
@@ layout
|
156
|
+
:plain
|
157
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
158
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
159
|
+
%html{:xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang=>"en"}
|
160
|
+
%head
|
161
|
+
%title= title
|
162
|
+
%link{:rel => 'stylesheet', :href => '/_stylesheet.css', :type => 'text/css', :media => 'all'}
|
163
|
+
%link{:rel => 'stylesheet', :href => '/_print.css', :type => 'text/css', :media => 'print'}
|
164
|
+
%meta{'http-equiv' => 'Content-Type', :content => 'text/html; charset=utf-8'}
|
165
|
+
%body
|
166
|
+
#wrapper
|
167
|
+
#action
|
168
|
+
%a{:href => '/tickets/new'} New Ticket
|
169
|
+
%ul#navigation
|
170
|
+
%li
|
171
|
+
%a{:href => '/'} All
|
172
|
+
%li
|
173
|
+
%a{:href => '/fs/open'} Open
|
174
|
+
%li
|
175
|
+
%a{:href => '/fs/resolved'} Resolved
|
176
|
+
%li
|
177
|
+
%a{:href => '/fs/hold'} Hold
|
178
|
+
%li
|
179
|
+
%a{:href => '/fs/invalid'} Invalid
|
180
|
+
- if @saved && !@saved.empty?
|
181
|
+
%li | Saved:
|
182
|
+
%ul.saved
|
183
|
+
- @saved.each do |s|
|
184
|
+
%li
|
185
|
+
%a{:href => "/sv/\#{s}"}= s
|
186
|
+
|
187
|
+
= yield
|
188
|
+
|
189
|
+
@@ list
|
190
|
+
%h1= title
|
191
|
+
- if @tickets.empty?
|
192
|
+
%p No tickets found.
|
193
|
+
- else
|
194
|
+
%table.long
|
195
|
+
%thead
|
196
|
+
%tr
|
197
|
+
%th SHA
|
198
|
+
%th.ticket Ticket
|
199
|
+
%th State
|
200
|
+
%th Created at
|
201
|
+
%th Created by
|
202
|
+
%th Tags
|
203
|
+
%tbody
|
204
|
+
- c = 'even'
|
205
|
+
- @tickets.each do |t|
|
206
|
+
%tr{:class => (c == 'even' ? c = 'odd' : c = 'even') }
|
207
|
+
%td
|
208
|
+
%a{:href => "/tickets/#{t.ticket_id}" }
|
209
|
+
%code= t.ticket_id[0,6]
|
210
|
+
%td
|
211
|
+
%strong
|
212
|
+
%a{:href => "/tickets/#{t.ticket_id}" }= t.title
|
213
|
+
.content~ t.description
|
214
|
+
%td
|
215
|
+
%span{:class => "state " + t.state, :style => "float: left;"}= t.state
|
216
|
+
%td= t.opened.strftime("%m/%d")
|
217
|
+
%td= t.assigned_name
|
218
|
+
%td
|
219
|
+
- t.tags.each do |tag|
|
220
|
+
%a.tag{:href => "/tag/#{tag}"}= tag
|
221
|
+
|
222
|
+
@@ show
|
223
|
+
%h1
|
224
|
+
= @ticket.title
|
225
|
+
.meta
|
226
|
+
%span{:class => "state " + @ticket.state}= @ticket.state
|
227
|
+
Ticket:
|
228
|
+
= @ticket.ticket_id
|
229
|
+
%br
|
230
|
+
opened at
|
231
|
+
%em= @ticket.opened.strftime("%Y-%m-%d")
|
232
|
+
assigned to
|
233
|
+
%em= @ticket.assigned
|
234
|
+
tagged with
|
235
|
+
- @ticket.tags.each do |t|
|
236
|
+
%a.tag{:href => "/tag/#{t}"}= t
|
237
|
+
|
238
|
+
.content~ @ticket.description
|
239
|
+
%hr/
|
240
|
+
%form{:action => "/tickets/#{@ticket.ticket_id}", :method => 'post'}
|
241
|
+
%input{:type => "hidden", :name => "_method", :value => "put"}
|
242
|
+
%table.twocol
|
243
|
+
%tr
|
244
|
+
%th State
|
245
|
+
%td
|
246
|
+
%select{:name => "state", :id => "ticket_state"}
|
247
|
+
- $ticgit.tic_states.each do |ts|
|
248
|
+
%option{:value => ts , :selected => (@ticket.state == ts ? "selected" : nil)}= ts
|
249
|
+
%tr
|
250
|
+
%th
|
251
|
+
Tags
|
252
|
+
%small (comma delimited)
|
253
|
+
%td
|
254
|
+
%textarea{:rows => 2, :name => "tags", :style => "width: 50%"}= @ticket.tags.join(", ")
|
255
|
+
%tr
|
256
|
+
%th
|
257
|
+
Comment
|
258
|
+
%small (optional)
|
259
|
+
%td
|
260
|
+
%textarea{:rows => 3, :name => "comment", :style => "width: 100%"}
|
261
|
+
%tr
|
262
|
+
%th
|
263
|
+
%td
|
264
|
+
%div.submit
|
265
|
+
%input{:type => 'submit', :value => 'Update ticket'}
|
266
|
+
|
267
|
+
%h3 Comments
|
268
|
+
|
269
|
+
%div.comments
|
270
|
+
- @ticket.comments.reverse.each do |t|
|
271
|
+
%div.comment
|
272
|
+
%span.head
|
273
|
+
Added
|
274
|
+
= t.added.strftime("%m/%d %H:%M")
|
275
|
+
by
|
276
|
+
= t.user
|
277
|
+
%div.comment-text
|
278
|
+
= t.comment.gsub("\n","<br/>")
|
279
|
+
|
280
|
+
@@ new
|
281
|
+
%h1 Create a New Ticket
|
282
|
+
%form{:action => '/tickets/new', :method => 'post'}
|
283
|
+
%table
|
284
|
+
%tr
|
285
|
+
%th Title
|
286
|
+
%td
|
287
|
+
%input{:type => 'text', :name => 'title', :size => 30, :style => "width: 100%"}
|
288
|
+
%tr
|
289
|
+
%th Description
|
290
|
+
%td
|
291
|
+
%textarea{:rows => 15, :cols => 30, :name => 'description', :style => "width: 100%"}
|
292
|
+
%tr
|
293
|
+
%th
|
294
|
+
%span Tags
|
295
|
+
%small (comma delimited)
|
296
|
+
%td
|
297
|
+
%textarea{:name => 'tags', :rows => 2, :style => "width: 50%"}
|
298
|
+
%tr
|
299
|
+
%th
|
300
|
+
%td
|
301
|
+
%label{:for => "addmore"}
|
302
|
+
%input{:type => "checkbox", :value => "1",:id => "addmore", :name => "addmore", :checked => (params["addmore"] ? "checked" : nil)}
|
303
|
+
%span Add another ticket
|
304
|
+
%tr
|
305
|
+
%td
|
306
|
+
%td
|
307
|
+
%input{:type => 'submit', :value => 'Create Ticket'}
|
308
|
+
|
309
|
+
@@ stylesheet_all
|
310
|
+
body
|
311
|
+
:font
|
312
|
+
family: Verdana, Arial, "Bitstream Vera Sans", Helvetica, sans-serif
|
313
|
+
color: black
|
314
|
+
size: 62.5%
|
315
|
+
line-height: 1.2
|
316
|
+
background-color: white
|
317
|
+
margin: 2em
|
318
|
+
|
319
|
+
#wrapper
|
320
|
+
font-size: 1.2em
|
321
|
+
width: 90%
|
322
|
+
margin: 0 auto
|
323
|
+
|
324
|
+
// Autoclearing
|
325
|
+
#navigation:after
|
326
|
+
content: "."
|
327
|
+
visibility: hidden
|
328
|
+
clear: both
|
329
|
+
display: block
|
330
|
+
height: 0px
|
331
|
+
|
332
|
+
// IE autoclearing
|
333
|
+
#navigation
|
334
|
+
zoom: 1
|
335
|
+
|
336
|
+
#navigation
|
337
|
+
li
|
338
|
+
float: left
|
339
|
+
margin-right: 0.5em
|
340
|
+
a
|
341
|
+
background-color: #e0e0e0
|
342
|
+
color: black
|
343
|
+
text-decoration: none
|
344
|
+
padding: 2px
|
345
|
+
margin: 0
|
346
|
+
list-style: none
|
347
|
+
padding: 5px
|
348
|
+
border-bottom: 1px black solid
|
349
|
+
|
350
|
+
#action
|
351
|
+
text-align: right
|
352
|
+
float: right
|
353
|
+
a
|
354
|
+
background: #005
|
355
|
+
padding: 5px 10px
|
356
|
+
font-weight: bold
|
357
|
+
color: #fff
|
358
|
+
float: left
|
359
|
+
|
360
|
+
.addtag
|
361
|
+
padding: 5px 0
|
362
|
+
clear: both
|
363
|
+
|
364
|
+
h1
|
365
|
+
display: block
|
366
|
+
padding-bottom: 5px
|
367
|
+
margin-bottom: 0
|
368
|
+
|
369
|
+
h4
|
370
|
+
margin: 0 0 0.5em 0
|
371
|
+
|
372
|
+
div.meta
|
373
|
+
margin-bottom: 1em
|
374
|
+
display: block
|
375
|
+
font-size: 0.9em
|
376
|
+
font-weight: normal
|
377
|
+
color: #666
|
378
|
+
em
|
379
|
+
color: #000
|
380
|
+
font-style: normal
|
381
|
+
font-weight: bold
|
382
|
+
a.tag
|
383
|
+
font-size: 1em
|
384
|
+
float: none
|
385
|
+
background: #eee
|
386
|
+
color: #000
|
387
|
+
span.state
|
388
|
+
float: left
|
389
|
+
margin-right: 1em
|
390
|
+
color: #000
|
391
|
+
|
392
|
+
div.content
|
393
|
+
padding-top: 1em
|
394
|
+
clear: both
|
395
|
+
|
396
|
+
form
|
397
|
+
padding: 10px
|
398
|
+
background: #f2f2f2
|
399
|
+
table
|
400
|
+
width: 100%
|
401
|
+
tr
|
402
|
+
th
|
403
|
+
width: 20%
|
404
|
+
th,td
|
405
|
+
border-bottom: none
|
406
|
+
.submit
|
407
|
+
margin: 0.5em 0 0 0
|
408
|
+
|
409
|
+
a
|
410
|
+
color: black
|
411
|
+
a.exists
|
412
|
+
font-weight: bold
|
413
|
+
a.unknown
|
414
|
+
font-style: italic
|
415
|
+
|
416
|
+
a.tag
|
417
|
+
padding: 2px 5px
|
418
|
+
background: #888
|
419
|
+
color: #fff
|
420
|
+
font-weight: normal
|
421
|
+
font-size: 80%
|
422
|
+
float: left
|
423
|
+
margin: 1px 2px
|
424
|
+
text-decoration: none
|
425
|
+
|
426
|
+
.comments
|
427
|
+
margin: 10px 0px
|
428
|
+
.comment
|
429
|
+
.head
|
430
|
+
font-weight: bold
|
431
|
+
display: block
|
432
|
+
padding: 4px
|
433
|
+
.comment-text
|
434
|
+
padding: 4px
|
435
|
+
padding-bottom: 10px
|
436
|
+
margin-bottom: 10px
|
437
|
+
color: #333
|
438
|
+
border-bottom: 1px solid #aaa
|
439
|
+
|
440
|
+
table.long
|
441
|
+
width: 100%
|
442
|
+
.content
|
443
|
+
padding: 0
|
444
|
+
|
445
|
+
|
446
|
+
// States
|
447
|
+
span.state
|
448
|
+
padding: 3px
|
449
|
+
|
450
|
+
span.open
|
451
|
+
background: #ada
|
452
|
+
span.resolved
|
453
|
+
background: #abd
|
454
|
+
span.hold
|
455
|
+
background: #dda
|
456
|
+
span.invalid
|
457
|
+
background: #aaa
|
458
|
+
|
459
|
+
|
460
|
+
table
|
461
|
+
font-size: 100%
|
462
|
+
border-collapse: collapse
|
463
|
+
th
|
464
|
+
small
|
465
|
+
font-weight: normal
|
466
|
+
td,th
|
467
|
+
vertical-align: top
|
468
|
+
tr.even
|
469
|
+
td
|
470
|
+
background: #eee
|
471
|
+
tr.odd
|
472
|
+
td
|
473
|
+
background: #fff
|
474
|
+
|
475
|
+
table
|
476
|
+
tr
|
477
|
+
td,th
|
478
|
+
padding: 3px 5px
|
479
|
+
border-bottom: 1px solid #fff
|
480
|
+
th
|
481
|
+
text-align: left
|
482
|
+
vertical-align: top
|
483
|
+
th.ticket
|
484
|
+
width: 50%
|
485
|
+
|
486
|
+
strong a
|
487
|
+
text-decoration: none
|
488
|
+
|
489
|
+
table
|
490
|
+
thead
|
491
|
+
tr
|
492
|
+
td,th
|
493
|
+
border-bottom: 1px solid #000
|
494
|
+
|
495
|
+
.submit
|
496
|
+
font-size: large
|
497
|
+
font-weight: bold
|
498
|
+
|
499
|
+
.page_title
|
500
|
+
font-size: xx-large
|
501
|
+
|
502
|
+
.edit_link
|
503
|
+
color: black
|
504
|
+
font-size: 14px
|
505
|
+
font-weight: bold
|
506
|
+
background-color: #e0e0e0
|
507
|
+
font-variant: small-caps
|
508
|
+
text-decoration: none
|
509
|
+
|
510
|
+
@@ stylesheet_print
|
511
|
+
#navigation, #action
|
512
|
+
display: none
|
513
|
+
|
514
|
+
table
|
515
|
+
tr.odd, tr.even
|
516
|
+
td
|
517
|
+
border-bottom: 1px solid #ddd
|