reapack-index 1.0beta3 → 1.0beta4

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