forked from jpetso/versioncontrol
-
Notifications
You must be signed in to change notification settings - Fork 1
/
versioncontrol.module
3905 lines (3594 loc) · 151 KB
/
versioncontrol.module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
// $Id$
/**
* @file
* Version Control API - An interface to version control systems
* whose functionality is provided by pluggable back-end modules.
*
* Copyright 2006, 2007 Derek Wright ("dww" , http://drupal.org/user/46549)
* Copyright 2007, 2008, 2009 by Jakob Petsovits ("jpetso", http://drupal.org/user/56020)
*/
/**
* @name VCS operations
* a.k.a. stuff that is recorded for display purposes.
*/
//@{
define('VERSIONCONTROL_OPERATION_COMMIT', 1);
define('VERSIONCONTROL_OPERATION_BRANCH', 2);
define('VERSIONCONTROL_OPERATION_TAG', 3);
//@}
/**
* @name backend capabilities
* Optional capabilities that backend modules can provide.
*/
//@{
define('VERSIONCONTROL_CAPABILITY_ATOMIC_COMMITS', 1);
define('VERSIONCONTROL_CAPABILITY_COMMIT_RESTRICTIONS', 2);
define('VERSIONCONTROL_CAPABILITY_BRANCH_TAG_RESTRICTIONS', 3);
define('VERSIONCONTROL_CAPABILITY_DIRECTORY_REVISIONS', 4);
//@}
/**
* @name VCS backend flags
* Flags that backends can set to specify if Version Control API
* should do work for them.
*/
//@{
define('VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES', 1);
//@}
/**
* @name VCS item types.
*/
//@{
define('VERSIONCONTROL_ITEM_FILE', 1);
define('VERSIONCONTROL_ITEM_DIRECTORY', 2);
/**
* @name VCS "Deleted" item types.
* Only used for items that don't exist in the repository (anymore), at least
* not in the given revision. That is mostly the case with items that were
* deleted by a commit and are returned as result by
* versioncontrol_get_operation_items(). A "deleted file" can also be returned
* by directory listings for CVS, representing "dead files".
*/
//@{
define('VERSIONCONTROL_ITEM_FILE_DELETED', 3);
define('VERSIONCONTROL_ITEM_DIRECTORY_DELETED', 4);
//@}
//@}
/**
* @name VCS actions
* for a single item (file or directory) in a commit, or for branches and tags.
*/
//@{
define('VERSIONCONTROL_ACTION_ADDED', 1);
define('VERSIONCONTROL_ACTION_MODIFIED', 2);
define('VERSIONCONTROL_ACTION_MOVED', 3); //< == renamed
define('VERSIONCONTROL_ACTION_COPIED', 4);
define('VERSIONCONTROL_ACTION_MERGED', 5);
define('VERSIONCONTROL_ACTION_DELETED', 6);
define('VERSIONCONTROL_ACTION_REPLACED', 7);
define('VERSIONCONTROL_ACTION_OTHER', 8); //< for example, SVN revprop-only changes
//@}
/**
* @name VCS flag for operation item database entries
* The flag for operation item database entries, specifying whether an item
* is actually a direct "member" item of that operation or just a cached
* source item that speeds up operation queries. (Background: we want some
* source items to be considered when the caller passes a 'paths' constraint,
* but joining those is unnecessarily inefficient. Therefore, searched source
* items get directly included in the {versioncontrol_operation_items} table.)
* This is an implementation detail, the API user won't get to see this. Ever.
*/
//@{
define('VERSIONCONTROL_OPERATION_MEMBER_ITEM', 1);
define('VERSIONCONTROL_OPERATION_CACHED_AFFECTED_ITEM', 2);
//@}
/**
* @name Constraint 'cardinality' key
* Allowed values for the 'cardinality' key in constraint descriptions
* provided by hook_versioncontrol_operation_constraint_info().
*/
//@{
define('VERSIONCONTROL_CONSTRAINT_MULTIPLE', 1); // default
define('VERSIONCONTROL_CONSTRAINT_SINGLE', 2);
define('VERSIONCONTROL_CONSTRAINT_SINGLE_OR_MULTIPLE', 3);
//@}
/**
* @name User relation constraints
* Allowed values for use with the 'user_relation' constraint in
* versioncontrol_get_operations() queries.
*/
//@{
define('VERSIONCONTROL_USER_ASSOCIATED', 1);
define('VERSIONCONTROL_USER_ASSOCIATED_ACTIVE', 2);
//@}
/** Used internally by the repository and account admin pages. Private constant. */
define('VERSIONCONTROL_FORM_CREATE', FALSE);
/**
* Implementation of hook_init():
* Code that is run on every page request, except for cached ones.
*/
function versioncontrol_init() {
// The backend-only part of the API.
module_load_include('inc', 'versioncontrol', 'versioncontrol-backend');
}
/**
* Implementation of hook_theme().
*/
function versioncontrol_theme() {
$theme = array();
$theme['versioncontrol_account_username'] = array(
'arguments' => array('uid', 'username', 'repository', 'options' => NULL),
);
$theme['versioncontrol_user_statistics_table'] = array(
'arguments' => array('statistics', 'options'),
);
$theme['versioncontrol_user_statistics_item_list'] = array(
'arguments' => array('statistics', 'more_link'),
);
$theme['versioncontrol_user_statistics_account'] = array(
'arguments' => array('user_stats'),
);
return $theme;
}
/**
* Implementation of hook_user():
* Register additional user account edit tabs,
* and delete VCS accounts when the associated user account is deleted.
*/
function versioncontrol_user($type, &$edit, &$user, $category = NULL) {
switch ($type) {
case 'categories':
$categories = array();
$categories[] = array(
'name' => 'versioncontrol',
// user_menu() pipes 'title' though check_plain() already.
'title' => 'Repository accounts',
'weight' => 99,
);
return $categories;
case 'delete':
$accounts = versioncontrol_get_accounts(array('uids' => array($user->uid)), TRUE);
if (empty($accounts)) {
return;
}
$accounts_flat = array();
$repo_ids = array();
foreach ($accounts as $uid => $usernames_by_repository) {
foreach ($usernames_by_repository as $repo_id => $username) {
$accounts_flat[] = array('uid' => $uid, 'username' => $username, 'repo_id' => $repo_id);
$repo_ids[] = $repo_id;
}
}
$repositories = versioncontrol_get_repositories(array('repo_ids' => $repo_ids));
foreach ($accounts_flat as $account) {
if (isset($repositories[$account['repo_id']])) {
versioncontrol_delete_account(
$repositories[$account['repo_id']], $account['uid'], $account['username']
);
}
}
return;
}
}
/**
* Implementation of hook_menu().
*/
function versioncontrol_menu() {
$items = array();
$admin = array(
'page callback' => 'drupal_get_form',
'access arguments' => array('administer version control systems'),
'file' => 'versioncontrol.admin.inc',
);
// If Version Control API is used without the Project module,
// we need to define our own version of /admin/project
// so the rest of our admin pages all work.
if (!module_exists('project')) {
$items['admin/project'] = array(
'title' => 'Project administration',
'description' => 'Administrative interface for project management and related modules.',
'position' => 'left',
'weight' => 3,
'page callback' => 'system_admin_menu_block_page',
'access arguments' => array('administer site configuration'),
'file' => 'system.admin.inc',
'file path' => drupal_get_path('module', 'system'),
);
}
$items['admin/project/versioncontrol-settings'] = array(
'title' => 'Version control settings',
'description' => 'Configure settings for Version Control API and related modules.',
'page arguments' => array('versioncontrol_admin_settings'),
'type' => MENU_NORMAL_ITEM,
) + $admin;
$items['admin/project/versioncontrol-settings/general'] = array(
'title' => 'General',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -1,
);
$items['admin/project/versioncontrol-repositories'] = array(
'title' => 'VCS repositories',
'description' => 'Define and configure what version control repositories are connected to your site, and how to integrate each repository with repository browser tools such as ViewVC or WebSVN.',
'page arguments' => array('versioncontrol_admin_repository_list'),
) + $admin;
$weight = 1;
$items['admin/project/versioncontrol-repositories/list'] = array(
'title' => 'List',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => $weight,
);
// former !$may_cache
/// TODO: Backend specific stuff was done in !$may_cache, as it once
/// screwed up after activating a new backend in admin/build/modules.
/// Make sure this works now.
foreach (versioncontrol_get_backends() as $vcs => $backend) {
$items['admin/project/versioncontrol-repositories/add-'. $vcs] = array(
'title' => 'Add @vcs repository',
'title arguments' => array('@vcs' => $backend['name']),
'page arguments' => array('versioncontrol_admin_repository_edit',
VERSIONCONTROL_FORM_CREATE, $vcs
),
'type' => MENU_LOCAL_TASK,
'weight' => ++$weight,
) + $admin;
}
// end former !$may_cache
$items['admin/project/versioncontrol-repositories/edit/%versioncontrol_repository'] = array(
'title' => 'Edit repository',
'page arguments' => array('versioncontrol_admin_repository_edit', 4),
'type' => MENU_CALLBACK,
) + $admin;
$items['admin/project/versioncontrol-repositories/delete/%versioncontrol_repository'] = array(
'title' => 'Delete repository',
'page arguments' => array('versioncontrol_admin_repository_delete_confirm', 4),
'type' => MENU_CALLBACK,
) + $admin;
$items['admin/project/versioncontrol-accounts'] = array(
'title' => 'VCS accounts',
'description' => 'Manage associations of Drupal users to VCS user accounts.',
'page arguments' => array('versioncontrol_admin_account_list_form'),
'type' => MENU_NORMAL_ITEM,
) + $admin;
$items['admin/project/versioncontrol-accounts/list'] = array(
'title' => 'List',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
// former !$may_cache
/// TODO: Backend specific stuff was done in !$may_cache, as it once
/// screwed up after activating a new backend in admin/build/modules.
/// Make sure this works now.
// TODO (sdb): this should all be reworked using a version of the loader that
// takes additional arguments for implementation checking.
foreach (versioncontrol_get_backends() as $vcs => $backend) {
if (versioncontrol_backend_implements($vcs, 'import_accounts')) {
$items['admin/project/versioncontrol-accounts/import'] = array(
'title' => 'Import',
'description' => 'Import an existing set of VCS user accounts.',
'page arguments' => array('versioncontrol_admin_account_import_form'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
) + $admin;
}
if (versioncontrol_backend_implements($vcs, 'export_accounts')) {
$items['admin/project/versioncontrol-accounts/export'] = array(
'title' => 'Export',
'description' => 'Export VCS user accounts of a specific repository.',
'page arguments' => array('versioncontrol_admin_account_export_form'),
'type' => MENU_LOCAL_TASK,
'weight' => 3,
) + $admin;
$items['admin/project/versioncontrol-accounts/export/%versioncontrol_repository'] = array(
'title' => 'Export',
'page callback' => 'versioncontrol_admin_account_export_page',
'page arguments' => array(4),
'type' => MENU_CALLBACK,
) + $admin;
}
}
// end former !$may_cache
// Account registration and editing pages for the regular user.
$items['versioncontrol/register'] = array(
'title' => 'Get commit access',
'page callback' => 'versioncontrol_account_register_page',
'access callback' => TRUE, // access checking is done in the page callback
'file' => 'versioncontrol.pages.inc',
'type' => MENU_SUGGESTED_ITEM,
);
$items['user/%versioncontrol_user_accounts/edit/versioncontrol'] = array(
// Load with $include_unauthorized == TRUE, so that the user can inspect
// his/her VCS accounts even if they are not approved by the admin yet.
'load arguments' => array(TRUE),
'title callback' => 'versioncontrol_user_accounts_title_callback',
'title arguments' => array(1),
'page callback' => 'versioncontrol_account_page',
'page arguments' => array(1),
'access callback' => 'versioncontrol_private_account_access',
'access arguments' => array(1),
'file' => 'versioncontrol.pages.inc',
'weight' => 99,
'type' => MENU_LOCAL_TASK,
);
// Autocomplete callback for Drupal usernames that have access to
// the repo_id given in arg(3). (No need to fetch the full repository,
// as the callback uses a raw & safe database query anyways.)
$items['versioncontrol/user/autocomplete'] = array(
'title' => 'Version control user autocomplete',
'page callback' => 'versioncontrol_user_autocomplete',
'access callback' => 'versioncontrol_user_access',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Custom access callback, determining if the current user (or the one given
* in @p $account, if set) is permitted to administer version control system
* functionality.
*/
function versioncontrol_admin_access($account = NULL) {
return user_access('administer version control systems', $account);
}
/**
* Custom access callback, determining if the current user (or the one given
* in @p $account, if set) is permitted to use version control system
* functionality.
*/
function versioncontrol_user_access($account = NULL) {
return user_access('use version control systems', $account)
|| user_access('administer version control systems', $account);
}
/**
* Custom access callback, determining if the current user (or the one given
* in @p $account, if set) is permitted to view version control account
* settings of the user specified the first user id in @p $vcs_accounts.
* (We take that parameter because it's what the '%versioncontrol_user_accounts'
* wildcard returns.)
*/
function versioncontrol_private_account_access($vcs_accounts, $account = NULL) {
$viewed_uid = key($vcs_accounts);
if (!$viewed_uid) {
return FALSE;
}
if (is_null($account)) {
global $user;
$account = clone $user;
}
return ($viewed_uid == $account->uid && user_access('use version control systems', $account))
|| user_access('administer version control systems', $account);
}
/**
* Menu wildcard loader for repository ids ('%versioncontrol_repository').
* Use this only for menu paths - if you want to retrieve a repository with
* your own code, use versioncontrol_get_repository() instead.
* (Yeah, I know duplicate functions are bad. Hopefully we can sort this out
* when repositories are made into real objects, as
* versioncontrol_get_repository() will be a static class method then.)
*/
function versioncontrol_repository_load($repo_id) {
$repository = versioncontrol_get_repository($repo_id);
return empty($repository) ? FALSE : $repository;
}
/**
* Title callback for repository arrays.
*/
function versioncontrol_repository_title_callback($repository) {
return check_plain($repository['name']);
}
/**
* Menu wildcard loader for '%versioncontrol_user_accounts':
* Load all VCS accounts of a given user (in the format that
* versioncontrol_get_accounts() returns) and return either that
* or FALSE if no VCS accounts exist for this user.
*
* @param $uid
* Drupal user id of the user whose VCS accounts should be loaded.
* @param $include_unauthorized
* Will be passed on to versioncontrol_get_accounts(), see the
* API documentation of that function.
*/
function versioncontrol_user_accounts_load($uid, $include_unauthorized = FALSE) {
$accounts = versioncontrol_get_accounts(array('uids' => array($uid)), $include_unauthorized);
return empty($accounts) ? FALSE : $accounts;
}
/**
* Title callback for the "user/%versioncontrol_user_accounts/edit/versioncontrol" tab.
*/
function versioncontrol_user_accounts_title_callback($accounts) {
$usernames = array();
foreach ($accounts as $uid => $user_accounts) {
foreach ($user_accounts as $repo_id => $username) {
$usernames[] = $username;
}
}
$repositories = versioncontrol_get_repositories(array(
'repo_ids' => array_keys(reset($accounts)), // a.k.a. list of account repo_ids
));
$vcses = array();
foreach ($repositories as $repository) {
$vcses[$repository['vcs']] = TRUE;
}
if (count($vcses) == 1) {
$backends = versioncontrol_get_backends();
$vcs = key($vcses);
return check_plain($backends[$vcs]['name']);
}
return t('Repository accounts');
}
/**
* Implementation of hook_perm().
*/
function versioncontrol_perm() {
return array(
'administer version control systems',
'use version control systems',
);
}
// API functions start here.
/**
* Get a list of all backends and more detailed information about each of them.
*
* @return
* A structured array containing information about all known backends.
* Array keys are the unique string identifier of the version control system.
* The corresponding array values are again structured arrays and consist
* of elements with the following keys:
*
* - 'name': The user-visible name of the VCS.
* - 'description': A short description of the backend, if possible
* not longer than one or two sentences.
* - 'capabilities': An array listing optional capabilities, in addition
* to the required functionality like retrieval of detailed
* commit information. Array values can be an arbitrary combination
* of VERSIONCONTROL_CAPABILITY_* values. If no additional capabilities
* are supported by the backend, this array will be empty.
* - 'flags': An array listing which tables should be managed by
* Version Control API instead of doing it manually in the backend.
* Array values can be an arbitrary combination of VERSIONCONTROL_FLAG_*
* values. If no array additions should be automatically managed,
* this array will be empty.
*
* If no single backends can be found, an empty array is returned.
*
* A real-life example of such a result array can be found
* in the FakeVCS example module.
*/
function versioncontrol_get_backends() {
static $backends;
if (!isset($backends)) {
$backends = module_invoke_all('versioncontrol_backends');
}
return $backends;
}
/**
* Convenience function, retrieving the backend information array for a
* single repository. So, the result is one of the elements in the result array
* of versioncontrol_get_backends(). As versioncontrol_get_repositories() only
* returns repositories for backends that actually exist, this function can be
* trusted to always return a valid backend array.
*/
function versioncontrol_get_backend($repository) {
$backends = versioncontrol_get_backends();
return $backends[$repository['vcs']];
}
/**
* Determine if a given backend module implements a specific backend function.
*
* @param $vcs
* The unique string identifier of the version control system.
* @param $function
* The function name without module prefix.
*
* @return
* TRUE if the backend implements the function, or FALSE otherwise.
*/
function versioncontrol_backend_implements($vcs, $function) {
if (function_exists('versioncontrol_'. $vcs .'_'. $function)) {
return TRUE;
}
return FALSE;
}
/**
* Call a function from the desired VCS backend and return its result value.
*
* @param $vcs
* The unique string identifier of the version control system.
* @param $function
* The function name without module prefix.
* @param $args
* An array of arguments that will be passed to the backend function.
*
* @return
* Returns the result of the backend function. The result value of calls
* where the backend function has no implementation is undefined, as they
* are supposed to be checked with versioncontrol_backend_implements()
* before those functions are called.
*/
function _versioncontrol_call_backend($vcs, $function, $args) {
return call_user_func_array('versioncontrol_'. $vcs .'_'. $function, $args);
}
/**
* Determine all user account authorization methods
* (free for all, only admin may create accounts, per-repository approval, ...)
* by invoking hook_versioncontrol_authorization_methods().
*
* @return
* A structured array with the unique string identifier of the method as keys
* and the user-visible description (wrapped in t()) as values.
*/
function versioncontrol_get_authorization_methods() {
static $methods;
if (!isset($methods)) {
$methods = module_invoke_all('versioncontrol_authorization_methods');
}
return $methods;
}
/**
* Implementation of hook_versioncontrol_authorization_methods().
*
* @return
* A structured array containing information about authorization methods
* provided by this module, wrapped in a structured array. Array keys are
* the unique string identifiers of each authorization method, and
* array values are the user-visible method descriptions (wrapped in t()).
*/
function versioncontrol_versioncontrol_authorization_methods() {
return array(
'versioncontrol_admin' => t('Only administrators can create accounts'),
'versioncontrol_none' => t('No approval required'),
);
}
function _versioncontrol_get_fallback_authorization_method() {
return 'versioncontrol_admin';
}
/**
* Convenience function for retrieving one single repository by repository id.
*
* @return
* A single repository array that consists of the following elements:
*
* - 'repo_id': The unique repository id.
* - 'name': The user-visible name of the repository.
* - 'vcs': The unique string identifier of the version control system
* that powers this repository.
* - 'root': The root directory of the repository. In most cases,
* this will be a local directory (e.g. '/var/repos/drupal'),
* but it may also be some specialized string for remote repository
* access. How this string may look like depends on the backend.
* - 'authorization_method': The string identifier of the repository's
* authorization method, that is, how users may register accounts
* in this repository. Modules can provide their own methods
* by implementing hook_versioncontrol_authorization_methods().
* - 'url_backend': The prefix (excluding the trailing underscore)
* for URL backend retrieval functions.
* - 'data': An array where modules can store additional information about
* the repository, for settings or other data.
* - '[xxx]_specific': An array of VCS specific additional repository
* information. How this array looks like is defined by the
* corresponding backend module (versioncontrol_[xxx]).
* (Deprecated, to be replaced by the more general 'data' property.)
*
* If no repository corresponds to the given repository id, NULL is returned.
*/
function versioncontrol_get_repository($repo_id) {
$repos = versioncontrol_get_repositories(array('repo_ids' => array($repo_id)));
foreach ($repos as $repo_id => $repository) {
return $repository;
}
return NULL; // in case of empty($repos)
}
/**
* Retrieve a set of repositories that match the given constraints.
*
* @param $constraints
* An optional array of constraints. Possible array elements are:
*
* - 'vcs': An array of strings, like array('cvs', 'svn', 'git').
* If given, only repositories for these backends will be returned.
* - 'repo_ids': An array of repository ids.
* If given, only the corresponding repositories will be returned.
* - 'names': An array of repository names, like
* array('Drupal CVS', 'Experimental SVN'). If given,
* only repositories with these repository names will be returned.
* - '[xxx]_specific': An array of VCS specific constraints. How this array
* looks like is defined by the corresponding backend module
* (versioncontrol_[xxx]). Other backend modules won't get to see this
* constraint, so in theory you can provide one of those for each backend
* in one single query.
*
* @return
* An array of repositories where the key of each element is the
* repository id. The corresponding value contains a structured array
* with the following keys:
*
* - 'repo_id': The unique repository id.
* - 'name': The user-visible name of the repository.
* - 'vcs': The unique string identifier of the version control system
* that powers this repository.
* - 'root': The root directory of the repository. In most cases,
* this will be a local directory (e.g. '/var/repos/drupal'),
* but it may also be some specialized string for remote repository
* access. How this string may look like depends on the backend.
* - 'authorization_method': The string identifier of the repository's
* authorization method, that is, how users may register accounts
* in this repository. Modules can provide their own methods
* by implementing hook_versioncontrol_authorization_methods().
* - 'url_backend': The prefix (excluding the trailing underscore)
* for URL backend retrieval functions.
* - 'data': An array where modules can store additional information about
* the repository, for settings or other data.
* - '[xxx]_specific': An array of VCS specific additional repository
* information. How this array looks like is defined by the
* corresponding backend module (versioncontrol_[xxx]).
* (Deprecated, to be replaced by the more general 'data' property.)
*
* If not a single repository matches these constraints,
* an empty array is returned.
*/
function versioncontrol_get_repositories($constraints = array()) {
static $repository_cache;
if (!isset($repository_cache)) {
$repository_cache = array();
}
$backends = versioncontrol_get_backends();
$auth_methods = versioncontrol_get_authorization_methods();
// "Normalize" repo_ids to integers so the cache doesn't distinguish
// between string and integer values.
if (isset($constraints['repo_ids'])) {
$repo_ids = array();
foreach ($constraints['repo_ids'] as $repo_id) {
$repo_ids[] = (int) $repo_id;
}
$constraints['repo_ids'] = $repo_ids;
}
$constraints_serialized = serialize($constraints);
if (isset($repository_cache[$constraints_serialized])) {
return $repository_cache[$constraints_serialized];
}
list($and_constraints, $params) =
_versioncontrol_construct_repository_constraints($constraints, $backends);
// All the constraints have been gathered, assemble them to a WHERE clause.
$where = empty($and_constraints) ? '' : ' WHERE '. implode(' AND ', $and_constraints);
$result = db_query('SELECT * FROM {versioncontrol_repositories} r'. $where, $params);
// Sort the retrieved repositories by backend.
$repositories_by_backend = array();
while ($repository = db_fetch_array($result)) {
if (!isset($backends[$repository['vcs']])) {
// don't include repositories for which no backend module exists
continue;
}
$repository['data'] = unserialize($repository['data']);
if (!isset($auth_methods[$repository['authorization_method']])) {
$repository['authorization_method'] = _versioncontrol_get_fallback_authorization_method();
}
if (!isset($repositories_by_backend[$repository['vcs']])) {
$repositories_by_backend[$repository['vcs']] = array();
}
$repository[$repository['vcs'] .'_specific'] = array();
$repositories_by_backend[$repository['vcs']][$repository['repo_id']] = $repository;
}
$repositories_by_backend = _versioncontrol_amend_repositories(
$repositories_by_backend, $backends
);
// Add the fully assembled repositories to the result array.
$result_repositories = array();
foreach ($repositories_by_backend as $vcs => $vcs_repositories) {
foreach ($vcs_repositories as $repository) {
$result_repositories[$repository['repo_id']] = $repository;
}
}
$repository_cache[$constraints_serialized] = $result_repositories; // cache the results
return $result_repositories;
}
/**
* Assemble a list of query constraints given as string array that's
* supposed to be imploded with an SQL "AND", and a $params array containing
* the corresponding parameter values for all the '%d' and '%s' placeholders.
*/
function _versioncontrol_construct_repository_constraints($constraints, $backends) {
$and_constraints = array();
$params = array();
// Filter out repositories of which the corresponding backend is not enabled,
// and handle the 'vcs' constraint at the same time.
$placeholders = array();
$vcses = array_keys($backends);
if (isset($constraints['vcs'])) {
$vcses = array_intersect($vcses, $constraints['vcs']);
}
if (empty($vcses)) {
$and_constraints[] = 'FALSE'; // no backends are enabled of those that have been requested
}
else {
foreach ($vcses as $vcs) {
$placeholders[] = "'%s'";
$params[] = $vcs;
}
$and_constraints[] = 'r.vcs IN ('. implode(',', $placeholders) .')';
}
if (isset($constraints['repo_ids'])) {
if (empty($constraints['repo_ids'])) {
$and_constraints[] = 'FALSE';
}
else {
$placeholders = array();
foreach ($constraints['repo_ids'] as $repo_id) {
$placeholders[] = '%d';
$params[] = $repo_id;
}
$and_constraints[] = 'r.repo_id IN ('. implode(',', $placeholders) .')';
}
}
if (isset($constraints['names'])) {
if (empty($constraints['names'])) {
$and_constraints[] = 'FALSE';
}
else {
$placeholders = array();
foreach ($constraints['names'] as $name) {
$placeholders[] = "'%s'";
$params[] = $name;
}
$and_constraints[] = 'r.name IN ('. implode(',', $placeholders) .')';
}
}
return array($and_constraints, $params);
}
/**
* Fetch VCS specific repository data additions, either by ourselves (if the
* VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES flag has been set by the backend)
* and/or by calling [vcs_backend]_alter_repositories().
*/
function _versioncontrol_amend_repositories($repositories_by_backend, $backends, $constraints = array()) {
foreach ($repositories_by_backend as $vcs => $vcs_repositories) {
$is_autoadd = in_array(VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES,
$backends[$vcs]['flags']);
if ($is_autoadd) {
$repo_ids = array();
foreach ($vcs_repositories as $repo_id => $repository) {
$repo_ids[] = $repo_id;
}
$additions = _versioncontrol_db_get_additions(
'versioncontrol_'. $vcs .'_repositories', 'repo_id', $repo_ids
);
foreach ($additions as $repo_id => $addition) {
if (isset($vcs_repositories[$repo_id])) {
$vcs_repositories[$repo_id][$vcs .'_specific'] = $addition;
}
}
}
$vcs_specific_constraints = isset($constraints[$vcs .'_specific'])
? $constraints[$vcs .'_specific']
: array();
// Provide an opportunity for the backend to add its own stuff.
if (versioncontrol_backend_implements($vcs, 'alter_repositories')) {
$function = 'versioncontrol_'. $vcs .'_alter_repositories';
$function($vcs_repositories, $vcs_specific_constraints);
}
$repositories_by_backend[$vcs] = $vcs_repositories;
}
return $repositories_by_backend;
}
/**
* Convenience function, calling versioncontrol_get_operations() with a preset
* of array(VERSIONCONTROL_OPERATION_COMMIT) for the 'types' constraint
* (so only commits are returned). Parameters and result array are the same
* as those from versioncontrol_get_operations().
*/
function versioncontrol_get_commit_operations($constraints = array(), $options = array()) {
if (isset($constraints['types']) && !in_array(VERSIONCONTROL_OPERATION_COMMIT, $constraints['types'])) {
return array(); // no commits in the original constraints, intersects to empty
}
$constraints['types'] = array(VERSIONCONTROL_OPERATION_COMMIT);
return versioncontrol_get_operations($constraints, $options);
}
/**
* Convenience function, calling versioncontrol_get_operations() with a preset
* of array(VERSIONCONTROL_OPERATION_BRANCH) for the 'types' constraint
* (so only branch operations or commits affecting emulated branches
* are returned). Parameters and result array are the same as those
* from versioncontrol_get_operations().
*/
function versioncontrol_get_branch_operations($constraints = array(), $options = array()) {
if (isset($constraints['types']) && !in_array(VERSIONCONTROL_OPERATION_BRANCH, $constraints['types'])) {
return array(); // no branches in the original constraints, intersects to empty
}
$constraints['types'] = array(VERSIONCONTROL_OPERATION_BRANCH);
return versioncontrol_get_operations($constraints, $options);
}
/**
* Convenience function, calling versioncontrol_get_operations() with a preset
* of array(VERSIONCONTROL_OPERATION_TAG) for the 'types' constraint
* (so only tag operations or commits affecting emulated tags are returned).
* Parameters and result array are the same as those
* from versioncontrol_get_operations().
*/
function versioncontrol_get_tag_operations($constraints = array(), $options = array()) {
if (isset($constraints['types']) && !in_array(VERSIONCONTROL_OPERATION_TAG, $constraints['types'])) {
return array(); // no tags in the original constraints, intersects to empty
}
$constraints['types'] = array(VERSIONCONTROL_OPERATION_TAG);
return versioncontrol_get_operations($constraints, $options);
}
/**
* Retrieve a set of commit, branch or tag operations that match
* the given constraints.
*
* @param $constraints
* An optional array of constraints. Possible array elements are:
*
* - 'vcs': An array of strings, like array('cvs', 'svn', 'git').
* If given, only operations for these backends will be returned.
* - 'repo_ids': An array of repository ids. If given, only operations
* for the corresponding repositories will be returned.
* - 'types': An array containing any combination of the three
* VERSIONCONTROL_OPERATION_{COMMIT,BRANCH,TAG} constants, like
* array(VERSIONCONTROL_OPERATION_COMMIT, VERSIONCONTROL_OPERATION_TAG).
* If given, only operations of this type will be returned.
* - 'branches': An array of strings, like array('HEAD', 'DRUPAL-5').
* If given, only commits or branch operations on one of these branches
* will be returned.
* - 'tags': An array of strings, like array('DRUPAL-6-1', 'DRUPAL-6--1-0').
* If given, only tag operations with one of these tag names will be
* returned.
* - 'revisions': An array of strings, each containing a VCS-specific
* (global) revision, like '27491' for Subversion or some SHA-1 key in
* various distributed version control systems. If given, only
* operations with that revision identifier will be returned. Note that
* this constraint only works for version control systems that support
* global revision identifiers, so this will filter out all
* CVS operations.
* - 'labels': A combination of the 'branches' and 'tags' constraints.
* - 'paths': An array of strings (item locations), like
* array(
* '/trunk/contributions/modules/versioncontrol',
* '/trunk/contributions/themes/b2',
* ).
* If given, only operations affecting one of these items
* (or its children, in case the item is a directory) will be returned.
* - 'message': A string, or an array of strings (which will be combined with
* an "OR" operator). If given, only operations containing the string(s)
* in their log message will be returned.
* - 'item_revision_ids': An array of item revision ids. If given, only
* operations affecting one of the items with that id will be returned.
* - 'item_revisions': An array of strings, each containing a VCS-specific
* file-level revision, like '1.15.2.3' for CVS, '27491' for Subversion,
* or some SHA-1 key in various distributed version control systems.
* If given, only operations affecting one of the items with that
* item revision will be returned.
* - 'vc_op_ids': An array of operation ids. If given, only operations
* matching those ids will be returned.
* - 'date_lower': A Unix timestamp. If given, no operations will be
* retrieved that were performed earlier than this lower bound.
* - 'date_lower': A Unix timestamp. If given, no operations will be
* retrieved that were performed later than this upper bound.
* - 'uids': An array of Drupal user ids. If given, the result set will only
* contain operations that were performed by any of the specified users.
* - 'usernames': An array of system-specific usernames (the ones that the
* version control systems themselves get to see), like
* array('dww', 'jpetso'). If given, the result set will only contain
* operations that were performed by any of the specified users.
* - 'user_relation': If set to VERSIONCONTROL_USER_ASSOCIATED, only
* operations whose authors can be associated to Drupal users will be
* returned. If set to VERSIONCONTROL_USER_ASSOCIATED_ACTIVE, only users
* will be considered that are not blocked.
*
* @param $options
* An optional array of additional options for retrieving the operations.
* The following array keys are supported:
*
* - 'query_type': If unset, the standard db_query() function is used to
* retrieve all operations that match the given constraints.
* Can be set to 'range' or 'pager' to use the db_query_range()
* or pager_query() functions instead. Additional options are required
* in this case.
* - 'count': Required if 'query_type' is either 'range' or 'pager'.
* Specifies the number of operations to be returned by this function.
* - 'from': Required if 'query_type' is 'range'. Specifies the first
* result row to return. (Usually you want to pass 0 for this one.)
* - 'pager_element': Optional for 'pager' as 'query_type'. An optional
* integer to distinguish between multiple pagers on one page.
*
* @return
* An array of operations, reversely sorted by the time of the operation.
* Each element contains an "operation array" with the 'vc_op_id' identifier
* as key (which doesn't influence the sorting) and the following keys:
*
* - 'vc_op_id': The Drupal-specific operation identifier (a simple integer)
* which is unique among all operations (commits, branch ops, tag ops)
* in all repositories.
* - 'type': The type of the operation - one of the
* VERSIONCONTROL_OPERATION_{COMMIT,BRANCH,TAG} constants.
* Note that if you pass branch or tag constraints, this function might
* nevertheless return commit operations too - that happens for version
* control systems without native branches or tags (like Subversion)
* when a branch or tag is affected by the commit.
* - 'repository': The repository where this operation occurred.
* This is a structured "repository array", like is returned
* by versioncontrol_get_repository().
* - 'date': The time when the operation was performed, given as
* Unix timestamp. (For commits, this is the time when the revision
* was committed, whereas for branch/tag operations it is the time
* when the files were branched or tagged.)
* - 'uid': The Drupal user id of the operation author, or 0 if no
* Drupal user could be associated to the author.
* - 'username': The system specific VCS username of the author.
* - 'message': The log message for the commit, tag or branch operation.
* If a version control system doesn't support messages for any of them,
* this element contains an empty string.
* - 'revision': The VCS specific repository-wide revision identifier,
* like '' in CVS, '27491' in Subversion or some SHA-1 key in various
* distributed version control systems. If there is no such revision
* (which may be the case for version control systems that don't support
* atomic commits) then the 'revision' element is an empty string.