whimsy-asf 0.0.74 → 0.0.75

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a3cda4938653e84801388a33933a5ff5753ccd8
4
- data.tar.gz: cbd46d98523e091ae41001899b2b88c086a8bc73
3
+ metadata.gz: fbe033a993bc2428dbed396c0a98fece7b33f82b
4
+ data.tar.gz: 17ca4624c5340b1354ff01f540999338d426f2b0
5
5
  SHA512:
6
- metadata.gz: e098ec455420ce5293b5457c9056c63ceb44f3187a0242f9d5e0798cd314b1ea8f4ea734f042359ec442e9e4e49578ff4bb948e29742b349db8c99a4e3d52620
7
- data.tar.gz: fd7b79fddc1883e8a57248496867ecef4ddd7aaf918d927796df7cacd4c6a9f049906547d74e62420ac749ef90733f5e215248693b5a78673ee4309a1dd662b0
6
+ metadata.gz: 801153db8122f912ead1cd2df9a56ad65349bb50c95b1719497dd65da332862c2b3ccbf0e26b92f17981dc1ccb5a34474b2bd1eb4266a6267eb5104b97de34cf
7
+ data.tar.gz: 28230fd4887ed1198b94f5d27bba0d2a38eec271ffeaeaa4d010d4aa9b07129a27ab0bb21b621058df05ed551c2ff621218237cff9a003496c3f5fc316d76824
data/asf.version CHANGED
@@ -1 +1 @@
1
- 0.0.74
1
+ 0.0.75
data/lib/whimsy/asf.rb CHANGED
@@ -17,4 +17,8 @@ module ASF
17
17
  times = sources.map {|source| File.mtime(source)}
18
18
  times.max.gmtime
19
19
  end
20
+ def self.library_gitinfo
21
+ return @info if @info
22
+ @info = `git show --format="%h %ci" -s HEAD`.chomp
23
+ end
20
24
  end
@@ -59,34 +59,47 @@ module ASF
59
59
  @@svn_change = Time.parse(
60
60
  `svn info #{file}`[/Last Changed Date: (.*) \(/, 1]).gmtime
61
61
 
62
+ # Split the file on lines starting "* ", i.e. the start of each group in section 3
62
63
  info = File.read(file).split(/^\* /)
64
+ # Extract the text before first entry in section 3 and split on section headers,
65
+ # keeping sections 1 (COMMITTEES) and 2 (REPORTING).
63
66
  head, report = info.shift.split(/^\d\./)[1..2]
67
+ # Drop lines which could match group entries
64
68
  head.gsub! /^\s+NAME\s+CHAIR\s*$/,'' # otherwise could match an entry with no e-mail
65
69
 
66
70
  # extract the committee chairs (e-mail address is required here)
71
+ # Note: this includes the non-PMC entries
67
72
  head.scan(/^[ \t]+(\w.*?)[ \t][ \t]+(.*)[ \t]+<(.*?)@apache\.org>/).
68
73
  each do |committee, name, id|
69
74
  list[committee].chairs << {name: name, id: id}
70
75
  end
71
76
 
72
77
  # Extract the non-PMC committees (e-mail address may be absent)
73
- @nonpmcs = head.sub(/.*?also has/m,'').
78
+ # first drop leading text so we only match non-PMCs
79
+ @nonpmcs = head.sub(/.*?also has /m,'').
74
80
  scan(/^[ \t]+(\w.*?)(?:[ \t][ \t]|[ \t]?$)/).flatten.uniq.
75
81
  map {|name| list[name]}
76
82
 
83
+ # for each committee in section 3
77
84
  info.each do |roster|
85
+ # extract the committee name and canonicalise
78
86
  committee = list[@@namemap.call(roster[/(\w.*?)\s+\(/,1])]
87
+ # get the start date
79
88
  committee.established = roster[/\(est\. (.*?)\)/, 1]
89
+ # Extract any emeritus members (now probably redundant)
80
90
  roster.gsub! /^.*\(\s*emeritus\s*\).*/i do |line|
81
91
  committee.emeritus += line.scan(/<(.*?)@apache\.org>/).flatten
82
92
  ''
83
93
  end
94
+ # extract the availids (is this used?)
84
95
  committee.info = roster.scan(/<(.*?)@apache\.org>/).flatten
96
+ # drop (chair) markers and extract 0: name, 1: availid, 2: [date], 3: date
85
97
  committee.roster = Hash[roster.gsub(/\(\w+\)/, '').
86
98
  scan(/^\s*(.*?)\s*<(.*?)@apache\.org>\s+(\[(.*?)\])?/).
87
99
  map {|list| [list[1], {name: list[0], date: list[3]}]}]
88
100
  end
89
101
 
102
+ # process report section
90
103
  report.scan(/^([^\n]+)\n---+\n(.*?)\n\n/m).each do |period, committees|
91
104
  committees.scan(/^ \s*(.*)/).each do |committee|
92
105
  committee, comment = committee.first.split(/\s+#\s+/,2)
@@ -101,7 +114,7 @@ module ASF
101
114
  end
102
115
  end
103
116
 
104
- @committee_info = list.values
117
+ @committee_info = list.values.uniq
105
118
  end
106
119
 
107
120
  def self.nonpmcs
@@ -9,12 +9,25 @@ module ASF
9
9
 
10
10
  @config = YAML.load_file("#@home/.whimsy") rescue {}
11
11
 
12
- # default :svn for backwards compatibility
13
- @config[:svn] ||= ['/srv/svn/*', '/home/whimsysvn/svn/*', "#{@home}/svn/*"]
12
+ # default :svn and :git
13
+ @config[:svn] ||= '/srv/svn/*'
14
+ @config[:git] ||= '/srv/git/*'
15
+
16
+ @config[:lib] ||= []
17
+
18
+ # add gems to lib
19
+ (@config[:gem] || {}).to_a.reverse.each do |name, version|
20
+ begin
21
+ gem = Gem::Specification.find_by_name(name, version)
22
+ @config[:lib] += Dir[gem.lib_dirs_glob]
23
+ rescue Gem::LoadError
24
+ end
25
+ end
14
26
 
15
27
  # add libraries to RUBYLIB, load path
16
28
  (@config[:lib] || []).reverse.each do |lib|
17
29
  next unless File.exist? lib
30
+ lib = File.realpath(lib)
18
31
  ENV['RUBYLIB']=([lib] + ENV['RUBYLIB'].to_s.split(':')).uniq.join(':')
19
32
  $LOAD_PATH.unshift lib.untaint unless $LOAD_PATH.include? lib
20
33
  end
@@ -1,6 +1,38 @@
1
+ #
2
+ # Encapsulate access to LDAP, caching results for performance. For best
3
+ # performance in applications that access large number of objects, make use
4
+ # of the preload methods to pre-fetch multiple objects in a single LDAP
5
+ # call, and rely on the cache to find the objects later.
6
+ #
7
+ # The cache makes heavy use of Weak References internally to enable garbage
8
+ # collection to reclaim objects; among other things, this ensures that
9
+ # LDAP results don't become too stale.
10
+ #
11
+ # Until garbage collection reclaims an object, calls to find methods for the
12
+ # same name is guaranteed to return the same object. Holding on to the
13
+ # results of find or preload calls (by assigning it to a variable) is
14
+ # sufficient to prevent reclaiming of objects.
15
+ #
16
+ # To illustrate, the following is likely to return the same id twice, followed
17
+ # by a new id:
18
+ # puts ASF::Person.find('rubys').__id__
19
+ # puts ASF::Person.find('rubys').__id__
20
+ # GC.start
21
+ # puts ASF::Person.find('rubys').__id__
22
+ #
23
+ # By contrast, the following is guaranteed to produce the same id three times:
24
+ # rubys1 = ASF::Person.find('rubys')
25
+ # rubys2 = ASF::Person.find('rubys')
26
+ # GC.start
27
+ # rubys3 = ASF::Person.find('rubys')
28
+ # puts [rubys1.__id__, rubys2.__id__, rubys3.__id__]
29
+ #
30
+
1
31
  require 'wunderbar'
2
32
  require 'ldap'
3
33
  require 'weakref'
34
+ require 'net/http'
35
+ require 'base64'
4
36
 
5
37
  module ASF
6
38
  module LDAP
@@ -8,41 +40,80 @@ module ASF
8
40
  # https://github.com/apache/infrastructure-puppet/blob/deployment/data/common.yaml (ldapserver::slapd_peers)
9
41
  HOSTS = %w(
10
42
  ldaps://ldap1-us-west.apache.org:636
11
- ldaps://ldap1-eu-central.apache.org:636
43
+ ldaps://ldap1-lw-us.apache.org:636
12
44
  ldaps://ldap2-us-west.apache.org:636
13
- ldaps://ldap1-us-east.apache.org:636
45
+ ldaps://ldap1-lw-eu.apache.org:636
14
46
  ldaps://snappy5.apache.org:636
47
+ ldaps://ldap2-lw-us.apache.org:636
48
+ ldaps://ldap2-lw-eu.apache.org:636
15
49
  )
16
- end
17
50
 
18
- # determine where ldap.conf resides
19
- if Dir.exist? '/etc/openldap'
20
- ETCLDAP = '/etc/openldap'
21
- else
22
- ETCLDAP = '/etc/ldap'
23
- end
51
+ # fetch configuration from apache/infrastructure-puppet
52
+ def self.puppet_config
53
+ return @puppet if @puppet
54
+ file = '/apache/infrastructure-puppet/deployment/data/common.yaml'
55
+ http = Net::HTTP.new('raw.githubusercontent.com', 443)
56
+ http.use_ssl = true
57
+ @puppet = YAML.load(http.request(Net::HTTP::Get.new(file)).body)
58
+ end
24
59
 
25
- # determine whether or not the LDAP API can be used
26
- def self.init_ldap
27
- @ldap = nil
28
- @mtime = Time.now
60
+ # extract the ldapcert from the puppet configuration
61
+ def self.puppet_cert
62
+ puppet_config['ldapclient::ldapcert']
63
+ end
29
64
 
30
- host = ASF::LDAP.host
65
+ # extract the ldap servers from the puppet configuration
66
+ def self.puppet_ldapservers
67
+ puppet_config['ldapserver::slapd_peers'].values.
68
+ map {|host| "ldaps://#{host}:636"}
69
+ rescue
70
+ nil
71
+ end
31
72
 
32
- Wunderbar.info "Connecting to LDAP server: #{host}"
73
+ # connect to LDAP
74
+ def self.connect
75
+ hosts.shuffle.each do |host|
76
+ Wunderbar.info "Connecting to LDAP server: #{host}"
77
+
78
+ begin
79
+ # request connection
80
+ uri = URI.parse(host)
81
+ if uri.scheme == 'ldaps'
82
+ ldap = ::LDAP::SSLConn.new(uri.host, uri.port)
83
+ else
84
+ ldap = ::LDAP::Conn.new(uri.host, uri.port)
85
+ end
86
+
87
+ # test the connection
88
+ ldap.bind
89
+
90
+ # save the host
91
+ @host = host
92
+
93
+ return ldap
94
+ rescue ::LDAP::ResultError => re
95
+ Wunderbar.warn "Error connecting to LDAP server #{host}: " +
96
+ re.message + " (continuing)"
97
+ end
33
98
 
34
- begin
35
- uri = URI.parse(host)
36
- if uri.scheme == 'ldaps'
37
- @ldap = ::LDAP::SSLConn.new(uri.host, uri.port)
38
- else
39
- @ldap = ::LDAP::Conn.new(uri.host, uri.port)
40
99
  end
41
- rescue ::LDAP::ResultError=>re
42
- Wunderbar.error "Error binding to LDAP server: message: ["+ re.message + "]"
100
+ Wunderbar.error "Failed to connect to any LDAP host"
101
+ return nil
43
102
  end
44
103
  end
45
104
 
105
+ # backwards compatibility for tools that called this interface
106
+ def self.init_ldap
107
+ @ldap ||= ASF::LDAP.connect
108
+ end
109
+
110
+ # determine where ldap.conf resides
111
+ if Dir.exist? '/etc/openldap'
112
+ ETCLDAP = '/etc/openldap'
113
+ else
114
+ ETCLDAP = '/etc/ldap'
115
+ end
116
+
46
117
  def self.ldap
47
118
  @ldap || self.init_ldap
48
119
  end
@@ -66,30 +137,34 @@ module ASF
66
137
  result
67
138
  end
68
139
 
69
- def self.refresh(symbol)
70
- if not @mtime or Time.now - @mtime > 300.0
71
- @mtime = Time.now
140
+ # safely dereference a weakref array attribute. Block provided is
141
+ # used when reference is not set or has been reclaimed.
142
+ def self.dereference_weakref(object, attr, &block)
143
+ attr = "@#{attr}"
144
+ value = object.instance_variable_get(attr) || block.call
145
+ value[0..-1]
146
+ rescue WeakRef::RefError
147
+ value = block.call
148
+ ensure
149
+ if value and not value.instance_of? WeakRef
150
+ object.instance_variable_set(attr, WeakRef.new(value))
72
151
  end
152
+ end
73
153
 
74
- if instance_variable_get("#{symbol}_mtime") != @mtime
75
- instance_variable_set("#{symbol}_mtime", @mtime)
76
- instance_variable_set(symbol, nil)
77
- end
154
+ def self.weakref(attr, &block)
155
+ self.dereference_weakref(self, attr, &block)
78
156
  end
79
157
 
80
158
  def self.pmc_chairs
81
- refresh(:@pmc_chairs)
82
- @pmc_chairs ||= Service.find('pmc-chairs').members
159
+ weakref(:pmc_chairs) {Service.find('pmc-chairs').members}
83
160
  end
84
161
 
85
162
  def self.committers
86
- refresh(:@committers)
87
- @committers ||= Group.find('committers').members
163
+ weakref(:committers) {Group.find('committers').members}
88
164
  end
89
165
 
90
166
  def self.members
91
- refresh(:@members)
92
- @members ||= Group.find('member').members
167
+ weakref(:members) {Group.find('member').members}
93
168
  end
94
169
 
95
170
  class Base
@@ -134,6 +209,10 @@ module ASF
134
209
  self
135
210
  end
136
211
 
212
+ def weakref(attr, &block)
213
+ ASF.dereference_weakref(self, attr, &block)
214
+ end
215
+
137
216
  unless Object.respond_to? :id
138
217
  def id
139
218
  @name
@@ -213,12 +292,16 @@ module ASF
213
292
  ASF::Member.status[name] or ASF.members.include? self
214
293
  end
215
294
 
295
+ def asf_officer_or_member?
296
+ asf_member? or ASF.pmc_chairs.include? self
297
+ end
298
+
216
299
  def asf_committer?
217
300
  ASF::Group.new('committers').include? self
218
301
  end
219
302
 
220
303
  def banned?
221
- not attrs['loginShell'] or attrs['loginShell'].include? "/usr/bin/false"
304
+ not attrs['loginShell'] or %w(/usr/bin/false bin/nologin bin/no-cla).any? {|a| attrs['loginShell'].first.include? a}
222
305
  end
223
306
 
224
307
  def mail
@@ -299,9 +382,29 @@ module ASF
299
382
  end
300
383
  end
301
384
 
385
+ def self.preload
386
+ Hash[ASF.search_one(base, "cn=*", %w(dn memberUid modifyTimestamp)).map do |results|
387
+ cn = results['dn'].first[/^cn=(.*?),/, 1]
388
+ group = ASF::Group.find(cn)
389
+ group.modifyTimestamp = results['modifyTimestamp'].first # it is returned as an array of 1 entry
390
+ members = results['memberUid']
391
+ group.members = members || []
392
+ [group, members]
393
+ end]
394
+ end
395
+
396
+ attr_accessor :modifyTimestamp
397
+
398
+ def members=(members)
399
+ @members = WeakRef.new(members)
400
+ end
401
+
302
402
  def members
303
- ASF.search_one(base, "cn=#{name}", 'memberUid').flatten.
304
- map {|uid| Person.find(uid)}
403
+ members = weakref(:members) do
404
+ ASF.search_one(base, "cn=#{name}", 'memberUid').flatten
405
+ end
406
+
407
+ members.map {|uid| Person.find(uid)}
305
408
  end
306
409
  end
307
410
 
@@ -312,9 +415,29 @@ module ASF
312
415
  ASF.search_one(base, filter, 'cn').flatten.map {|cn| Committee.find(cn)}
313
416
  end
314
417
 
418
+ def self.preload
419
+ Hash[ASF.search_one(base, "cn=*", %w(dn member modifyTimestamp)).map do |results|
420
+ cn = results['dn'].first[/^cn=(.*?),/, 1]
421
+ committee = ASF::Committee.find(cn)
422
+ committee.modifyTimestamp = results['modifyTimestamp'].first # it is returned as an array of 1 entry
423
+ members = results['member']
424
+ committee.members = members
425
+ [committee, members]
426
+ end]
427
+ end
428
+
429
+ attr_accessor :modifyTimestamp
430
+
431
+ def members=(members)
432
+ @members = WeakRef.new(members)
433
+ end
434
+
315
435
  def members
316
- ASF.search_one(base, "cn=#{name}", 'member').flatten.
317
- map {|uid| Person.find uid[/uid=(.*?),/,1]}
436
+ members = weakref(:members) do
437
+ ASF.search_one(base, "cn=#{name}", 'member').flatten
438
+ end
439
+
440
+ members.map {|uid| Person.find uid[/uid=(.*?),/,1]}
318
441
  end
319
442
 
320
443
  def dn
@@ -354,59 +477,112 @@ module ASF
354
477
  module LDAP
355
478
  def self.bind(user, password, &block)
356
479
  dn = ASF::Person.new(user).dn
480
+ raise ::LDAP::ResultError.new('Unknown user') unless dn
481
+
482
+ ASF.ldap.unbind rescue nil
357
483
  if block
358
484
  ASF.ldap.bind(dn, password, &block)
485
+ ASF.init_ldap
359
486
  else
360
487
  ASF.ldap.bind(dn, password)
361
488
  end
362
- ASF.init_ldap
363
489
  end
364
490
 
365
- # select LDAP host
366
- def self.host
491
+ # validate HTTP authorization, and optionally invoke a block bound to
492
+ # that user.
493
+ def self.http_auth(string, &block)
494
+ auth = Base64.decode64(string.to_s[/Basic (.*)/, 1] || '')
495
+ user, password = auth.split(':', 2)
496
+ return unless password
497
+
498
+ if block
499
+ self.bind(user, password, &block)
500
+ else
501
+ begin
502
+ ASF::LDAP.bind(user, password) {}
503
+ return ASF::Person.new(user)
504
+ rescue ::LDAP::ResultError
505
+ return nil
506
+ end
507
+ end
508
+ end
509
+
510
+ # determine what LDAP hosts are available
511
+ def self.hosts
367
512
  # try whimsy config
368
- host = ASF::Config.get(:ldap)
513
+ hosts = Array(ASF::Config.get(:ldap))
369
514
 
370
515
  # check system configuration
371
- unless host
516
+ if hosts.empty?
372
517
  conf = "#{ETCLDAP}/ldap.conf"
373
518
  if File.exist? conf
374
- host = File.read(conf)[/^uri\s+(ldaps?:\/\/\S+?:\d+)/i, 1]
519
+ uris = File.read(conf)[/^uri\s+(.*)/i, 1].to_s
520
+ hosts = uris.scan(/ldaps?:\/\/\S+?:\d+/)
375
521
  end
376
522
  end
377
523
 
378
- # if all else fails, pick one at random
379
- host = ASF::LDAP::HOSTS.sample unless host
524
+ # if all else fails, use default list
525
+ hosts = ASF::LDAP::HOSTS if hosts.empty?
526
+
527
+ hosts
528
+ end
380
529
 
381
- host
530
+ # select LDAP host
531
+ def self.host
532
+ @host ||= hosts.sample
382
533
  end
383
534
 
384
535
  # query and extract cert from openssl output
385
- def self.cert
536
+ def self.extract_cert
386
537
  host = LDAP.host[%r{//(.*?)(/|$)}, 1]
387
- query = "openssl s_client -connect #{host} -showcerts"
388
- output = `#{query} < /dev/null 2> /dev/null`
389
- output[/^-+BEGIN.*?\n-+END[^\n]+\n/m]
538
+ puts ['openssl', 's_client', '-connect', host, '-showcerts'].join(' ')
539
+ out, err, rc = Open3.capture3 'openssl', 's_client',
540
+ '-connect', host, '-showcerts'
541
+ out[/^-+BEGIN.*?\n-+END[^\n]+\n/m]
390
542
  end
391
543
 
392
544
  # update /etc/ldap.conf. Usage:
545
+ #
393
546
  # sudo ruby -r whimsy/asf -e "ASF::LDAP.configure"
547
+ #
394
548
  def self.configure
395
- if not File.exist? "#{ETCLDAP}/asf-ldap-client.pem"
396
- File.write "#{ETCLDAP}/asf-ldap-client.pem", self.cert
549
+ cert = Dir["#{ETCLDAP}/asf*-ldap-client.pem"].first
550
+
551
+ # verify/obtain/write the cert
552
+ if not cert
553
+ cert = "#{ETCLDAP}/asf-ldap-client.pem"
554
+ File.write cert, ASF::LDAP.puppet_cert || self.extract_cert
397
555
  end
398
556
 
557
+ # read the current configuration file
399
558
  ldap_conf = "#{ETCLDAP}/ldap.conf"
400
559
  content = File.read(ldap_conf)
401
- unless content.include? 'asf-ldap-client.pem'
402
- content.gsub!(/^TLS_CACERT/, '# TLS_CACERT')
403
- content.gsub!(/^TLS_REQCERT/, '# TLS_REQCERT')
560
+
561
+ # ensure that the right cert is used
562
+ unless content =~ /asf.*-ldap-client\.pem/
563
+ content.gsub!(/^TLS_CACERT/i, '# TLS_CACERT')
404
564
  content += "TLS_CACERT #{ETCLDAP}/asf-ldap-client.pem\n"
405
- content += "uri #{LDAP.host}\n"
565
+ end
566
+
567
+ # provide the URIs of the ldap hosts
568
+ content.gsub!(/^URI/, '# URI')
569
+ content += "uri \n" unless content =~ /^uri /
570
+ content[/uri (.*)\n/, 1] = hosts.join(' ')
571
+
572
+ # verify/set the base
573
+ unless content.include? 'base dc=apache'
574
+ content.gsub!(/^BASE/i, '# BASE')
406
575
  content += "base dc=apache,dc=org\n"
407
- content += "TLS_REQCERT allow\n" if ETCLDAP.include? 'openldap'
408
- File.write(ldap_conf, content)
409
576
  end
577
+
578
+ # ensure TLS_REQCERT is allow (Mac OS/X only)
579
+ if ETCLDAP.include? 'openldap' and not content.include? 'REQCERT allow'
580
+ content.gsub!(/^TLS_REQCERT/i, '# TLS_REQCERT')
581
+ content += "TLS_REQCERT allow\n"
582
+ end
583
+
584
+ # write the configuration if there were any changes
585
+ File.write(ldap_conf, content) unless content == File.read(ldap_conf)
410
586
  end
411
587
  end
412
588
  end
@@ -21,12 +21,12 @@ module ASF
21
21
  def self.decode(env)
22
22
  class << env; attr_accessor :user, :password; end
23
23
 
24
- if env['HTTP_AUTHORIZATION']
24
+ if env['HTTP_AUTHORIZATION'].to_s.empty?
25
+ env.user = env['REMOTE_USER'] || ENV['USER'] || Etc.getpwuid.name
26
+ else
25
27
  require 'base64'
26
28
  env.user, env.password = Base64.decode64(env['HTTP_AUTHORIZATION'][
27
- /^Basic ([A-Za-z0-9+\/=]+)$/,1]).split(':',2)
28
- else
29
- env.user = env['REMOTE_USER'] || ENV['USER'] || Etc.getpwuid.name
29
+ /^Basic ([A-Za-z0-9+\/=]+)$/,1].to_s).split(':',2)
30
30
  end
31
31
 
32
32
  env['REMOTE_USER'] ||= env.user
@@ -189,6 +189,7 @@ module ASF
189
189
  env['SCRIPT_URI'] += '/'
190
190
  end
191
191
  end
192
+
192
193
  return @app.call(env)
193
194
  end
194
195
  end
@@ -12,8 +12,9 @@ module ASF
12
12
 
13
13
  def self.repos
14
14
  @semaphore.synchronize do
15
- svn = ASF::Config.get(:svn).map {|dir| dir.untaint}
15
+ svn = Array(ASF::Config.get(:svn)).map {|dir| dir.untaint}
16
16
  @repos ||= Hash[Dir[*svn].map { |name|
17
+ next unless Dir.exist? name.untaint
17
18
  Dir.chdir name.untaint do
18
19
  out, err, status = Open3.capture3('svn', 'info')
19
20
  if status.success?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whimsy-asf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.74
4
+ version: 0.0.75
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-12 00:00:00.000000000 Z
11
+ date: 2016-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -104,6 +104,8 @@ extra_rdoc_files: []
104
104
  files:
105
105
  - asf.gemspec
106
106
  - asf.version
107
+ - lib/whimsy/asf.rb
108
+ - lib/whimsy/asf/agenda.rb
107
109
  - lib/whimsy/asf/agenda/attachments.rb
108
110
  - lib/whimsy/asf/agenda/back.rb
109
111
  - lib/whimsy/asf/agenda/committee.rb
@@ -111,7 +113,6 @@ files:
111
113
  - lib/whimsy/asf/agenda/front.rb
112
114
  - lib/whimsy/asf/agenda/minutes.rb
113
115
  - lib/whimsy/asf/agenda/special.rb
114
- - lib/whimsy/asf/agenda.rb
115
116
  - lib/whimsy/asf/auth.rb
116
117
  - lib/whimsy/asf/bundler.rb
117
118
  - lib/whimsy/asf/committee.rb
@@ -126,7 +127,6 @@ files:
126
127
  - lib/whimsy/asf/site.rb
127
128
  - lib/whimsy/asf/svn.rb
128
129
  - lib/whimsy/asf/watch.rb
129
- - lib/whimsy/asf.rb
130
130
  homepage: https://whimsy.apache.org/
131
131
  licenses:
132
132
  - Apache License, Version 2.0
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
147
  version: '0'
148
148
  requirements: []
149
149
  rubyforge_project:
150
- rubygems_version: 2.0.14
150
+ rubygems_version: 2.5.1
151
151
  signing_key:
152
152
  specification_version: 4
153
153
  summary: Whimsy 'model' of the ASF