Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 85
- Log:
Extensive additions to support SVN-like collections revisions presented
in a similar manner to changesets under Collaboa. Integration with
"Ticket [#]xyz" text in check-in logs - generates links to Collboa tickets
if the text is spotted. Note that revision collation is a little slow and
not perfect; it works on directories, since revisions relate to paths and
unlike changesets are not repository-unique. See lib/revision_parser.rb
for more. Requires a CVShistory installation to function - future versions
might be smart enough to extract the information directly.
- Author:
- adh
- Date:
- Sun Oct 15 13:47:21 +0100 2006
- Size:
- 6895 Bytes
1 | require 'strscan' |
2 | require 'rss' |
3 | |
4 | class RevisionDetails |
5 | attr_accessor(:title, |
6 | :revision, |
7 | :category, |
8 | :description, |
9 | :author, |
10 | :date, |
11 | :path, |
12 | :folder, |
13 | :link, |
14 | :log) |
15 | |
16 | def initialize(title, |
17 | revision, |
18 | category, |
19 | description, |
20 | author, |
21 | date, |
22 | path, |
23 | folder, |
24 | link, |
25 | log) |
26 | |
27 | self.title = title |
28 | self.revision = revision |
29 | self.category = category |
30 | self.description = description |
31 | self.author = author |
32 | self.date = date |
33 | self.folder = folder |
34 | self.path = path |
35 | self.link = link |
36 | self.log = log |
37 | end |
38 | end |
39 | |
40 | class RevisionParser |
41 | |
42 | # Initialize the object - pass a CVSHistory RSS feed URL. |
43 | # |
44 | def initialize(feed) |
45 | @feed = feed |
46 | end |
47 | |
48 | # Fetch and parse the CVSHistory feed, returning a hash keyed |
49 | # by revision number (as a string). Each revision entry contains |
50 | # an array of hashes of revision data. The optional parameter is |
51 | # set to 'true' to try and fetch and parse log data using 'cvs |
52 | # rlog'. Obviously, this slows down operation though it makes the |
53 | # returned data more comprehensive. By default the parameter is |
54 | # set to 'false' so logs are not retrieved. Note that logs in |
55 | # RevisionDetails objects will never be an empty string - they |
56 | # will either be a message saying log data couldn't be retrieved |
57 | # or contain some parsed log data. |
58 | # |
59 | # The keys for the hash are revision numbers as strings, but in |
60 | # CVS revisions apply to directories - revision "1.2" does not |
61 | # uniquely identify a single group of files. The path to which |
62 | # the revision applies is thus used as a prefix for the revision |
63 | # number to form the key string, with a ": " separator - e.g. |
64 | # "/CVSROOT: 1.2". |
65 | # |
66 | def fetch_and_parse(extract_logs = false) |
67 | |
68 | revisions = {} |
69 | rss = RSS::Parser.parse(@feed) |
70 | |
71 | rss.items.each do |item| |
72 | # Description format: |
73 | # "authorname: Category X.Y (path/of/file/from/cvs/root)" |
74 | |
75 | description = item.description |
76 | category = item.category.content |
77 | |
78 | if (category and category != '' and description and description != '') |
79 | # Match so that [1] = author, [2] = revision, [3] = (ignore), [4] = path. |
80 | |
81 | parsed = description.match("^(.*?): #{category} (([0-9]+\.?)+) \\((.*)\\)$") |
82 | |
83 | unless(parsed.nil? or |
84 | parsed.size < 5 or |
85 | parsed[1].empty? or |
86 | parsed[2].empty? or |
87 | parsed[4].empty?) |
88 | |
89 | # We only use the item.author field if the parser couldn't find much of |
90 | # any use; CVSHistory tries to generate e-mail addresses for the author |
91 | # but they don't really make much sense. |
92 | |
93 | author = parsed[1] || item.author |
94 | revision = parsed[2] |
95 | |
96 | # Now we can construct revision key for the hash. |
97 | |
98 | revision_key = "/#{parsed[4]}: #{revision}" |
99 | |
100 | # The path is just a path to the changed file - add on the leafname. |
101 | # This could be extracted from the 'guid' field in the RSS data but |
102 | # that's sufficiently opaque to have no confidence in its format for |
103 | # a variety of CVS operations. Instead, use the CVSweb link and take |
104 | # the leafname (or leaf directory) from that. |
105 | |
106 | folder = parsed[4] + '/' |
107 | path = folder |
108 | link = item.link |
109 | index = (link[-1] == '/') ? link.rindex('/', -2) : link.rindex('/') |
110 | path += index.nil? ? link : link[(index + 1)..-1] |
111 | |
112 | # Should we try to use the link to fetch log data? |
113 | |
114 | log_cache = {} |
115 | cache_size = 0 |
116 | log = nil |
117 | |
118 | if (extract_logs) |
119 | # Construct the CVS command to retrieve log information. |
120 | |
121 | error = nil |
122 | command = "cvs rlog -lS -r#{revision} #{path} 2> /dev/null" |
123 | |
124 | # Store log data in a temporary internal cache to avoid fetching |
125 | # logs on a particular file over and over again. Very crude cache |
126 | # size management - just ditch the cache if it gets too big! |
127 | |
128 | if (cache_size > 1048576) # 1 MiB |
129 | log_cache = {} |
130 | cache_size = 0 |
131 | end |
132 | |
133 | if (log_cache[command].nil?) |
134 | begin |
135 | log_cache[command] = `#{command}` |
136 | cache_size += log_cache[command].length |
137 | rescue |
138 | error = $! |
139 | log_cache[command] = '' |
140 | end |
141 | end |
142 | |
143 | # Synthesise log entries if log data retrieval failed, else look |
144 | # for the log's descriptive text. |
145 | |
146 | if (error.nil?) |
147 | sscan = StringScanner.new(log_cache[command]) |
148 | found = sscan.scan_until(/^revision #{revision}\n/) |
149 | found = sscan.scan(/^date:.*?\n/) if (found) |
150 | found = sscan.scan_until(/^=============================================================================$/) if (found) |
151 | log = found[0..-(sscan.matched_size + 2)] if (found) |
152 | |
153 | # Trim white space and chop off '\n' at the start or end of the |
154 | # log text. Reset the log to 'nil' if the string ends up empty. |
155 | |
156 | if (log) |
157 | log.strip! |
158 | log.chomp! |
159 | log = log[1..-1] while log[0..0] == "\n" |
160 | log = nil if (log.empty?) |
161 | end |
162 | else |
163 | log = "Log data could not be retrieved: '#{error.to_s}'" |
164 | end |
165 | end # From 'if (extract_logs)' |
166 | |
167 | # Push the entry onto an array in the revisions hash, creating an |
168 | # empty array beforehand for the first entry under the current key. |
169 | |
170 | revisions[revision_key] = [] if revisions[revision_key].nil? |
171 | revisions[revision_key].push( { :title => item.title, |
172 | :revision => revision, |
173 | :category => category, |
174 | :description => description, |
175 | :author => author, |
176 | :date => item.pubDate, |
177 | :path => path, |
178 | :folder => folder, |
179 | :link => link, |
180 | :log => log.nil? ? 'Log data not available.' : log |
181 | } ) |
182 | end |
183 | end |
184 | end # For 'each' iterator |
185 | |
186 | return revisions |
187 | |
188 | end # For function defininition |
189 | end # For class defintion |