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.
@@ -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: