TicGit-ng 1.0.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.
- data/LICENSE_GPL +681 -0
- data/LICENSE_MIT +29 -0
- data/README.mkd +425 -0
- data/bin/ti +13 -0
- data/bin/ticgitweb +313 -0
- data/lib/ticgit-ng/base.rb +365 -0
- data/lib/ticgit-ng/cli.rb +240 -0
- data/lib/ticgit-ng/command/assign.rb +43 -0
- data/lib/ticgit-ng/command/checkout.rb +14 -0
- data/lib/ticgit-ng/command/comment.rb +36 -0
- data/lib/ticgit-ng/command/list.rb +80 -0
- data/lib/ticgit-ng/command/milestone.rb +26 -0
- data/lib/ticgit-ng/command/new.rb +46 -0
- data/lib/ticgit-ng/command/points.rb +28 -0
- data/lib/ticgit-ng/command/recent.rb +29 -0
- data/lib/ticgit-ng/command/show.rb +18 -0
- data/lib/ticgit-ng/command/state.rb +45 -0
- data/lib/ticgit-ng/command/sync.rb +29 -0
- data/lib/ticgit-ng/command/tag.rb +31 -0
- data/lib/ticgit-ng/command.rb +77 -0
- data/lib/ticgit-ng/comment.rb +17 -0
- data/lib/ticgit-ng/ticket.rb +233 -0
- data/lib/ticgit-ng/version.rb +3 -0
- data/lib/ticgit-ng.rb +35 -0
- metadata +120 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module TicGitNG
|
2
|
+
module Command
|
3
|
+
COMMANDS = {}
|
4
|
+
DOC = {}
|
5
|
+
|
6
|
+
def self.register(mod_name, doc, *commands)
|
7
|
+
autoload(mod_name, "ticgit-ng/command/#{mod_name.downcase}")
|
8
|
+
DOC[commands] = doc
|
9
|
+
commands.each{|cmd| COMMANDS[cmd] = mod_name }
|
10
|
+
end
|
11
|
+
|
12
|
+
register 'Assign', 'Assings a ticket to someone', 'assign'
|
13
|
+
register 'Attach', 'Attach file to ticket', 'attach'
|
14
|
+
register 'Checkout', 'Checkout a ticket', 'checkout', 'co'
|
15
|
+
register 'Comment', 'Comment on a ticket', 'comment'
|
16
|
+
register 'List', 'List tickets', 'list'
|
17
|
+
register 'Milestone', 'List and modify milestones', 'milestone'
|
18
|
+
register 'New', 'Create a new ticket', 'new'
|
19
|
+
register 'Points', 'Assign points to a ticket', 'points'
|
20
|
+
register 'Recent', 'List recent activities', 'recent'
|
21
|
+
register 'Show', 'Show a ticket', 'show'
|
22
|
+
register 'State', 'Change state of a ticket', 'state'
|
23
|
+
register 'Tag', 'Modify tags of a ticket', 'tag'
|
24
|
+
register 'Sync', 'Sync tickets', 'sync'
|
25
|
+
|
26
|
+
def self.get(command)
|
27
|
+
if mod_name = COMMANDS[command]
|
28
|
+
const_get(mod_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.usage(action, args)
|
33
|
+
option_parser = parser(action, &method(:default_usage))
|
34
|
+
option_parser.parse!(args)
|
35
|
+
option_parser
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.default_usage(o)
|
39
|
+
o.banner = "Usage: ti COMMAND [FLAGS] [ARGS]"
|
40
|
+
o.top.append ' ', nil, nil
|
41
|
+
o.top.append 'The available ticgit commands are:', nil, nil
|
42
|
+
|
43
|
+
DOC.each do |commands, doc|
|
44
|
+
# get the longest version
|
45
|
+
command = commands.sort_by{|cmd| cmd.size }.last
|
46
|
+
o.top.append(" %-32s %s" % [command, doc], nil, nil)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.parser(action, &block)
|
51
|
+
OptionParser.new do |o|
|
52
|
+
o.banner = "Usage: ti #{action} [FLAGS] [ARGS]"
|
53
|
+
|
54
|
+
o.base.append ' ', nil, nil
|
55
|
+
o.base.append 'Common options:', nil, nil
|
56
|
+
o.on_tail('-v', '--version', 'Show the version number'){
|
57
|
+
puts TicGitNG::VERSION
|
58
|
+
exit
|
59
|
+
}
|
60
|
+
o.on_tail('-h', '--help', 'Display this help'){
|
61
|
+
puts o
|
62
|
+
exit
|
63
|
+
}
|
64
|
+
|
65
|
+
if block_given?
|
66
|
+
yield(o) if block_given?
|
67
|
+
unless o.top.list.empty?
|
68
|
+
if action
|
69
|
+
o.top.prepend "Options for #{action} command:", nil, nil
|
70
|
+
o.top.prepend ' ', nil, nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TicGitNG
|
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
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module TicGitNG
|
2
|
+
class Ticket
|
3
|
+
|
4
|
+
attr_reader :base, :opts
|
5
|
+
attr_accessor :ticket_id, :ticket_name
|
6
|
+
attr_accessor :title, :state, :milestone, :assigned, :opened, :points
|
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
|
+
@state = 'open' # by default
|
17
|
+
@comments = []
|
18
|
+
@tags = []
|
19
|
+
@attachments = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.create(base, title, options = {})
|
23
|
+
t = Ticket.new(base, options)
|
24
|
+
t.title = title
|
25
|
+
t.ticket_name = self.create_ticket_name(title)
|
26
|
+
t.save_new
|
27
|
+
t
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.open(base, ticket_name, ticket_hash, options = {})
|
31
|
+
tid = nil
|
32
|
+
|
33
|
+
t = Ticket.new(base, options)
|
34
|
+
t.ticket_name = ticket_name
|
35
|
+
|
36
|
+
title, date = self.parse_ticket_name(ticket_name)
|
37
|
+
t.opened = date
|
38
|
+
|
39
|
+
ticket_hash['files'].each do |fname, value|
|
40
|
+
if fname == 'TICKET_ID'
|
41
|
+
tid = value
|
42
|
+
elsif fname == 'TICKET_TITLE'
|
43
|
+
t.title = base.git.gblob(value).contents
|
44
|
+
else
|
45
|
+
# matching
|
46
|
+
data = fname.split('_')
|
47
|
+
|
48
|
+
case data[0]
|
49
|
+
when 'ASSIGNED'
|
50
|
+
t.assigned = data[1]
|
51
|
+
when 'ATTACHMENT'
|
52
|
+
t.attachments << TicGitNG::Attachment.new(base, fname, value)
|
53
|
+
when 'COMMENT'
|
54
|
+
t.comments << TicGitNG::Comment.new(base, fname, value)
|
55
|
+
when 'POINTS'
|
56
|
+
t.points = base.git.gblob(value).contents.to_i
|
57
|
+
when 'STATE'
|
58
|
+
t.state = data[1]
|
59
|
+
when 'TAG'
|
60
|
+
t.tags << data[1]
|
61
|
+
when 'TITLE'
|
62
|
+
t.title = base.git.gblob(value).contents
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
t.ticket_id = tid
|
68
|
+
t
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def self.parse_ticket_name(name)
|
73
|
+
epoch, title, rand = name.split('_')
|
74
|
+
title = title.gsub('-', ' ')
|
75
|
+
return [title, Time.at(epoch.to_i)]
|
76
|
+
end
|
77
|
+
|
78
|
+
# write this ticket to the git database
|
79
|
+
def save_new
|
80
|
+
base.in_branch do |wd|
|
81
|
+
base.logger.info "saving #{ticket_name}"
|
82
|
+
|
83
|
+
Dir.mkdir(ticket_name)
|
84
|
+
Dir.chdir(ticket_name) do
|
85
|
+
base.new_file('TICKET_ID', ticket_name)
|
86
|
+
base.new_file('TICKET_TITLE', title)
|
87
|
+
base.new_file('ASSIGNED_' + email, email)
|
88
|
+
base.new_file('STATE_' + state, state)
|
89
|
+
base.new_file('TITLE', title)
|
90
|
+
|
91
|
+
# add initial comment
|
92
|
+
#COMMENT_080315060503045__schacon_at_gmail
|
93
|
+
base.new_file(comment_name(email), opts[:comment]) if opts[:comment]
|
94
|
+
|
95
|
+
# add initial tags
|
96
|
+
if opts[:tags] && opts[:tags].size > 0
|
97
|
+
opts[:tags] = opts[:tags].map { |t| t.strip }.compact
|
98
|
+
opts[:tags].each do |tag|
|
99
|
+
if tag.size > 0
|
100
|
+
tag_filename = 'TAG_' + Ticket.clean_string(tag)
|
101
|
+
if !File.exists?(tag_filename)
|
102
|
+
base.new_file(tag_filename, tag_filename)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
base.git.add
|
109
|
+
base.git.commit("added ticket #{ticket_name}")
|
110
|
+
end
|
111
|
+
# ticket_id
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.clean_string(string)
|
115
|
+
string.downcase.gsub(/[^a-z0-9]+/i, '-')
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_comment(comment)
|
119
|
+
return false if !comment
|
120
|
+
base.in_branch do |wd|
|
121
|
+
Dir.chdir(ticket_name) do
|
122
|
+
base.new_file(comment_name(email), comment)
|
123
|
+
end
|
124
|
+
base.git.add
|
125
|
+
base.git.commit("added comment to ticket #{ticket_name}")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def change_state(new_state)
|
130
|
+
return false if !new_state
|
131
|
+
return false if new_state == state
|
132
|
+
|
133
|
+
base.in_branch do |wd|
|
134
|
+
Dir.chdir(ticket_name) do
|
135
|
+
base.new_file('STATE_' + new_state, new_state)
|
136
|
+
end
|
137
|
+
base.git.remove(File.join(ticket_name,'STATE_' + state))
|
138
|
+
base.git.add
|
139
|
+
base.git.commit("added state (#{new_state}) to ticket #{ticket_name}")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def change_assigned(new_assigned)
|
144
|
+
new_assigned ||= email
|
145
|
+
old_assigned= assigned || ''
|
146
|
+
return false if new_assigned == old_assigned
|
147
|
+
|
148
|
+
base.in_branch do |wd|
|
149
|
+
Dir.chdir(ticket_name) do
|
150
|
+
base.new_file('ASSIGNED_' + new_assigned, new_assigned)
|
151
|
+
end
|
152
|
+
base.git.remove(File.join(ticket_name,'ASSIGNED_' + old_assigned))
|
153
|
+
base.git.add
|
154
|
+
base.git.commit("assigned #{new_assigned} to ticket #{ticket_name}")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def change_points(new_points)
|
159
|
+
return false if new_points == points
|
160
|
+
|
161
|
+
base.in_branch do |wd|
|
162
|
+
Dir.chdir(ticket_name) do
|
163
|
+
base.new_file('POINTS', new_points)
|
164
|
+
end
|
165
|
+
base.git.add
|
166
|
+
base.git.commit("set points to #{new_points} for ticket #{ticket_name}")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def add_tag(tag)
|
171
|
+
return false if !tag
|
172
|
+
added = false
|
173
|
+
tags = tag.split(',').map { |t| t.strip }
|
174
|
+
base.in_branch do |wd|
|
175
|
+
Dir.chdir(ticket_name) do
|
176
|
+
tags.each do |add_tag|
|
177
|
+
if add_tag.size > 0
|
178
|
+
tag_filename = 'TAG_' + Ticket.clean_string(add_tag)
|
179
|
+
if !File.exists?(tag_filename)
|
180
|
+
base.new_file(tag_filename, tag_filename)
|
181
|
+
added = true
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
if added
|
187
|
+
base.git.add
|
188
|
+
base.git.commit("added tags (#{tag}) to ticket #{ticket_name}")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def remove_tag(tag)
|
194
|
+
return false if !tag
|
195
|
+
removed = false
|
196
|
+
tags = tag.split(',').map { |t| t.strip }
|
197
|
+
base.in_branch do |wd|
|
198
|
+
tags.each do |add_tag|
|
199
|
+
tag_filename = File.join(ticket_name, 'TAG_' + Ticket.clean_string(add_tag))
|
200
|
+
if File.exists?(tag_filename)
|
201
|
+
base.git.remove(tag_filename)
|
202
|
+
removed = true
|
203
|
+
end
|
204
|
+
end
|
205
|
+
if removed
|
206
|
+
base.git.commit("removed tags (#{tag}) from ticket #{ticket_name}")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def path
|
212
|
+
File.join(state, ticket_name)
|
213
|
+
end
|
214
|
+
|
215
|
+
def comment_name(email)
|
216
|
+
'COMMENT_' + Time.now.to_i.to_s + '_' + email
|
217
|
+
end
|
218
|
+
|
219
|
+
def email
|
220
|
+
opts[:user_email] || 'anon'
|
221
|
+
end
|
222
|
+
|
223
|
+
def assigned_name
|
224
|
+
assigned.split('@').first rescue ''
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.create_ticket_name(title)
|
228
|
+
[Time.now.to_i.to_s, Ticket.clean_string(title), rand(999).to_i.to_s].join('_')
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
data/lib/ticgit-ng.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'logger'
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'set'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
# Add the directory containing this file to the start of the load path if it
|
9
|
+
# isn't there already.
|
10
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
11
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
# requires git >= 1.0.5
|
15
|
+
require 'git'
|
16
|
+
require 'ticgit-ng/base'
|
17
|
+
require 'ticgit-ng/cli'
|
18
|
+
|
19
|
+
module TicGitNG
|
20
|
+
autoload :VERSION, 'ticgit-ng/version'
|
21
|
+
autoload :Comment, 'ticgit-ng/comment'
|
22
|
+
autoload :Ticket, 'ticgit-ng/ticket'
|
23
|
+
|
24
|
+
# options
|
25
|
+
# :logger => Logger.new(STDOUT)
|
26
|
+
def self.open(git_dir, options = {})
|
27
|
+
Base.new(git_dir, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
class OpenStruct < ::OpenStruct
|
31
|
+
def to_hash
|
32
|
+
@table.dup
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: TicGit-ng
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Scott Chacon
|
14
|
+
- Jeff Welling
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-02-15 00:00:00 -05:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: git
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
description: TicGit-ng is a simple ticketing system, roughly similar to the Lighthouse model, that is based in git.
|
51
|
+
email:
|
52
|
+
- Jeff.Welling@gmail.com
|
53
|
+
executables:
|
54
|
+
- ti
|
55
|
+
- ticgitweb
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files: []
|
59
|
+
|
60
|
+
files:
|
61
|
+
- bin/ti
|
62
|
+
- bin/ticgitweb
|
63
|
+
- lib/ticgit-ng.rb
|
64
|
+
- lib/ticgit-ng/base.rb
|
65
|
+
- lib/ticgit-ng/ticket.rb
|
66
|
+
- lib/ticgit-ng/cli.rb
|
67
|
+
- lib/ticgit-ng/version.rb
|
68
|
+
- lib/ticgit-ng/command/assign.rb
|
69
|
+
- lib/ticgit-ng/command/state.rb
|
70
|
+
- lib/ticgit-ng/command/new.rb
|
71
|
+
- lib/ticgit-ng/command/recent.rb
|
72
|
+
- lib/ticgit-ng/command/sync.rb
|
73
|
+
- lib/ticgit-ng/command/show.rb
|
74
|
+
- lib/ticgit-ng/command/points.rb
|
75
|
+
- lib/ticgit-ng/command/milestone.rb
|
76
|
+
- lib/ticgit-ng/command/tag.rb
|
77
|
+
- lib/ticgit-ng/command/comment.rb
|
78
|
+
- lib/ticgit-ng/command/list.rb
|
79
|
+
- lib/ticgit-ng/command/checkout.rb
|
80
|
+
- lib/ticgit-ng/command.rb
|
81
|
+
- lib/ticgit-ng/comment.rb
|
82
|
+
- LICENSE_MIT
|
83
|
+
- LICENSE_GPL
|
84
|
+
- README.mkd
|
85
|
+
has_rdoc: true
|
86
|
+
homepage: https://github.com/jeffWelling/ticgit
|
87
|
+
licenses: []
|
88
|
+
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
version: "0"
|
112
|
+
requirements: []
|
113
|
+
|
114
|
+
rubyforge_project: ticgit-ng
|
115
|
+
rubygems_version: 1.3.7
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: Git based distributed ticketing system
|
119
|
+
test_files: []
|
120
|
+
|