git-trac 0.0.20080205 → 0.0.20080206
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/lib/git/trac/attachment.rb +11 -24
- data/lib/git/trac/pager.rb +2 -0
- data/lib/git/trac/patch.rb +18 -16
- data/lib/git/trac/runner.rb +17 -229
- data/lib/git/trac/runner/apply.rb +65 -0
- data/lib/git/trac/runner/cleanup.rb +44 -0
- data/lib/git/trac/runner/download.rb +44 -0
- data/lib/git/trac/runner/fetch.rb +63 -0
- data/lib/git/trac/runner/show.rb +45 -0
- data/lib/git/trac/runner/upload_patch.rb +62 -0
- data/lib/git/trac/ticket.rb +34 -7
- data/test/attachment_test.rb +31 -0
- metadata +9 -2
data/lib/git/trac/attachment.rb
CHANGED
@@ -6,33 +6,20 @@ module Git
|
|
6
6
|
attr_reader :ticket, :filename, :user, :time
|
7
7
|
attr_accessor :description
|
8
8
|
|
9
|
-
def initialize(ticket, filename, user, time)
|
9
|
+
def initialize(ticket, filename, user, time, description)
|
10
10
|
@ticket, @filename, @user, @time = ticket, filename, user, time
|
11
|
+
@description = description
|
11
12
|
end
|
12
13
|
|
13
|
-
def self.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
element.traverse_text {|t| str << t.to_s}
|
22
|
-
last.description = str if last
|
23
|
-
elsif element.name = "dt"
|
24
|
-
texts = []
|
25
|
-
element.traverse_text {|x| texts << x.to_s}
|
26
|
-
if texts.first =~ /./
|
27
|
-
time = Time.parse("#{texts[3]} +0000").utc
|
28
|
-
collection << new(ticket, texts[0], texts[2], time)
|
29
|
-
last = collection.last
|
30
|
-
else
|
31
|
-
last = nil
|
32
|
-
end
|
33
|
-
end
|
14
|
+
def self.from_html(ticket, html)
|
15
|
+
require 'time'
|
16
|
+
unesc = lambda do |string|
|
17
|
+
string.gsub(/&(\w+);/) { {"lt"=>"<","rt"=>">","amp"=>"&"}[$1]||$& }
|
18
|
+
end
|
19
|
+
html.scan(%r{<dt><a .*?>(.*?)</a>.*? <em>(.*?)</em>\s*on\s+(.*?)\.</dt>\s*(?:<dd>(.*?)</dd>)?}).map do |(filename, user, time, desc)|
|
20
|
+
time = Time.parse("#{time} +0000").utc
|
21
|
+
new(ticket, unesc[filename], unesc[user], time, desc && unesc[desc])
|
34
22
|
end
|
35
|
-
collection
|
36
23
|
end
|
37
24
|
|
38
25
|
def extension
|
@@ -103,7 +90,7 @@ module Git
|
|
103
90
|
repository.with_index("tracindex#{$$}") do
|
104
91
|
parent = repository.exec("git","rev-list","--max-count=1","--before=#{timestamp}",options[:branch] || 'trunk').chomp
|
105
92
|
repository.exec("git","read-tree",parent)
|
106
|
-
applied = patch.apply(options) or return [nil,applied]
|
93
|
+
applied = patch.apply(options.merge(:cached => true)) or return [nil,applied]
|
107
94
|
tree = repository.exec("git","write-tree").chomp
|
108
95
|
ENV["GIT_AUTHOR_NAME"] = ENV["GIT_COMMITTER_NAME"] = username
|
109
96
|
ENV["GIT_AUTHOR_EMAIL"] = ENV["GIT_COMMITTER_EMAIL"] = email
|
data/lib/git/trac/pager.rb
CHANGED
data/lib/git/trac/patch.rb
CHANGED
@@ -10,24 +10,20 @@ module Git
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def body
|
13
|
-
@body.respond_to?(:call) ? @body.call : body
|
13
|
+
@body.respond_to?(:call) ? @body.call : @body
|
14
14
|
end
|
15
15
|
|
16
16
|
alias to_s body
|
17
17
|
|
18
|
-
def
|
19
|
-
|
20
|
-
1
|
21
|
-
else
|
22
|
-
0
|
23
|
-
end
|
18
|
+
def git?
|
19
|
+
body[0,10] == "diff --git" || body[0,6] == "--- a/"
|
24
20
|
end
|
25
21
|
|
26
22
|
# Rewrite filenames in a patch to be relative to +root+. For each .. at
|
27
23
|
# the end of +root+, a path is stripped off the original filename. The
|
28
24
|
# rewritten patch is returned as a string.
|
29
25
|
def with_root(root = nil)
|
30
|
-
if root && !root.empty? &&
|
26
|
+
if root && !root.empty? && !git?
|
31
27
|
patch = ""
|
32
28
|
body.each_line do |line|
|
33
29
|
line.sub!(/^([+-]{3} |Index: )(?!\/dev\/null)(.*)$/) do
|
@@ -46,7 +42,9 @@ module Git
|
|
46
42
|
end
|
47
43
|
|
48
44
|
def apply(options = {})
|
49
|
-
|
45
|
+
command = %W(git apply -p#{git? ? 1 : 0} --whitespace=nowarn)
|
46
|
+
command << "--cached" if options[:cached]
|
47
|
+
repository.popen3(*command) do |inn,out,err|
|
50
48
|
inn.puts with_root(options[:root])
|
51
49
|
inn.close
|
52
50
|
if err.read.empty?
|
@@ -57,27 +55,31 @@ module Git
|
|
57
55
|
end
|
58
56
|
end
|
59
57
|
end
|
60
|
-
if options[:depth].to_i > 0 &&
|
58
|
+
if options[:depth].to_i > 0 && !git?
|
61
59
|
repository.in_work_tree do
|
62
60
|
last_roots = [options[:root]]
|
63
|
-
|
64
|
-
1.upto(
|
65
|
-
parent = File.join(*(%w(..)*d+[
|
66
|
-
result = apply(
|
61
|
+
opts = options.dup
|
62
|
+
1.upto(opts.delete(:depth).to_i) do |d|
|
63
|
+
parent = File.join(*(%w(..)*d+[opts[:root]]).compact)
|
64
|
+
result = apply(opts.merge(:root => parent)) and return result
|
67
65
|
roots = []
|
68
66
|
last_roots.each do |root|
|
69
67
|
Dir.entries(root || ".").each do |dir|
|
70
68
|
path = File.join(*[root,dir].compact)
|
71
69
|
next if dir =~ /\A\./ || !File.directory?(path)
|
72
70
|
roots << path
|
73
|
-
result = apply(
|
71
|
+
result = apply(opts.merge(:root => path)) and return result
|
74
72
|
end
|
75
73
|
end
|
76
74
|
last_roots = roots
|
77
75
|
end
|
78
76
|
end
|
79
77
|
end
|
80
|
-
|
78
|
+
if body.include?("\r")
|
79
|
+
Patch.new(@repository, body.delete("\r")).apply(options)
|
80
|
+
else
|
81
|
+
false
|
82
|
+
end
|
81
83
|
end
|
82
84
|
|
83
85
|
end
|
data/lib/git/trac/runner.rb
CHANGED
@@ -29,6 +29,7 @@ module Git
|
|
29
29
|
Usage: git-trac <command> [options] [arguments]
|
30
30
|
|
31
31
|
Available commands:
|
32
|
+
apply Apply a patch directly to the work tree
|
32
33
|
cleanup Remove old branches for a ticket
|
33
34
|
download Download all patches for a ticket to the cwd
|
34
35
|
fetch Create brances for all patches of a ticket
|
@@ -141,145 +142,18 @@ Available commands:
|
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
144
|
-
def
|
145
|
+
def fetch_unless_local
|
145
146
|
unless options[:local]
|
146
|
-
@repository.
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
class Download < Base #:nodoc:
|
157
|
-
|
158
|
-
def description
|
159
|
-
<<-EOF
|
160
|
-
Download all attachments that look like patches to the current working
|
161
|
-
directory.
|
162
|
-
EOF
|
163
|
-
end
|
164
|
-
|
165
|
-
def banner_arguments
|
166
|
-
"[options] [ticket[/filename]] ..."
|
167
|
-
end
|
168
|
-
|
169
|
-
def add_options(opts)
|
170
|
-
require_ticket_number
|
171
|
-
opts.separator("Options:")
|
172
|
-
opts.on("--root DIR","prefix patch paths with DIR") do |dir|
|
173
|
-
options[:root] = dir
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def run
|
178
|
-
each_ticket_argument do |number, filename|
|
179
|
-
@repository.ticket(number).attachments.each do |attach|
|
180
|
-
next if filename && attach.filename != filename
|
181
|
-
File.open(attach.filename, "w") do |f|
|
182
|
-
f.puts attach.patch.with_root(options[:root])
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
class Show < Base #:nodoc:
|
191
|
-
|
192
|
-
def banner_arguments
|
193
|
-
"[ticket[/filename]]"
|
194
|
-
end
|
195
|
-
|
196
|
-
def description
|
197
|
-
<<-EOF
|
198
|
-
Show a crude ticket summary or a patch.
|
199
|
-
EOF
|
200
|
-
end
|
201
|
-
|
202
|
-
def add_options(opts)
|
203
|
-
require_ticket_number
|
204
|
-
end
|
205
|
-
|
206
|
-
def run
|
207
|
-
each_ticket_argument do |number, filename|
|
208
|
-
ticket = @repository.ticket(number)
|
209
|
-
attachments = ticket.attachments
|
210
|
-
if filename
|
211
|
-
attachment = attachments.detect {|a|a.filename == filename}
|
212
|
-
abort "no such attachment" unless attachment
|
213
|
-
@repository.pager(attachment.body, true)
|
147
|
+
if @repository.config("svn-remote.svn")
|
148
|
+
command = "git svn fetch"
|
149
|
+
elsif @repository.config("origin")
|
150
|
+
command = "git fetch origin"
|
214
151
|
else
|
215
|
-
|
216
|
-
csv = ticket.csv
|
217
|
-
csv.reject {|k,v| k == "description"}.sort.each do |(k,v)|
|
218
|
-
body << "#{k}: #{v}\n" if v
|
219
|
-
end
|
220
|
-
body << "attachments:\n"
|
221
|
-
attachments.each do |attachment|
|
222
|
-
body << "#{number}/#{attachment.filename}"
|
223
|
-
body << " (#{attachment.description})" if attachment.description
|
224
|
-
body << "\n"
|
225
|
-
end
|
226
|
-
body << "description:\n"
|
227
|
-
body << csv["description"]
|
228
|
-
body << "\n" unless body =~ /\n\z/
|
229
|
-
@repository.pager(body)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
end
|
235
|
-
|
236
|
-
class Fetch < Base #:nodoc:
|
237
|
-
|
238
|
-
def banner_arguments
|
239
|
-
"[options] [ticket[/filename]] ..."
|
240
|
-
end
|
241
|
-
|
242
|
-
def description
|
243
|
-
<<-EOF
|
244
|
-
Download all branches that look like patches. For each patch, find the
|
245
|
-
revision of trunk that most recently proceeds the the time of upload, apply
|
246
|
-
the patch to it, create a new commit, and add a remote head of the form
|
247
|
-
refs/remotes/trac/ticketnumber/file_name.ext. For each unique base name
|
248
|
-
(filename without extension), a branch is created pointing to the newest
|
249
|
-
patch. Existing branches will not be overridden, but there is an implied
|
250
|
-
`git cleanup <patch>` that runs beforehand which could potentially remove
|
251
|
-
conflicting branches first.
|
252
|
-
EOF
|
253
|
-
end
|
254
|
-
|
255
|
-
def add_options(opts)
|
256
|
-
require_ticket_number
|
257
|
-
opts.on("--branch BRANCH","apply against branch BRANCH") do |b|
|
258
|
-
options[:branch] = b
|
259
|
-
end
|
260
|
-
opts.on("--depth NUM","search NUM directories deep for a root") do |n|
|
261
|
-
options[:depth] = n
|
262
|
-
end
|
263
|
-
opts.on("--root DIR","apply patches relative to DIR") do |dir|
|
264
|
-
options[:root] = dir
|
265
|
-
end
|
266
|
-
add_local_option(opts)
|
267
|
-
end
|
268
|
-
|
269
|
-
def run
|
270
|
-
svn_fetch_unless_local
|
271
|
-
each_ticket_argument do |number, filename|
|
272
|
-
loop_opts = options.dup
|
273
|
-
if filename
|
274
|
-
loop_opts[:filter] = "\\A#{Regexp.escape(filename)}"
|
152
|
+
return
|
275
153
|
end
|
276
|
-
@repository.
|
277
|
-
|
278
|
-
|
279
|
-
elsif dir
|
280
|
-
puts "#{attachment.tag_name} (#{dir})"
|
281
|
-
else
|
282
|
-
$stderr.puts "#{attachment.tag_name} FAILED"
|
154
|
+
@repository.in_work_tree do
|
155
|
+
unless system(command)
|
156
|
+
abort "#{command} failed (use --local to skip)"
|
283
157
|
end
|
284
158
|
end
|
285
159
|
end
|
@@ -287,99 +161,13 @@ conflicting branches first.
|
|
287
161
|
|
288
162
|
end
|
289
163
|
|
290
|
-
class Cleanup < Base #:nodoc:
|
291
|
-
|
292
|
-
def banner_arguments
|
293
|
-
"[options] [ticket[/filename]] ..."
|
294
|
-
end
|
295
|
-
|
296
|
-
def description
|
297
|
-
<<-EOF
|
298
|
-
Remove remote heads for a given ticket (e.g., trac/12345/work_patch). Also
|
299
|
-
removes branches that point to one of these heads. Branches that have been
|
300
|
-
committed to will not be removed. The default is to target tickets that have
|
301
|
-
been closed, but you can also specify ticket numbers explicitly or use --all.
|
302
|
-
EOF
|
303
|
-
end
|
304
|
-
|
305
|
-
def add_options(opts)
|
306
|
-
opts.separator("Options:")
|
307
|
-
opts.on("-a","--all", "cleanup all tickets") { options[:all] = true }
|
308
|
-
end
|
309
|
-
|
310
|
-
def run
|
311
|
-
if options[:all]
|
312
|
-
@repository.working_tickets.each do |t|
|
313
|
-
t.cleanup
|
314
|
-
end
|
315
|
-
elsif @argv.any?
|
316
|
-
each_ticket_argument do |number, filename|
|
317
|
-
@repository.ticket(number).cleanup(:attachment => filename)
|
318
|
-
end
|
319
|
-
else
|
320
|
-
@repository.working_tickets.each do |t|
|
321
|
-
t.cleanup unless t.open?
|
322
|
-
end
|
323
|
-
end
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
class UploadPatch < Base #:nodoc:
|
328
|
-
|
329
|
-
def banner_arguments
|
330
|
-
"[options] [ticket]"
|
331
|
-
end
|
332
|
-
|
333
|
-
def description
|
334
|
-
<<-EOF
|
335
|
-
Do a `git diff` against trunk (or another branch) and upload the result as an
|
336
|
-
attachment to a ticket. This command is experimental to take care when using
|
337
|
-
it against a production trac server.
|
338
|
-
EOF
|
339
|
-
end
|
340
|
-
|
341
|
-
def add_options(opts)
|
342
|
-
require_ticket_number
|
343
|
-
opts.on("--branch BRANCH", "git diff BRANCH (default trunk...HEAD)") do |b|
|
344
|
-
options[:branch] = b
|
345
|
-
end
|
346
|
-
opts.on("--description TEXT", "use TEXT as description") do |text|
|
347
|
-
options[:description] = text
|
348
|
-
end
|
349
|
-
opts.on("--[no-]force", "do not prompt before uploading") do |force|
|
350
|
-
options[:force] = force
|
351
|
-
end
|
352
|
-
add_local_option(opts)
|
353
|
-
end
|
354
|
-
|
355
|
-
def run
|
356
|
-
number = get_ticket_number
|
357
|
-
svn_fetch_unless_local
|
358
|
-
ticket = @repository.ticket(number)
|
359
|
-
if $stdin.tty? && !options[:force]
|
360
|
-
block = lambda do
|
361
|
-
@repository.in_work_tree do
|
362
|
-
system("git","diff", options[:branch] || "trunk...HEAD")
|
363
|
-
end
|
364
|
-
description = "##{number} (#{ticket.csv["summary"]}"
|
365
|
-
cols = ENV["COLUMNS"].to_i
|
366
|
-
cols = 80 if cols.zero?
|
367
|
-
description.sub!(/^(.{#{cols-22}}).{4,}/,"\\1...")
|
368
|
-
print "#{description}) Proceed? [yN] "
|
369
|
-
$stdin.gets[0,1] == "y"
|
370
|
-
end
|
371
|
-
else
|
372
|
-
block = lambda { true }
|
373
|
-
end
|
374
|
-
if uri = ticket.upload_patch(options,&block)
|
375
|
-
puts uri
|
376
|
-
else
|
377
|
-
exit 1
|
378
|
-
end
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
164
|
end
|
383
|
-
|
384
165
|
end
|
385
166
|
end
|
167
|
+
|
168
|
+
require 'git/trac/runner/apply'
|
169
|
+
require 'git/trac/runner/cleanup'
|
170
|
+
require 'git/trac/runner/download'
|
171
|
+
require 'git/trac/runner/fetch'
|
172
|
+
require 'git/trac/runner/show'
|
173
|
+
require 'git/trac/runner/upload_patch'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Apply < Base #:nodoc:
|
6
|
+
|
7
|
+
def banner_arguments
|
8
|
+
"[ticket[/filename]]"
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
<<-EOF
|
13
|
+
Apply a patch directly to the work tree. This command is a building block used
|
14
|
+
by other, more powerful commands and most users will never need to invoke it
|
15
|
+
directly.
|
16
|
+
|
17
|
+
The argument can be either a "ticket/filename" specifiying the patch to apply,
|
18
|
+
or simply ticket, in which case the tickets's last patch is applied. If no
|
19
|
+
argument is given, the patch is read from stdin.
|
20
|
+
|
21
|
+
The --depth option specifies how many directories deep to recursively attempt
|
22
|
+
to apply the patch in. For a depth of 2, the patch will be attempted in the
|
23
|
+
repositority root, foo, foo/bar, but not foo/baz. It will also try stripping
|
24
|
+
off up to 2 directories off of the paths inside the patch, the same as -p0,
|
25
|
+
-p1, and -p2 with patch(1). The option is ignored if the patch appears to have
|
26
|
+
been generated with git. The default value is 0, but this can be changed with
|
27
|
+
the trac.depth configuration option.
|
28
|
+
|
29
|
+
If the patch fails to apply even after a depth search, and the patch contains
|
30
|
+
carriage returns, a second pass is made with those carriage returns stripped.
|
31
|
+
EOF
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_options(opts)
|
35
|
+
require_ticket_number
|
36
|
+
opts.on("--depth NUM","search NUM directories deep for a root") do |n|
|
37
|
+
options[:depth] = n
|
38
|
+
end
|
39
|
+
opts.on("--root DIR","apply patches relative to DIR") do |dir|
|
40
|
+
options[:root] = dir
|
41
|
+
end
|
42
|
+
add_local_option(opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
if @argv.empty?
|
47
|
+
patch = Patch.new(@repository,$stdin.read)
|
48
|
+
elsif @argv.size > 1
|
49
|
+
abort "too many arguments"
|
50
|
+
else
|
51
|
+
each_ticket_argument do |number, filename|
|
52
|
+
patch = @repository.ticket(number).attachment(filename).patch
|
53
|
+
end
|
54
|
+
end
|
55
|
+
abort "no changes" if patch.body.empty?
|
56
|
+
unless patch.apply(options)
|
57
|
+
abort "patch failed to apply"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Cleanup < Base #:nodoc:
|
6
|
+
|
7
|
+
def banner_arguments
|
8
|
+
"[options] [ticket[/filename]] ..."
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
<<-EOF
|
13
|
+
Remove remote heads for a given ticket (e.g., trac/12345/work_patch). Also
|
14
|
+
removes branches that point to one of these heads. Branches that have been
|
15
|
+
committed to will not be removed. The default is to target tickets that have
|
16
|
+
been closed, but you can also specify ticket numbers explicitly or use --all.
|
17
|
+
EOF
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_options(opts)
|
21
|
+
opts.separator("Options:")
|
22
|
+
opts.on("-a","--all", "cleanup all tickets") { options[:all] = true }
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
if options[:all]
|
27
|
+
@repository.working_tickets.each do |t|
|
28
|
+
t.cleanup
|
29
|
+
end
|
30
|
+
elsif @argv.any?
|
31
|
+
each_ticket_argument do |number, filename|
|
32
|
+
@repository.ticket(number).cleanup(:attachment => filename)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@repository.working_tickets.each do |t|
|
36
|
+
t.cleanup unless t.open?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Download < Base #:nodoc:
|
6
|
+
|
7
|
+
def description
|
8
|
+
<<-EOF
|
9
|
+
Download all attachments that look like patches to the current working
|
10
|
+
directory.
|
11
|
+
|
12
|
+
This command is a candidate for removal. Individual patches can be downloaded
|
13
|
+
by redirecting git-trac show to a file.
|
14
|
+
EOF
|
15
|
+
end
|
16
|
+
|
17
|
+
def banner_arguments
|
18
|
+
"[options] [ticket[/filename]] ..."
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_options(opts)
|
22
|
+
require_ticket_number
|
23
|
+
opts.separator("Options:")
|
24
|
+
opts.on("--root DIR","prefix patch paths with DIR") do |dir|
|
25
|
+
options[:root] = dir
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
each_ticket_argument do |number, filename|
|
31
|
+
@repository.ticket(number).attachments.each do |attach|
|
32
|
+
next if filename && attach.filename != filename
|
33
|
+
File.open(attach.filename, "w") do |f|
|
34
|
+
f.puts attach.patch.with_root(options[:root])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Fetch < Base #:nodoc:
|
6
|
+
|
7
|
+
def banner_arguments
|
8
|
+
"[options] [ticket[/filename]] ..."
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
<<-EOF
|
13
|
+
Download all branches that look like patches. For each patch, find the
|
14
|
+
revision of trunk that most recently proceeds the the time of upload, apply
|
15
|
+
the patch to it, create a new commit, and add a remote head of the form
|
16
|
+
refs/remotes/trac/ticketnumber/file_name.ext.
|
17
|
+
|
18
|
+
For each unique base name (filename without extension), a branch is created
|
19
|
+
pointing to the newest patch. Existing branches will not be overridden, but
|
20
|
+
there is an implied `git cleanup <patch>` that runs beforehand which could
|
21
|
+
potentially remove conflicting branches first. Automatic branch creation is
|
22
|
+
informally deprecated and may be removed in a future release.
|
23
|
+
EOF
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_options(opts)
|
27
|
+
require_ticket_number
|
28
|
+
opts.on("--branch BRANCH","apply against branch BRANCH") do |b|
|
29
|
+
options[:branch] = b
|
30
|
+
end
|
31
|
+
opts.on("--depth NUM","search depth (see git-trac help apply)") do |n|
|
32
|
+
options[:depth] = n
|
33
|
+
end
|
34
|
+
opts.on("--root DIR","apply patches relative to DIR") do |dir|
|
35
|
+
options[:root] = dir
|
36
|
+
end
|
37
|
+
add_local_option(opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
fetch_unless_local
|
42
|
+
each_ticket_argument do |number, filename|
|
43
|
+
loop_opts = options.dup
|
44
|
+
if filename
|
45
|
+
loop_opts[:filter] = "\\A#{Regexp.escape(filename)}"
|
46
|
+
end
|
47
|
+
@repository.ticket(number).fetch(loop_opts) do |attachment, dir|
|
48
|
+
if dir == "."
|
49
|
+
puts "#{attachment.tag_name}"
|
50
|
+
elsif dir
|
51
|
+
puts "#{attachment.tag_name} (#{dir})"
|
52
|
+
else
|
53
|
+
$stderr.puts "#{attachment.tag_name} FAILED"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class Show < Base #:nodoc:
|
6
|
+
|
7
|
+
def banner_arguments
|
8
|
+
"[ticket[/filename]]"
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
<<-EOF
|
13
|
+
Show the given ticket attachment. The patch will be highlighted in a manner
|
14
|
+
If the filename is omitted, show a list of attachments for the ticket.
|
15
|
+
|
16
|
+
Attachments can be downloaded by redirecting to a file. The following example
|
17
|
+
shows how one might download all patches for a given ticket:
|
18
|
+
|
19
|
+
for patch in $(git-trac show 123); do
|
20
|
+
git-trac show $patch > $(basename $patch)
|
21
|
+
done
|
22
|
+
EOF
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
each_ticket_argument do |number, filename|
|
27
|
+
ticket = @repository.ticket(number)
|
28
|
+
if filename
|
29
|
+
attachment = ticket.attachment(filename)
|
30
|
+
@repository.pager(attachment.body, filename =~ /\.(diff|patch)$/)
|
31
|
+
else
|
32
|
+
body = ticket.attachments.map do |attachment|
|
33
|
+
"#{number}/#{attachment.filename}\n"
|
34
|
+
end.join
|
35
|
+
exit(1) if body.empty?
|
36
|
+
@repository.pager(body)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Git
|
2
|
+
module Trac
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
class UploadPatch < Base #:nodoc:
|
6
|
+
|
7
|
+
def banner_arguments
|
8
|
+
"[options] [ticket]"
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
<<-EOF
|
13
|
+
Do a `git diff` against trunk (or another branch) and upload the result as an
|
14
|
+
attachment to a ticket. The potential patch will be shown in a pager and you
|
15
|
+
will be given the opportunity to cancel.
|
16
|
+
EOF
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_options(opts)
|
20
|
+
require_ticket_number
|
21
|
+
opts.on("--branch BRANCH", "git diff BRANCH (default trunk...HEAD)") do |b|
|
22
|
+
options[:branch] = b
|
23
|
+
end
|
24
|
+
opts.on("--description TEXT", "use TEXT as description") do |text|
|
25
|
+
options[:description] = text
|
26
|
+
end
|
27
|
+
opts.on("--[no-]force", "do not prompt before uploading") do |force|
|
28
|
+
options[:force] = force
|
29
|
+
end
|
30
|
+
add_local_option(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def run
|
34
|
+
number = get_ticket_number
|
35
|
+
fetch_unless_local
|
36
|
+
ticket = @repository.ticket(number)
|
37
|
+
if $stdin.tty? && !options[:force]
|
38
|
+
block = lambda do
|
39
|
+
@repository.in_work_tree do
|
40
|
+
system("git","diff", options[:branch] || "trunk...HEAD")
|
41
|
+
end
|
42
|
+
description = "##{number} (#{ticket.csv["summary"]}"
|
43
|
+
cols = ENV["COLUMNS"].to_i
|
44
|
+
cols = 80 if cols.zero?
|
45
|
+
description.sub!(/^(.{#{cols-22}}).{4,}/,"\\1...")
|
46
|
+
print "#{description}) Proceed? [yN] "
|
47
|
+
$stdin.gets[0,1] == "y"
|
48
|
+
end
|
49
|
+
else
|
50
|
+
block = lambda { true }
|
51
|
+
end
|
52
|
+
if uri = ticket.upload_patch(options,&block)
|
53
|
+
puts uri
|
54
|
+
else
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/git/trac/ticket.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'hpricot'
|
2
1
|
require 'fileutils'
|
3
2
|
|
4
3
|
module Git
|
@@ -25,10 +24,17 @@ module Git
|
|
25
24
|
"#{@repository.url}/attachment/ticket/#{@number}"
|
26
25
|
end
|
27
26
|
|
28
|
-
def
|
27
|
+
def get_response(uri)
|
29
28
|
require 'net/http'
|
30
29
|
require 'uri'
|
31
|
-
|
30
|
+
Net::HTTP.get_response(URI.parse(uri))
|
31
|
+
end
|
32
|
+
|
33
|
+
def csv
|
34
|
+
response = get_response(url(:tab))
|
35
|
+
no_such_ticket if response.kind_of?(Net::HTTPInternalServerError)
|
36
|
+
response.error! unless response.kind_of?(Net::HTTPSuccess)
|
37
|
+
body = response.body
|
32
38
|
headers, values = body.split(/\r?\n/).map do |line|
|
33
39
|
line.split("\t").map do |column|
|
34
40
|
column.gsub(/\\(.)/) do
|
@@ -46,10 +52,25 @@ module Git
|
|
46
52
|
|
47
53
|
attr_reader :number
|
48
54
|
|
49
|
-
def attachments
|
50
|
-
|
51
|
-
|
52
|
-
|
55
|
+
def attachments
|
56
|
+
response = get_response(attachment_url)
|
57
|
+
if html = response.body[/<dl class="attachments">.*?<\/dl>/m]
|
58
|
+
return Attachment.from_html(self,html)
|
59
|
+
elsif response.kind_of?(Net::HTTPSuccess)
|
60
|
+
return []
|
61
|
+
else
|
62
|
+
response.error!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def attachment(name)
|
67
|
+
if name
|
68
|
+
attachments.detect {|a| a.filename == name} or
|
69
|
+
raise Git::Trac::Error, "no such attachment #{number}/#{name}"
|
70
|
+
else
|
71
|
+
attachments.last or
|
72
|
+
raise Git::Trac::Error, "no attachments for #{number}"
|
73
|
+
end
|
53
74
|
end
|
54
75
|
|
55
76
|
def trac_dir
|
@@ -148,6 +169,12 @@ module Git
|
|
148
169
|
end
|
149
170
|
end
|
150
171
|
|
172
|
+
private
|
173
|
+
|
174
|
+
def no_such_ticket
|
175
|
+
raise Git::Trac::Error, "no such ticket #{number}", caller
|
176
|
+
end
|
177
|
+
|
151
178
|
end
|
152
179
|
|
153
180
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
$: << File.join(File.dirname(File.dirname(__FILE__)),'lib')
|
4
|
+
require 'git/trac/attachment'
|
5
|
+
|
6
|
+
class AttacmentTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
HTML = <<-EOF
|
9
|
+
<dl class="attachments">
|
10
|
+
<dd>red herring description</dd>
|
11
|
+
<dt><a href="/attachment/ticket/123/first.patch" title="View attachment">first.patch</a> (456 bytes) - added by <em>tpope</em> on 01/02/08 12:34:56.</dt>
|
12
|
+
<dd>foo & bar</dd>
|
13
|
+
<dt><a href="/attachment/ticket/123/second.diff" title="View attachment">second.diff</a> (7 kB) - added by <em>jhope</em> on 02/01/08 23:45:01.</dt>
|
14
|
+
</dl>
|
15
|
+
EOF
|
16
|
+
|
17
|
+
def test_should_parse_html
|
18
|
+
attachments = Git::Trac::Attachment.from_html(nil, HTML)
|
19
|
+
assert_equal 2, attachments.size
|
20
|
+
first, second = *attachments
|
21
|
+
assert_equal "first.patch", first.filename
|
22
|
+
assert_equal "tpope", first.user
|
23
|
+
assert_equal Time.utc(2008,1,2,12,34,56), first.time
|
24
|
+
assert_equal "foo & bar", first.description
|
25
|
+
assert_equal "second.diff", second.filename
|
26
|
+
assert_equal "jhope", second.user
|
27
|
+
assert_equal "2008-02-01_23:45:01_+0000", second.timestamp
|
28
|
+
assert_nil second.description
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git-trac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.20080206
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Pope
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-02-
|
12
|
+
date: 2008-02-06 00:00:00 -06:00
|
13
13
|
default_executable: git-trac
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -41,7 +41,14 @@ files:
|
|
41
41
|
- lib/git/trac/ticket.rb
|
42
42
|
- lib/git/trac/pager.rb
|
43
43
|
- lib/git/trac/patch.rb
|
44
|
+
- lib/git/trac/runner/download.rb
|
45
|
+
- lib/git/trac/runner/show.rb
|
46
|
+
- lib/git/trac/runner/fetch.rb
|
47
|
+
- lib/git/trac/runner/cleanup.rb
|
48
|
+
- lib/git/trac/runner/upload_patch.rb
|
49
|
+
- lib/git/trac/runner/apply.rb
|
44
50
|
- test/execution_test.rb
|
51
|
+
- test/attachment_test.rb
|
45
52
|
has_rdoc: true
|
46
53
|
homepage: http://git-trac.rubyforge.org
|
47
54
|
post_install_message:
|