2 // $Id: versioncontrol_git.module,v 1.25 2007/11/10 00:32:25 jpetso Exp $
5 define('VERSIONCONTROL_GIT_UPDATE_CRON', 0);
6 define('VERSIONCONTROL_GIT_UPDATE_XGIT', 1);
8 // The admin and user edit pages.
9 include_once(drupal_get_path('module', 'versioncontrol_git') .'/versioncontrol_git.admin.inc');
12 * Implementation of hook_versioncontrol_backends().
15 * A structured array containing information about this known backends.
16 * Array key is the unique string identifier of the version control system.
17 * The corresponding array values are again structured arrays and consist
18 * of elements with the following keys:
20 * 'name': The user-visible name of the VCS.
21 * 'description': A short description of the backend, if possible not longer
22 * than one or two sentences.
23 * 'capabilities': An array listing optional capabilities, in addition to the
24 * required functionality like retrieval of detailed
25 * commit information. Array values can be an arbitrary
26 * combination of VERSIONCONTROL_CAPABILITY_* values. If no
27 * additional capabilities are supported by the backend,
28 * this array will be empty.
29 * 'autoadd': An array listing which tables should be managed by
30 * Version Control API instead of doing it manually in
31 * the backend. Array values can be an arbitrary combination of
32 * VERSIONCONTROL_AUTOADD_* values. If no array additions
33 * should be automatically managed, this array will be empty.
35 function versioncontrol_git_versioncontrol_backends() {
37 // The array key is up to 8 characters long, and used as unique identifier
38 // for this VCS, in functions, URLs and in the database.
40 // The user-visible name of the VCS.
43 // A short description of the VCS, if possible not longer than one or two sentences.
44 'description' => t('GIT (the stupid content tracker) is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals.'),
46 // A list of optional capabilities, in addition to the required retrieval
47 // of detailed commit information.
48 'capabilities' => array(
49 // Able to cancel commits if the committer lacks permissions
50 // to commit to specific paths and/or branches.
51 VERSIONCONTROL_CAPABILITY_COMMIT_RESTRICTIONS,
52 // Able to cancel branch or tag assignments if the committer lacks
53 // permissions to create/update/delete those.
54 VERSIONCONTROL_CAPABILITY_BRANCH_TAG_RESTRICTIONS,
57 // An array listing which tables should be managed by Version Control API
58 // instead of doing it manually in the backend.
60 // versioncontrol_insert_repository() will automatically insert
61 // array elements from $repository['git_specific'] into
62 // {versioncontrol_git_repositories} and versioncontrol_get_repositories()
63 // will automatically fetch it from there.
64 VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES,
65 // versioncontrol_insert_commit() will automatically insert
66 // array elements from $commit['git_specific'] into
67 // {versioncontrol_git_commits} and versioncontrol_get_commits()
68 // will automatically fetch it from there.
69 VERSIONCONTROL_FLAG_AUTOADD_COMMITS,
77 * Implementation of [versioncontrol_backend]_get_commit_actions():
78 * Retrieve detailed information about what happened in a single commit.
81 * The commit whose actions should be retrieved.
84 * A structured array containing the exact details of what happened to
85 * each item in this commit. Array keys are the current/new paths, also for
86 * VERSIONCONTROL_ACTION_DELETED actions even if the file actually doesn't
87 * exist anymore. The corresponding array values are again structured arrays
88 * and consist of elements with the following keys:
90 * 'action': Specifies how the item was modified.
91 * One of the predefined VERSIONCONTROL_ACTION_* values.
92 * 'modified': Boolean value, specifies if a file was modified in addition
93 * to the other action in the 'action' element of the array.
94 * Only exists for the VERSIONCONTROL_ACTION_MOVED
95 * and VERSIONCONTROL_ACTION_COPIED actions.
96 * 'current item': The updated state of the modified item. Exists for all
97 * actions except VERSIONCONTROL_ACTION_DELETED.
98 * 'source items': An array with the previous state(s) of the modified item.
99 * Path and branch will always be the same as in the current
100 * item except for the VERSIONCONTROL_ACTION_MOVED,
101 * VERSIONCONTROL_ACTION_COPIED and
102 * VERSIONCONTROL_ACTION_MERGED actions.
103 * Exists for all actions except VERSIONCONTROL_ACTION_ADDED.
105 * Item values are structured arrays and consist of elements
106 * with the following keys:
108 * 'type': Specifies the item type, which is either
109 * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY.
110 * 'path': The path of the item at the specific revision.
111 * 'revision': The (file-level) revision when the item was changed.
112 * If there is no such revision (which may be the case for
113 * directory items) then the 'revision' element is
115 * '[xxx]_specific': May be set by the backend to remember additional
116 * item info. ("[xxx]" is the unique string identifier
117 * of the respective version control system.)
119 function versioncontrol_git_get_commit_actions($commit) {
121 $result = db_query('SELECT item_revision_id, action, type, path,
122 revision, source_revision
123 FROM {versioncontrol_git_item_revisions}
124 WHERE commit_id = %d', $commit['commit_id']);
126 while ($item_revision = db_fetch_object($result)) {
128 'action' => $item_revision->action,
131 if ($item_revision->action != VERSIONCONTROL_ACTION_DELETED) {
132 $action['current item'] = array(
133 'type' => $item_revision->type,
134 'path' => $item_revision->path,
135 'revision' => $item_revision->revision,
136 'git_specific' => array(
137 'item_revision_id' => $item_revision->item_revision_id,
138 'selected_branch_id' => $commit['git_specific']['branch_id'],
139 'selected_date' => $commit['date'],
143 if ($item_revision->action != VERSIONCONTROL_ACTION_ADDED) {
144 $action['source items'] = array(array(
145 'type' => $item_revision->type,
146 'path' => $item_revision->path,
147 'revision' => $item_revision->source_revision,
148 'git_specific' => array(
149 'item_revision_id' => $item_revision->item_revision_id,
150 'selected_branch_id' => $commit['git_specific']['branch_id'],
151 'selected_date' => $commit['date'],
156 $actions[$item_revision->path] = $action;
163 * Implementation of [versioncontrol_backend]_get_directory_item():
164 * Retrieve the item of the deepest-level directory in the repository that is
165 * common to all the changed/branched/tagged items in a commit, branch or
166 * tag operation. In other words, this function gets you the item
167 * for $operation['directory'].
170 * The commit, branch or tag operation whose deepest-level
171 * changed/branched/tagged directory should be retrieved.
174 * The requested directory item. Item values are structured arrays and
175 * consist of elements with the following keys:
177 * - 'type': Specifies the item type, which in this case can only be
178 * VERSIONCONTROL_ITEM_DIRECTORY.
179 * - 'path': The path of the directory, which will be the same
180 * as $operation['directory'].
181 * - 'revision': The (file-level) revision when the item was last changed.
182 * If there is no such revision (which may be the case for
183 * directory items) then the 'revision' element is an empty string.
184 * - '[xxx]_specific': May be set by the backend to remember additional
185 * item info. ("[xxx]" is the unique string identifier
186 * of the respective version control system.)
188 function versioncontrol_git_get_directory_item($operation) {
190 'type' => VERSIONCONTROL_ITEM_DIRECTORY,
191 'path' => $operation['directory'],
193 'git_specific' => array(
194 'selected_date' => $operation['date'],
197 if (isset($operation['git_specific']['branch_id'])) { // it's a commit or branch
198 $item['selected_branch_id'] = $operation['git_specific']['branch_id'];
200 if (isset($operation['tag_op_id'])) { // it's a tag
201 $item['selected_tag_op'] = $operation;
207 * Implementation of [versioncontrol_backend]_get_commit_branches():
208 * Retrieve the branches that have been affected by the given commit.
211 * An array of strings that identify a branch in the respective repository,
212 * or an empty array if no branches were affected at all. (For GIT, there
213 * should always be a exactly one branch in the resulting array.)
215 function versioncontrol_git_get_commit_branches($commit) {
216 if (!isset($commit['git_specific']['branch_id'])) {
220 $branch = versioncontrol_get_branch($commit['git_specific']['branch_id']);
221 if (!isset($branch)) {
222 return array(); // should only happen in case of database inconsistencies
224 return array($branch['branch_name']);
228 * Retrieve the set of items that were affected by a branch operation.
231 * The branch operation whose items should be retrieved. This is an array
232 * like the one returned by versioncontrol_get_branch_operation().
235 * An array of all items that were affected by the branching operation.
236 * An empty result array means that the whole repository has been branched.
237 * Item values are structured arrays and consist of elements
238 * with the following keys:
240 * - 'type': Specifies the item type, which is either
241 * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY.
242 * - 'path': The path of the item at the specific revision.
243 * - 'revision': The (file-level) revision when the item was changed.
244 * If there is no such revision (which may be the case for
245 * directory items) then the 'revision' element is an empty string.
246 * - 'source branch': Optional, may be set by the backend if the
247 * source branch (the one that this one branched off) can be retrieved.
248 * If given, this is a string with the original branch name.
249 * - '[xxx]_specific': May be set by the backend to remember additional
250 * item info. ("[xxx]" is the unique string identifier of the respective
251 * version control system.)
253 function versioncontrol_git_get_branched_items($branch) {
255 $result = db_query('SELECT ir.item_revision_id, ir.type, ir.path, ir.revision
256 FROM {versioncontrol_git_item_branch_points} ib
257 INNER JOIN {versioncontrol_git_item_revisions} ir
258 ON ib.item_revision_id = ir.item_revision_id
259 WHERE ib.branch_op_id = %d', $branch['branch_op_id']);
261 while ($item_revision = db_fetch_object($result)) {
263 'type' => $item_revision->type,
264 'path' => $item_revision->path,
265 'revision' => $item_revision->revision,
266 'git_specific' => array(
267 'item_revision_id' => $item_revision->item_revision_id,
268 'selected_branch_id' => $branch['git_specific']['branch_id'],
269 'selected_date' => $branch['date'],
277 * Retrieve the set of items that were affected by a tag operation.
280 * The tag operation whose items should be retrieved. This is an array
281 * like the one returned by versioncontrol_get_tag_operation().
284 * An array of all items that were affected by the tagging operation.
285 * An empty result array means that the whole repository has been tagged.
286 * Item values are structured arrays and consist of elements
287 * with the following keys:
289 * - 'type': Specifies the item type, which is either
290 * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY.
291 * - 'path': The path of the item at the specific revision.
292 * - 'revision': The (file-level) revision when the item was changed.
293 * If there is no such revision (which may be the case for
294 * directory items) then the 'revision' element is an empty string.
295 * - 'source branch': Optional, may be set by the backend if the
296 * source branch (the one that this tag comes from) can be retrieved.
297 * If given, this is a string with the original branch name.
298 * - '[xxx]_specific': May be set by the backend to remember additional
299 * item info. ("[xxx]" is the unique string identifier of the respective
300 * version control system.)
302 function versioncontrol_git_get_tagged_items($tag) {
304 $result = db_query('SELECT ir.item_revision_id, ir.type, ir.path, ir.revision
305 FROM {versioncontrol_git_item_tags} it
306 INNER JOIN {versioncontrol_git_item_revisions} ir
307 ON it.item_revision_id = ir.item_revision_id
308 WHERE it.tag_op_id = %d', $tag['tag_op_id']);
310 while ($item_revision = db_fetch_object($result)) {
312 'type' => $item_revision->type,
313 'path' => $item_revision->path,
314 'revision' => $item_revision->revision,
315 'git_specific' => array(
316 'item_revision_id' => $item_revision->item_revision_id,
317 'selected_tag_op' => $tag,
318 'selected_date' => $tag['date'],
326 * Implementation of [versioncontrol_backend]_get_current_item_branch():
327 * Retrieve the current branch that this item is in. If this item was part of
328 * the result of versioncontrol_get_commit_actions(), this will probably be
329 * the branch that this item was committed to. The main branch ('HEAD' for GIT)
330 * is also a valid branch and should be expected as return value.
333 * The repository that the item is located in.
335 * The item whose current branch should be retrieved.
338 * A string containing the current item branch, or NULL if no branch
339 * is known or applicable.
341 function versioncontrol_git_get_current_item_branch($repository, $item) {
342 if (!isset($item['git_specific']['selected_branch_id'])) {
346 $branch = versioncontrol_get_branch($item['git_specific']['selected_branch_id']);
347 if (!isset($branch)) {
350 return $branch['branch_name'];
354 * Implementation of [versioncontrol_backend]_get_current_item_tag():
355 * Retrieve the current tag of this item.
358 * The repository that the item is located in.
360 * The item whose current branch should be retrieved.
363 * A tag operation array like the return value
364 * of versioncontrol_get_tag_operation(), or NULL if no tag
365 * is known or applicable.
367 function versioncontrol_git_get_current_item_tag($repository, $item) {
368 if (!isset($item['git_specific']['selected_tag_op'])) {
371 return $item['git_specific']['selected_tag_op'];
375 * Implementation of [vcs_backend]_get_parent_item():
376 * Retrieve the parent (directory) item of a given item.
379 * The repository that the item is located in.
381 * The item whose parent should be retrieved.
382 * @param $parent_path
383 * NULL if the direct parent of the given item should be retrieved,
384 * or a parent path that is further up the directory tree.
387 * The parent directory item at the same revision as the given item.
388 * If $parent_path is not set and the item is already the topmost one
389 * in the repository, the item is returned as is. It also stays the same
390 * if $parent_path is given and the same as the path of the given item.
391 * If the given directory path does not correspond to a parent item,
394 function versioncontrol_git_get_parent_item($repository, $item, $parent_path = NULL) {
395 if (!isset($parent_path)) {
396 $item['path'] = dirname($item['path']);
399 else if (strpos($item['path'] .'/', $parent_path .'/') !== FALSE) {
400 $item['path'] = $parent_path;
408 * Implementation of [versioncontrol_backend]_commit():
409 * Manage (insert or delete) additional commit data in the database.
412 * Either 'insert' when the commit is in the process of being created,
413 * or 'delete' if it will be deleted after this function has been called.
415 * A single commit array, like the ones returned
416 * by versioncontrol_get_commits().
417 * @param $commit_actions
418 * A structured array containing the exact details of what happened to
419 * each item in this commit. The structure of this array is the same as
420 * the return value of versioncontrol_get_commit_actions().
422 function versioncontrol_git_commit($op, $commit, $commit_actions) {
425 foreach ($commit_actions as $path => $action) {
427 $source_revision = '';
429 // If available, get item type and revision from the contained items.
430 if (isset($action['current item'])) {
431 $type = $action['current item']['type'];
432 $revision = $action['current item']['revision'];
434 if (isset($action['source items'])) {
435 $type = $action['source items'][0]['type']; // only one source item for GIT
436 $source_revision = $action['source items'][0]['revision'];
439 $item_revision_id = db_next_id('{versioncontrol_git_item_revisions}_item_revision_id');
441 "INSERT INTO {versioncontrol_git_item_revisions}
442 (item_revision_id, commit_id, type, path, revision,
443 action, lines_added, lines_removed, source_revision)
444 VALUES (%d, %d, %d, '%s', '%s', %d, %d, %d, '%s')",
445 $item_revision_id, $commit['commit_id'], $type, $path, $revision,
446 $action['action'], $action['git_specific']['lines_added'],
447 $action['git_specific']['lines_removed'], $source_revision
453 $result = db_query('SELECT item_revision_id
454 FROM {versioncontrol_git_item_revisions}
455 WHERE commit_id = %d', $commit['commit_id']);
457 while ($revision = db_fetch_object($result)) {
458 db_query('DELETE FROM {versioncontrol_git_item_tags}
459 WHERE item_revision_id = %d',
460 $revision->item_revision_id);
461 db_query('DELETE FROM {versioncontrol_git_item_branch_points}
462 WHERE item_revision_id = %d',
463 $revision->item_revision_id);
465 db_query('DELETE FROM {versioncontrol_git_item_revisions}
466 WHERE commit_id = %d', $commit['commit_id']);
472 * Implementation of [versioncontrol_backend]_branch_operation():
473 * Manage (insert or delete) additional branch operation data in the database.
476 * Either 'insert' when the branch operation is in the process of being created,
477 * or 'delete' if it will be deleted after this function has been called.
479 * A single branch operation array, like the one returned
480 * by versioncontrol_get_branch_operation().
481 * @param $branched_items
482 * An array of all items that are affected by the branching operation.
483 * Compared to standard item arrays, the ones in here may not have the
484 * 'revision' element set (however, the GIT backend always provides those)
485 * and can optionally contain a 'source branch' element that specifies
486 * the original branch name of this item.
487 * (For $op == 'delete', 'source branch' is never set.)
488 * An empty $branched_items array means that the whole repository has been
489 * branched (which is not used for GIT, as branches/tags are always assigned
490 * to specific files).
492 function versioncontrol_git_branch_operation($op, $branch, $branched_items) {
493 _versioncontrol_git_branch_or_tag_operation(
494 $op, $branch, $branched_items, 'item_branch_points', 'branch_op_id'
499 * Implementation of [versioncontrol_backend]_tag_operation():
500 * Manage (insert or delete) additional tag operation data in the database.
503 * Either 'insert' when the tag operation is in the process of being created,
504 * or 'delete' if it will be deleted after this function has been called.
506 * A single tag operation array, like the one returned
507 * by versioncontrol_get_tag_operation().
508 * @param $tagged_items
509 * An array of all items that are affected by the tagging operation.
510 * Compared to standard item arrays, the ones in here may not have the
511 * 'revision' element set (however, the GIT backend always provides those)
512 * and can optionally contain a 'source branch' element that specifies
513 * the original branch name of this item.
514 * (For $op == 'move' or $op == 'delete', 'source branch' is never set.)
515 * An empty $tagged_items array means that the whole repository has been
516 * tagged (which is not used for GIT, as branches/tags are always assigned
517 * to specific files).
519 function versioncontrol_git_tag_operation($op, $tag, $tagged_items) {
520 _versioncontrol_git_branch_or_tag_operation(
521 $op, $tag, $tagged_items, 'item_tags', 'tag_op_id'
526 * The implementation of the branch and tag operation hooks is essentially
527 * the same (just different table and primary key names),
528 * so let's share the code in this function.
530 function _versioncontrol_git_branch_or_tag_operation($op, $branch_or_tag, $items, $table_name, $primary_key_name) {
533 foreach ($items as $item) {
534 $result = db_query("SELECT item_revision_id
535 FROM {versioncontrol_git_item_revisions}
536 WHERE path = '%s' AND revision = '%s'",
537 $item['path'], $item['revision']);
539 while ($revision = db_fetch_object($result)) {
540 db_query('INSERT INTO {versioncontrol_git_'. $table_name .'}
541 ('. $primary_key_name .', item_revision_id)
543 $branch_or_tag[$primary_key_name],
544 $revision->item_revision_id);
550 db_query('DELETE FROM {versioncontrol_git_'. $table_name .'}
551 WHERE '. $primary_key_name .' = %d',
552 $branch_or_tag[$primary_key_name]);
558 * Implementation of [versioncontrol_backend]_account():
559 * Manage (insert, update or delete) additional GIT user account data
563 * Either 'insert' when the account is in the process of being created,
564 * or 'update' when username or additional module data change,
565 * or 'delete' if it will be deleted after this function has been called.
567 * The Drupal user id corresponding to the VCS account.
569 * The VCS specific username (a string).
571 * The repository where the user has its VCS account.
572 * @param $additional_data
573 * An array of additional author information.
575 function versioncontrol_git_account($op, $uid, $username, $repository, $additional_data = array()) {
576 $git_specific = $additional_data['git_specific'];
580 if (!isset($git_specific) || !isset($git_specific['password'])) {
581 drupal_set_message(t('Error: no GIT password given on account creation!'), 'error');
584 db_query("INSERT INTO {versioncontrol_git_accounts}
585 (uid, repo_id, password)
586 VALUES (%d, %d, '%s')",
587 $uid, $repository['repo_id'], $git_specific['password']);
591 if (!isset($git_specific) || !isset($git_specific['password'])) {
592 return; // the user didn't update the password in the process.
594 db_query("UPDATE {versioncontrol_git_accounts}
596 WHERE uid = %d AND repo_id = %d",
597 $git_specific['password'], $uid, $repository['repo_id']);
599 if (!user_access('administer version control systems')) {
600 // Admins get "The account has been updated successfully" anyways.
601 drupal_set_message(t('The GIT password has been updated successfully.'));
606 db_query('DELETE FROM {versioncontrol_git_accounts}
607 WHERE uid = %d AND repo_id = %d',
608 $uid, $repository['repo_id']);
614 * Implementation of [vcs_backend]_import_accounts():
615 * Import accounts into a repository, given text data from the accounts file.
616 * No accounts are deleted, new accounts are inserted, and existing accounts
617 * are updated with imported ones.
620 * The repository where the accounts will be imported.
622 * The contents of the "account data" text area where the user has to
623 * enter/copy the contents of the version control system's accounts file.
625 function versioncontrol_git_import_accounts($repository, $data) {
626 $lines = explode("\n", $data);
629 foreach ($lines as $line) {
630 if (preg_match('/^\s*(#.*)?$/', $line)) { // filter out empty and commented lines
633 // Extract the account information and create or update the user accounts.
634 list($username, $password, $run_as_user) = explode(':', $line);
635 if (!empty($username) && !empty($password)) {
636 $additional_data = array(
637 'git_specific' => array('password' => $password),
639 $uid = versioncontrol_get_account_uid_for_username($repository['repo_id'], $username, TRUE);
642 versioncontrol_update_account($repository, $uid, $username, $additional_data);
643 $names[] = t('updated !username', array('!username' => $username));
646 $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $username));
648 versioncontrol_insert_account($repository, $uid, $username, $additional_data);
649 $names[] = t('added !username', array('!username' => $username));
652 $names[] = t('didn\'t add !username (no matching Drupal username exists)',
653 array('!username' => $username));
660 drupal_set_message(t('Failed to import GIT accounts.'), 'error');
663 drupal_set_message(theme('item_list', $names, t('The import of GIT accounts has been completed successfully:')));
668 * Implementation of [vcs_backend]_export_accounts():
669 * Export accounts of a repository to text data that is suitable for
670 * copying to the version control system's accounts file.
673 * The repository whose accounts will be exported.
675 * The list (array) of accounts that should be exported, given in the same
676 * format as the return value of versioncontrol_get_accounts().
677 * All accounts in this list are from the above repository.
680 * The exported textual representation of the account list.
682 function versioncontrol_git_export_accounts($repository, $accounts) {
683 if (empty($accounts)) {
684 return '# '. t('no user accounts available to export');
687 $accounts_flat = array();
688 $uid_constraints = array();
689 $params = array($repository['repo_id']);
691 foreach ($accounts as $uid => $usernames_per_repository) {
692 foreach ($usernames_per_repository as $repo_id => $username) {
693 $accounts_flat[$uid] = array('uid' => $uid, 'username' => $username);
694 $uid_constraints[] = 'uid = %d';
699 $result = db_query('SELECT uid, password FROM {versioncontrol_git_accounts}
701 AND ('. implode(' OR ', $uid_constraints) .')',
703 while ($account = db_fetch_object($result)) {
704 $accounts_flat[$account->uid]['password'] = $account->password;
708 if (!empty($repository['run_as_user'])) {
709 $run_as_user = ':'. $repository['run_as_user'];
712 foreach ($accounts_flat as $uid => $account) {
713 $data .= '# '. url('user/'. $uid, NULL, NULL, TRUE) ."\n";
714 $data .= $account['username'] .':'. $account['password'] . $run_as_user ."\n\n";