Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 434
- Log:
Fix breakage of graphical revisions by character set conversion of
all data from the CVS viewer script; just parse text/* types.Switch from forced HTTP fetching of related resources to forced
HTTPS fetching, with certificate chain support.
- Author:
- rool
- Date:
- Fri Aug 30 03:16:00 +0100 2013
- Size:
- 7903 Bytes
1 | require 'strscan' |
2 | require 'uri' |
3 | require 'net/https' |
4 | require 'rss' |
5 | |
6 | class RevisionDetails |
7 | attr_accessor(:title, |
8 | :revision, |
9 | :category, |
10 | :description, |
11 | :author, |
12 | :date, |
13 | :path, |
14 | :folder, |
15 | :link, |
16 | :log) |
17 | |
18 | def initialize(title, |
19 | revision, |
20 | category, |
21 | description, |
22 | author, |
23 | date, |
24 | path, |
25 | folder, |
26 | link, |
27 | log) |
28 | |
29 | self.title = title |
30 | self.revision = revision |
31 | self.category = category |
32 | self.description = description |
33 | self.author = author |
34 | self.date = date |
35 | self.folder = folder |
36 | self.path = path |
37 | self.link = link |
38 | self.log = log |
39 | end |
40 | end |
41 | |
42 | class RevisionParser |
43 | |
44 | # Initialize the object - pass a CVSHistory RSS feed URL. |
45 | # |
46 | def initialize(feed) |
47 | @feed = feed |
48 | end |
49 | |
50 | # Fetch and parse the CVSHistory feed, returning a hash keyed |
51 | # by revision number (as a string). Each revision entry contains |
52 | # an array of hashes of revision data. The optional parameter is |
53 | # set to 'true' to try and fetch and parse log data using 'cvs |
54 | # rlog'. Obviously, this slows down operation though it makes the |
55 | # returned data more comprehensive. By default the parameter is |
56 | # set to 'false' so logs are not retrieved. Note that logs in |
57 | # RevisionDetails objects will never be an empty string - they |
58 | # will either be a message saying log data couldn't be retrieved |
59 | # or contain some parsed log data. |
60 | # |
61 | # The keys for the hash are revision numbers as strings, but in |
62 | # CVS revisions apply to directories - revision "1.2" does not |
63 | # uniquely identify a single group of files. The path to which |
64 | # the revision applies is thus used as a prefix for the revision |
65 | # number to form the key string, with a ": " separator - e.g. |
66 | # "/CVSROOT: 1.2". |
67 | # |
68 | def fetch_and_parse(extract_logs = false) |
69 | |
70 | # Site-specific issue: At ROOL, the SSL certificate issuer uses |
71 | # a certificate chain which isn't known about by Ruby initially. |
72 | # This causes SLL failures if we were to just try and get the |
73 | # RSS parser to fetch & parse the data by passing it "@feed" in |
74 | # "RSS::Parser.parse()". Instead we have to manually do the SSL |
75 | # foot work and pass the parser the fetched data. |
76 | |
77 | uri = URI.parse( @feed ) |
78 | https = Net::HTTP.new( uri.host, uri.port ) |
79 | https.use_ssl = true |
80 | https.verify_mode = OpenSSL::SSL::VERIFY_PEER |
81 | https.ca_file = SSL_CERT_CHAIN unless ( SSL_CERT_CHAIN.nil? || SSL_CERT_CHAIN.empty? ) |
82 | |
83 | feed_data = https.start do | http | |
84 | request = Net::HTTP::Get.new( uri.request_uri ) |
85 | response = https.request( request ) |
86 | |
87 | raise "#{ response.code }: #{ response.messages }" unless ( response.code.to_i >= 200 && response.code.to_i <= 299 ) |
88 | |
89 | response.body |
90 | end |
91 | |
92 | revisions = {} |
93 | rss = RSS::Parser.parse( feed_data ) |
94 | |
95 | rss.items.each do |item| |
96 | # Description format: |
97 | # "authorname: Category X.Y (path/of/file/from/cvs/root)" |
98 | |
99 | description = item.description |
100 | category = item.category.content |
101 | |
102 | if (category and category != '' and description and description != '') |
103 | # Match so that [1] = author, [2] = revision, [3] = (ignore), [4] = path. |
104 | |
105 | parsed = description.match("^(.*?): #{category} (([0-9]+\.?)+) \\((.*)\\)$") |
106 | |
107 | unless(parsed.nil? or |
108 | parsed.size < 5 or |
109 | parsed[1].empty? or |
110 | parsed[2].empty? or |
111 | parsed[4].empty?) |
112 | |
113 | # We only use the item.author field if the parser couldn't find much of |
114 | # any use; CVSHistory tries to generate e-mail addresses for the author |
115 | # but they don't really make much sense. |
116 | |
117 | author = parsed[1] || item.author |
118 | revision = parsed[2] |
119 | |
120 | # Now we can construct revision key for the hash. |
121 | |
122 | revision_key = "/#{parsed[4]}: #{revision}" |
123 | |
124 | # The path is just a path to the changed file - add on the leafname. |
125 | # This could be extracted from the 'guid' field in the RSS data but |
126 | # that's sufficiently opaque to have no confidence in its format for |
127 | # a variety of CVS operations. Instead, use the CVSweb link and take |
128 | # the leafname (or leaf directory) from that. |
129 | |
130 | folder = parsed[4] + '/' |
131 | path = folder |
132 | link = item.link |
133 | index = (link[-1] == '/') ? link.rindex('/', -2) : link.rindex('/') |
134 | path += index.nil? ? link : link[(index + 1)..-1] |
135 | |
136 | # Should we try to use the link to fetch log data? |
137 | |
138 | log_cache = {} |
139 | cache_size = 0 |
140 | log = nil |
141 | |
142 | if (extract_logs) |
143 | # Construct the CVS command to retrieve log information. |
144 | |
145 | error = nil |
146 | command = "cvs rlog -lS -r#{revision} #{path} 2> /dev/null" |
147 | |
148 | # Store log data in a temporary internal cache to avoid fetching |
149 | # logs on a particular file over and over again. Very crude cache |
150 | # size management - just ditch the cache if it gets too big! |
151 | |
152 | if (cache_size > 1048576) # 1 MiB |
153 | log_cache = {} |
154 | cache_size = 0 |
155 | end |
156 | |
157 | if (log_cache[command].nil?) |
158 | begin |
159 | log_cache[command] = `#{command}` |
160 | cache_size += log_cache[command].length |
161 | rescue |
162 | error = $! |
163 | log_cache[command] = '' |
164 | end |
165 | end |
166 | |
167 | # Synthesise log entries if log data retrieval failed, else look |
168 | # for the log's descriptive text. |
169 | |
170 | if (error.nil?) |
171 | sscan = StringScanner.new(log_cache[command]) |
172 | found = sscan.scan_until(/^revision #{revision}\n/) |
173 | found = sscan.scan(/^date:.*?\n/) if (found) |
174 | found = sscan.scan_until(/^=============================================================================$/) if (found) |
175 | log = found[0..-(sscan.matched_size + 2)] if (found) |
176 | |
177 | # Trim white space and chop off '\n' at the start or end of the |
178 | # log text. Reset the log to 'nil' if the string ends up empty. |
179 | |
180 | if (log) |
181 | log.strip! |
182 | log.chomp! |
183 | log = log[1..-1] while log[0..0] == "\n" |
184 | log = nil if (log.empty?) |
185 | end |
186 | else |
187 | log = "Log data could not be retrieved: '#{error.to_s}'" |
188 | end |
189 | end # From 'if (extract_logs)' |
190 | |
191 | # Push the entry onto an array in the revisions hash, creating an |
192 | # empty array beforehand for the first entry under the current key. |
193 | |
194 | revisions[revision_key] = [] if revisions[revision_key].nil? |
195 | revisions[revision_key].push( { :title => item.title, |
196 | :revision => revision, |
197 | :category => category, |
198 | :description => description, |
199 | :author => author, |
200 | :date => item.pubDate, |
201 | :path => path, |
202 | :folder => folder, |
203 | :link => link, |
204 | :log => log.nil? ? 'Log data not available.' : log |
205 | } ) |
206 | end |
207 | end |
208 | end # For 'each' iterator |
209 | |
210 | return revisions |
211 | |
212 | end # For function defininition |
213 | end # For class defintion |