/[drupal]/contributions/modules/versioncontrol_hg/versioncontrol_hg.log.inc
ViewVC logotype

Contents of /contributions/modules/versioncontrol_hg/versioncontrol_hg.log.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.10 - (show annotations) (download) (as text)
Sun Feb 3 04:01:12 2008 UTC (21 months, 3 weeks ago) by ezyang
Branch: MAIN
CVS Tags: DRUPAL-5--1-0-RC1, HEAD
Changes since 1.9: +3 -2 lines
File MIME type: text/x-php
Minor coding format corrections.
1 <?php
2 // $Id: versioncontrol_hg.log.inc,v 1.9 2008/02/03 03:24:35 ezyang Exp $
3
4 /**
5 * @file
6 * Mercurial backend for Version Control API - Provides Mercurial commit
7 * information and account management as a pluggable backend.
8 *
9 * This file defines internal functions for processing logs.
10 *
11 * Copyright 2008 by Edward Z. Yang (ezyang, http://drupal.org/user/211688)
12 */
13
14 /**
15 * Update repository's database tables by interfacing with Mercurial.
16 *
17 * @warning
18 * This code should not directly interface with the Mercurial
19 * executable; the hg_* functions defined in hg/hg.inc should be used
20 * instead. If you need functionality not yet defined, submit a patch
21 * for hg/hg.inc implementing it.
22 *
23 * @warning
24 * This function does not account for memory exhaustion.
25 * An alternative implementation could read each log entry one by
26 * one from the file (as CVS's implementation does), however, the
27 * resulting data-structure would still cause large amounts of memory
28 * usage (this is a primary deficiency of CVS) and would require
29 * multiple database queries. After determining which heuristics
30 * need to be able to access previous and later log entries, we will
31 * refactor accordingly; an alternate implementation would be to
32 * limit rev_range so that only batches of the logs are parsed at
33 * a time. Nevertheless, real world usage data is necessary before
34 * we can make a decision in this respect.
35 *
36 * @param $repository
37 * Repository variable as returned by versioncontrol_get_repository()
38 */
39 function _versioncontrol_hg_log_update_repository(&$repository) {
40
41 // Because we only support local repositories, `hg pull` is NOT called
42 // before we perform these changes. It is the responsibility of all
43 // branch maintainers to push their changes to this repository so that
44 // their changes are registered.
45
46 $latest_rev = $repository['hg_specific']['latest_rev'];
47 if ($latest_rev === false) {
48 // This is necessary to ensure that the revisions are ordered
49 // properly, from earliest to latest. We need logs in this order
50 // because some algorithms reference earlier information.
51 $rev_range = '0:';
52 }
53 else {
54 $rev_range = ($latest_rev + 1) .':';
55 }
56 $raw_logs = hg_log($repository['root'], $rev_range);
57 // watchdog('special', hg_last_cmd());
58
59 if (empty($raw_logs)) {
60 // No new logs, abort.
61 return;
62 }
63
64 // In the future, this could be replaced with a one-way iterator
65 // which would let us process log entries piecemeal.
66 foreach ($raw_logs as $log) {
67
68 _versioncontrol_hg_log_preprocess_log($log);
69
70 // For branches and tags, the second parameter is invariably array();
71 // we might want to move it out to simplify the if-then logic.
72
73 // Branch handling must occur before commits
74 $raw_branch = _versioncontrol_hg_log_parse_branch_operation($log, $repository);
75 if ($raw_branch !== false) {
76 list($branch, $branched_items) = $raw_branch;
77 versioncontrol_insert_branch_operation($branch, $branched_items);
78 }
79
80 // Multiple tags operations can occur in one commit
81 $tags = _versioncontrol_hg_log_parse_tag_operation($log, $repository);
82 if ($tags !== false) {
83 foreach ($tags as $tag) {
84 versioncontrol_insert_tag_operation($tag, array());
85 }
86 }
87
88 // Commit handling
89 list($commit, $commit_actions) = _versioncontrol_hg_log_parse_commit($log, $repository);
90 versioncontrol_insert_commit($commit, $commit_actions);
91
92 }
93
94 // This depends on the previous foreach loop
95 $latest_rev = $log['rev'];
96
97 // Update latest revision
98 $repository['hg_specific']['latest_rev'] = $latest_rev;
99 db_query('UPDATE {versioncontrol_hg_repositories}
100 SET latest_rev = %d WHERE repo_id = %d',
101 $repository['hg_specific']['latest_rev'], $repository['repo_id']);
102
103 }
104
105 /**
106 * Perform common pre-processing on a log entry from hg_logs(), although no
107 * major changes to the array's structure.
108 *
109 * @warning
110 * This function MUST NOT be lossy or fundamentally change the log
111 * structure; any results of this pre-processing should be stored in
112 * unique keys prefixed with an underscore, so as to prevent collisions
113 * with the keys specified in hg/templates/changeset.tmpl.
114 *
115 * @param
116 * Raw logs variable to pre-process. The following additional indexes
117 * will be added:
118 *
119 * '_date' => String UTC Unix timestamp of changeset
120 * '_offset' => Integer time zone offset of commiter
121 * '_manifest' => Array parent nodeid
122 * '_parents' => array(0 => First parent nodeid, 1 => Second parent nodeid)
123 * '_author' => String author name w/o email
124 * '_email' => String author's email
125 * '_branch' => Either the contents of branches, or 'default'
126 *
127 * Nodeids take the form of array(string revision number, string SHA-1 hash)
128 */
129 function _versioncontrol_hg_log_preprocess_log(&$log) {
130
131 // Parse Mercurial's log format into Unix timestamp and offset.
132 // We do NOT convert the timestamp into an integer to prevent overflow
133 list($log['_date'], $offset) = explode('.', $log['date']);
134 $log['_offset'] = (int) $offset;
135
136 // Split manifest nodeid (note that we are not converting the revision
137 // number to an integer either.)
138 $log['_manifest'] = explode(':', $log['manifest']);
139
140 // Split parents, and then split their nodeids
141 $parents = explode(' ', $log['parents']);
142 foreach ($parents as $k => $v) {
143 $log['_parents'][$k] = explode(':', $v);
144 }
145
146 // Parse author into author name and email
147 // This is somewhat fragile, but should work as long as a user does
148 // not have '>' in their name! :-)
149 $bits = explode('<', $log['author']);
150 $log['_author'] = rtrim($bits[0]);
151 if (!empty($bits[1])) {
152 $log['_email'] = rtrim($bits[1], '>');
153 }
154 else {
155 $log['_email'] = null;
156 }
157
158 $log['_branch'] = ($log['branches'] !== '') ? $log['branches'] : 'default';
159
160 }
161
162 /**
163 * Parses an array item of hg_log() into form ready for
164 * versioncontrol_insert_commit().
165 *
166 * @param $log
167 * Item of array from hg_log() to be parsed.
168 * @param $repository
169 * Repository array as per versioncontrol_get_repository().
170 * @return
171 * Array in form array(0 => $commit, 1 => $commit_actions)
172 * which are ready for insertion using versioncontrol_insert_commit().
173 */
174 function _versioncontrol_hg_log_parse_commit($log, $repository) {
175
176 // Build $commit:
177 $commit = _versioncontrol_hg_log_parse_operation($log, $repository);
178 $commit['message'] = $log['desc'];
179 $commit['revision'] = $log['node'];
180 $commit['hg_specific'] = array(
181 'branch_id' => versioncontrol_ensure_branch($log['_branch'], $repository['repo_id']),
182 'rev' => $log['rev'],
183 // Using key [1] to retrieve only SHA-1 hash; the revision number
184 // should be the same as 'rev'
185 'manifest' => $log['_manifest'][1],
186 // We'll need this in order to look up the vc_op_id. In
187 // this case, we use revision because it's easier to handle and
188 // this is all internal processing.
189 'parent1' => $log['_parents'][0][0],
190 'parent2' => $log['_parents'][1][0],
191 );
192
193 // Build $commit_actions:
194
195 // Under very special conditions (basically, when a merge adds
196 // a file, but no edits were made to it), file_adds can contain
197 // files that are not in files. So, we need to merge these two
198 // arrays.
199 $files = array_unique(array_merge($log['files'], $log['file_adds']));
200
201 // These lookups must be tested with isset(), NOT !empty()
202 $lookup_adds = array_flip($log['file_adds']);
203 $lookup_dels = array_flip($log['file_dels']);
204 $lookup_copies = array_flip($log['file_copies']);
205
206 $commit_actions = array();
207 foreach ($files as $raw_file) {
208 $commit_action = array();
209
210 // Normalize Mercurial's file declaration "CVS" style to accomodate
211 // commitlog.
212 $file = "/$raw_file";
213
214 // CVS's implementation involves building a large array with
215 // everything you could possibly need, and then excising things
216 // based on options. While that approach may take less code,
217 // it is less robust to change.
218
219 // We need to use $raw_file to reference items in the logs.
220 if (isset($lookup_adds[$raw_file])) {
221 $commit_action['action'] = VERSIONCONTROL_ACTION_ADDED;
222 }
223 elseif (isset($lookup_dels[$raw_file])) {
224 $commit_action['action'] = VERSIONCONTROL_ACTION_DELETED;
225 }
226 elseif (isset($lookup_copies[$raw_file])) {
227 // This doesn't ever seem to happen.
228 $commit_action['action'] = VERSIONCONTROL_ACTION_COPIED;
229 }
230 else {
231 // File was modified
232 $commit_action['action'] = VERSIONCONTROL_ACTION_MODIFIED;
233 }
234
235 // Logs do not have information for 'modified', because
236 // file_copies is never filled with anything.
237 $commit_action['modified'] = FALSE;
238
239 $commit_action['current item'] = array();
240
241 // This isn't actually used, but we set it for completeness-sake.
242 $commit_action['current item']['path'] = $file;
243
244 if ($commit_action['action'] != VERSIONCONTROL_ACTION_DELETED) {
245 $commit_action['current item']['type'] = VERSIONCONTROL_ITEM_FILE;
246 }
247 else {
248 $commit_action['current item']['type'] = VERSIONCONTROL_ITEM_FILE_DELETED;
249 }
250
251 // This will be set later in versioncontrol_hg_commit(), because we
252 // need to ask the database for some information (the log doesn't tell
253 // us very much about this.)
254 $commit_action['source items'] = array();
255
256 $commit_actions[$file] = $commit_action;
257
258 }
259
260 return array($commit, $commit_actions);
261 }
262
263 /**
264 * Parse an array item of hg_log() into form ready for
265 * versioncontrol_insert_branch_operation().
266 *
267 * @return
268 * Array in form of array($branch, $branched_items = array()), with
269 * variables corresponding to parameters of versioncontrol_insert_branch_operation()
270 */
271 function _versioncontrol_hg_log_parse_branch_operation($log, $repository) {
272 $branch_id = versioncontrol_get_branch_id($log['_branch'], $repository['repo_id'], TRUE);
273 if ($branch_id !== null) {
274 return false;
275 }
276 $commit = _versioncontrol_hg_log_parse_operation($log, $repository);
277 $commit['branch_name'] = $log['_branch'];
278 $commit['action'] = VERSIONCONTROL_ACTION_ADDED;
279 // All items are affected, so there's no need to check.
280 versioncontrol_ensure_branch($log['_branch'], $repository['repo_id']);
281 return array($commit, array());
282 }
283
284 /**
285 * Parses an array item of hg_log() into form ready for
286 * versioncontrol_insert_tag_operation().
287 */
288 function _versioncontrol_hg_log_parse_tag_operation($log, $repository) {
289 if (!in_array('.hgtags', $log['files']) && !in_array('.hgtags', $log['file_adds'])) {
290 // No changes to .hgtags, so no tag operations
291 return false;
292 }
293 // We need to grab the contents of .hgtags at that revision
294 $hgtags = hg_cat($repository['root'], '.hgtags', $log['rev']);
295 $tags = _hg_parse_hgtags($hgtags);
296
297 // Now, lets grab the old set of tags
298 $result = db_query(
299 "SELECT t.name, c.revision FROM {versioncontrol_hg_tags} t
300 JOIN {versioncontrol_commits} c ON t.vc_op_id = c.vc_op_id
301 WHERE t.repo_id = %d
302 ",
303 $repository['repo_id']
304 );
305 $old_tags = array();
306 while ($row = db_fetch_object($result)) {
307 $old_tags[$row->name] = $row->revision;
308 }
309
310 // Now, let's determine the changes
311 // We purposely never use VERSIONCONTROL_ACTION_MOVED because
312 // Mercurial doesn't think of tags this way; it'd be represented
313 // as an add and a delete.
314
315 $ret = array();
316
317 // This is to be copied for each tag operation we make.
318 $default = _versioncontrol_hg_log_parse_operation($log, $repository);
319 $default['message'] = $commit['des'];
320
321 foreach ($old_tags as $name => $nodeid) {
322 $op = $default;
323 $op['tag_name'] = $name;
324 if (!isset($tags[$name])) {
325 $op['action'] = VERSIONCONTROL_ACTION_DELETED;
326 }
327 elseif ($nodeid != $tags[$name]) {
328 $op['action'] = VERSIONCONTROL_ACTION_DELETED;
329 }
330 else {
331 // Everything's good!
332 unset($tags[$name]);
333 continue;
334 }
335 $ret[] = $op;
336 }
337
338 foreach ($tags as $name => $nodeid) {
339 $op = $default;
340 $op['tag_name'] = $name;
341 $op['action'] = VERSIONCONTROL_ACTION_ADDED;
342 $ret[] = $op;
343 }
344
345 return $ret;
346
347 }
348
349 /**
350 * Returns an operation array with the common values for all types
351 * of operations (commit, branch, tag) instantiated.
352 */
353 function _versioncontrol_hg_log_parse_operation($log, $repository) {
354 return array(
355 'repository' => $repository,
356 'date' => $log['_date'],
357 'username' => $log['_email'],
358 );
359 }

  ViewVC Help
Powered by ViewVC 1.1.2