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
@@ -0,0 +1,224 @@
|
|
1
|
+
module TicGit
|
2
|
+
class Ticket
|
3
|
+
|
4
|
+
attr_reader :base, :opts
|
5
|
+
attr_accessor :ticket_id, :ticket_name
|
6
|
+
attr_accessor :title, :description, :state, :milestone, :assigned, :opened
|
7
|
+
attr_accessor :comments, :tags, :attachments # arrays
|
8
|
+
|
9
|
+
def initialize(base, options = {})
|
10
|
+
options[:user_name] ||= base.git.config('user.name')
|
11
|
+
options[:user_email] ||= base.git.config('user.email')
|
12
|
+
|
13
|
+
@base = base
|
14
|
+
@opts = options || {}
|
15
|
+
|
16
|
+
@description = options.delete(:description)
|
17
|
+
@state = 'open' # by default
|
18
|
+
@comments = []
|
19
|
+
@tags = []
|
20
|
+
@attachments = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.create(base, title, options = {})
|
24
|
+
t = Ticket.new(base, options)
|
25
|
+
t.title = title
|
26
|
+
t.ticket_name = self.create_ticket_name(title)
|
27
|
+
t.save_new
|
28
|
+
t
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.open(base, ticket_name, ticket_hash, options = {})
|
32
|
+
tid = nil
|
33
|
+
|
34
|
+
t = Ticket.new(base, options)
|
35
|
+
t.ticket_name = ticket_name
|
36
|
+
|
37
|
+
basic_title, date = self.parse_ticket_name(ticket_name)
|
38
|
+
|
39
|
+
# t.title will be overridden below if the file TICKET_TITLE exists
|
40
|
+
t.title = basic_title
|
41
|
+
t.opened = date
|
42
|
+
|
43
|
+
ticket_hash['files'].each do |fname, value|
|
44
|
+
if fname == 'TICKET_ID'
|
45
|
+
t.ticket_id = value
|
46
|
+
elsif fname == 'TICKET_TITLE'
|
47
|
+
t.title = base.git.gblob(value).contents rescue nil
|
48
|
+
elsif fname == 'TICKET_DESCRIPTION'
|
49
|
+
t.description = base.git.gblob(value).contents rescue nil
|
50
|
+
else
|
51
|
+
# matching
|
52
|
+
data = fname.split('_')
|
53
|
+
if data[0] == 'ASSIGNED'
|
54
|
+
t.assigned = data[1]
|
55
|
+
end
|
56
|
+
if data[0] == 'COMMENT'
|
57
|
+
t.comments << TicGit::Comment.new(base, fname, value)
|
58
|
+
end
|
59
|
+
if data[0] == 'TAG'
|
60
|
+
t.tags << data[1]
|
61
|
+
end
|
62
|
+
if data[0] == 'STATE'
|
63
|
+
t.state = data[1]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
t
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def self.parse_ticket_name(name)
|
74
|
+
unless name =~ /^([^_]+)_(.+)_([^_]+)$/
|
75
|
+
raise "invalid ticket name #{name.inspect}"
|
76
|
+
end
|
77
|
+
epoch, title, rand = $1, $2.gsub('-', ' '), $3
|
78
|
+
return [title, Time.at(epoch.to_i)]
|
79
|
+
end
|
80
|
+
|
81
|
+
# write this ticket to the git database
|
82
|
+
def save_new
|
83
|
+
base.in_branch do |wd|
|
84
|
+
base.logger.info "saving #{ticket_name}"
|
85
|
+
|
86
|
+
Dir.mkdir(ticket_name)
|
87
|
+
Dir.chdir(ticket_name) do
|
88
|
+
base.new_file('TICKET_ID', ticket_name)
|
89
|
+
base.new_file('ASSIGNED_' + email, email)
|
90
|
+
base.new_file('STATE_' + state, state)
|
91
|
+
base.new_file('TICKET_TITLE',self.title)
|
92
|
+
base.new_file('TICKET_DESCRIPTION',self.description) if self.description
|
93
|
+
|
94
|
+
# add initial comment
|
95
|
+
#COMMENT_080315060503045__schacon_at_gmail
|
96
|
+
base.new_file(comment_name(email), opts[:comment]) if opts[:comment]
|
97
|
+
|
98
|
+
# add initial tags
|
99
|
+
if opts[:tags] && opts[:tags].size > 0
|
100
|
+
opts[:tags] = opts[:tags].map { |t| t.strip }.compact
|
101
|
+
opts[:tags].each do |tag|
|
102
|
+
if tag.size > 0
|
103
|
+
tag_filename = 'TAG_' + Ticket.clean_string(tag)
|
104
|
+
if !File.exists?(tag_filename)
|
105
|
+
base.new_file(tag_filename, tag_filename)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
base.git.add
|
113
|
+
base.git.commit("added ticket #{ticket_name}")
|
114
|
+
end
|
115
|
+
# ticket_id
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.clean_string(string)
|
119
|
+
string.downcase.gsub(/[^a-zA-Z0-9_:;,.]+/, '-')
|
120
|
+
end
|
121
|
+
|
122
|
+
def add_comment(comment)
|
123
|
+
return false if !comment
|
124
|
+
base.in_branch do |wd|
|
125
|
+
Dir.chdir(ticket_name) do
|
126
|
+
base.new_file(comment_name(email), comment)
|
127
|
+
end
|
128
|
+
base.git.add
|
129
|
+
base.git.commit("added comment to ticket #{ticket_name}")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def change_state(new_state)
|
134
|
+
return false if !new_state
|
135
|
+
return false if new_state == state
|
136
|
+
|
137
|
+
base.in_branch do |wd|
|
138
|
+
Dir.chdir(ticket_name) do
|
139
|
+
base.new_file('STATE_' + new_state, new_state)
|
140
|
+
end
|
141
|
+
base.git.remove(File.join(ticket_name,'STATE_' + state))
|
142
|
+
base.git.add
|
143
|
+
base.git.commit("added state (#{new_state}) to ticket #{ticket_name}")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def change_assigned(new_assigned)
|
148
|
+
new_assigned ||= email
|
149
|
+
return false if new_assigned == assigned
|
150
|
+
|
151
|
+
base.in_branch do |wd|
|
152
|
+
Dir.chdir(ticket_name) do
|
153
|
+
base.new_file('ASSIGNED_' + new_assigned, new_assigned)
|
154
|
+
end
|
155
|
+
base.git.remove(File.join(ticket_name,'ASSIGNED_' + assigned))
|
156
|
+
base.git.add
|
157
|
+
base.git.commit("assigned #{new_assigned} to ticket #{ticket_name}")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def add_tag(tag)
|
162
|
+
return false if !tag
|
163
|
+
added = false
|
164
|
+
tags = tag.split(',').map { |t| t.strip }
|
165
|
+
base.in_branch do |wd|
|
166
|
+
Dir.chdir(ticket_name) do
|
167
|
+
tags.each do |add_tag|
|
168
|
+
if add_tag.size > 0
|
169
|
+
tag_filename = 'TAG_' + Ticket.clean_string(add_tag)
|
170
|
+
if !File.exists?(tag_filename)
|
171
|
+
base.new_file(tag_filename, tag_filename)
|
172
|
+
added = true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
if added
|
178
|
+
base.git.add
|
179
|
+
base.git.commit("added tags (#{tag}) to ticket #{ticket_name}")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def remove_tag(tag)
|
185
|
+
return false if !tag
|
186
|
+
removed = false
|
187
|
+
tags = tag.split(',').map { |t| t.strip }
|
188
|
+
base.in_branch do |wd|
|
189
|
+
tags.each do |add_tag|
|
190
|
+
tag_filename = File.join(ticket_name, 'TAG_' + Ticket.clean_string(add_tag))
|
191
|
+
if File.exists?(tag_filename)
|
192
|
+
base.git.remove(tag_filename)
|
193
|
+
removed = true
|
194
|
+
end
|
195
|
+
end
|
196
|
+
if removed
|
197
|
+
base.git.commit("removed tags (#{tag}) from ticket #{ticket_name}")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def path
|
203
|
+
File.join(state, ticket_name)
|
204
|
+
end
|
205
|
+
|
206
|
+
def comment_name(email)
|
207
|
+
'COMMENT_' + Time.now.to_i.to_s + '_' + email
|
208
|
+
end
|
209
|
+
|
210
|
+
def email
|
211
|
+
opts[:user_email] || 'anon'
|
212
|
+
end
|
213
|
+
|
214
|
+
def assigned_name
|
215
|
+
assigned.split('@').first rescue ''
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.create_ticket_name(title)
|
219
|
+
[Time.now.to_i.to_s, Ticket.clean_string(title), rand(999).to_i.to_s].join('_')
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
end
|
224
|
+
end
|
data/lib/ticgit.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Add the directory containing this file to the start of the load path if it
|
2
|
+
# isn't there already.
|
3
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
4
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
# requires git >= 1.0.5
|
8
|
+
require 'git'
|
9
|
+
require 'ticgit/base'
|
10
|
+
require 'ticgit/ticket'
|
11
|
+
require 'ticgit/comment'
|
12
|
+
|
13
|
+
require 'ticgit/cli'
|
14
|
+
|
15
|
+
# TicGit Library
|
16
|
+
#
|
17
|
+
# This library implements a git based ticketing system in a git repo
|
18
|
+
#
|
19
|
+
# Author:: Scott Chacon (mailto:schacon@gmail.com)
|
20
|
+
# License:: MIT License
|
21
|
+
#
|
22
|
+
module TicGit
|
23
|
+
# options
|
24
|
+
# :logger => Logger.new(STDOUT)
|
25
|
+
def self.open(git_dir, options = {})
|
26
|
+
Base.new(git_dir, options)
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flurin-ticgit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Chacon
|
8
|
+
- "Mislav Marohni\xC4\x87"
|
9
|
+
- Flurin Egger
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2008-09-11 00:00:00 -07:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: schacon-git
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.0.5
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: sinatra
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.1
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: wycats-thor
|
37
|
+
version_requirement:
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.9.5
|
43
|
+
version:
|
44
|
+
description:
|
45
|
+
email: schacon@gmail.com
|
46
|
+
executables:
|
47
|
+
- ti
|
48
|
+
- ticgitweb
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files: []
|
52
|
+
|
53
|
+
files:
|
54
|
+
- lib/ticgit/base.rb
|
55
|
+
- lib/ticgit/cli.rb
|
56
|
+
- lib/ticgit/comment.rb
|
57
|
+
- lib/ticgit/ticket.rb
|
58
|
+
- lib/ticgit.rb
|
59
|
+
- bin/ti
|
60
|
+
- bin/ticgitweb
|
61
|
+
has_rdoc: false
|
62
|
+
homepage: http://github.com/schacon/ticgit/wikis
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.2.0
|
84
|
+
signing_key:
|
85
|
+
specification_version: 2
|
86
|
+
summary: A distributed ticketing system for Git projects.
|
87
|
+
test_files: []
|
88
|
+
|