/[drupal]/contributions/modules/versioncontrol_hg/versioncontrol_hg.module
ViewVC logotype

Contents of /contributions/modules/versioncontrol_hg/versioncontrol_hg.module

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


Revision 1.15 - (show annotations) (download) (as text)
Tue Feb 19 00:46:21 2008 UTC (21 months, 1 week ago) by ezyang
Branch: MAIN
CVS Tags: DRUPAL-5--1-0-RC1, HEAD
Changes since 1.14: +14 -1 lines
File MIME type: text/x-php
Fix #222899 (display shortened changeset identifiers)
1 <?php
2 // $Id: versioncontrol_hg.module,v 1.14 2008/02/19 00:43:48 ezyang Exp $
3 /**
4 * @file
5 * Mercurial backend for Version Control API - Provides Mercurial commit
6 * information and account management as a pluggable backend.
7 *
8 * @note
9 * Docblocks for standard functions are omitted; check versioncontrol
10 * for the most up-to-date versions.
11 *
12 * Copyright 2008 by Edward Z. Yang (ezyang, http://drupal.org/user/211688)
13 */
14
15 // Mercurial PHP interface functions
16 include_once(drupal_get_path('module', 'versioncontrol_hg') .'/hg/hg.inc');
17
18 /**
19 * Implementation of hook_versioncontrol_backends().
20 */
21 function versioncontrol_hg_versioncontrol_backends() {
22 return array(
23 // The array key is up to 8 characters long, and used as unique identifier
24 // for this VCS, in functions, URLs and in the database.
25 'hg' => array(
26 // The user-visible name of the VCS.
27 'name' => 'Mercurial',
28
29 // A short description of the VCS, if possible not longer than one or two sentences.
30 'description' => t('Mercurial is a distributed revision control system written in Python.'),
31
32 // A list of optional capabilities, in addition to the required retrieval
33 // of detailed commit information. All allowed values are listed below.
34 'capabilities' => array(
35 // Able to retrieve a file or its revision number based on a global
36 // revision identifier.
37 VERSIONCONTROL_CAPABILITY_ATOMIC_COMMITS,
38 ),
39
40 // An array listing which tables should be managed by Version Control API
41 // instead of doing it manually in the backend.
42 // All allowed values are listed below.
43 'flags' => array(
44 // versioncontrol_insert_repository() will automatically insert
45 // array elements from $repository['[xxx]_specific'] into
46 // {versioncontrol_[xxx]_repositories} and versioncontrol_get_repositories()
47 // will automatically fetch it from there.
48 VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES,
49
50 // versioncontrol_insert_commit() will automatically insert
51 // array elements from $commit['[xxx]_specific'] into
52 // {versioncontrol_[xxx]_commits} and versioncontrol_get_commits()
53 // will automatically fetch it from there.
54 VERSIONCONTROL_FLAG_AUTOADD_COMMITS,
55 ),
56 ),
57 );
58 }
59
60 /**
61 * Implementation of hook_cron(): Update repositories that have cron updates
62 * enabled.
63 */
64 function versioncontrol_hg_cron() {
65 $result = db_query("SELECT repo_id FROM {versioncontrol_hg_repositories}");
66 // We have purposely omitted set_time_limit, as this is something that
67 // should be set by cron itself and not us!
68 while ($repo = db_fetch_object($result)) {
69 $repository = versioncontrol_get_repository($repo->repo_id);
70 if (isset($repository)) {
71 _versioncontrol_hg_update_repository($repository);
72 }
73 }
74 }
75
76 /**
77 * Performs all updates on a repository (currently just logs).
78 */
79 function _versioncontrol_hg_update_repository(&$repository) {
80 include_once(drupal_get_path('module', 'versioncontrol_hg') .'/versioncontrol_hg.log.inc');
81 _versioncontrol_hg_log_update_repository($repository);
82 }
83
84 /**
85 * Implementation of [versioncontrol_backend]_get_directory_item().
86 *
87 * @warning
88 * I don't understand why this can't have a generic implementation.
89 */
90 function versioncontrol_hg_get_directory_item($operation) {
91 $item = array(
92 'type' => VERSIONCONTROL_ITEM_DIRECTORY,
93 'path' => $operation['directory'],
94 'revision' => '',
95 // We removed selected_op; might need to readd it later
96 );
97 if (isset($operation['hg_specific']['branch_id'])) {
98 $item['hg_specific']['selected_branch_id'] = $operation['hg_specific']['branch_id'];
99 }
100 return $item;
101 }
102
103 /**
104 * Implementation of [versioncontrol_backend]_get_commit_branches().
105 *
106 * @warning
107 * This one could have a generic implementation too! The biggest problem
108 * is that versioncontrol doesn't directly handle branches, but if
109 * the module is going to call this function, it really ought to.
110 */
111 function versioncontrol_hg_get_commit_branches($commit) {
112 if (!isset($commit['hg_specific']['branch_id'])) {
113 return array();
114 }
115 $branch = versioncontrol_get_branch($commit['hg_specific']['branch_id']);
116 if (!isset($branch)) {
117 // Only for database inconsistencies:
118 return array();
119 }
120 return array($branch['branch_name']);
121 }
122
123 /**
124 * Implementation of [versioncontrol_backend]_commit():
125 *
126 * @warning
127 * An important precondition for our particular implementation is that
128 * all earlier revisions must be in the database.
129 */
130 function versioncontrol_hg_commit($op, $commit, $commit_actions) {
131 switch ($op) {
132 case 'insert':
133 foreach ($commit_actions as $path => $action) {
134
135 if ($path === '/.hgtags') {
136 $repo_id = $commit['repository']['repo_id'];
137 // We've got to handle the tags!
138 // This has nothing to do with what's going on below.
139 db_query("DELETE FROM {versioncontrol_hg_tags} WHERE repo_id = %d", $repo_id);
140 $hgtags = hg_cat($commit['repository']['root'], '.hgtags', $commit['hg_specific']['rev']);
141 $tags = _hg_parse_hgtags($hgtags);
142 foreach ($tags as $name => $nodeid) {
143 $vc_op_id = db_result(db_query(
144 "SELECT vc_op_id FROM {versioncontrol_commits} WHERE revision = '%s';",
145 $nodeid
146 ));
147 $tag_id = db_next_id('{versioncontrol_hg_tags}_tag_id');
148 db_query("INSERT INTO {versioncontrol_hg_tags}
149 (tag_id, vc_op_id, repo_id, name) VALUES (%d, %d, %d, '%s');",
150 $tag_id, $vc_op_id, $repo_id, $name
151 );
152 }
153 }
154
155 // Determine source information for this particular path
156 // based on both the commit's parent information and this
157 // filename. This is *very* Mercurial-specific.
158
159 $source = array();
160 foreach (array(1, 2) as $k) {
161 $rev = (string) $commit['hg_specific']["parent$k"];
162 $source[$k] = array();
163 if ($rev === '-1') {
164 // No parent, so our job is really easy.
165 $source[$k]['path'] = null;
166 $source[$k]['vc_op_id'] = null;
167 continue;
168 }
169 // Determine the branch of the parent
170 $branch_id = db_result(db_query(
171 "SELECT branch_id FROM {versioncontrol_hg_commits}
172 WHERE rev = %d",
173 $rev)
174 );
175 if ($branch_id === false) {
176 // Database inconsistency!
177 $source[$k]['path'] = null;
178 $source[$k]['vc_op_id'] = null;
179 continue;
180 }
181
182 // This step may need to be done multiple times
183 do {
184
185 // Now, let's determine the latest commit_action for this
186 // path on this branch: this will be the parent revision.
187 $result = db_fetch_array(db_query(
188 "SELECT actions.vc_op_id, actions.type, commits.rev
189 FROM {versioncontrol_hg_commit_actions} AS actions
190 JOIN {versioncontrol_hg_commits} AS commits
191 ON actions.vc_op_id = commits.vc_op_id
192 WHERE
193 actions.path = '%s' AND
194 commits.branch_id = %d AND
195 commits.rev <= %d
196 ORDER BY commits.rev DESC
197 LIMIT 1
198 ",
199 $path, $branch_id, $rev));
200
201 if (!$result) {
202 // Ok, the last item with this name either doesn't exist,
203 // so we're barking up the wrong
204 // tree: a branch had taken place, and the source file
205 // is in a different branch. (Note we don't have to worry
206 // about merges because they show up in commit actions.)
207 // We need to go to the top of the current branch and
208 // see what source it used.
209 $previous_branch = db_fetch_array(db_query(
210 "SELECT a.branch_id, a.rev FROM {versioncontrol_hg_commits} a
211 WHERE a.rev = (
212 SELECT parent1 FROM {versioncontrol_hg_commits}
213 WHERE branch_id = %d
214 ORDER BY rev ASC
215 LIMIT 1
216 )
217 ORDER BY a.rev DESC
218 LIMIT 1
219 ",
220 $branch_id
221 ));
222
223 if (!$previous_branch) {
224 // Database inconsistency!
225 $source_path = null;
226 $last_vc_op_id = null;
227 break;
228 }
229
230 $branch_id = $previous_branch['branch_id'];
231 $rev = $previous_branch['rev'];
232
233 continue;
234 }
235 elseif ($result['type'] == VERSIONCONTROL_ITEM_FILE_DELETED &&
236 $action['current item']['type'] != VERSIONCONTROL_ITEM_FILE_DELETED) {
237 // At some point earlier in the branch, this file was
238 // deleted, and now has been "rised from the dead" via
239 // a merge. Don't attempt to search any further.
240 // Note that if our action is a delete, this indicates
241 // a delete was merged in, and the parent IS valid (so
242 // we don't handle it here.)
243 $source_path = null;
244 $last_vc_op_id = null;
245 break;
246 }
247
248 // Note that this variable indicates, for merges, the
249 // operation of the other delete that is being merged in
250 $last_vc_op_id = $result['vc_op_id'];
251
252 // We currently have no way of telling if a file was copied.
253 $source_path = $path;
254
255 break;
256
257 } while (1);
258
259 $source[$k]['path'] = $source_path;
260 $source[$k]['vc_op_id'] = $last_vc_op_id;
261
262 }
263
264 $commit_action_id = db_next_id('{versioncontrol_hg_commit_actions}_commit_action_id');
265
266 // Use this to support nulls. Call it a nasty cludge... I think it's art. :-)
267 // Commented placeholders are used to maintain parameter order.
268 $s1p = ($source[1]['path'] === null) ? 'NULL /*%s*/' : "'%s'";
269 $s2p = ($source[2]['path'] === null) ? 'NULL /*%s*/' : "'%s'";
270 $s1v = ($source[1]['vc_op_id'] === null) ? 'NULL /*%d*/' : '%d';
271 $s2v = ($source[2]['vc_op_id'] === null) ? 'NULL /*%d*/' : '%d';
272
273 db_query(
274 "INSERT INTO {versioncontrol_hg_commit_actions}
275 (commit_action_id, vc_op_id, type, path, action,
276 source1_path, source2_path, source1_vc_op_id, source2_vc_op_id)
277 VALUES (%d, %d, %d, '%s', %d,
278 $s1p, $s2p, $s1v, $s2v)",
279 // Primary key:
280 $commit_action_id,
281 // Foreign key:
282 $commit['vc_op_id'],
283 $action['current item']['type'],
284 $path,
285 $action['action'],
286 // [Newline]
287 $source[1]['path'],
288 $source[2]['path'],
289 $source[1]['vc_op_id'],
290 $source[2]['vc_op_id']
291 );
292 }
293 break;
294
295 case 'delete':
296 $result = db_query('SELECT commit_action_id
297 FROM {versioncontrol_hg_commit_actions}
298 WHERE vc_op_id = %d', $commit['vc_op_id']);
299
300 // We have not implemented tags yet, but this is fairly sensible
301 // "unhooking" of tags from the deleted revisions.
302
303 //while ($revision = db_fetch_object($result)) {
304 // db_query('DELETE FROM {versioncontrol_hg_item_tags}
305 // WHERE item_revision_id = %d',
306 // $revision->item_revision_id);
307 //}
308 db_query('DELETE FROM {versioncontrol_hg_commit_actions}
309 WHERE vc_op_id = %d', $commit['vc_op_id']);
310 break;
311 }
312 }
313
314 /**
315 * Implementation of [versioncontrol_backend]_get_commit_actions().
316 */
317 function versioncontrol_hg_get_commit_actions($commit) {
318 $commit_actions = array();
319
320 $result = db_query('SELECT commit_action_id, action, type, path,
321 source1_path, source2_path, source1_vc_op_id, source2_vc_op_id
322 FROM {versioncontrol_hg_commit_actions}
323 WHERE vc_op_id = %d', $commit['vc_op_id']);
324
325 while ($raw_commit_action = db_fetch_array($result)) {
326 $commit_action = array(
327 'action' => $raw_commit_action['action'],
328 'modified' => FALSE,
329 'hg_specific' => array(
330 // Some housekeeping values:
331 // Not sure who would need this, but we'll keep it anyway:
332 'commit_action_id' => $raw_commit_action['commit_action_id'],
333 // This is for versioncontrol_hg_get_current_item_branch():
334 'selected_branch_id' => $commit['hg_specific']['branch_id'],
335 // Don't know who will need this yet:
336 // 'selected_op' => $commit,
337 ),
338 );
339
340 if ($raw_commit_action['action'] !== VERSIONCONTROL_ACTION_DELETED) {
341 $commit_action['current item'] = array(
342 'type' => $raw_commit_action['type'],
343 'path' => $raw_commit_action['path'],
344 'revision' => $commit['revision'],
345 );
346 }
347
348 // Most implementations check action, but since there are only
349 // two to check, we won't bother:
350 $commit_action['source items'] = array();
351 foreach (array(1, 2) as $k) {
352 if ($raw_commit_action["source{$k}_vc_op_id"] === null) {
353 continue;
354 }
355 // Pull the changeset node for the operation
356 // We could use versioncontrol_get_commits(), but that's total
357 // overkill for our needs. We could also have done a JOIN.
358 $revision = db_result(db_query(
359 "SELECT revision FROM {versioncontrol_commits}
360 WHERE vc_op_id = %d",
361 $raw_commit_action["source{$k}_vc_op_id"])
362 );
363 if (!$revision) {
364 // Database error, abort!
365 continue;
366 }
367 $commit_action['source items'][] = array(
368 'type' => $raw_commit_action['type'],
369 'path' => $raw_commit_action["source{$k}_path"],
370 'revision' => $revision,
371 );
372 }
373
374 $commit_actions[$raw_commit_action['path']] = $commit_action;
375 }
376
377 return $commit_actions;
378 }
379
380 /**
381 * Implementation of [versioncontrol_backend]_get_short_revision_identifier():
382 * Return a shortened version of the revision identifier, as plaintext.
383 * This is used by versioncontrol_format_commit_identifier()
384 * and versioncontrol_format_item_revision().
385 */
386 function versioncontrol_hg_format_short_revision_identifier($revision) {
387 // Let's return only the first 12 characters of the commit identifier,
388 // like Mercurial (including hgweb) does by default. This might result
389 // in collisions.
390 return substr($revision, 0, 12);
391 }
392
393 /**
394 * Implementation of [versioncontrol_backend]_get_current_item_branch().
395 */
396 function versioncontrol_hg_get_current_item_branch($repository, $item) {
397 if (!isset($item['hg_specific']['selected_branch_id'])) {
398 return NULL;
399 }
400
401 $branch = versioncontrol_get_branch($item['hg_specific']['selected_branch_id']);
402 if (!isset($branch)) {
403 return NULL;
404 }
405 return $branch['branch_name'];
406 }
407
408 /**
409 * Retrieve the set of items that were affected by a branch operation.
410 *
411 * @warning
412 * This function returns an empty array because all branches are
413 * repository-wide, so all files are affected.
414 */
415 function versioncontrol_hg_get_branched_items($branch) {
416 return array();
417 }
418
419 /**
420 * Retrieve the set of items that were affected by a tag operation.
421 *
422 * @warning
423 * This function returns an empty array because all tags are
424 * repository-wide, so all files are affected.
425 */
426 function versioncontrol_hg_get_tagged_items($tag) {
427 return array();
428 }
429
430 /**
431 * Implementation of [vcs_backend]_get_parent_item().
432 */
433 function versioncontrol_hg_get_parent_item($repository, $item, $parent_path = NULL) {
434 if (!isset($parent_path)) {
435 $item['path'] = dirname($item['path']);
436 return $item;
437 }
438 else if (strpos($item['path'] .'/', $parent_path .'/') !== FALSE) {
439 $item['path'] = $parent_path;
440 return $item;
441 }
442 return NULL;
443 }
444
445 /**
446 * Implementation of [versioncontrol_backend]_get_current_item_tag():
447 *
448 * @warning
449 * Because there are no tagged items, this function should not be called.
450 */
451 function versioncontrol_hg_get_current_item_tag() {
452 return NULL;
453 }
454

  ViewVC Help
Powered by ViewVC 1.1.2