git-trac 0.0.20080205 → 0.0.20080206

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.from_hpricot(ticket, html)
14
- collection = []
15
- last = nil
16
- return collection unless html
17
- html.children.each do |element|
18
- if !element.elem?
19
- elsif element.name == "dd"
20
- str = ""
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
@@ -27,6 +27,8 @@ module Git
27
27
  io.puts body
28
28
  end
29
29
  end
30
+ rescue Errno::EPIPE
31
+ # Pager was terminated
30
32
  end
31
33
 
32
34
  private
@@ -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 p_level
19
- if body[0,10] == "diff --git" || body[0,6] == "--- a/"
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? && p_level.zero?
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
- repository.popen3("git","apply", "-p#{p_level}", "--cached", "--whitespace=nowarn") do |inn,out,err|
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 && p_level.zero?
58
+ if options[:depth].to_i > 0 && !git?
61
59
  repository.in_work_tree do
62
60
  last_roots = [options[:root]]
63
- options = options.dup
64
- 1.upto(options.delete(:depth).to_i) do |d|
65
- parent = File.join(*(%w(..)*d+[options[:root]]).compact)
66
- result = apply(options.merge(:root => parent)) and return result
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(options.merge(:root => path)) and return result
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
- false
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
@@ -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 svn_fetch_unless_local
145
+ def fetch_unless_local
145
146
  unless options[:local]
146
- @repository.in_work_tree do
147
- unless system("git","svn","fetch")
148
- abort "git svn fetch failed (use --local to skip)"
149
- end
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
- body = ""
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.ticket(number).fetch(loop_opts) do |attachment, dir|
277
- if dir == "."
278
- puts "#{attachment.tag_name}"
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
@@ -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 csv
27
+ def get_response(uri)
29
28
  require 'net/http'
30
29
  require 'uri'
31
- body = Net::HTTP.get_response(URI.parse(url(:tab))).body
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(&block)
50
- entries = repository.agent.get(attachment_url).at("dl.attachments")
51
- require 'git/trac/attachment'
52
- Attachment.from_hpricot(self,entries)
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 &amp; 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.20080205
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-05 00:00:00 -06:00
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: