whimsy-asf 0.0.76 → 0.0.77
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.
- 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
|