@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Add a basic push log for recording repository push events

Summary:
Ref T4195. This log serves two purposes:

- It's a log, so you can see what happened. Particularly, in Git/Hg, there is no other way to tell:
- Who //pushed// a change (vs committed / authored)?
- When was a change pushed?
- What was the old value of some tag/branch before someone destroyed it?
- We can hand these objects off to Herald to implement pre-commit rules.

This is a very basic implementation, but gets some data written and has a basic UI for it.

Test Plan: {F87339}

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4195

Differential Revision: https://secure.phabricator.com/D7705

+446 -2
+25
resources/sql/patches/20131204.pushlog.sql
··· 1 + CREATE TABLE {$NAMESPACE}_repository.repository_pushlog ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + epoch INT UNSIGNED NOT NULL, 4 + repositoryPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + pusherPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 6 + remoteAddress INT UNSIGNED, 7 + remoteProtocol VARCHAR(32), 8 + transactionKey CHAR(12) NOT NULL COLLATE latin1_bin, 9 + refType VARCHAR(12) NOT NULL COLLATE utf8_bin, 10 + refNameHash VARCHAR(12) COLLATE latin1_bin, 11 + refNameRaw LONGTEXT COLLATE latin1_bin, 12 + refNameEncoding VARCHAR(16) COLLATE utf8_bin, 13 + refOld VARCHAR(40) COLLATE latin1_bin, 14 + refNew VARCHAR(40) NOT NULL COLLATE latin1_bin, 15 + mergeBase VARCHAR(40) COLLATE latin1_bin, 16 + changeFlags INT UNSIGNED NOT NULL, 17 + rejectCode INT UNSIGNED NOT NULL, 18 + rejectDetails VARCHAR(64) COLLATE utf8_bin, 19 + 20 + KEY `key_repository` (repositoryPHID), 21 + KEY `key_ref` (repositoryPHID, refNew), 22 + KEY `key_pusher` (pusherPHID), 23 + KEY `key_name` (repositoryPHID, refNameHash) 24 + 25 + ) ENGINE=InnoDB, COLLATE=utf8_general_ci;
+16
src/__phutil_library_map__.php
··· 522 522 'DiffusionPathQuery' => 'applications/diffusion/query/DiffusionPathQuery.php', 523 523 'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php', 524 524 'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php', 525 + 'DiffusionPushLogListController' => 'applications/diffusion/controller/DiffusionPushLogListController.php', 525 526 'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php', 526 527 'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php', 527 528 'DiffusionRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRemarkupRule.php', ··· 1793 1794 'PhabricatorRepositoryPHIDTypeRepository' => 'applications/repository/phid/PhabricatorRepositoryPHIDTypeRepository.php', 1794 1795 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', 1795 1796 'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php', 1797 + 'PhabricatorRepositoryPushLog' => 'applications/repository/storage/PhabricatorRepositoryPushLog.php', 1798 + 'PhabricatorRepositoryPushLogQuery' => 'applications/repository/query/PhabricatorRepositoryPushLogQuery.php', 1799 + 'PhabricatorRepositoryPushLogSearchEngine' => 'applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php', 1796 1800 'PhabricatorRepositoryQuery' => 'applications/repository/query/PhabricatorRepositoryQuery.php', 1797 1801 'PhabricatorRepositorySearchEngine' => 'applications/repository/query/PhabricatorRepositorySearchEngine.php', 1798 1802 'PhabricatorRepositoryShortcut' => 'applications/repository/storage/PhabricatorRepositoryShortcut.php', ··· 2848 2852 'DiffusionPathCompleteController' => 'DiffusionController', 2849 2853 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', 2850 2854 'DiffusionPathValidateController' => 'DiffusionController', 2855 + 'DiffusionPushLogListController' => 2856 + array( 2857 + 0 => 'DiffusionController', 2858 + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', 2859 + ), 2851 2860 'DiffusionQuery' => 'PhabricatorQuery', 2852 2861 'DiffusionRawDiffQuery' => 'DiffusionQuery', 2853 2862 'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject', ··· 4309 4318 'PhabricatorRepositoryPHIDTypeRepository' => 'PhabricatorPHIDType', 4310 4319 'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine', 4311 4320 'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon', 4321 + 'PhabricatorRepositoryPushLog' => 4322 + array( 4323 + 0 => 'PhabricatorRepositoryDAO', 4324 + 1 => 'PhabricatorPolicyInterface', 4325 + ), 4326 + 'PhabricatorRepositoryPushLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4327 + 'PhabricatorRepositoryPushLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 4312 4328 'PhabricatorRepositoryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4313 4329 'PhabricatorRepositorySearchEngine' => 'PhabricatorApplicationSearchEngine', 4314 4330 'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO',
+3 -1
src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
··· 46 46 'new/' => 'DiffusionRepositoryNewController', 47 47 '(?P<edit>create)/' => 'DiffusionRepositoryCreateController', 48 48 '(?P<edit>import)/' => 'DiffusionRepositoryCreateController', 49 + 'pushlog/(?:query/(?P<queryKey>[^/]+)/)?' 50 + => 'DiffusionPushLogListController', 51 + 49 52 '(?P<callsign>[A-Z]+)/' => array( 50 53 '' => 'DiffusionRepositoryController', 51 54 ··· 58 61 'tags/(?P<dblob>.*)' => 'DiffusionTagListController', 59 62 'branches/(?P<dblob>.*)' => 'DiffusionBranchTableController', 60 63 'lint/(?P<dblob>.*)' => 'DiffusionLintController', 61 - 62 64 'commit/(?P<commit>[a-z0-9]+)/branches/' 63 65 => 'DiffusionCommitBranchesController', 64 66 'commit/(?P<commit>[a-z0-9]+)/tags/'
+96
src/applications/diffusion/controller/DiffusionPushLogListController.php
··· 1 + <?php 2 + 3 + final class DiffusionPushLogListController extends DiffusionController 4 + implements PhabricatorApplicationSearchResultsControllerInterface { 5 + 6 + private $queryKey; 7 + 8 + public function shouldAllowPublic() { 9 + return true; 10 + } 11 + 12 + public function willProcessRequest(array $data) { 13 + $this->queryKey = idx($data, 'queryKey'); 14 + } 15 + 16 + public function processRequest() { 17 + $request = $this->getRequest(); 18 + $controller = id(new PhabricatorApplicationSearchController($request)) 19 + ->setQueryKey($this->queryKey) 20 + ->setSearchEngine(new PhabricatorRepositoryPushLogSearchEngine()) 21 + ->setNavigation($this->buildSideNavView()); 22 + 23 + return $this->delegateToController($controller); 24 + } 25 + 26 + public function renderResultsList( 27 + array $logs, 28 + PhabricatorSavedQuery $query) { 29 + $viewer = $this->getRequest()->getUser(); 30 + 31 + $this->loadHandles(mpull($logs, 'getPusherPHID')); 32 + 33 + $rows = array(); 34 + foreach ($logs as $log) { 35 + $callsign = $log->getRepository()->getCallsign(); 36 + $rows[] = array( 37 + phutil_tag( 38 + 'a', 39 + array( 40 + 'href' => $this->getApplicationURI($callsign.'/'), 41 + ), 42 + $callsign), 43 + $this->getHandle($log->getPusherPHID())->renderLink(), 44 + $log->getRefType(), 45 + $log->getRefName(), 46 + $log->getRefOldShort(), 47 + $log->getRefNewShort(), 48 + phabricator_datetime($log->getEpoch(), $viewer), 49 + ); 50 + } 51 + 52 + $table = id(new AphrontTableView($rows)) 53 + ->setHeaders( 54 + array( 55 + pht('Repository'), 56 + pht('Pusher'), 57 + pht('Type'), 58 + pht('Name'), 59 + pht('Old'), 60 + pht('New'), 61 + pht('Date'), 62 + )) 63 + ->setColumnClasses( 64 + array( 65 + '', 66 + '', 67 + '', 68 + 'wide', 69 + 'n', 70 + 'n', 71 + 'date', 72 + )); 73 + 74 + $box = id(new PHUIBoxView()) 75 + ->addMargin(PHUI::MARGIN_LARGE) 76 + ->appendChild($table); 77 + 78 + return $box; 79 + } 80 + 81 + public function buildSideNavView($for_app = false) { 82 + $viewer = $this->getRequest()->getUser(); 83 + 84 + $nav = new AphrontSideNavFilterView(); 85 + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); 86 + 87 + id(new PhabricatorRepositoryPushLogSearchEngine()) 88 + ->setViewer($viewer) 89 + ->addNavigationItems($nav->getMenu()); 90 + 91 + $nav->selectFilter(null); 92 + 93 + return $nav; 94 + } 95 + 96 + }
+47
src/applications/diffusion/engine/DiffusionCommitHookEngine.php
··· 1 1 <?php 2 2 3 + /** 4 + * @task git Git Hooks 5 + * @task hg Mercurial Hooks 6 + * @task svn Subversion Hooks 7 + */ 3 8 final class DiffusionCommitHookEngine extends Phobject { 4 9 5 10 private $viewer; ··· 76 81 $updates = $this->findGitNewCommits($updates); 77 82 78 83 // TODO: Now, do content checks. 84 + 85 + // TODO: Generalize this; just getting some data in the database for now. 86 + $transaction_key = PhabricatorHash::digestForIndex( 87 + Filesystem::readRandomBytes(64)); 88 + 89 + $logs = array(); 90 + foreach ($updates as $update) { 91 + $log = PhabricatorRepositoryPushLog::initializeNewLog($this->getViewer()) 92 + ->setRepositoryPHID($this->getRepository()->getPHID()) 93 + ->setEpoch(time()) 94 + ->setRemoteAddress(null) // TODO: Populate this where possible. 95 + ->setRemoteProtocol(null) // TODO: Populate this where possible. 96 + ->setTransactionKey($transaction_key) 97 + ->setRefType($update['type']) 98 + ->setRefNameHash(PhabricatorHash::digestForIndex($update['ref'])) 99 + ->setRefNameRaw($update['ref']) 100 + ->setRefNameEncoding(phutil_is_utf8($update['ref']) ? 'utf8' : null) 101 + ->setRefOld($update['old']) 102 + ->setRefNew($update['new']) 103 + ->setMergeBase(idx($update, 'merge-base')) 104 + ->setRejectCode(PhabricatorRepositoryPushLog::REJECT_ACCEPT) 105 + ->setRejectDetails(null); 106 + 107 + $flags = 0; 108 + if ($update['operation'] == 'create') { 109 + $flags = $flags | PhabricatorRepositoryPushLog::CHANGEFLAG_ADD; 110 + } else if ($update['operation'] == 'delete') { 111 + $flags = $flags | PhabricatorRepositoryPushLog::CHANGEFLAG_DELETE; 112 + } else { 113 + // TODO: This isn't correct; these might be APPEND or REWRITE, and 114 + // if they're REWRITE they might be DANGEROUS. Fix this when this 115 + // gets generalized. 116 + $flags = $flags | PhabricatorRepositoryPushLog::CHANGEFLAG_APPEND; 117 + } 118 + 119 + $log->setChangeFlags($flags); 120 + $logs[] = $log; 121 + } 122 + 123 + foreach ($logs as $log) { 124 + $log->save(); 125 + } 79 126 80 127 return 0; 81 128 }
+86
src/applications/repository/query/PhabricatorRepositoryPushLogQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorRepositoryPushLogQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $repositoryPHIDs; 8 + 9 + public function withIDs(array $ids) { 10 + $this->ids = $ids; 11 + return $this; 12 + } 13 + 14 + public function withRepositoryPHIDs(array $repository_phids) { 15 + $this->repositoryPHIDs = $repository_phids; 16 + return $this; 17 + } 18 + 19 + protected function loadPage() { 20 + $table = new PhabricatorRepositoryPushLog(); 21 + $conn_r = $table->establishConnection('r'); 22 + 23 + $data = queryfx_all( 24 + $conn_r, 25 + 'SELECT * FROM %T %Q %Q %Q', 26 + $table->getTableName(), 27 + $this->buildWhereClause($conn_r), 28 + $this->buildOrderClause($conn_r), 29 + $this->buildLimitClause($conn_r)); 30 + 31 + return $table->loadAllFromArray($data); 32 + } 33 + 34 + public function willFilterPage(array $logs) { 35 + $repository_phids = mpull($logs, 'getRepositoryPHID'); 36 + if ($repository_phids) { 37 + $repositories = id(new PhabricatorRepositoryQuery()) 38 + ->setViewer($this->getViewer()) 39 + ->withPHIDs($repository_phids) 40 + ->execute(); 41 + $repositories = mpull($repositories, null, 'getPHID'); 42 + } else { 43 + $repositories = array(); 44 + } 45 + 46 + foreach ($logs as $key => $log) { 47 + $phid = $log->getRepositoryPHID(); 48 + if (empty($repositories[$phid])) { 49 + unset($logs[$key]); 50 + continue; 51 + } 52 + $log->attachRepository($repositories[$phid]); 53 + } 54 + 55 + return $logs; 56 + } 57 + 58 + 59 + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { 60 + $where = array(); 61 + 62 + if ($this->ids) { 63 + $where[] = qsprintf( 64 + $conn_r, 65 + 'id IN (%Ld)', 66 + $this->ids); 67 + } 68 + 69 + if ($this->repositoryPHIDs) { 70 + $where[] = qsprintf( 71 + $conn_r, 72 + 'repositoryPHID IN (%Ls)', 73 + $this->repositoryPHIDs); 74 + } 75 + 76 + $where[] = $this->buildPagingClause($conn_r); 77 + 78 + return $this->formatWhereClause($where); 79 + } 80 + 81 + 82 + public function getQueryApplicationClass() { 83 + return 'PhabricatorApplicationDiffusion'; 84 + } 85 + 86 + }
+49
src/applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorRepositoryPushLogSearchEngine 4 + extends PhabricatorApplicationSearchEngine { 5 + 6 + public function buildSavedQueryFromRequest(AphrontRequest $request) { 7 + $saved = new PhabricatorSavedQuery(); 8 + 9 + return $saved; 10 + } 11 + 12 + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { 13 + $query = id(new PhabricatorRepositoryPushLogQuery()); 14 + 15 + return $query; 16 + } 17 + 18 + public function buildSearchForm( 19 + AphrontFormView $form, 20 + PhabricatorSavedQuery $saved_query) { 21 + 22 + } 23 + 24 + protected function getURI($path) { 25 + return '/diffusion/pushlog/'.$path; 26 + } 27 + 28 + public function getBuiltinQueryNames() { 29 + $names = array( 30 + 'all' => pht('All Push Logs'), 31 + ); 32 + 33 + return $names; 34 + } 35 + 36 + public function buildSavedQueryFromBuiltin($query_key) { 37 + 38 + $query = $this->newSavedQuery(); 39 + $query->setQueryKey($query_key); 40 + 41 + switch ($query_key) { 42 + case 'all': 43 + return $query; 44 + } 45 + 46 + return parent::buildSavedQueryFromBuiltin($query_key); 47 + } 48 + 49 + }
+117
src/applications/repository/storage/PhabricatorRepositoryPushLog.php
··· 1 + <?php 2 + 3 + /** 4 + * Records a push to a hosted repository. This allows us to store metadata 5 + * about who pushed commits, when, and from where. We can also record the 6 + * history of branches and tags, which is not normally persisted outside of 7 + * the reflog. 8 + * 9 + * This log is written by commit hooks installed into hosted repositories. 10 + * See @{class:DiffusionCommitHookEngine}. 11 + */ 12 + final class PhabricatorRepositoryPushLog 13 + extends PhabricatorRepositoryDAO 14 + implements PhabricatorPolicyInterface { 15 + 16 + const REFTYPE_BRANCH = 'branch'; 17 + const REFTYPE_TAG = 'tag'; 18 + const REFTYPE_BOOKMARK = 'bookmark'; 19 + const REFTYPE_SVN = 'svn'; 20 + const REFTYPE_COMMIT = 'commit'; 21 + 22 + const CHANGEFLAG_ADD = 1; 23 + const CHANGEFLAG_DELETE = 2; 24 + const CHANGEFLAG_APPEND = 4; 25 + const CHANGEFLAG_REWRITE = 8; 26 + const CHANGEFLAG_DANGEROUS = 16; 27 + 28 + const REJECT_ACCEPT = 0; 29 + const REJECT_DANGEROUS = 1; 30 + const REJECT_HERALD = 2; 31 + const REJECT_EXTERNAL = 3; 32 + 33 + protected $repositoryPHID; 34 + protected $epoch; 35 + protected $pusherPHID; 36 + protected $remoteAddress; 37 + protected $remoteProtocol; 38 + protected $transactionKey; 39 + protected $refType; 40 + protected $refNameHash; 41 + protected $refNameRaw; 42 + protected $refNameEncoding; 43 + protected $refOld; 44 + protected $refNew; 45 + protected $mergeBase; 46 + protected $changeFlags; 47 + protected $rejectCode; 48 + protected $rejectDetails; 49 + 50 + private $repository = self::ATTACHABLE; 51 + 52 + public static function initializeNewLog(PhabricatorUser $viewer) { 53 + return id(new PhabricatorRepositoryPushLog()) 54 + ->setPusherPHID($viewer->getPHID()); 55 + } 56 + 57 + public function getConfiguration() { 58 + return array( 59 + self::CONFIG_TIMESTAMPS => false, 60 + ) + parent::getConfiguration(); 61 + } 62 + 63 + public function attachRepository(PhabricatorRepository $repository) { 64 + $this->repository = $repository; 65 + return $this; 66 + } 67 + 68 + public function getRepository() { 69 + return $this->assertAttached($this->repository); 70 + } 71 + 72 + public function getRefName() { 73 + if ($this->getRefNameEncoding() == 'utf8') { 74 + return $this->getRefNameRaw(); 75 + } 76 + return phutil_utf8ize($this->getRefNameRaw()); 77 + } 78 + 79 + public function getRefOldShort() { 80 + if ($this->getRepository()->isSVN()) { 81 + return $this->getRefOld(); 82 + } 83 + return substr($this->getRefOld(), 0, 12); 84 + } 85 + 86 + public function getRefNewShort() { 87 + if ($this->getRepository()->isSVN()) { 88 + return $this->getRefNew(); 89 + } 90 + return substr($this->getRefNew(), 0, 12); 91 + } 92 + 93 + 94 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 95 + 96 + 97 + public function getCapabilities() { 98 + return array( 99 + PhabricatorPolicyCapability::CAN_VIEW, 100 + ); 101 + } 102 + 103 + public function getPolicy($capability) { 104 + return $this->getRepository()->getPolicy($capability); 105 + } 106 + 107 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 108 + return $this->getRepository()->hasAutomaticCapability($capability, $viewer); 109 + } 110 + 111 + public function describeAutomaticCapability($capability) { 112 + return pht( 113 + "A repository's push logs are visible to users who can see the ". 114 + "repository."); 115 + } 116 + 117 + }
+3 -1
src/docs/user/userguide/diffusion_hosting.diviner
··· 31 31 | Reads | Yes | Yes | 32 32 | Writes | Yes | Yes | 33 33 | Authenticated Access | Yes | Yes | 34 + | Push Logs | Yes | Yes | 35 + | Commit Hooks | Yes | Yes | 34 36 | Anonymous Access | No | Yes | 35 - | Security | Better (Asymetric Key) | Okay (Password) | 37 + | Security | Better (Asymmetric Key) | Okay (Password) | 36 38 | Performance | Better | Okay | 37 39 | Setup | Hard | Easy | 38 40
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1800 1800 'type' => 'sql', 1801 1801 'name' => $this->getPatchPath('20131205.buildtargets.sql'), 1802 1802 ), 1803 + '20131204.pushlog.sql' => array( 1804 + 'type' => 'sql', 1805 + 'name' => $this->getPatchPath('20131204.pushlog.sql'), 1806 + ), 1803 1807 ); 1804 1808 } 1805 1809 }