whimsy-asf 0.0.76 → 0.0.77
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/asf.version +1 -1
- data/lib/whimsy/asf.rb +16 -12
- data/lib/whimsy/asf/agenda.rb +9 -0
- data/lib/whimsy/asf/agenda/special.rb +22 -7
- data/lib/whimsy/asf/auth.rb +16 -1
- data/lib/whimsy/asf/committee.rb +12 -6
- data/lib/whimsy/asf/git.rb +48 -0
- data/lib/whimsy/asf/icla.rb +9 -80
- data/lib/whimsy/asf/ldap.rb +260 -141
- data/lib/whimsy/asf/mail.rb +25 -1
- data/lib/whimsy/asf/member.rb +74 -8
- data/lib/whimsy/asf/nominees.rb +1 -1
- data/lib/whimsy/asf/person.rb +81 -0
- data/lib/whimsy/asf/podlings.rb +111 -28
- data/lib/whimsy/asf/rack.rb +4 -4
- data/lib/whimsy/asf/site.rb +1 -1
- data/lib/whimsy/asf/svn.rb +131 -2
- metadata +4 -2
data/lib/whimsy/asf/mail.rb
CHANGED
@@ -48,6 +48,30 @@ module ASF
|
|
48
48
|
|
49
49
|
public_private ? @lists : @lists.keys
|
50
50
|
end
|
51
|
+
|
52
|
+
# common configuration for sending mail
|
53
|
+
def self.configure
|
54
|
+
# fetch overrides
|
55
|
+
sendmail = ASF::Config.get(:sendmail)
|
56
|
+
|
57
|
+
if sendmail
|
58
|
+
# convert string keys to symbols
|
59
|
+
options = Hash[sendmail.map {|key, value| [key.to_sym, value.untaint]}]
|
60
|
+
|
61
|
+
# extract delivery method
|
62
|
+
method = options.delete(:delivery_method).to_sym
|
63
|
+
else
|
64
|
+
# provide defaults that work on whimsy-vm* infrastructure. Since
|
65
|
+
# procmail is configured with a self-signed certificate, verification
|
66
|
+
# isn't a possibility
|
67
|
+
method = :smtp
|
68
|
+
options = {openssl_verify_mode: 'none'}
|
69
|
+
end
|
70
|
+
|
71
|
+
::Mail.defaults do
|
72
|
+
delivery_method method, options
|
73
|
+
end
|
74
|
+
end
|
51
75
|
end
|
52
76
|
|
53
77
|
class Person < Base
|
@@ -93,7 +117,7 @@ module ASF
|
|
93
117
|
when 'executive assistant'
|
94
118
|
'ea@apache.org'
|
95
119
|
when 'legal affairs'
|
96
|
-
'legal-
|
120
|
+
'legal-internal@apache.org'
|
97
121
|
when 'marketing and publicity'
|
98
122
|
'press@apache.org'
|
99
123
|
when 'tac'
|
data/lib/whimsy/asf/member.rb
CHANGED
@@ -3,7 +3,8 @@ require 'weakref'
|
|
3
3
|
module ASF
|
4
4
|
class Member
|
5
5
|
include Enumerable
|
6
|
-
|
6
|
+
@@text = nil
|
7
|
+
@@mtime = 0
|
7
8
|
|
8
9
|
def self.find_text_by_id(value)
|
9
10
|
new.each do |id, text|
|
@@ -42,14 +43,14 @@ module ASF
|
|
42
43
|
|
43
44
|
def self.status
|
44
45
|
begin
|
46
|
+
@status = nil if @mtime != @@mtime
|
47
|
+
@mtime = @@mtime
|
45
48
|
return Hash[@status.to_a] if @status
|
46
|
-
rescue
|
49
|
+
rescue
|
47
50
|
end
|
48
51
|
|
49
52
|
status = {}
|
50
|
-
|
51
|
-
return status unless foundation
|
52
|
-
sections = File.read("#{foundation}/members.txt").split(/(.*\n===+)/)
|
53
|
+
sections = ASF::Member.text.split(/(.*\n===+)/)
|
53
54
|
sections.shift(3)
|
54
55
|
sections.each_slice(2) do |header, text|
|
55
56
|
header.sub!(/s\n=+/,'')
|
@@ -61,8 +62,7 @@ module ASF
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def each
|
64
|
-
|
65
|
-
File.read("#{foundation}/members.txt").split(/^ \*\) /).each do |section|
|
65
|
+
ASF::Member.text.split(/^ \*\) /).each do |section|
|
66
66
|
id = section[/Avail ID: (.*)/,1]
|
67
67
|
yield id, section.sub(/\n.*\n===+\s*?\n(.*\n)+.*/,'').strip if id
|
68
68
|
end
|
@@ -84,15 +84,81 @@ module ASF
|
|
84
84
|
file = "#{foundation}/members.txt"
|
85
85
|
return Time.parse(`svn info #{file}`[/Last Changed Date: (.*) \(/, 1]).gmtime
|
86
86
|
end
|
87
|
+
|
88
|
+
# sort an entire members.txt file
|
89
|
+
def self.sort(source)
|
90
|
+
# split into sections
|
91
|
+
sections = source.split(/^([A-Z].*\n===+\n\n)/)
|
92
|
+
|
93
|
+
# sort sections that contain names
|
94
|
+
sections.map! do |section|
|
95
|
+
next section unless section =~ /^\s\*\)\s/
|
96
|
+
|
97
|
+
# split into entries, and normalize those entries
|
98
|
+
entries = section.split(/^\s\*\)\s/)
|
99
|
+
header = entries.shift
|
100
|
+
entries.map! {|entry| " *) " + entry.strip + "\n\n"}
|
101
|
+
|
102
|
+
# sort the entries
|
103
|
+
entries.sort_by! do |entry|
|
104
|
+
ASF::Person.sortable_name(entry[/\)\s(.*?)\s*(\/\*|$)/, 1])
|
105
|
+
end
|
106
|
+
|
107
|
+
header + entries.join
|
108
|
+
end
|
109
|
+
|
110
|
+
sections.join
|
111
|
+
end
|
112
|
+
|
113
|
+
# cache the contents of members.txt. Primary purpose isn't performance,
|
114
|
+
# but rather to have a local copy that can be updated and used until
|
115
|
+
# the svn working copy catches up
|
116
|
+
def self.text
|
117
|
+
foundation = ASF::SVN.find('private/foundation')
|
118
|
+
return nil unless foundation
|
119
|
+
|
120
|
+
begin
|
121
|
+
text = @@text[0..-1] if @@text
|
122
|
+
rescue WeakRef::RefError
|
123
|
+
@@mtime = 0
|
124
|
+
end
|
125
|
+
|
126
|
+
if File.mtime("#{foundation}/members.txt").to_i > @@mtime.to_i
|
127
|
+
@@mtime = File.mtime("#{foundation}/members.txt")
|
128
|
+
text = File.read("#{foundation}/members.txt")
|
129
|
+
@@text = WeakRef.new(text)
|
130
|
+
end
|
131
|
+
|
132
|
+
text
|
133
|
+
end
|
134
|
+
|
135
|
+
# update local copy of members.txt
|
136
|
+
def self.text=(text)
|
137
|
+
# normalize text: sort and update active count
|
138
|
+
text = ASF::Member.sort(text)
|
139
|
+
pattern = /^Active.*?^=+\n+(.*?)^Emeritus/m
|
140
|
+
text[/We now number (\d+) active members\./, 1] =
|
141
|
+
text[pattern].scan(/^\s\*\)\s/).length.to_s
|
142
|
+
|
143
|
+
# save
|
144
|
+
@@mtime = Time.now
|
145
|
+
@@text = WeakRef.new(text)
|
146
|
+
end
|
87
147
|
end
|
88
148
|
|
89
149
|
class Person
|
90
|
-
def members_txt
|
150
|
+
def members_txt(full = false)
|
151
|
+
prefix, suffix = " *) ", "\n\n" if full
|
91
152
|
@members_txt ||= ASF::Member.find_text_by_id(id)
|
153
|
+
"#{prefix}#{@members_txt}#{suffix}" if @members_txt
|
92
154
|
end
|
93
155
|
|
94
156
|
def member_emails
|
95
157
|
ASF::Member.emails(members_txt)
|
96
158
|
end
|
159
|
+
|
160
|
+
def member_name
|
161
|
+
members_txt[/(\w.*?)\s*(\/|$)/, 1] if members_txt
|
162
|
+
end
|
97
163
|
end
|
98
164
|
end
|
data/lib/whimsy/asf/nominees.rb
CHANGED
@@ -0,0 +1,81 @@
|
|
1
|
+
#
|
2
|
+
# support for sorting of names
|
3
|
+
#
|
4
|
+
|
5
|
+
module ASF
|
6
|
+
|
7
|
+
class Person
|
8
|
+
# sort support
|
9
|
+
|
10
|
+
def self.asciize(name)
|
11
|
+
if name.match /[^\x00-\x7F]/
|
12
|
+
# digraphs. May be culturally sensitive
|
13
|
+
name.gsub! /\u00df/, 'ss'
|
14
|
+
name.gsub! /\u00e4|a\u0308/, 'ae'
|
15
|
+
name.gsub! /\u00e5|a\u030a/, 'aa'
|
16
|
+
name.gsub! /\u00e6/, 'ae'
|
17
|
+
name.gsub! /\u00f1|n\u0303/, 'ny'
|
18
|
+
name.gsub! /\u00f6|o\u0308/, 'oe'
|
19
|
+
name.gsub! /\u00fc|u\u0308/, 'ue'
|
20
|
+
|
21
|
+
# latin 1
|
22
|
+
name.gsub! /\u00c9/, 'e'
|
23
|
+
name.gsub! /\u00d3/, 'o'
|
24
|
+
name.gsub! /[\u00e0-\u00e5]/, 'a'
|
25
|
+
name.gsub! /\u00e7/, 'c'
|
26
|
+
name.gsub! /[\u00e8-\u00eb]/, 'e'
|
27
|
+
name.gsub! /[\u00ec-\u00ef]/, 'i'
|
28
|
+
name.gsub! /[\u00f2-\u00f6]|\u00f8/, 'o'
|
29
|
+
name.gsub! /[\u00f9-\u00fc]/, 'u'
|
30
|
+
name.gsub! /[\u00fd\u00ff]/, 'y'
|
31
|
+
|
32
|
+
# Latin Extended-A
|
33
|
+
name.gsub! /[\u0100-\u0105]/, 'a'
|
34
|
+
name.gsub! /[\u0106-\u010d]/, 'c'
|
35
|
+
name.gsub! /[\u010e-\u0111]/, 'd'
|
36
|
+
name.gsub! /[\u0112-\u011b]/, 'e'
|
37
|
+
name.gsub! /[\u011c-\u0123]/, 'g'
|
38
|
+
name.gsub! /[\u0124-\u0127]/, 'h'
|
39
|
+
name.gsub! /[\u0128-\u0131]/, 'i'
|
40
|
+
name.gsub! /[\u0132-\u0133]/, 'ij'
|
41
|
+
name.gsub! /[\u0134-\u0135]/, 'j'
|
42
|
+
name.gsub! /[\u0136-\u0138]/, 'k'
|
43
|
+
name.gsub! /[\u0139-\u0142]/, 'l'
|
44
|
+
name.gsub! /[\u0143-\u014b]/, 'n'
|
45
|
+
name.gsub! /[\u014C-\u0151]/, 'o'
|
46
|
+
name.gsub! /[\u0152-\u0153]/, 'oe'
|
47
|
+
name.gsub! /[\u0154-\u0159]/, 'r'
|
48
|
+
name.gsub! /[\u015a-\u0162]/, 's'
|
49
|
+
name.gsub! /[\u0162-\u0167]/, 't'
|
50
|
+
name.gsub! /[\u0168-\u0173]/, 'u'
|
51
|
+
name.gsub! /[\u0174-\u0175]/, 'w'
|
52
|
+
name.gsub! /[\u0176-\u0178]/, 'y'
|
53
|
+
name.gsub! /[\u0179-\u017e]/, 'z'
|
54
|
+
|
55
|
+
# denormalized diacritics
|
56
|
+
name.gsub! /[\u0300-\u036f]/, ''
|
57
|
+
end
|
58
|
+
|
59
|
+
name.strip.gsub /[^\w]+/, '-'
|
60
|
+
end
|
61
|
+
|
62
|
+
SUFFIXES = /^([Jj][Rr]\.?|I{2,3}|I?V|VI{1,3}|[A-Z]\.)$/
|
63
|
+
|
64
|
+
# rearrange line in an order suitable for sorting
|
65
|
+
def self.sortable_name(name)
|
66
|
+
name = name.split.reverse
|
67
|
+
suffix = (name.shift if name.first =~ SUFFIXES)
|
68
|
+
suffix += ' ' + name.shift if name.first =~ SUFFIXES
|
69
|
+
name << name.shift
|
70
|
+
# name << name.shift if name.first=='van'
|
71
|
+
name.last.sub! /^IJ/, 'Ij'
|
72
|
+
name.unshift(suffix) if suffix
|
73
|
+
name.map! {|word| asciize(word)}
|
74
|
+
name.reverse.join(' ').downcase
|
75
|
+
end
|
76
|
+
|
77
|
+
def sortable_name
|
78
|
+
Person.sortable_name(self.public_name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/whimsy/asf/podlings.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'nokogiri'
|
2
|
+
require 'date'
|
2
3
|
require_relative '../asf'
|
3
4
|
|
4
5
|
module ASF
|
5
|
-
class
|
6
|
+
class Podling
|
6
7
|
include Enumerable
|
8
|
+
attr_accessor :name, :status, :description, :mentors, :champion, :reporting
|
7
9
|
|
10
|
+
# three consecutive months, starting with this one
|
8
11
|
def quarter
|
9
12
|
[
|
10
13
|
Date.today.strftime('%B'),
|
@@ -13,36 +16,116 @@ module ASF
|
|
13
16
|
]
|
14
17
|
end
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
19
|
+
# create a podling from a Nokogiri node built from podlings.xml
|
20
|
+
def initialize(node)
|
21
|
+
@name = node['name']
|
22
|
+
@resource = node['resource']
|
23
|
+
@status = node['status']
|
24
|
+
@enddate = node['enddate']
|
25
|
+
@startdate = node['startdate']
|
26
|
+
@description = node.at('description').text
|
27
|
+
@mentors = node.search('mentor').map {|mentor| mentor['username']}
|
28
|
+
@champion = node.at('champion')['availid'] if node.at('champion')
|
29
|
+
|
30
|
+
@reporting = node.at('reporting')
|
31
|
+
end
|
32
|
+
|
33
|
+
# map resource to name
|
34
|
+
def name
|
35
|
+
@resource
|
36
|
+
end
|
37
|
+
|
38
|
+
# also map resource to id
|
39
|
+
def id
|
40
|
+
@resource
|
41
|
+
end
|
42
|
+
|
43
|
+
# map name to display_name
|
44
|
+
def display_name
|
45
|
+
@name || @resource
|
46
|
+
end
|
47
|
+
|
48
|
+
# parse startdate
|
49
|
+
def startdate
|
50
|
+
return unless @startdate
|
51
|
+
return Date.parse("#@startdate-15") if @startdate.length < 8
|
52
|
+
Date.parse(@startdate)
|
53
|
+
rescue ArgumentError
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# parse enddate
|
58
|
+
def enddate
|
59
|
+
return unless @enddate
|
60
|
+
return Date.parse("#@enddate-15") if @enddate.length < 8
|
61
|
+
Date.parse(@enddate)
|
62
|
+
rescue ArgumentError
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# lazy evaluation of reporting
|
67
|
+
def reporting
|
68
|
+
if @reporting.instance_of? Nokogiri::XML::Element
|
69
|
+
group = @reporting['group']
|
70
|
+
monthly = @reporting.text.split(/,\s*/) if @reporting['monthly']
|
71
|
+
@reporting = %w(January April July October) if group == '1'
|
72
|
+
@reporting = %w(February May August November) if group == '2'
|
73
|
+
@reporting = %w(March June September December) if group == '3'
|
74
|
+
@reporting.rotate! until quarter.include? @reporting.first
|
75
|
+
|
76
|
+
if monthly
|
77
|
+
monthly.shift until monthly.empty? or quarter.include? monthly.first
|
78
|
+
@reporting = (monthly + @reporting).uniq
|
34
79
|
end
|
80
|
+
end
|
81
|
+
|
82
|
+
@reporting
|
83
|
+
end
|
84
|
+
|
85
|
+
# list of podlings
|
86
|
+
def self.list
|
87
|
+
incubator_content = ASF::SVN['asf/incubator/public/trunk/content']
|
88
|
+
podlings_xml = "#{incubator_content}/podlings.xml"
|
35
89
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
43
|
-
data[:champion] = node.at('champion')['availid'] if node.at('champion')
|
44
|
-
yield node['resource'], data
|
90
|
+
if @mtime != File.mtime(podlings_xml)
|
91
|
+
@list = []
|
92
|
+
podlings = Nokogiri::XML(File.read(podlings_xml))
|
93
|
+
podlings.search('podling').map do |node|
|
94
|
+
@list << new(node)
|
95
|
+
end
|
45
96
|
end
|
97
|
+
|
98
|
+
@list
|
99
|
+
end
|
100
|
+
|
101
|
+
# find a podling by name
|
102
|
+
def self.find(name)
|
103
|
+
list.find {|podling| podling.name == name}
|
104
|
+
end
|
105
|
+
|
106
|
+
# below is for backwards compatibility
|
107
|
+
|
108
|
+
# make class itself enumerable
|
109
|
+
class << self
|
110
|
+
include Enumerable
|
111
|
+
end
|
112
|
+
|
113
|
+
# return the entire list as a hash
|
114
|
+
def self.to_h
|
115
|
+
Hash[self.to_a]
|
116
|
+
end
|
117
|
+
|
118
|
+
# provide a list of podling names and descriptions
|
119
|
+
def self.each(&block)
|
120
|
+
list.each {|podling| block.call podling.name, podling}
|
121
|
+
end
|
122
|
+
|
123
|
+
# allow attributes to be accessed as hash
|
124
|
+
def [](name)
|
125
|
+
return self.send name if self.respond_to? name
|
46
126
|
end
|
47
127
|
end
|
128
|
+
|
129
|
+
# more backwards compatibility
|
130
|
+
Podlings = Podling
|
48
131
|
end
|
data/lib/whimsy/asf/rack.rb
CHANGED
@@ -6,15 +6,15 @@ require 'thread'
|
|
6
6
|
module ASF
|
7
7
|
module Auth
|
8
8
|
DIRECTORS = {
|
9
|
-
'rbowen' => 'rb',
|
10
9
|
'curcuru' => 'sc',
|
11
10
|
'bdelacretaz' => 'bd',
|
11
|
+
'isabel' => 'id',
|
12
|
+
'marvin' => 'mh',
|
12
13
|
'jim' => 'jj',
|
13
14
|
'mattmann' => 'cm',
|
14
|
-
'ke4qqq' => 'dn',
|
15
15
|
'brett' => 'bp',
|
16
|
-
'
|
17
|
-
'
|
16
|
+
'gstein' => 'gs',
|
17
|
+
'markt' => 'mt'
|
18
18
|
}
|
19
19
|
|
20
20
|
# decode HTTP authorization, when present
|
data/lib/whimsy/asf/site.rb
CHANGED
@@ -53,7 +53,7 @@ module ASF
|
|
53
53
|
templates = ASF::SVN['asf/infrastructure/site/trunk/content']
|
54
54
|
file = "#{templates}/index.html"
|
55
55
|
if not File.exist?(file)
|
56
|
-
Wunderbar.
|
56
|
+
Wunderbar.error "Unable to find 'infrastructure/site/trunk/content'"
|
57
57
|
return {}
|
58
58
|
end
|
59
59
|
return @@list if not @@list.empty? and File.mtime(file) == @@mtime
|
data/lib/whimsy/asf/svn.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'thread'
|
3
3
|
require 'open3'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
4
6
|
|
5
7
|
module ASF
|
6
8
|
|
@@ -30,6 +32,10 @@ module ASF
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def self.[](name)
|
35
|
+
self.find!(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find(name)
|
33
39
|
return @testdata[name] if @testdata[name]
|
34
40
|
|
35
41
|
result = repos[(@mock+name.sub('private/','')).to_s.sub(/\/*$/, '')] ||
|
@@ -39,13 +45,136 @@ module ASF
|
|
39
45
|
|
40
46
|
# recursively try parent directory
|
41
47
|
if name.include? '/'
|
42
|
-
base = File.basename(name)
|
43
|
-
result =
|
48
|
+
base = File.basename(name).untaint
|
49
|
+
result = find(File.dirname(name))
|
44
50
|
if result and File.exist?(File.join(result, base))
|
45
51
|
File.join(result, base)
|
46
52
|
end
|
47
53
|
end
|
48
54
|
end
|
55
|
+
|
56
|
+
def self.find!(name)
|
57
|
+
result = self.find(name)
|
58
|
+
|
59
|
+
if not result
|
60
|
+
raise Exception.new("Unable to find svn checkout for #{@base + name}")
|
61
|
+
end
|
62
|
+
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
# retrieve revision, content for a file in svn
|
67
|
+
def self.get(path, user=nil, password=nil)
|
68
|
+
# build svn info command
|
69
|
+
cmd = ['svn', 'info', path, '--non-interactive']
|
70
|
+
|
71
|
+
# password was supplied, add credentials
|
72
|
+
if password
|
73
|
+
cmd += ['--username', user, '--password', password, '--no-auth-cache']
|
74
|
+
end
|
75
|
+
|
76
|
+
# default the values to return
|
77
|
+
revision = '0'
|
78
|
+
content = nil
|
79
|
+
|
80
|
+
# issue svn info command
|
81
|
+
stdout, status = Open3.capture2(*cmd)
|
82
|
+
if status.success?
|
83
|
+
# extract revision number
|
84
|
+
revision = stdout[/^Revision: (\d+)/, 1]
|
85
|
+
|
86
|
+
# extract contents
|
87
|
+
cmd[1] = 'cat'
|
88
|
+
content, status = Open3.capture2(*cmd)
|
89
|
+
end
|
90
|
+
|
91
|
+
# return results
|
92
|
+
return revision, content
|
93
|
+
end
|
94
|
+
|
95
|
+
# update a file or directory in SVN, working entirely in a temporary
|
96
|
+
# directory
|
97
|
+
def self.update(path, msg, env, _, options={})
|
98
|
+
if File.directory? path
|
99
|
+
dir = path
|
100
|
+
basename = nil
|
101
|
+
else
|
102
|
+
dir = File.dirname(path)
|
103
|
+
basename = File.basename(path)
|
104
|
+
end
|
105
|
+
|
106
|
+
if path.start_with? '/' and not path.include? '..' and File.exist?(path)
|
107
|
+
dir.untaint
|
108
|
+
basename.untaint
|
109
|
+
end
|
110
|
+
|
111
|
+
tmpdir = Dir.mktmpdir.untaint
|
112
|
+
|
113
|
+
begin
|
114
|
+
# create an empty checkout
|
115
|
+
_.system ['svn', 'checkout', '--depth', 'empty',
|
116
|
+
['--username', env.user, '--password', env.password],
|
117
|
+
`svn info #{dir}`[/URL: (.*)/, 1], tmpdir]
|
118
|
+
|
119
|
+
# retrieve the file to be updated (may not exist)
|
120
|
+
if basename
|
121
|
+
tmpfile = File.join(tmpdir, basename).untaint
|
122
|
+
_.system ['svn', 'update',
|
123
|
+
['--username', env.user, '--password', env.password],
|
124
|
+
tmpfile]
|
125
|
+
else
|
126
|
+
tmpfile = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
# determine the new contents
|
130
|
+
if not tmpfile
|
131
|
+
# updating a directory
|
132
|
+
previous_contents = contents = nil
|
133
|
+
yield tmpdir, ''
|
134
|
+
elsif File.file? tmpfile
|
135
|
+
# updating an existing file
|
136
|
+
previous_contents = File.read(tmpfile)
|
137
|
+
contents = yield tmpdir, File.read(tmpfile)
|
138
|
+
else
|
139
|
+
# updating a new file
|
140
|
+
previous_contents = nil
|
141
|
+
contents = yield tmpdir, ''
|
142
|
+
previous_contents = File.read(tmpfile) if File.file? tmpfile
|
143
|
+
end
|
144
|
+
|
145
|
+
# create/update the temporary copy
|
146
|
+
if contents and not contents.empty?
|
147
|
+
File.write tmpfile, contents
|
148
|
+
if not previous_contents
|
149
|
+
_.system ['svn', 'add',
|
150
|
+
['--username', env.user, '--password', env.password],
|
151
|
+
tmpfile]
|
152
|
+
end
|
153
|
+
elsif tmpfile and File.file? tmpfile
|
154
|
+
File.unlink tmpfile
|
155
|
+
_.system ['svn', 'delete',
|
156
|
+
['--username', env.user, '--password', env.password],
|
157
|
+
tmpfile]
|
158
|
+
end
|
159
|
+
|
160
|
+
if options[:dryrun]
|
161
|
+
# show what would have been committed
|
162
|
+
rc = _.system ['svn', 'diff', tmpfile]
|
163
|
+
else
|
164
|
+
# commit the changes
|
165
|
+
rc = _.system ['svn', 'commit', tmpfile || tmpdir,
|
166
|
+
['--username', env.user, '--password', env.password],
|
167
|
+
'--message', msg.untaint]
|
168
|
+
end
|
169
|
+
|
170
|
+
# fail if there are pending changes
|
171
|
+
unless rc == 0 and `svn st #{tmpfile || tmpdir}`.empty?
|
172
|
+
raise "svn failure #{path.inspect}"
|
173
|
+
end
|
174
|
+
ensure
|
175
|
+
FileUtils.rm_rf tmpdir
|
176
|
+
end
|
177
|
+
end
|
49
178
|
end
|
50
179
|
|
51
180
|
end
|