git-trac 0.0.20080204 → 0.0.20080205

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,4 +12,5 @@ end
12
12
  require 'git/trac/repository'
13
13
  require 'git/trac/ticket'
14
14
  require 'git/trac/attachment'
15
+ require 'git/trac/patch'
15
16
  require 'git/trac/runner'
@@ -23,7 +23,7 @@ module Git
23
23
  elsif element.name = "dt"
24
24
  texts = []
25
25
  element.traverse_text {|x| texts << x.to_s}
26
- if texts.first =~ /\A([\w-]*)(\.\d+)?\.(diff|patch)\Z/
26
+ if texts.first =~ /./
27
27
  time = Time.parse("#{texts[3]} +0000").utc
28
28
  collection << new(ticket, texts[0], texts[2], time)
29
29
  last = collection.last
@@ -81,46 +81,46 @@ module Git
81
81
  @body ||= repository.agent.get_file(url)
82
82
  end
83
83
 
84
- def p_level
85
- if body[0,10] == "diff --git"
86
- 1
87
- else
88
- 0
89
- end
90
- end
91
-
92
84
  def repository
93
85
  ticket.repository
94
86
  end
95
87
 
96
- # Rewrite filenames in a patch to be relative to +root+. For each .. at
97
- # the end of +root+, a path is stripped off the original filename. The
98
- # rewritten patch is returned as a string.
99
- def patch_with_root(root = nil)
100
- if root && !root.empty? && p_level.zero?
101
- patch = ""
102
- body.each_line do |line|
103
- line.sub!(/^([+-]{3} |Index: )([^\/].*)$/) do
104
- head = $1
105
- original = File.join(root, $2)
106
- while original.sub!(%r{(.*/|^)\.\./[^/]*/},'\\1')
107
- end
108
- head + original
109
- end
110
- patch << line
111
- end
112
- patch
113
- else
114
- body
88
+ def patch
89
+ @patch ||= Git::Trac::Patch.new(repository, lambda { body })
90
+ end
91
+
92
+ def cleanup(options = {})
93
+ repository.each_ref("refs/remotes/#{tag_name}") do |object, ref|
94
+ repository.exec("git","update-ref","-d",ref,object)
95
+ repository.cleanup_branches(object)
96
+ return object
115
97
  end
116
98
  end
117
99
 
118
- def apply(options = {})
119
- repository.popen3("git","apply", "-p#{p_level}", "--cached", "--whitespace=nowarn") do |inn,out,err|
120
- inn.puts patch_with_root(options[:root])
121
- inn.close
122
- err.read.empty?
100
+ def fetch(options = {})
101
+ cleanup(options)
102
+ FileUtils.mkdir_p(ticket.trac_dir)
103
+ repository.with_index("tracindex#{$$}") do
104
+ parent = repository.exec("git","rev-list","--max-count=1","--before=#{timestamp}",options[:branch] || 'trunk').chomp
105
+ repository.exec("git","read-tree",parent)
106
+ applied = patch.apply(options) or return [nil,applied]
107
+ tree = repository.exec("git","write-tree").chomp
108
+ ENV["GIT_AUTHOR_NAME"] = ENV["GIT_COMMITTER_NAME"] = username
109
+ ENV["GIT_AUTHOR_EMAIL"] = ENV["GIT_COMMITTER_EMAIL"] = email
110
+ ENV["GIT_AUTHOR_DATE"] = ENV["GIT_COMMITTER_DATE"] = timestamp
111
+ commit = repository.popen3("git commit-tree #{tree} -p #{parent}") do |i,o,e|
112
+ i.puts "#{ticket.number}/#{filename}"
113
+ i.puts "\n#{description}" if description
114
+ i.close
115
+ o.read.chomp
116
+ end
117
+ File.open(tag_path,"w") do |f|
118
+ f.puts commit
119
+ end
120
+ return [commit, applied]
123
121
  end
122
+ ensure
123
+ %w(GIT_AUTHOR_NAME GIT_COMMITTER_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_EMAIL GIT_AUTHOR_DATE GIT_COMMITTER_DATE).each {|e| ENV[e] = nil}
124
124
  end
125
125
 
126
126
  end
@@ -0,0 +1,62 @@
1
+ module Git
2
+ module Trac
3
+
4
+ class Repository
5
+
6
+ DIFF_COLORS = {
7
+ :reset => "\033[m",
8
+ :plain => "",
9
+ :meta => "\033[1m",
10
+ :frag => "\033[36m",
11
+ :old => "\033[31m",
12
+ :new => "\033[32m",
13
+ :commit => "\033[33m",
14
+ :whitespace => "\033[41m"
15
+ }
16
+
17
+ def pager(body, diff = false)
18
+ pager = ENV["GIT_PAGER"] || config("core","pager") || ENV["PAGER"] || "less"
19
+ return colorize_diff_output($stdout, body) if pager.empty? && diff
20
+ return $stdout.puts(body) if !$stdout.tty? || pager.empty?
21
+ ENV["LESS"] ||= "FRSX"
22
+
23
+ IO.popen(pager, "w") do |io|
24
+ if diff && !%w(false no 0).include?(config("color","pager").to_s.downcase)
25
+ colorize_diff_output(io, body)
26
+ else
27
+ io.puts body
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def colorize_diff_output(io, body)
35
+ if %w(true yes 1 always auto).include?(config("color","diff").to_s.downcase)
36
+ state = nil
37
+ body.each_line do |line|
38
+ if state.nil? && line !~ /^(diff|Index:) /
39
+ io.puts line
40
+ next
41
+ end
42
+ color = case line
43
+ when /^diff/ then state = true; :meta
44
+ when /^index/i then state = true; :meta
45
+ when /^@@/ then state = false; :frag
46
+ when /^\+/ then state ? :meta : :new
47
+ when /^-/ then state ? :meta : :old
48
+ when /^ / then state = false; :normal
49
+ when "" then state = nil
50
+ else state ? :meta : :normal
51
+ end
52
+ io.puts "#{DIFF_COLORS[color]}#{line}"
53
+ end
54
+ else
55
+ io.puts body
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,86 @@
1
+ module Git
2
+ module Trac
3
+
4
+ class Patch
5
+
6
+ attr_reader :repository
7
+
8
+ def initialize(repository, body)
9
+ @repository, @body = repository, body
10
+ end
11
+
12
+ def body
13
+ @body.respond_to?(:call) ? @body.call : body
14
+ end
15
+
16
+ alias to_s body
17
+
18
+ def p_level
19
+ if body[0,10] == "diff --git" || body[0,6] == "--- a/"
20
+ 1
21
+ else
22
+ 0
23
+ end
24
+ end
25
+
26
+ # Rewrite filenames in a patch to be relative to +root+. For each .. at
27
+ # the end of +root+, a path is stripped off the original filename. The
28
+ # rewritten patch is returned as a string.
29
+ def with_root(root = nil)
30
+ if root && !root.empty? && p_level.zero?
31
+ patch = ""
32
+ body.each_line do |line|
33
+ line.sub!(/^([+-]{3} |Index: )(?!\/dev\/null)(.*)$/) do
34
+ head = $1
35
+ original = File.join(root, $2)
36
+ while original.sub!(%r{(.*/|^)\.\./[^/]*/},'\\1')
37
+ end
38
+ head + original
39
+ end
40
+ patch << line
41
+ end
42
+ patch
43
+ else
44
+ body
45
+ end
46
+ end
47
+
48
+ def apply(options = {})
49
+ repository.popen3("git","apply", "-p#{p_level}", "--cached", "--whitespace=nowarn") do |inn,out,err|
50
+ inn.puts with_root(options[:root])
51
+ inn.close
52
+ if err.read.empty?
53
+ if options[:root]
54
+ return options[:root].sub(%r{((?:^\.\.|/\.\.)+)/([^.].*)},'\\2/\\1')
55
+ else
56
+ return "."
57
+ end
58
+ end
59
+ end
60
+ if options[:depth].to_i > 0 && p_level.zero?
61
+ repository.in_work_tree do
62
+ 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
67
+ roots = []
68
+ last_roots.each do |root|
69
+ Dir.entries(root || ".").each do |dir|
70
+ path = File.join(*[root,dir].compact)
71
+ next if dir =~ /\A\./ || !File.directory?(path)
72
+ roots << path
73
+ result = apply(options.merge(:root => path)) and return result
74
+ end
75
+ end
76
+ last_roots = roots
77
+ end
78
+ end
79
+ end
80
+ false
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
@@ -192,6 +192,15 @@ module Git
192
192
  hash
193
193
  end
194
194
 
195
+ def cleanup_branches(*revs)
196
+ return if revs.empty?
197
+ each_ref("refs/heads") do |object, ref|
198
+ if revs.include?(object) && current_checkout != File.basename(ref)
199
+ exec("git","branch","-D",File.basename(ref))
200
+ end
201
+ end
202
+ end
203
+
195
204
  # See if the current commit or an ancestor was previously tagged as a
196
205
  # trac ticket. Also check git config branch.branchname.tracticket
197
206
  def guess_current_ticket_number
@@ -220,3 +229,5 @@ module Git
220
229
 
221
230
  end
222
231
  end
232
+
233
+ require 'git/trac/pager.rb'
@@ -135,6 +135,22 @@ Available commands:
135
135
  self.class.name[/[^:]*$/].gsub(/(.)([A-Z])/) { $1+"-"+$2 }.downcase
136
136
  end
137
137
 
138
+ def add_local_option(opts)
139
+ opts.on("--[no-]local","don't run git-svn fetch") do |bool|
140
+ options[:local] = bool
141
+ end
142
+ end
143
+
144
+ def svn_fetch_unless_local
145
+ 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
+
138
154
  end
139
155
 
140
156
  class Download < Base #:nodoc:
@@ -153,9 +169,6 @@ directory.
153
169
  def add_options(opts)
154
170
  require_ticket_number
155
171
  opts.separator("Options:")
156
- opts.on("--filter PATTERN","only get matching filenames (deprecated)") do |pattern|
157
- options[:filter] = pattern
158
- end
159
172
  opts.on("--root DIR","prefix patch paths with DIR") do |dir|
160
173
  options[:root] = dir
161
174
  end
@@ -163,15 +176,10 @@ directory.
163
176
 
164
177
  def run
165
178
  each_ticket_argument do |number, filename|
166
- filter = if filename
167
- "\\A#{Regexp.escape(filename)}"
168
- else
169
- options[:filter]
170
- end
171
179
  @repository.ticket(number).attachments.each do |attach|
172
- next if filter && attach.filename !~ /#{filter}/
180
+ next if filename && attach.filename != filename
173
181
  File.open(attach.filename, "w") do |f|
174
- f.puts attach.patch_with_root(options[:root])
182
+ f.puts attach.patch.with_root(options[:root])
175
183
  end
176
184
  end
177
185
  end
@@ -182,12 +190,12 @@ directory.
182
190
  class Show < Base #:nodoc:
183
191
 
184
192
  def banner_arguments
185
- "[ticket]"
193
+ "[ticket[/filename]]"
186
194
  end
187
195
 
188
196
  def description
189
197
  <<-EOF
190
- Show a crude ticket summary.
198
+ Show a crude ticket summary or a patch.
191
199
  EOF
192
200
  end
193
201
 
@@ -196,13 +204,31 @@ Show a crude ticket summary.
196
204
  end
197
205
 
198
206
  def run
199
- number = get_ticket_number
200
- csv = @repository.ticket(number).csv
201
- csv.reject {|k,v| k == "description"}.sort.each do |(k,v)|
202
- puts "#{k}: #{v}" if v
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)
214
+ 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
203
231
  end
204
- puts "description:"
205
- puts csv["description"]
206
232
  end
207
233
 
208
234
  end
@@ -218,13 +244,11 @@ Show a crude ticket summary.
218
244
  Download all branches that look like patches. For each patch, find the
219
245
  revision of trunk that most recently proceeds the the time of upload, apply
220
246
  the patch to it, create a new commit, and add a remote head of the form
221
- refs/remotes/trac/ticketnumber/file_name_ext. Patches with the same root
222
- name (filename.diff, filename.2.diff, filename.patch, etc.) will committed in
223
- parent child relationships based on upload time. This means filename.2.diff
224
- might cite filename.diff as a parent. For each root name, a branch is created
225
- pointing to the newest patch. Existing branches will not be overridden, but
226
- there is an implied `git cleanup <patch>` that runs beforehand which could
227
- potentially remove conflicted branches first.
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.
228
252
  EOF
229
253
  end
230
254
 
@@ -233,33 +257,29 @@ potentially remove conflicted branches first.
233
257
  opts.on("--branch BRANCH","apply against branch BRANCH") do |b|
234
258
  options[:branch] = b
235
259
  end
236
- opts.on("--filter PATTERN","only fetch matching filenames (deprecated)") do |pattern|
237
- options[:filter] = pattern
260
+ opts.on("--depth NUM","search NUM directories deep for a root") do |n|
261
+ options[:depth] = n
238
262
  end
239
263
  opts.on("--root DIR","apply patches relative to DIR") do |dir|
240
264
  options[:root] = dir
241
265
  end
242
- opts.on("--[no-]local","don't run git-svn fetch") do |bool|
243
- options[:local] = bool
244
- end
266
+ add_local_option(opts)
245
267
  end
246
268
 
247
269
  def run
248
- unless options[:local]
249
- @repository.in_work_tree do
250
- system("git","svn","fetch")
251
- end
252
- end
270
+ svn_fetch_unless_local
253
271
  each_ticket_argument do |number, filename|
254
272
  loop_opts = options.dup
255
273
  if filename
256
274
  loop_opts[:filter] = "\\A#{Regexp.escape(filename)}"
257
275
  end
258
- @repository.ticket(number).fetch(loop_opts) do |attachment, branch|
259
- if branch
260
- puts "#{attachment.filename}: #{branch}"
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})"
261
281
  else
262
- $stderr.puts "#{attachment.filename} FAILED"
282
+ $stderr.puts "#{attachment.tag_name} FAILED"
263
283
  end
264
284
  end
265
285
  end
@@ -294,8 +314,7 @@ been closed, but you can also specify ticket numbers explicitly or use --all.
294
314
  end
295
315
  elsif @argv.any?
296
316
  each_ticket_argument do |number, filename|
297
- filter = "\\A#{Regexp.escape(filename.to_s)}"
298
- @repository.ticket(number).cleanup(:filter => filter)
317
+ @repository.ticket(number).cleanup(:attachment => filename)
299
318
  end
300
319
  else
301
320
  @repository.working_tickets.each do |t|
@@ -330,18 +349,12 @@ it against a production trac server.
330
349
  opts.on("--[no-]force", "do not prompt before uploading") do |force|
331
350
  options[:force] = force
332
351
  end
333
- opts.on("--[no-]local","don't run git-svn fetch") do |bool|
334
- options[:local] = bool
335
- end
352
+ add_local_option(opts)
336
353
  end
337
354
 
338
355
  def run
339
356
  number = get_ticket_number
340
- unless options[:local]
341
- @repository.in_work_tree do
342
- system("git","svn","fetch")
343
- end
344
- end
357
+ svn_fetch_unless_local
345
358
  ticket = @repository.ticket(number)
346
359
  if $stdin.tty? && !options[:force]
347
360
  block = lambda do
@@ -122,69 +122,30 @@ module Git
122
122
  def cleanup(options = {})
123
123
  revs = []
124
124
  repository.each_ref("refs/remotes/trac/#{number}") do |object, ref|
125
- if File.basename(ref) =~ /#{options[:filter]}/
125
+ if File.basename(ref) == options[:attachment] || !options[:attachment]
126
126
  revs << object
127
127
  repository.exec("git","update-ref","-d",ref,object)
128
128
  end
129
129
  end
130
- unless revs.empty?
131
- repository.each_ref("refs/heads") do |object, ref|
132
- if revs.include?(object) && repository.current_checkout != File.basename(ref)
133
- repository.exec("git","branch","-D",File.basename(ref))
134
- end
135
- end
136
- true
137
- end
130
+ repository.cleanup_branches(*revs)
131
+ revs.any?
138
132
  end
139
133
 
140
134
  def fetch(options = {})
141
135
  cleanup(options)
142
136
  seen = {}
143
- repository.with_index("tracindex#{$$}") do
144
- FileUtils.mkdir_p(trac_dir)
145
- attachments.select do |attachment|
146
- attachment.filename =~ /#{options[:filter]}/
147
- end.map do |attachment|
148
- parent = repository.exec("git","rev-list","--max-count=1","--before=#{attachment.timestamp}",options[:branch] || 'trunk').chomp
149
- repository.exec("git","read-tree",parent)
150
- unless attachment.apply(options)
151
- yield attachment, false if block_given?
152
- next
153
- end
154
- tree = repository.exec("git","write-tree").chomp
155
- ENV["GIT_AUTHOR_NAME"] = ENV["GIT_COMMITTER_NAME"] = attachment.username
156
- ENV["GIT_AUTHOR_EMAIL"] = ENV["GIT_COMMITTER_EMAIL"] = attachment.email
157
- ENV["GIT_AUTHOR_DATE"] = ENV["GIT_COMMITTER_DATE"] = attachment.timestamp
158
- if repeated = seen[attachment.name]
159
- if repeated.last == parent
160
- parents = " -p #{repeated.first}"
161
- else
162
- parents = " -p #{repeated.first} -p #{parent}"
163
- end
164
- else
165
- parents = " -p #{parent}"
166
- end
167
- commit = repository.popen3("git commit-tree #{tree}#{parents}") do |i,o,e|
168
- i.puts "#{number}/#{attachment.filename}"
169
- i.puts "\n#{attachment.description}" if attachment.description
170
- i.close
171
- o.read.chomp
172
- end
173
- File.open(attachment.tag_path,"w") do |f|
174
- f.puts commit
175
- end
176
- yield attachment, attachment.tag_name if block_given?
177
- seen[attachment.name] = [commit, parent]
178
- commit
179
- end
137
+ attachments.select do |attachment|
138
+ attachment.filename =~ /#{options[:filter] || "\\.(diff|patch)$"}/
139
+ end.map do |attachment|
140
+ commit, applied = attachment.fetch(options)
141
+ yield attachment, applied if block_given?
142
+ seen[attachment.name] = commit if commit
180
143
  end
181
144
  seen.each do |k,v|
182
145
  if !File.exists?(path = "#{repository.git_dir}/refs/heads/#{k}")
183
- File.open(path, "w") {|f| f.puts v.first}
146
+ File.open(path, "w") {|f| f.puts v}
184
147
  end
185
148
  end
186
- ensure
187
- %w(GIT_AUTHOR_NAME GIT_COMMITTER_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_EMAIL GIT_AUTHOR_DATE GIT_COMMITTER_DATE).each {|e| ENV[e] = nil}
188
149
  end
189
150
 
190
151
  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.20080204
4
+ version: 0.0.20080205
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-04 00:00:00 -06:00
12
+ date: 2008-02-05 00:00:00 -06:00
13
13
  default_executable: git-trac
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -39,6 +39,8 @@ files:
39
39
  - lib/git/trac/repository.rb
40
40
  - lib/git/trac/runner.rb
41
41
  - lib/git/trac/ticket.rb
42
+ - lib/git/trac/pager.rb
43
+ - lib/git/trac/patch.rb
42
44
  - test/execution_test.rb
43
45
  has_rdoc: true
44
46
  homepage: http://git-trac.rubyforge.org