reapack-index 1.0beta3 → 1.0beta4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,120 @@
1
+ class ReaPack::Index
2
+ class ConflictDetector
3
+ Entry = Struct.new :key, :platform, :file
4
+
5
+ class Selector
6
+ def initialize(bucket, key, cdetector)
7
+ @bucket, @key, @cdetector = bucket, key, cdetector
8
+ @entries = @cdetector.bucket bucket
9
+ end
10
+
11
+ def push(platform, file)
12
+ @entries << Entry.new(@key, platform, file).freeze
13
+ end
14
+
15
+ def clear
16
+ @entries.reject! {|e| e.key == @key }
17
+ end
18
+
19
+ def resolve
20
+ @cdetector.resolve @bucket, @key
21
+ end
22
+ end
23
+
24
+ def initialize
25
+ @buckets = {}
26
+ end
27
+
28
+ def initialize_clone(other)
29
+ super
30
+ other.instance_variable_set :@buckets,
31
+ Hash[@buckets.map {|k, v| [k, v.clone] }]
32
+ end
33
+
34
+ def [](bucket, key)
35
+ Selector.new bucket, key, self
36
+ end
37
+
38
+ def bucket(name)
39
+ @buckets[name] ||= []
40
+ end
41
+
42
+ def resolve(bucket, key = nil)
43
+ return unless bucket = @buckets[bucket]
44
+
45
+ dups = bucket.group_by {|e| e.file }.values.select {|a| a.size > 1 }
46
+
47
+ errors = dups.map {|a|
48
+ packages = a.map {|e| e.key }.uniq
49
+ next if key && !packages.include?(key)
50
+
51
+ if packages.size == 1 || !key
52
+ original = sort(a)[1]
53
+ msg = "duplicate file '#{original.file}'"
54
+ else
55
+ original = sort(a.select {|e| e.key != key }).first
56
+ msg = "'#{original.file}' conflicts with '#{original.key}'"
57
+ end
58
+
59
+ platforms = a.map {|e| e.platform }.uniq
60
+
61
+ if platforms.size > 1
62
+ # check platform inheritance
63
+ platforms.any? {|p|
64
+ loop do
65
+ p = Source::PLATFORMS[p] or break false
66
+ break true if platforms.include? p
67
+ end
68
+ } or next
69
+ end
70
+
71
+ platform = original.platform
72
+ platform == :all ? msg : "#{msg} on #{platform}"
73
+ }.compact
74
+
75
+ errors unless errors.empty?
76
+ end
77
+
78
+ def load_xml(node)
79
+ Category.find_all(node).each {|cat|
80
+ Package.find_all(cat.node).each {|pkg|
81
+ pkgroot = File.join(cat.name, pkg.name)
82
+
83
+ pkg.versions.last&.children(Source::TAG)&.each {|src|
84
+ entry = Entry.new pkgroot, src[:platform].to_sym
85
+
86
+ if src[:file]
87
+ entry.file = ReaPack::Index.expand(src[:file], cat.name)
88
+ else
89
+ entry.file = pkgroot
90
+ end
91
+
92
+ bucket(pkg.type) << entry
93
+ }
94
+ }
95
+ }
96
+ end
97
+
98
+ private
99
+ def sort(set)
100
+ sorted = set.sort_by {|e| levels[e.platform] }
101
+ sorted.sort_by! {|e| Source::PLATFORMS.keys.index e.platform }
102
+ end
103
+
104
+ def levels
105
+ @@levels ||= begin
106
+ Hash[Source::PLATFORMS.map {|name, parent|
107
+ levels = 0
108
+
109
+ loop do
110
+ break unless parent
111
+ levels += 1
112
+ parent = Source::PLATFORMS[parent]
113
+ end
114
+
115
+ [name, levels]
116
+ }]
117
+ end
118
+ end
119
+ end
120
+ end
@@ -1,17 +1,24 @@
1
1
  class ReaPack::Index::CLI
2
+ attr_reader :index
3
+
2
4
  def initialize(argv = [])
3
5
  @opts = parse_options(argv)
4
6
  path = argv.last || Dir.pwd
5
7
 
6
8
  return unless @exit.nil?
7
9
 
8
- @git = Rugged::Repository.discover path
10
+ @git = ReaPack::Index::Git.new path
9
11
  @opts = parse_options(read_config).merge @opts unless @opts[:noconfig]
10
12
 
11
13
  @opts = DEFAULTS.merge @opts
14
+ return unless @exit.nil?
15
+
16
+ log Hash[@opts.sort].inspect
12
17
 
13
- log Hash[@opts.sort].inspect if @exit.nil?
14
- rescue Rugged::OSError, Rugged::RepositoryError => e
18
+ @index = ReaPack::Index.new expand_path(@opts[:output])
19
+ @index.amend = @opts[:amend]
20
+ set_url_template
21
+ rescue Rugged::OSError, Rugged::RepositoryError, ReaPack::Index::Error => e
15
22
  $stderr.puts e.message
16
23
  @exit = false
17
24
  end
@@ -19,11 +26,8 @@ class ReaPack::Index::CLI
19
26
  def run
20
27
  return @exit unless @exit.nil?
21
28
 
22
- @db = ReaPack::Index.new expand_path(@opts[:output])
23
- @db.amend = @opts[:amend]
24
-
25
29
  if @opts[:check]
26
- return check
30
+ return do_check
27
31
  end
28
32
 
29
33
  if @opts[:lslinks]
@@ -32,135 +36,101 @@ class ReaPack::Index::CLI
32
36
  end
33
37
 
34
38
  if @opts[:dump_about]
35
- print @db.description
39
+ print @index.description
36
40
  return true
37
41
  end
38
42
 
39
- begin
40
- tpl = @opts[:url_template]
41
- is_custom = tpl != DEFAULTS[:url_template]
43
+ do_name; do_about; eval_links; do_scan
42
44
 
43
- @db.url_template = is_custom ? tpl : auto_url_tpl
44
- rescue ReaPack::Index::Error => e
45
- warn '--url-template: ' + e.message if is_custom
46
- end
47
-
48
- do_name; do_about; eval_links; scan_commits
49
-
50
- unless @db.modified?
45
+ unless @index.modified?
51
46
  $stderr.puts 'Nothing to do!' unless @opts[:quiet]
52
- return true
47
+ return success_code
53
48
  end
54
49
 
55
50
  # changelog will be cleared by Index#write!
56
- changelog = @db.changelog
51
+ changelog = @index.changelog
57
52
  puts changelog unless @opts[:quiet]
58
53
 
59
- @db.write!
54
+ @index.write!
60
55
  commit changelog
61
56
 
62
- true
57
+ success_code
63
58
  end
64
59
 
65
60
  private
66
- def scan_commits
61
+ def success_code
62
+ @exit.nil? ? true : @exit
63
+ end
64
+
65
+ def set_url_template
66
+ tpl = @opts[:url_template]
67
+ is_custom = tpl != DEFAULTS[:url_template]
68
+
69
+ @index.url_template = is_custom ? tpl : @git.guess_url_template
70
+ rescue ReaPack::Index::Error => e
71
+ warn '--url-template: ' + e.message if is_custom
72
+ end
73
+
74
+ def do_scan
67
75
  if @git.empty?
68
76
  warn 'The current branch does not contains any commit.'
69
77
  return
70
78
  end
71
79
 
72
- walker = Rugged::Walker.new @git
73
- walker.sorting Rugged::SORT_TOPO | Rugged::SORT_REVERSE
74
- walker.push @git.head.target_id
75
-
76
- last_commit = @db.commit.to_s
77
- if Rugged.valid_full_oid?(last_commit) && last_commit.size <= 40
78
- walker.hide last_commit if @git.include? last_commit
80
+ if @opts[:scan].empty?
81
+ commits = @git.commits_since @index.commit.to_s
82
+ else
83
+ commits = @opts[:scan].map {|hash|
84
+ @git.get_commit hash or begin
85
+ $stderr.puts '--scan: bad revision: %s' % @opts[:scan]
86
+ @exit = false
87
+ nil
88
+ end
89
+ }.compact
79
90
  end
80
91
 
81
- commits = walker.each.to_a
82
-
83
- @done, @total = 0, commits.size
84
-
85
92
  unless commits.empty?
86
- print_progress
87
- commits.each {|commit| process commit }
88
- $stderr.print "\n" if @add_nl
93
+ progress_wrapper commits.size do
94
+ commits.each {|commit| process_commit commit }
95
+ end
89
96
  end
90
97
  end
91
98
 
92
- def process(commit)
99
+ def process_commit(commit)
93
100
  if @opts[:verbose]
94
- sha = commit.oid[0..6]
95
- message = commit.message.lines.first.chomp
96
- log "processing %s: %s" % [sha, message]
101
+ log 'processing %s: %s' % [commit.short_id, commit.summary]
97
102
  end
98
103
 
99
- @db.commit = commit.oid
100
- @db.time = commit.time
101
- @db.files = lsfiles commit.tree
104
+ @index.commit = commit.id
105
+ @index.time = commit.time
106
+ @index.files = commit.filelist
102
107
 
103
- parent = commit.parents.first
104
-
105
- if parent
106
- diff = parent.diff commit.oid
107
- else
108
- diff = commit.diff
109
- end
110
-
111
- diff.each_delta {|delta| index delta, parent.nil? }
108
+ commit.each_diff {|diff| process_diff diff }
112
109
  ensure
113
- @done += 1
114
- print_progress
110
+ bump_progress
115
111
  end
116
112
 
117
- def index(delta, is_initial)
118
- if is_initial
119
- status = 'new'
120
- file = delta.old_file
121
- else
122
- status = delta.status
123
- file = delta.new_file
124
- end
113
+ def process_diff(diff)
114
+ return if ignored? expand_path(diff.file)
115
+ return unless ReaPack::Index.type_of diff.file
125
116
 
126
- return if ignored? expand_path(file[:path])
127
- return unless ReaPack::Index.type_of file[:path]
117
+ log "-> indexing #{diff.status} file #{diff.file}"
128
118
 
129
- log "-> indexing #{status} file #{file[:path]}"
130
-
131
- if status == :deleted
132
- @db.remove file[:path]
119
+ if diff.status == :deleted
120
+ @index.remove diff.file
133
121
  else
134
- blob = @git.lookup file[:oid]
135
-
136
122
  begin
137
- @db.scan file[:path], blob.content.force_encoding("UTF-8")
123
+ @index.scan diff.file, diff.new_content
138
124
  rescue ReaPack::Index::Error => e
139
- warn "#{file[:path]}: #{e.message}"
125
+ warn "#{diff.file}:\n#{indent e.message}"
140
126
  end
141
127
  end
142
128
  end
143
129
 
144
- def lsfiles(tree, base = String.new)
145
- files = []
146
-
147
- tree.each {|obj|
148
- fullname = base.empty? ? obj[:name] : File.join(base, obj[:name])
149
- case obj[:type]
150
- when :blob
151
- files << fullname
152
- when :tree
153
- files.concat lsfiles(@git.lookup(obj[:oid]), fullname)
154
- end
155
- }
156
-
157
- files
158
- end
159
-
160
130
  def eval_links
161
131
  Array(@opts[:links]).each {|link|
162
132
  begin
163
- @db.eval_link *link
133
+ @index.eval_link *link
164
134
  rescue ReaPack::Index::Error => e
165
135
  opt = case link.first
166
136
  when :website
@@ -177,7 +147,7 @@ private
177
147
  def print_links
178
148
  ReaPack::Index::Link::VALID_TYPES.each {|type|
179
149
  prefix = "[#{type}]".bold.light_black
180
- @db.links(type).each {|link|
150
+ @index.links(type).each {|link|
181
151
  display = link.name == link.url ? link.url : '%s (%s)' % [link.name, link.url]
182
152
  puts '%s %s' % [prefix, display]
183
153
  }
@@ -185,74 +155,85 @@ private
185
155
  end
186
156
 
187
157
  def do_name
188
- @db.name = @opts[:name] if @opts[:name]
189
-
190
- if @db.name.empty?
191
- warn "The name of this index is unset. " \
192
- "Run the following command with a name of your choice:" \
193
- "\n #{$0} --name 'FooBar Scripts'"
194
- end
158
+ @index.name = @opts[:name] if @opts[:name]
159
+ check_name
195
160
  rescue ReaPack::Index::Error => e
196
161
  warn '--name: ' + e.message
197
162
  end
198
163
 
164
+ def check_name
165
+ if @index.name.empty?
166
+ warn 'This index is unnamed. ' \
167
+ 'Run the following command to set a name of your choice:' \
168
+ "\n #{File.basename $0} --name 'FooBar Scripts'"
169
+ end
170
+ end
171
+
199
172
  def do_about
200
173
  path = @opts[:about]
201
174
 
202
175
  unless path
203
- @db.description = String.new if @opts[:rmabout]
176
+ @index.description = String.new if @opts[:rmabout]
204
177
  return
205
178
  end
206
179
 
207
180
  log "converting #{path} into RTF..."
208
181
 
209
182
  # look for the file in the working directory, not on the repository root
210
- @db.description = File.read(path)
183
+ @index.description = File.read(path)
211
184
  rescue Errno::ENOENT => e
212
185
  warn '--about: ' + e.message.sub(' @ rb_sysopen', '')
213
186
  rescue ReaPack::Index::Error => e
214
187
  warn e.message
215
188
  end
216
189
 
217
- def check
190
+ def do_check
191
+ check_name
192
+
193
+ @index.amend = true # enable checks for released versions as well
218
194
  failures = []
219
- root = @git.workdir
220
195
 
221
- types = ReaPack::Index::FILE_TYPES.keys
222
- files = Dir.glob "#{Regexp.quote(root)}**/*.{#{types.join ','}}"
223
- count = 0
196
+ pkgs = Hash[Dir.glob("#{Regexp.quote(@git.path)}/**/*").sort.map {|abs|
197
+ rel = @git.relative_path abs
198
+ @index.files << rel
224
199
 
225
- files.sort.each {|file|
226
- next if ignored? file
200
+ next if !File.file?(abs) || ignored?(abs) || !ReaPack::Index.is_package?(abs)
227
201
 
228
- errors = ReaPack::Index.validate_file file
229
- count += 1
202
+ [abs, rel]
203
+ }.compact]
230
204
 
231
- if errors
232
- $stderr.print 'F' unless @opts[:quiet]
233
- prefix = "\n - "
234
- file[0..root.size-1] = ''
205
+ pkgs.each_pair {|abs, rel|
206
+ begin
207
+ @index.scan rel, File.read(abs)
235
208
 
236
- failures << "%s contains invalid metadata:#{prefix}%s" %
237
- [file, errors.join(prefix)]
238
- else
239
- $stderr.print '.' unless @opts[:quiet]
209
+ if @opts[:verbose]
210
+ $stderr.puts '%s: passed' % rel
211
+ elsif !@opts[:quiet]
212
+ $stderr.print '.'
213
+ end
214
+ rescue ReaPack::Index::Error => e
215
+ if @opts[:verbose]
216
+ $stderr.puts '%s: failed' % rel
217
+ elsif !@opts[:quiet]
218
+ $stderr.print 'F'
219
+ end
220
+
221
+ failures << "%s failed:\n%s" % [rel, indent(e.message).yellow]
240
222
  end
241
223
  }
242
224
 
243
- $stderr.puts "\n" unless @opts[:quiet]
225
+ $stderr.puts "\n" unless @opts[:quiet] || @opts[:verbose]
244
226
 
245
227
  failures.each_with_index {|msg, index|
246
228
  $stderr.puts unless @opts[:quiet] && index == 0
247
- $stderr.puts msg.yellow
229
+ $stderr.puts '%d) %s' % [index + 1, msg]
248
230
  }
249
231
 
250
232
  unless @opts[:quiet]
251
233
  $stderr.puts "\n"
252
-
253
- $stderr.puts "Finished checks for %d package%s with %d failure%s" % [
254
- count, count == 1 ? '' : 's',
255
- failures.size, failures.size == 1 ? '' : 's'
234
+ $stderr.puts 'Finished checks for %d package%s with %d failure%s' % [
235
+ pkgs.size, pkgs.size == 1 ? '' : 's',
236
+ failures.size, failures.size == 1 ? '' : 's',
256
237
  ]
257
238
  end
258
239
 
@@ -267,29 +248,7 @@ private
267
248
  prompt 'Commit the new index?'
268
249
  end
269
250
 
270
- old_index = @git.index
271
- target = @git.empty? ? nil : @git.head.target
272
-
273
- if target
274
- old_index.read_tree target.tree
275
- else
276
- old_index.clear
277
- end
278
-
279
- root = Pathname.new @git.workdir
280
- file = Pathname.new @db.path
281
-
282
- index = @git.index
283
- index.add file.relative_path_from(root).to_s
284
-
285
- Rugged::Commit.create @git, \
286
- tree: index.write_tree(@git),
287
- message: "index: #{changelog}",
288
- parents: [target].compact,
289
- update_ref: 'HEAD'
290
-
291
- old_index.write
292
-
251
+ @git.create_commit "index: #{changelog}", [@index.path]
293
252
  $stderr.puts 'commit created'
294
253
  end
295
254
 
@@ -316,7 +275,19 @@ private
316
275
  @add_nl = false
317
276
  end
318
277
 
319
- $stderr.puts "Warning: #{line}".yellow
278
+ $stderr.puts "warning: #{line}".yellow
279
+ end
280
+
281
+ def progress_wrapper(total, &block)
282
+ @done, @total = 0, total
283
+ print_progress
284
+ block[]
285
+ $stderr.print "\n" if @add_nl
286
+ end
287
+
288
+ def bump_progress
289
+ @done += 1
290
+ print_progress
320
291
  end
321
292
 
322
293
  def print_progress
@@ -330,8 +301,10 @@ private
330
301
  end
331
302
 
332
303
  def ignored?(path)
304
+ path = path + '/'
305
+
333
306
  @opts[:ignore].each {|pattern|
334
- return true if path.start_with? pattern
307
+ return true if path.start_with? pattern + '/'
335
308
  }
336
309
 
337
310
  false
@@ -340,19 +313,12 @@ private
340
313
  def expand_path(path)
341
314
  # expand from the repository root or from the current directory if
342
315
  # the repository is not yet initialized
343
- File.expand_path path, @git ? @git.workdir : Dir.pwd
316
+ File.expand_path path, @git ? @git.path : Dir.pwd
344
317
  end
345
318
 
346
- def auto_url_tpl
347
- remote = @git.remotes['origin']
348
- return unless remote
349
-
350
- uri = Gitable::URI.parse remote.url
351
- return unless uri.path =~ /\A\/?(?<user>[^\/]+)\/(?<repo>[^\/]+)\.git\Z/
352
-
353
- tpl = uri.to_web_uri
354
- tpl.path += '/raw/$commit/$path'
355
-
356
- tpl.to_s
319
+ def indent(input)
320
+ output = String.new
321
+ input.lines {|l| output += "\x20\x20#{l}" }
322
+ output
357
323
  end
358
324
  end