diff --git a/config/filter.php b/config/filter.php index d63a32bd24..54198ff2d5 100644 --- a/config/filter.php +++ b/config/filter.php @@ -60,6 +60,7 @@ $filter->git = new stdclass(); $filter->svn = new stdclass(); $filter->search = new stdclass(); $filter->gitlab = new stdclass(); +$filter->mr = new stdclass(); $filter->ci = new stdclass(); $filter->block->default = new stdclass(); @@ -140,6 +141,7 @@ $filter->repo->ajaxsynccommit = new stdclass(); $filter->search->index = new stdclass(); $filter->gitlab->webhook = new stdclass(); $filter->gitlab->importissue = new stdclass(); +$filter->mr->diff = new stdclass(); $filter->ci->checkCompileStatus = new stdclass(); $filter->execution->export = new stdclass(); @@ -356,4 +358,6 @@ $filter->gitlab->importissue->get['product'] = 'string'; $filter->gitlab->importissue->get['project'] = 'int'; $filter->gitlab->importissue->get['repo'] = 'int'; +$filter->mr->diff->cookie['arrange'] = 'reg::word'; + $filter->ci->checkCompileStatus->get['gitlabOnly'] = 'string'; diff --git a/config/zentaopms.php b/config/zentaopms.php index f2567d0504..57f78af9c1 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -212,9 +212,10 @@ define('TABLE_LOG', '`' . $config->db->prefix . 'log`'); define('TABLE_SCORE', '`' . $config->db->prefix . 'score`'); define('TABLE_NOTIFY', '`' . $config->db->prefix . 'notify`'); define('TABLE_OAUTH', '`' . $config->db->prefix . 'oauth`'); -define('TABLE_PIPELINE', '`' . $config->db->prefix . 'pipeline`'); +define('TABLE_PIPELINE', '`' . $config->db->prefix . 'pipeline`'); define('TABLE_JOB', '`' . $config->db->prefix . 'job`'); define('TABLE_COMPILE', '`' . $config->db->prefix . 'compile`'); +define('TABLE_MR', '`' . $config->db->prefix . 'mr`'); define('TABLE_REPO', '`' . $config->db->prefix . 'repo`'); define('TABLE_RELATION', '`' . $config->db->prefix . 'relation`'); @@ -257,6 +258,7 @@ $config->objectTables['stakeholder'] = TABLE_STAKEHOLDER; $config->objectTables['job'] = TABLE_JOB; $config->objectTables['team'] = TABLE_TEAM; $config->objectTables['pipeline'] = TABLE_PIPELINE; +$config->objectTables['mr'] = TABLE_MR; /* Program privs.*/ $config->programPriv = new stdclass(); diff --git a/db/update15.3.sql b/db/update15.3.sql index 58685d2dd9..9e900e468d 100644 --- a/db/update15.3.sql +++ b/db/update15.3.sql @@ -2,3 +2,6 @@ ALTER TABLE `zt_testtask` ADD `realFinishedDate` datetime NOT NULL AFTER `end`; ALTER TABLE `zt_doc` ADD `draft` longtext NOT NULL AFTER `views`; ALTER TABLE `zt_release` ADD `mailto` text AFTER `desc`; ALTER TABLE `zt_release` ADD `notify` varchar(255) AFTER `mailto`; + +INSERT INTO `zt_cron` (`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`, `lastTime`) VALUES +('*/5', '*', '*', '*', '*', 'moduleName=mr&methodName=syncMR', '定时同步GitLab合并数据到禅道数据库', 'zentao', 1, 'normal', '0000-00-00 00:00:00'); diff --git a/db/update15.4.sql b/db/update15.4.sql index 47611ba9f9..190152b7b2 100644 --- a/db/update15.4.sql +++ b/db/update15.4.sql @@ -2,3 +2,25 @@ UPDATE `zt_action` SET `action` = 'reviewpassed' WHERE `action` = 'passreviewed' UPDATE `zt_action` SET `action` = 'reviewrejected' WHERE `action` = 'reviewclosed'; UPDATE `zt_action` SET `action` = 'reviewclarified' WHERE `action` = 'clarifyreviewed'; DELETE FROM `zt_score` WHERE `module` = 'tutorial' AND `method` = 'finish'; +CREATE TABLE `zt_mr` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `gitlabID` mediumint(8) unsigned NOT NULL, + `sourceProject` int unsigned NOT NULL, + `sourceBranch` varchar(100) NOT NULL, + `targetProject` int unsigned NOT NULL, + `targetBranch` varchar(100) NOT NULL, + `mriid` int unsigned NOT NULL, + `title` varchar(255) NOT NULL, + `description` text NOT NULL, + `assignee` varchar(255) NOT NULL, + `reviewer` varchar(255) NOT NULL, + `createdBy` varchar(30) NOT NULL, + `createdDate` datetime NOT NULL, + `editedBy` varchar(30) NOT NULL, + `editedDate` datetime NOT NULL, + `deleted` tinyint(1) NOT NULL, + `status` char(30) NOT NULL, + `mergeStatus` char(30) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + diff --git a/db/zentao.sql b/db/zentao.sql index c5a8495397..4c585b6b8b 100644 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -545,6 +545,28 @@ CREATE TABLE IF NOT EXISTS `zt_module` ( KEY `type` (`type`), KEY `path` (`path`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; +-- DROP TABLE IF EXISTS `zt_mr`; +CREATE TABLE `zt_mr` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `gitlabID` mediumint(8) unsigned NOT NULL, + `sourceProject` int unsigned NOT NULL, + `sourceBranch` varchar(100) NOT NULL, + `targetProject` int unsigned NOT NULL, + `targetBranch` varchar(100) NOT NULL, + `mriid` int unsigned NOT NULL, + `title` varchar(255) NOT NULL, + `description` text NOT NULL, + `assignee` varchar(255) NOT NULL, + `reviewer` varchar(255) NOT NULL, + `createdBy` varchar(30) NOT NULL, + `createdDate` datetime NOT NULL, + `editedBy` varchar(30) NOT NULL, + `editedDate` datetime NOT NULL, + `deleted` tinyint(1) NOT NULL, + `status` char(30) NOT NULL, + `mergeStatus` char(30) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- DROP TABLE IF EXISTS `zt_notify`; CREATE TABLE IF NOT EXISTS `zt_notify` ( `id` mediumint unsigned NOT NULL AUTO_INCREMENT, diff --git a/lib/scm/gitlab.class.php b/lib/scm/gitlab.class.php index d360de0edb..f39526765d 100644 --- a/lib/scm/gitlab.class.php +++ b/lib/scm/gitlab.class.php @@ -278,13 +278,15 @@ class gitlab * @access public * @return array */ - public function diff($path, $fromRevision, $toRevision) + public function diff($path, $fromRevision, $toRevision, $fromProject = '') { if(!scm::checkRevision($fromRevision)) return array(); if(!scm::checkRevision($toRevision)) return array(); - $api = "compare"; - $params = array('from' => $fromRevision, 'to' => $toRevision, 'straight' => 1); + $api = "compare"; + $params = array('from' => $fromRevision, 'to' => $toRevision); + if($fromProject) $params['from_project_id'] = $fromProject; + if($toRevision == 'HEAD' and $this->branch) $params['to'] = $this->branch; $results = $this->fetch($api, $params); foreach($results->diffs as $key => $diff) @@ -303,7 +305,6 @@ class gitlab foreach($diffLines as $diffLine) $lines[] = $diffLine; } return $lines; - //return $results->diffs; } /** diff --git a/lib/scm/scm.class.php b/lib/scm/scm.class.php index 24f9687e29..d148cc166c 100644 --- a/lib/scm/scm.class.php +++ b/lib/scm/scm.class.php @@ -5,8 +5,8 @@ class scm /** * Set engine. - * - * @param object $repo + * + * @param object $repo * @access public * @return void */ @@ -19,10 +19,10 @@ class scm } /** - * List files. - * - * @param string $path - * @param string $revision + * List files. + * + * @param string $path + * @param string $revision * @access public * @return array */ @@ -34,10 +34,10 @@ class scm /** * Get tags. - * - * @param string $path - * @param string $revision - * @param bool $onlyDir + * + * @param string $path + * @param string $revision + * @param bool $onlyDir * @access public * @return array */ @@ -49,7 +49,7 @@ class scm /** * Get branch. - * + * * @access public * @return array */ @@ -60,11 +60,11 @@ class scm /** * Get log. - * - * @param string $path - * @param string $fromRevision - * @param string $toRevision - * @param int $count + * + * @param string $path + * @param string $fromRevision + * @param string $toRevision + * @param int $count * @access public * @return array */ @@ -78,9 +78,9 @@ class scm /** * Blame file. - * - * @param string $path - * @param string $revision + * + * @param string $path + * @param string $revision * @access public * @return array */ @@ -92,9 +92,9 @@ class scm /** * Get last log. - * - * @param string $path - * @param int $count + * + * @param string $path + * @param int $count * @access public * @return array */ @@ -105,20 +105,21 @@ class scm /** * Diff file. - * - * @param string $path - * @param string $fromRevision - * @param string $toRevision - * @param string $parse + * + * @param string $path + * @param string $fromRevision + * @param string $toRevision + * @param string $parse * @access public * @return array */ - public function diff($path, $fromRevision = 0, $toRevision = 'HEAD', $parse = 'yes') + public function diff($path, $fromRevision = 0, $toRevision = 'HEAD', $parse = 'yes', $extra = '') { if(!scm::checkRevision($fromRevision)) return array(); if(!scm::checkRevision($toRevision)) return array(); - $diffs = $this->engine->diff($path, $fromRevision, $toRevision); + if(!$extra) $diffs = $this->engine->diff($path, $fromRevision, $toRevision); + if($extra) $diffs = $this->engine->diff($path, $fromRevision, $toRevision, $extra); if($parse != 'yes') return implode("\n", $diffs); return $this->engine->parseDiff($diffs); @@ -126,23 +127,23 @@ class scm /** * Cat file. - * - * @param string $entry - * @param string $revision + * + * @param string $entry + * @param string $revision * @access public * @return string */ public function cat($entry, $revision = 'HEAD') { if(!scm::checkRevision($revision)) return false; - return $this->engine->cat($entry, $revision); + return $this->engine->cat($entry, $revision); } /** * Get info. - * - * @param string $entry - * @param string $revision + * + * @param string $entry + * @param string $revision * @access public * @return object */ @@ -154,8 +155,8 @@ class scm /** * Exec scm cmd. - * - * @param string $cmd + * + * @param string $cmd * @access public * @return array */ @@ -165,47 +166,47 @@ class scm } /** - * Get commit count - * - * @param int $commits - * @param string $lastVersion + * Get commit count + * + * @param int $commits + * @param string $lastVersion * @access public * @return int */ public function getCommitCount($commits = 0, $lastVersion = 0) { if(!scm::checkRevision($lastVersion)) return false; - return $this->engine->getCommitCount($commits, $lastVersion); + return $this->engine->getCommitCount($commits, $lastVersion); } /** * Get latest revision. - * + * * @access public * @return string */ public function getLatestRevision() { - return $this->engine->getLatestRevision(); + return $this->engine->getLatestRevision(); } /** * Get first revision. - * + * * @access public * @return string */ public function getFirstRevision() { - return $this->engine->getFirstRevision(); + return $this->engine->getFirstRevision(); } /** * Get commits. - * - * @param string $version - * @param int $count - * @param string $branch + * + * @param string $version + * @param int $count + * @param string $branch * @access public * @return array */ @@ -216,9 +217,9 @@ class scm } /** - * Check revision - * - * @param int|string $revision + * Check revision + * + * @param int|string $revision * @static * @access public * @return bool @@ -231,9 +232,9 @@ class scm } /** - * Escape command. - * - * @param string $cmd + * Escape command. + * + * @param string $cmd * @access public * @return string */ @@ -246,12 +247,12 @@ function escapeCmd($cmd) } /** - * Execute command. - * - * @param string $cmd - * @param string $return - * @param int $result - * @param string $type + * Execute command. + * + * @param string $cmd + * @param string $return + * @param int $result + * @param string $type * @access public * @return array|string */ diff --git a/module/common/lang/en.php b/module/common/lang/en.php index df6fe59f6e..6c39ea498a 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -242,6 +242,7 @@ $lang->testcase->testsuite = 'Test Suite'; $lang->testcase->caselib = 'Case Library'; $lang->devops->compile = 'Compile'; +$lang->devops->mr = 'Merge Request'; $lang->devops->repo = 'Repo'; $lang->devops->rules = 'Rule'; diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php index 40adf74343..c48cbed074 100644 --- a/module/common/lang/menu.php +++ b/module/common/lang/menu.php @@ -238,6 +238,7 @@ $lang->scrum->menu->settings['subMenu']->whitelist = array('link' => "{$lang-> $lang->scrum->menu->settings['subMenu']->stakeholder = array('link' => "{$lang->stakeholder->common}|stakeholder|browse|project=%s", 'subModule' => 'stakeholder'); $lang->scrum->menu->settings['subMenu']->group = array('link' => "{$lang->priv}|project|group|project=%s", 'alias' => 'group,manageview,managepriv'); + /* Execution menu. */ $lang->execution->homeMenu = new stdclass(); $lang->execution->homeMenu->all = array('link' => "{$lang->execution->all}|execution|all|", 'alias' => 'batchedit'); @@ -333,6 +334,7 @@ $lang->qa->dividerMenu = ',bug,testtask,caselib,'; $lang->devops->menu = new stdclass(); $lang->devops->menu->code = array('link' => "{$lang->repo->common}|repo|browse|repoID=%s", 'alias' => 'diff,view,revision,log,blame,showsynccommit'); $lang->devops->menu->compile = array('link' => "{$lang->devops->compile}|job|browse", 'subModule' => 'compile,job'); +$lang->devops->menu->mr = array('link' => "{$lang->devops->mr}|mr|browse"); $lang->devops->menu->gitlab = array('link' => "GitLab|gitlab|browse", 'alias' => 'create,edit'); $lang->devops->menu->jenkins = array('link' => "Jenkins|jenkins|browse", 'alias' => 'create,edit'); $lang->devops->menu->maintain = array('link' => "{$lang->devops->repo}|repo|maintain", 'alias' => 'create,edit'); @@ -340,11 +342,11 @@ $lang->devops->menu->rules = array('link' => "{$lang->devops->rules}|repo|set $lang->devops->menuOrder[5] = 'code'; $lang->devops->menuOrder[10] = 'compile'; -$lang->devops->menuOrder[15] = 'gitlab'; -$lang->devops->menuOrder[20] = 'jenkins'; -$lang->devops->menuOrder[25] = 'maintain'; -$lang->devops->menuOrder[30] = 'rules'; - +$lang->devops->menuOrder[15] = 'mr'; +$lang->devops->menuOrder[20] = 'gitlab'; +$lang->devops->menuOrder[25] = 'jenkins'; +$lang->devops->menuOrder[30] = 'maintain'; +$lang->devops->menuOrder[35] = 'rules'; /* Doc menu. */ $lang->doc->menu = new stdclass(); $lang->doc->menu->dashboard = array('link' => "{$lang->dashboard}|doc|index"); @@ -538,6 +540,7 @@ $lang->navGroup->devops = 'devops'; $lang->navGroup->repo = 'devops'; $lang->navGroup->job = 'devops'; $lang->navGroup->jenkins = 'devops'; +$lang->navGroup->mr = 'devops'; $lang->navGroup->gitlab = 'devops'; $lang->navGroup->compile = 'devops'; $lang->navGroup->ci = 'devops'; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index 634f5a18d5..e96e03cf19 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -241,9 +241,11 @@ $lang->testcase->case = '用例'; $lang->testcase->testsuite = '套件'; $lang->testcase->caselib = '用例库'; -$lang->devops->compile = '构建'; -$lang->devops->repo = '版本库'; -$lang->devops->rules = '指令'; +$lang->devops->compile = '构建'; +$lang->devops->mr = '合并请求'; +$lang->devops->repo = '版本库'; +$lang->devops->rules = '指令'; +$lang->devops->settings = '合并请求设置'; $lang->admin->system = '系统'; $lang->admin->entry = '应用'; diff --git a/module/compile/model.php b/module/compile/model.php index 5fe8999488..accbed3c93 100644 --- a/module/compile/model.php +++ b/module/compile/model.php @@ -129,17 +129,30 @@ class compileModel extends model if(!$job) return false; - $data = new stdclass(); - $data->PARAM_TAG = $compile->tag; - $data->ZENTAO_DATA = "compile={$compile->id}"; + $compileID = $compile->id; + $repo = $this->loadModel('repo')->getRepoById($job->repo); - $url = $this->getBuildUrl($job); - $build = new stdclass(); - $build->queue = $this->loadModel('ci')->sendRequest($url->url, $data, $url->userPWD); - $build->status = $build->queue ? 'created' : 'create_fail'; - $build->updateDate = helper::now(); - $this->dao->update(TABLE_COMPILE)->data($build)->where('id')->eq($compile->id)->exec(); - $this->dao->update(TABLE_JOB)->set('lastStatus')->eq($build->status)->set('lastExec')->eq($build->updateDate)->where('id')->eq($compile->job)->exec(); + if($job->triggerType == 'tag') + { + $lastTag = $this->getLastTagByRepo($repo); + if($lastTag) + { + $job->lastTag = $lastTag; + $this->dao->update(TABLE_JOB)->set('lastTag')->eq($lastTag)->where('id')->eq($job->id)->exec(); + } + + $this->dao->update(TABLE_COMPILE)->set('tag')->eq($lastTag)->where('id')->eq($compile->id)->exec(); + } + + if($job->engine == 'gitlab') $compile = $this->loadModel('job')->execGitlabPipeline($job, $compileID); + if($job->engine == 'jenkins') $compile = $this->job->execJenkinsPipeline($job, $repo, $compile->id); + + $this->dao->update(TABLE_COMPILE)->data($compile)->where('id')->eq($compileID)->exec(); + $this->dao->update(TABLE_JOB) + ->set('lastStatus')->eq($build->status) + ->set('lastExec')->eq($build->updateDate) + ->where('id')->eq($job->id) + ->exec(); return !dao::isError(); } diff --git a/module/gitlab/control.php b/module/gitlab/control.php index 78de078d11..cd6d073e68 100644 --- a/module/gitlab/control.php +++ b/module/gitlab/control.php @@ -58,6 +58,8 @@ class gitlab extends control $gitlabID = $this->gitlab->create(); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + $this->loadModel('action'); + $actionID = $this->action->create('gitlab', $gitlabID, 'create'); return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); } @@ -66,6 +68,24 @@ class gitlab extends control $this->display(); } + /** + * view a gitlab. + * @param int $id + * @access public + * @return void + */ + public function view($id) + { + $gitlab = $this->gitlab->getByID($id); + + $this->view->title = $this->lang->gitlab->common . $this->lang->colon . $this->lang->gitlab->view; + $this->view->gitlab = $gitlab; + $this->view->users = $this->loadModel('user')->getPairs('noclosed'); + $this->view->actions = $this->loadModel('action')->getList('gitlab', $id); + $this->view->preAndNext = $this->loadModel('common')->getPreAndNextObject('pipeline', $id); + $this->display(); + } + /** * Edit a gitlab. * @@ -75,17 +95,24 @@ class gitlab extends control */ public function edit($id) { - $gitlab = $this->gitlab->getByID($id); + $oldGitLab = $this->gitlab->getByID($id); + if($_POST) { $this->checkToken(); $this->gitlab->update($id); + $gitLab = $this->gitlab->getByID($id); if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->loadModel('action'); + $actionID = $this->action->create('gitlab', $id, 'edit'); + $changes = common::createChanges($oldGitLab, $gitLab); + $this->action->logHistory($actionID, $changes); return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); } $this->view->title = $this->lang->gitlab->common . $this->lang->colon . $this->lang->gitlab->edit; - $this->view->gitlab = $gitlab; + $this->view->gitlab = $oldGitLab; $this->display(); } @@ -178,7 +205,14 @@ class gitlab extends control { if($confim != 'yes') die(js::confirm($this->lang->gitlab->confirmDelete, inlink('delete', "id=$id&confirm=yes"))); + $oldGitLab = $this->gitlab->getByID($id); + $this->loadModel('action'); $this->gitlab->delete(TABLE_PIPELINE, $id); + + $gitLab = $this->gitlab->getByID($id); + $actionID = $this->action->create('gitlab', $id, 'delete'); + $changes = common::createChanges($oldGitLab, $gitLab); + $this->action->logHistory($actionID, $changes); die(js::reload('parent')); } @@ -367,4 +401,54 @@ class gitlab extends control } return $this->send($options); } + + /** + * AJAX: Get project branches. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return void + */ + public function ajaxGetProjectBranches($gitlabID, $projectID) + { + if(!$gitlabID or !$projectID) return $this->send(array('message' => array())); + + $branches = $this->gitlab->apiGetBranches($gitlabID, $projectID); + $options = ""; + foreach($branches as $branch) + { + $options .= ""; + } + $this->send($options); + } + + /** + * AJAX: Get MR user pairs to select assignee_ids and reviewer_ids. + * Attention: The user must be a member of the GitLab project. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return void + */ + public function ajaxGetMRUserPairs($gitlabID, $projectID) + { + if(!$gitlabID) return $this->send(array('message' => array())); + + $bindedUsers = $this->gitlab->getUserIdRealnamePairs($gitlabID); + $rawProjectUsers = $this->gitlab->apiGetProjectUsers($gitlabID, $projectID); + $users = array(); + foreach($rawProjectUsers as $rawProjectUser) + { + if(!empty($bindedUsers[$rawProjectUser->id])) $users[$rawProjectUser->id] = $bindedUsers[$rawProjectUser->id]; + } + + $options = ""; + foreach($users as $index => $user) + { + $options .= ""; + } + $this->send($options); + } } diff --git a/module/gitlab/lang/zh-cn.php b/module/gitlab/lang/zh-cn.php index f565ee11df..9467979803 100644 --- a/module/gitlab/lang/zh-cn.php +++ b/module/gitlab/lang/zh-cn.php @@ -4,6 +4,7 @@ $lang->gitlab->common = 'GitLab'; $lang->gitlab->browse = '浏览GitLab'; $lang->gitlab->create = '添加GitLab'; $lang->gitlab->edit = '编辑GitLab'; +$lang->gitlab->view = '查看GitLab'; $lang->gitlab->bindUser = '绑定用户'; $lang->gitlab->webhook = 'webhook'; $lang->gitlab->bindProduct = '关联产品'; diff --git a/module/gitlab/model.php b/module/gitlab/model.php index d9d32ee23f..8d9e2f4828 100644 --- a/module/gitlab/model.php +++ b/module/gitlab/model.php @@ -34,12 +34,15 @@ class gitlabModel extends model */ public function getList($orderBy = 'id_desc', $pager = null) { - return $this->loadModel('pipeline')->getList('gitlab', $orderBy, $pager); + $gitlabList = $this->loadModel('pipeline')->getList('gitlab', $orderBy, $pager); + + return $gitlabList; } /** * Get gitlab pairs. * + * @access public * @return array */ @@ -63,7 +66,25 @@ class gitlabModel extends model } /** - * Get gitlab user id zentao account pairs of one gitlab. + * Get gitlab user id and realname pairs of one gitlab. + * + * @param int $gitlabID + * @access public + * @return void + */ + public function getUserIdRealnamePairs($gitlabID) + { + return $this->dao->select('oauth.openID as openID,user.realname as realname') + ->from(TABLE_OAUTH)->alias('oauth') + ->leftJoin(TABLE_USER)->alias('user') + ->on("oauth.account = user.account") + ->where('providerType')->eq('gitlab') + ->andWhere('providerID')->eq($gitlabID) + ->fetchPairs(); + } + + /** + * Get gitlab user id and zentao account pairs of one gitlab. * * @param int $gitlab * @access public @@ -255,42 +276,60 @@ class gitlabModel extends model /** * Get gitlab project name of one gitlab project. * - * @param int $jobID + * @param int $gitlabID + * @param int $projectID * @access public * @return string|false */ - public function getObjectNameForJob($gitlabID, $projectID) + public function getProjectName($gitlabID, $projectID) { $project = $this->apiGetSingleProject($gitlabID, $projectID); - if(isset($project->name)) return $project->name; + if(is_object($project) and isset($project->name)) return $project->name; return false; } /** - * Get ref option menus. + * Get reference option menus. * * @param int $gitlabID * @param int $projectID * @access public * @return array */ - public function getRefOptions($gitlabID, $projectID) + public function getReferenceOptions($gitlabID, $projectID) { - $refList = array(); - $branches = $this->loadModel('gitlab')->apiGetBranches($gitlabID, $projectID); - $tags = $this->loadModel('gitlab')->apiGetTags($gitlabID, $projectID); + $refList = array(); - /* fix bug 14612*/ - if(isset($branches->message)) return array(); + $branches = $this->apiGetBranches($gitlabID, $projectID); + foreach($branches as $branch) $refList[$branch->name] = "Branch::" . $branch->name; - if(isset($branches->error)) return array(); + $tags = $this->apiGetTags($gitlabID, $projectID); + foreach($tags as $tag) $refList[$tag->name] = "Tag::" . $tag->name; - foreach($branches as $branch) $refList[] = "Branch::" . $branch->name; - foreach($tags as $tag) $refList[] = "Tag::" . $tag->name; return $refList; } + /** + * Get branches. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return array + */ + public function getBranches($gitlabID, $projectID) + { + $rawBranches = $this->apiGetBranches($gitlabID, $projectID); + + $branches = array(); + foreach($rawBranches as $branch) + { + $branches[] = $branch->name; + } + return $branches; + } + /** * Create a gitlab. * @@ -352,6 +391,23 @@ class gitlabModel extends model return json_decode(commonModel::http($url, $data, $options)); } + /** + * Get a list of to-do items. + * + * @see https://docs.gitlab.com/ee/api/todos.html + * @param int $gitlabID + * @param int $projectID + * @access public + * @return object + */ + public function apiTodoList($gitlabID, $projectID) + { + $gitlab = $this->loadModel('gitlab')->getByID($gitlabID); + if(!$gitlab) return ''; + $url = rtrim($gitlab->url, '/')."/api/v4/todos?project_id=$projectID&type=MergeRequest&private_token={$gitlab->token}"; + return json_decode(commonModel::http($url)); + } + /** * Get current user. * @@ -413,10 +469,8 @@ class gitlabModel extends model $allResults = array(); for($page = 1; true; $page ++) { - $results = json_decode(commonModel::http($host . "?private_token={$gitlab->token}&simple=true&membership=true&page={$page}&per_page=100")); - if(isset($results->error)) break; + $results = json_decode(commonModel::http($host . "?private_token={$gitlab->token}&simple=true&page={$page}&per_page=100")); if(empty($results) or $page > 10) break; - $allResults = array_merge($allResults, $results); } @@ -437,6 +491,94 @@ class gitlabModel extends model return json_decode(commonModel::http($url)); } + /** + * Get project users. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return object + */ + public function apiGetProjectUsers($gitlabID, $projectID) + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/$projectID/users"); + return json_decode(commonModel::http($url)); + } + + /** + * Get project all members(users and users in groups). + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return object + */ + public function apiGetProjectMembers($gitlabID, $projectID) + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/$projectID/members/all"); + return json_decode(commonModel::http($url)); + } + + /** + * Get the member detail in project. + * + * @param int $gitlabID + * @param int $projectID + * @param int $userID + * @access public + * @return object + */ + public function apiGetProjectMember($gitlabID, $projectID, $userID) + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/$projectID/members/all/$userID"); + return json_decode(commonModel::http($url)); + } + + /** + * Get single branch by API. + * + * @param int $gitlabID + * @param int $projectID + * @param string $branch + * @access public + * @return object + */ + public function apiGetSingleBranch($gitlabID, $projectID, $branch) + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/$projectID/repository/branches/$branch"); + return json_decode(commonModel::http($url)); + } + + /** + * Get Forks of a project by API. + * + * @docs https://docs.gitlab.com/ee/api/projects.html#list-forks-of-a-project + * @param int $gitlabID + * @param int $projectID + * @access public + * @return object + */ + public function apiGetForks($gitlabID, $projectID) + { + $url = sprintf($this->getApiRoot($gitlabID), "/projects/$projectID/forks"); + return json_decode(commonModel::http($url)); + } + + /** + * Get upstream project by API. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return void + */ + public function apiGetUpstream($gitlabID,$projectID) + { + $currentProject = $this->apiGetSingleProject($gitlabID, $projectID); + if(isset($currentProject->forked_from_project)) return $currentProject->forked_from_project; + return array(); + } + /** * Get hooks. * @@ -726,25 +868,26 @@ class gitlabModel extends model /** * Create a new pipeline by api. * - * @param integer $gitlabID - * @param integer $projectID - * @param string $reference + * @param int $gitlabID + * @param int $projectID + * @param object $params * @access public * @return object * @docment https://docs.gitlab.com/ee/api/pipelines.html#create-a-new-pipeline */ - public function apiCreatePipeline($gitlabID, $projectID, $reference) + public function apiCreatePipeline($gitlabID, $projectID, $params) { + if(!is_string($params)) $params = json_encode($params); $url = sprintf($this->getApiRoot($gitlabID), "/projects/{$projectID}/pipeline"); - return json_decode(commonModel::http($url, $reference, null, array("Content-Type: application/json"))); + return json_decode(commonModel::http($url, $params, null, array("Content-Type: application/json"))); } /** * Get single pipline by api. * - * @param integer $gitlabID - * @param integer $projectID - * @param integer $pipelineID + * @param int $gitlabID + * @param int $projectID + * @param int $pipelineID * @access public * @return object * @docment https://docs.gitlab.com/ee/api/pipelines.html#get-a-single-pipeline @@ -758,9 +901,9 @@ class gitlabModel extends model /** * List pipeline jobs by api. * - * @param integer $gitlabID - * @param integer $projectID - * @param integer $pipelineID + * @param int $gitlabID + * @param int $projectID + * @param int $pipelineID * @return object * @docment https://docs.gitlab.com/ee/api/jobs.html#list-pipeline-jobs */ @@ -773,9 +916,9 @@ class gitlabModel extends model /** * Get a single job by api. * - * @param integer $gitlabID - * @param integer $projectID - * @param integer $jobID + * @param int $gitlabID + * @param int $projectID + * @param int $jobID * @return object * @docment https://docs.gitlab.com/ee/api/jobs.html#get-a-single-job */ @@ -788,9 +931,9 @@ class gitlabModel extends model /** * Get a log file by api. * - * @param integer $gitlabID - * @param integer $projectID - * @param integer $jobID + * @param int $gitlabID + * @param int $projectID + * @param int $jobID * @return string * @docment https://docs.gitlab.com/ee/api/jobs.html#get-a-log-file */ diff --git a/module/gitlab/view/browse.html.php b/module/gitlab/view/browse.html.php index 1fc67219a1..1943eb0ef9 100644 --- a/module/gitlab/view/browse.html.php +++ b/module/gitlab/view/browse.html.php @@ -42,7 +42,7 @@ $gitlab): ?> - name;?> + ">name;?> url;?> + * @package GitLab + * @version $Id: view.html.php 4728 2013-05-03 06:14:34Z david18810279601@gmail.com $ + * @link http://www.zentao.net + * */ +?> + + + +
+
+
+
+
+ + + diff --git a/module/group/js/managepriv.js b/module/group/js/managepriv.js index 94b96a75ab..6b7ac166ac 100644 --- a/module/group/js/managepriv.js +++ b/module/group/js/managepriv.js @@ -5,8 +5,8 @@ function showPriv(value) /** * Control the actions select control for a module. - * - * @param string $module + * + * @param string $module * @access public * @return void */ diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php index 76cfb4cc88..89e9eac316 100644 --- a/module/group/lang/resource.php +++ b/module/group/lang/resource.php @@ -63,6 +63,7 @@ $lang->moduleOrder[205] = 'cron'; $lang->moduleOrder[210] = 'dev'; $lang->moduleOrder[215] = 'message'; $lang->moduleOrder[220] = 'gitlab'; +$lang->moduleOrder[225] = 'mr'; $lang->resource = new stdclass(); @@ -1146,6 +1147,7 @@ $lang->resource->gitlab->importIssue = 'importIssue'; $lang->resource->gitlab->delete = 'delete'; $lang->resource->gitlab->bindUser = 'bindUser'; $lang->resource->gitlab->bindProduct = 'bindProduct'; + //$lang->resource->gitlab->webhook = 'webhook'; $lang->gitlab->methodOrder[5] = 'browse'; @@ -1154,9 +1156,22 @@ $lang->gitlab->methodOrder[15] = 'edit'; $lang->gitlab->methodOrder[20] = 'importIssue'; $lang->gitlab->methodOrder[30] = 'delete'; $lang->gitlab->methodOrder[35] = 'bindUser'; -$lang->gitlab->methodOrder[40] = 'bindProduct'; //$lang->gitlab->methodOrder[45] = 'webhook'; +/* merge request. */ +$lang->resource->mr = new stdclass(); +$lang->resource->mr->create = 'create'; +$lang->resource->mr->browse = 'browse'; +$lang->resource->mr->edit = 'edit'; +$lang->resource->mr->delete = 'delete'; +$lang->resource->mr->settings = 'settings'; + +$lang->mr->methodOrder[10] = 'create'; +$lang->mr->methodOrder[15] = 'browse'; +$lang->mr->methodOrder[20] = 'edit'; +$lang->mr->methodOrder[25] = 'delete'; +$lang->mr->methodOrder[35] = 'settings'; + /* Git. */ $lang->resource->git = new stdclass(); $lang->resource->git->diff = 'diff'; diff --git a/module/group/view/managepriv.html.php b/module/group/view/managepriv.html.php index 4bd7c24837..d89dbd8064 100644 --- a/module/group/view/managepriv.html.php +++ b/module/group/view/managepriv.html.php @@ -10,7 +10,7 @@ * @link http://www.zentao.net */ ?> -install->cronList['moduleName=todo&methodName=createCycle'] = '生 $lang->install->cronList['moduleName=ci&methodName=initQueue'] = '创建周期性任务'; $lang->install->cronList['moduleName=ci&methodName=checkCompileStatus'] = '同步Jenkins任务状态'; $lang->install->cronList['moduleName=ci&methodName=exec'] = '执行Jenkins任务'; +$lang->install->cronList['moduleName=mr&methodName=syncMR'] = '定时同步GitLabMR信息'; $lang->install->success = "安装成功"; $lang->install->login = '登录禅道管理系统'; diff --git a/module/job/control.php b/module/job/control.php index a31f2a8dff..a206bac9f7 100644 --- a/module/job/control.php +++ b/module/job/control.php @@ -168,6 +168,8 @@ class job extends control $repo = $this->loadModel('repo')->getRepoByID($job->repo); $this->view->repo = $this->loadModel('repo')->getRepoByID($job->repo); + if($repo->SCM == 'Gitlab') $this->view->refList = $this->loadModel('gitlab')->getReferenceOptions($repo->gitlab, $repo->project); + $repoList = $this->repo->getList($this->projectID); $repoPairs = array(0 => '', $repo->id => $repo->name); $gitlabRepos = array(0 => ''); @@ -306,49 +308,22 @@ class job extends control * @access public * @return void */ - public function exec($id, $showForm = 'no') + public function exec($id) { - if($showForm == 'yes' or $_POST) + $job = $this->job->getByID($id); + if(strtolower($job->engine) == 'gitlab') { - $job = $this->job->getByID($id); - $refList = $this->loadModel('gitlab')->getRefOptions($job->server, $job->pipeline); - - if($_POST) + if(!isset($job->reference) or !$job->reference) { - $reference = new stdclass; - $explodedRefs = explode("::", $refList[$this->post->ref]); - $reference->ref = $explodedRefs[1]; - - $variables = array(); - foreach($this->post->keys as $key => $field) - { - $variable = array(); - if(!trim($this->post->values[$key])) continue; - $variable['key'] = $field; - $variable['value'] = $this->post->values[$key]; - $variable['variable_type'] = "env_var"; - - $variables[] = $variable; - } - - $reference->variables = $variables; - $compile = $this->job->exec($id, json_encode($reference)); - return $this->send(array('result' => 'success', 'message' => $this->lang->job->execSuccess, 'locate' => $this->createLink('compile', 'logs', "buildID={$compile->id}"))); + return $this->send(array('result' => 'fail', 'message' => $this->lang->job->setReferenceTips, 'locate' => inlink('edit', "id=$id"))); } - - $this->view->title = $this->lang->job->runPipeline; - $this->view->refList = $refList; - $this->view->job = $job; - $this->view->pipelineTips = $this->lang->job->pipelineTips; - return $this->display(); } $compile = $this->job->exec($id); - if(dao::isError()) die(js::error(dao::getError())); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); $this->app->loadLang('compile'); - echo js::alert(sprintf($this->lang->job->sendExec, zget($this->lang->compile->statusList, $compile->status))); - die(js::reload('parent')); + return $this->send(array('result' => 'success', 'message' => sprintf($this->lang->job->sendExec, zget($this->lang->compile->statusList, $compile->status)))); } /** @@ -383,4 +358,19 @@ class job extends control $productName = $this->loadModel('product')->getByID($repo->product)->name; die(json_encode(array($productName => $repo->product))); } + + /** + * Ajax get reference list function. + * + * @param int $repoID + * @access public + * @return void + */ + public function ajaxGetRefList($repoID) + { + $repo = $this->loadModel('repo')->getRepoByID($repoID); + if($repo->SCM != 'Gitlab') $this->send(array('result' => 'fail')); + $refList = $this->loadModel('gitlab')->getReferenceOptions($repo->gitlab, $repo->project); + $this->send(array('result' => 'success', 'refList' => $refList)); + } } diff --git a/module/job/js/browse.js b/module/job/js/browse.js new file mode 100644 index 0000000000..b801c52f3b --- /dev/null +++ b/module/job/js/browse.js @@ -0,0 +1,16 @@ +$(document).ready(function() +{ + $('.icon-job-exec').parent().click(function() + { + link = $(this).attr('href'); + $.getJSON(link, function(response) + { + if(response.result == 'success') bootbox.alert(response.message); + if(response.result != 'success') bootbox.alert(response.message, function() + { + if(typeof(response.locate) == 'string') location.href = response.locate; + }); + }); + return false; + }); +}); diff --git a/module/job/js/create.js b/module/job/js/create.js index cbe8c58a18..18b121ba44 100644 --- a/module/job/js/create.js +++ b/module/job/js/create.js @@ -129,19 +129,37 @@ $(document).ready(function() if($(this).val() == 'gitlab') { - $('#triggerType').find('[value=schedule]').remove(); $('tr.gitlabRepo').show(); $('tr.commonRepo').hide(); } else if($('#triggerType').find('[value=schedule]').size() == 0 ) { - $('#triggerType').append(scheduleOption); $('tr.gitlabRepo').hide(); $('tr.commonRepo').show(); } }); $('#engine').change(); + $('#gitlabRepo').change(function() + { + $('#reference option').remove(); + + var repoID = $(this).val(); + if(repoID > 0) + { + $.getJSON(createLink('job', 'ajaxGetRefList', "repoID=" + repoID), function(response) + { + if(response.result == 'success') + { + $.each(response.refList, function(reference, name) + { + $('#reference').append(""); + }); + } + $('#reference').trigger('chosen:updated'); + }); + } + }); $('#triggerType').change(); }); diff --git a/module/job/js/edit.js b/module/job/js/edit.js index 2b76c48b1c..f91c2aef1b 100644 --- a/module/job/js/edit.js +++ b/module/job/js/edit.js @@ -146,6 +146,27 @@ $(document).ready(function() } }); + $('#gitlabRepo').change(function() + { + $('#reference option').remove(); + + var repoID = $(this).val(); + if(repoID > 0) + { + $.getJSON(createLink('job', 'ajaxGetRefList', "repoID=" + repoID), function(response) + { + if(response.result == 'success') + { + $.each(response.refList, function(reference, name) + { + $('#reference').append(""); + }); + } + $('#reference').trigger('chosen:updated'); + }); + } + }); + $('#engine').change(); $('#jkServer').change(); diff --git a/module/job/lang/zh-cn.php b/module/job/lang/zh-cn.php index 6209ba46a4..d95c034f77 100644 --- a/module/job/lang/zh-cn.php +++ b/module/job/lang/zh-cn.php @@ -86,3 +86,4 @@ $lang->job->pipelineVariables = "变量"; $lang->job->pipelineVariablesKeyPlaceHolder = "输入变量的名称"; $lang->job->pipelineVariablesValuePlaceHolder = "输入变量的值"; $lang->job->pipelineVariablesTips = "指定要在此次运行中使用的变量值。CI/CD设置中指定的值将用作默认值。"; +$lang->job->setReferenceTips = "在执行构建前,请先设置代码库的分支信息。"; diff --git a/module/job/model.php b/module/job/model.php index 1bbcf23110..3195bd5179 100644 --- a/module/job/model.php +++ b/module/job/model.php @@ -20,7 +20,14 @@ class jobModel extends model */ public function getByID($id) { - return $this->dao->select('*')->from(TABLE_JOB)->where('id')->eq($id)->fetch(); + $job = $this->dao->select('*')->from(TABLE_JOB)->where('id')->eq($id)->fetch(); + if(strtolower($job->engine) == 'gitlab') + { + $pipeline = json_decode($job->pipeline); + $job->project = $pipeline->project; + $job->reference = $pipeline->reference; + } + return $job; } /** @@ -117,7 +124,7 @@ class jobModel extends model ->setDefault('atDay', '') ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) - ->remove('repoType') + ->remove('repoType,reference') ->get(); if($job->engine == 'jenkins') @@ -128,10 +135,12 @@ class jobModel extends model if(strtolower($job->engine) == 'gitlab') { + $repo = $this->loadModel('repo')->getRepoByID($job->gitlabRepo); + $project = zget($repo, 'project'); + $job->repo = $job->gitlabRepo; - $repo = $this->loadModel('repo')->getRepoByID($job->repo); $job->server = (int)zget($repo, 'gitlab', 0); - $job->pipeline = zget($repo, 'project', ''); + $job->pipeline = json_encode(array('project' => $project, 'reference' => $this->post->reference)); } unset($job->jkServer); @@ -203,7 +212,7 @@ class jobModel extends model ->setIF($this->post->triggerType != 'tag', 'lastTag', '') ->add('editedBy', $this->app->user->account) ->add('editedDate', helper::now()) - ->remove('repoType') + ->remove('repoType,reference') ->get(); if($job->engine == 'jenkins') @@ -214,10 +223,12 @@ class jobModel extends model if(strtolower($job->engine) == 'gitlab') { + $repo = $this->loadModel('repo')->getRepoByID($job->gitlabRepo); + $project = zget($repo, 'project'); + $job->repo = $job->gitlabRepo; - $repo = $this->loadModel('repo')->getRepoByID($job->repo); $job->server = (int)zget($repo, 'gitlab', 0); - $job->pipeline = zget($repo, 'project', ''); + $job->pipeline = json_encode(array('project' => $project, 'reference' => $this->post->reference)); } unset($job->jkServer); @@ -252,6 +263,7 @@ class jobModel extends model if(!empty($paramName)) $customParam[$paramName] = $paramValue; } + unset($job->paramName); unset($job->paramValue); unset($job->custom); @@ -326,102 +338,165 @@ class jobModel extends model * @access public * @return string|bool */ - public function exec($id, $reference = null) + public function exec($id) { $job = $this->dao->select('t1.id,t1.name,t1.product,t1.repo,t1.server,t1.pipeline,t1.triggerType,t1.atTime,t1.customParam,t1.engine,t2.name as jenkinsName,t2.url,t2.account,t2.token,t2.password') ->from(TABLE_JOB)->alias('t1') ->leftJoin(TABLE_PIPELINE)->alias('t2')->on('t1.server=t2.id') ->where('t1.id')->eq($id) ->fetch(); + if(!$job) return false; - $build = new stdclass(); - $build->job = $job->id; - $build->name = $job->name; - - $now = helper::now(); - $data = new stdclass(); $repo = $this->loadModel('repo')->getRepoById($job->repo); - $data->PARAM_TAG = ''; - if($job->triggerType == 'tag') - { - $lastTag = ''; - if($repo->SCM == 'Subversion') - { - $dirs = $this->loadModel('svn')->getRepoTags($repo, $job->svnDir); - if($dirs) - { - end($dirs); - $lastTag = current($dirs); - $lastTag = rtrim($repo->path , '/') . '/' . trim($job->svnDir, '/') . '/' . $lastTag; - } - } - else - { - $tags = $this->loadModel('git')->getRepoTags($repo); - if($tags) - { - end($tags); - $lastTag = current($tags); - } - } - - if($lastTag) - { - $build->tag = $lastTag; - $this->dao->update(TABLE_JOB)->set('lastTag')->eq($lastTag)->where('id')->eq($job->id)->exec(); - $data->PARAM_TAG = $lastTag; - } - } - elseif($job->triggerType == 'schedule') - { - $build->atTime = $job->atTime; - } + $now = helper::now(); + /* Save compile data. */ + $build = new stdclass(); + $build->job = $job->id; + $build->name = $job->name; $build->createdBy = $this->app->user->account; $build->createdDate = $now; $build->updateDate = $now; + + if($job->triggerType == 'schedule') $build->atTime = $job->atTime; + + if($job->triggerType == 'tag') + { + $lastTag = $this->getLastTagByRepo($repo); + if($lastTag) + { + $build->tag = $lastTag; + $job->lastTag = $lastTag; + $this->dao->update(TABLE_JOB)->set('lastTag')->eq($lastTag)->where('id')->eq($job->id)->exec(); + } + } + $this->dao->insert(TABLE_COMPILE)->data($build)->exec(); $compileID = $this->dao->lastInsertId(); - $data->ZENTAO_DATA = "compile={$compileID}"; + if($job->engine == 'jenkins') $compile = $this->execJenkinsPipeline($job, $repo, $compileID); + if($job->engine == 'gitlab') $compile = $this->execGitlabPipeline($job); + + $this->dao->update(TABLE_COMPILE)->data($compile)->where('id')->eq($compileID)->exec(); + + $this->dao->update(TABLE_JOB) + ->set('lastExec')->eq($now) + ->set('lastStatus')->eq($compile->status) + ->where('id')->eq($job->id) + ->exec(); + + return $compile; + } + + /** + * Exec jenkins pipeline. + * + * @param object $job + * @param object $repo + * @param int $compileID + * @access public + * @return object + */ + public function execJenkinsPipeline($job, $repo, $compileID) + { + $pipeline = new stdclass(); + $pipeline->PARAM_TAG = ''; + $pipeline->ZENTAO_DATA = "compile={$compileID}"; + if($job->triggerType == 'tag') $pipeline->PARAM_TAG = $job->lastTag; /* Add custom parameters to the data. */ foreach(json_decode($job->customParam) as $paramName => $paramValue) { - $paramValue = str_replace('$zentao_version', $this->config->version, $paramValue); - $paramValue = str_replace('$zentao_account', $this->app->user->account, $paramValue); - $paramValue = str_replace('$zentao_product', $job->product, $paramValue); + $paramValue = str_replace('$zentao_version', $this->config->version, $paramValue); + $paramValue = str_replace('$zentao_account', $this->app->user->account, $paramValue); + $paramValue = str_replace('$zentao_product', $job->product, $paramValue); $paramValue = str_replace('$zentao_repopath', $repo->path, $paramValue); - $data->$paramName = $paramValue; + + $pipeline->$paramName = $paramValue; } + $url = $this->loadModel('compile')->getBuildUrl($job); + $compile = new stdclass(); - $compile->id = $compileID; - - if($job->engine == 'jenkins') - { - $url = $this->loadModel('compile')->getBuildUrl($job); - $compile->queue = $this->loadModel('ci')->sendRequest($url->url, $data, $url->userPWD); - $compile->status = $compile->queue ? 'created' : 'create_fail'; - } - elseif($job->engine == 'gitlab' and $reference) - { - $pipeline = $this->loadModel('gitlab')->apiCreatePipeline($job->server, $job->pipeline, $reference); - if(empty($pipeline->id)) - { - $compile->status = 'create_fail'; - } - else - { - $compile->queue = $pipeline->id; - $compile->status = zget($pipeline, 'status', 'create_fail'); - } - } - - $this->dao->update(TABLE_COMPILE)->data($compile)->where('id')->eq($compileID)->exec(); - $this->dao->update(TABLE_JOB)->set('lastExec')->eq($now)->set('lastStatus')->eq($compile->status)->where('id')->eq($job->id)->exec(); + $compile->id = $compileID; + $compile->queue = $this->loadModel('ci')->sendRequest($url->url, $pipeline, $url->userPWD); + $compile->status = $compile->queue ? 'created' : 'create_fail'; return $compile; } + + /** + * Exec gitlab pipeline. + * + * @param int $job + * @access public + * @return void + */ + public function execGitlabPipeline($job) + { + $pipeline = json_decode($job->pipeline); + + $pipelineParams = new stdclass; + $pipelineParams->ref = $pipeline->reference; + + $customParams = json_decode($job->customParam); + $variables = array(); + foreach($customParams as $paramName => $paramValue) + { + $variable = array(); + $variable['key'] = $paramName; + $variable['value'] = $paramValue; + $variable['variable_type'] = "env_var"; + + $variables[] = $variable; + } + + if(!empty($variables)) $pipelineParams->variables = $variables; + + $compile = new stdclass; + + $pipeline = $this->loadModel('gitlab')->apiCreatePipeline($job->server, $pipeline->project, $pipelineParams); + if(empty($pipeline->id)) $compile->status = 'create_fail'; + + if(!empty($pipeline->id)) + { + $compile->queue = $pipeline->id; + $compile->status = zget($pipeline, 'status', 'create_fail'); + } + + return $compile; + } + + /** + * Get last tag of one repo. + * + * @param object $repo + * @access public + * @return void + */ + public function getLastTagByRepo($repo) + { + if($repo->SCM == 'Subversion') + { + $dirs = $this->loadModel('svn')->getRepoTags($repo, $job->svnDir); + if($dirs) + { + end($dirs); + $lastTag = current($dirs); + return rtrim($repo->path , '/') . '/' . trim($job->svnDir, '/') . '/' . $lastTag; + } + } + else + { + $tags = $this->loadModel('git')->getRepoTags($repo); + if($tags) + { + end($tags); + return current($tags); + } + } + + return ''; + } } diff --git a/module/job/view/browse.html.php b/module/job/view/browse.html.php index 88b556604c..f7a6eebef4 100644 --- a/module/job/view/browse.html.php +++ b/module/job/view/browse.html.php @@ -50,13 +50,20 @@ $job):?> +I engine) == 'gitlab') + { + $pipeline = json_decode($job->pipeline); + if(is_numeric($job->pipeline)) $job->pipeline = $this->loadModel('gitlab')->getProjectName($job->server, $job->pipeline); + if(isset($pipeline->reference)) $job->pipeline = $this->loadModel('gitlab')->getProjectName($job->server, $pipeline->project); + } + ?> createLink('job', 'view', "jobID={$job->id}", 'html', true), $job->name, '', "class='iframe' data-width='90%'") : $job->name;?> repoName;?> job->engineList, $job->engine);?> job->frameList, $job->frame);?> - engine) == 'gitlab') $job->pipeline = $this->loadModel('gitlab')->getObjectNameForJob($job->server, $job->pipeline);?> pipeline) . '@' . $job->jenkinsName;?> job->getTriggerConfig($job);?> @@ -67,8 +74,7 @@ engine) == 'jenkins') common::printIcon('job', 'exec', "jobID=$id", '', 'list', 'play', 'hiddenwin'); - if(strtolower($job->engine) == 'gitlab') common::printIcon('job', 'exec', "jobID=$id&showForm=yes", '', 'list', 'play', '', '', false, "data-toggle='modal' data-type='ajax'"); + common::printIcon('job', 'exec', "jobID=$id", '', 'list', 'play'); if(common::hasPriv('job', 'delete')) echo html::a($this->createLink('job', 'delete', "jobID=$id"), '', 'hiddenwin', "title='{$lang->job->delete}' class='btn'"); ?> diff --git a/module/job/view/create.html.php b/module/job/view/create.html.php index 99a6b1b54c..1822bcbe2d 100644 --- a/module/job/view/create.html.php +++ b/module/job/view/create.html.php @@ -48,7 +48,8 @@ job->repo; ?> - + + job->product; ?> @@ -95,7 +96,7 @@
- job->pipeline; ?> + job->pipeline;?> ''), '', "class='form-control chosen'"); ?>
diff --git a/module/job/view/edit.html.php b/module/job/view/edit.html.php index 82f235fe65..9c06425b7a 100644 --- a/module/job/view/edit.html.php +++ b/module/job/view/edit.html.php @@ -12,7 +12,7 @@ ?> - + triggerType);?> pipeline);?> job->dirChange);?> @@ -43,7 +43,8 @@ job->repo; ?> - repo, "class='form-control'"); ?> + repo, "class='chosen form-control'");?> + reference, "class='chosen form-control'");?> job->product;?> diff --git a/module/job/view/view.html.php b/module/job/view/view.html.php index cab49151d0..95a6d3cd61 100644 --- a/module/job/view/view.html.php +++ b/module/job/view/view.html.php @@ -55,7 +55,7 @@ job->server;?> - engine) == 'gitlab') $job->pipeline = $this->loadModel('gitlab')->getObjectNameForJob($job->server, $job->pipeline);?> + engine) == 'gitlab') $job->pipeline = $this->loadModel('gitlab')->getProjectName($job->server, $job->pipeline);?> pipeline) . '@' . $jenkins->name;?> diff --git a/module/mr/config.php b/module/mr/config.php new file mode 100644 index 0000000000..c64305044e --- /dev/null +++ b/module/mr/config.php @@ -0,0 +1,19 @@ +MR = new stdclass(); +$config->MR->create = new stdclass(); +$config->MR->create->requiredFields = 'gitlabID,sourceProject,sourceBranch,targetProject,targetBranch,title'; +$config->MR->create->skippedFields = 'projectID'; + +$config->MR->maps = new stdclass; +$config->MR->maps->sync = array(); +$config->MR->maps->sync['title'] = 'title|field|'; +$config->MR->maps->sync['description'] = 'description|field|'; +$config->MR->maps->sync['assignee'] = 'assignees|userPairs|id'; +$config->MR->maps->sync['reviewer'] = 'reviewers|userPairs|id'; +$config->MR->maps->sync['targetBranch'] = 'target_branch|field|'; +$config->MR->maps->sync['sourceBranch'] = 'source_branch|field|'; +$config->MR->maps->sync['sourceProject'] = 'source_project_id|field|'; +$config->MR->maps->sync['targetProject'] = 'target_project_id|field|'; +$config->MR->maps->sync['status'] = 'state|field|'; +$config->MR->maps->sync['mergeStatus'] = 'merge_status|field|'; + diff --git a/module/mr/control.php b/module/mr/control.php new file mode 100644 index 0000000000..8798eabf5d --- /dev/null +++ b/module/mr/control.php @@ -0,0 +1,287 @@ +app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + $MRList = $this->mr->getList($orderBy, $pager); + + /* Save current URI to session. */ + $this->session->set('mrList', $this->app->getURI(true), 'repo'); + + /* Sync GitLab MR to ZenTao Database. */ + $MRList = $this->mr->batchSyncMR($MRList); + + $this->view->title = $this->lang->mr->common . $this->lang->colon . $this->lang->mr->browse; + $this->view->MRList = $MRList; + $this->view->orderBy = $orderBy; + $this->view->objectID = $objectID; + $this->view->pager = $pager; + $this->display(); + } + + /** + * Create MR function. + * + * @access public + * @return void + */ + public function create() + { + if($_POST) + { + $result = $this->mr->create(); + return $this->send($result); + } + + $this->view->title = $this->lang->mr->create; + $this->view->gitlabHosts = $this->loadModel('gitlab')->getPairs(); + $this->display(); + } + + /** + * Edit MR function. + * + * @access public + * @return void + */ + public function edit($MRID) + { + if($_POST) + { + $result = $this->mr->update($MRID); + return $this->send($result); + } + + $MR = $this->mr->getByID($MRID); + + $branchList = $this->loadModel('gitlab')->getBranches($MR->gitlabID, $MR->targetProject); + $targetBranchList = array(); + foreach($branchList as $branch) $targetBranchList[$branch] = $branch; + + /* Fetch user list both in Zentao and current GitLab project. */ + $bindedUsers = $this->gitlab->getUserIdRealnamePairs($MR->gitlabID); + $rawProjectUsers = $this->gitlab->apiGetProjectUsers($MR->gitlabID, $MR->targetProject); + $users = array(); + foreach($rawProjectUsers as $rawProjectUser) + { + if(!empty($bindedUsers[$rawProjectUser->id])) $users[$rawProjectUser->id] = $bindedUsers[$rawProjectUser->id]; + } + + $gitlabUsers = $this->gitlab->getUserAccountIdPairs($MR->gitlabID); + + $this->view->title = $this->lang->mr->edit; + $this->view->MR = $MR; + $this->view->targetBranchList = $targetBranchList; + $this->view->users = array("" => "") + $users; + $this->view->assignee = zget($gitlabUsers, $MR->assignee, ''); + $this->view->reviewer = zget($gitlabUsers, $MR->reviewer, ''); + + $this->display(); + } + + /** + * Delete a MR. + * + * @param int $id + * @access public + * @return void + */ + public function delete($id, $confim = 'no') + { + if($confim != 'yes') die(js::confirm($this->lang->gitlab->confirmDelete, inlink('delete', "id=$id&confirm=yes"))); + + $MR = $this->mr->getByID($id); + + $this->dao->delete()->from(TABLE_MR)->where('id')->eq($id)->exec(); + $this->mr->apiDeleteMR($MR->gitlabID, $MR->sourceProject, $MR->mriid); + + die(js::locate(inlink('browse'), 'parent')); + } + + /** + * View a MR. + * + * @access public + * @return void + */ + public function view($id) + { + $MR = $this->mr->getByID($id); + if(isset($MR->gitlabID)) $rawMR = $this->mr->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid); + + $this->view->title = $this->lang->mr->view; + $this->view->MR = $MR; + $this->view->rawMR = isset($rawMR) ? $rawMR : false; + + $this->loadModel('gitlab'); + $sourceProject = $this->gitlab->apiGetSingleProject($MR->gitlabID, $MR->sourceProject); + $targetProject = $this->gitlab->apiGetSingleProject($MR->gitlabID, $MR->targetProject); + $sourceBranch = $this->gitlab->apiGetSingleBranch($MR->gitlabID, $MR->sourceProject, $MR->sourceBranch); + $targetBranch = $this->gitlab->apiGetSingleBranch($MR->gitlabID, $MR->targetProject, $MR->targetBranch); + + $this->view->sourceProjectName = $sourceProject->name_with_namespace; + $this->view->targetProjectName = $targetProject->name_with_namespace; + $this->view->sourceProjectURL = $sourceBranch ->web_url; + $this->view->targetProjectURL = $targetBranch ->web_url; + + /* Those variables are used to render $lang->mr->commandDocument. */ + $this->view->httpRepoURL = $sourceProject->http_url_to_repo; + $this->view->branchPath = $sourceProject->path_with_namespace . '-' . $rawMR->source_branch; + + $this->display(); + } + + /** + * Crontab sync MR from GitLab API to Zentao database, default time 5 minutes to execute once. + * + * @access public + * @return void + */ + public function syncMR() + { + $MRList = $this->mr->getList(); + $this->mr->batchSyncMR($MRList); + + if(dao::isError()) + { + echo json_encode(dao::getError()); + return true; + } + + echo 'success'; + } + + /** + * Accept a MR. + * + * @param int $MRID + * @access public + * @return void + */ + public function accept($MRID) + { + $MR = $this->mr->getByID($MRID); + + /* Accept MR by using the mapped user in GitLab. */ + $sudoUser = $this->mr->getSudoUsername($MR->gitlabID, $MR->targetProject); + + if(isset($MR->gitlabID)) + { + if(!empty($sudoUser)) $rawMR = $this->mr->apiAcceptMR($MR->gitlabID, $MR->targetProject, $MR->mriid, $sudo = $sudoUser); + if(empty($sudoUser)) $rawMR = $this->mr->apiAcceptMR($MR->gitlabID, $MR->targetProject, $MR->mriid); + } + if(isset($rawMR->state) and $rawMR->state == 'merged') + { + /* Force reload when locate to the url. */ + $random = uniqid(); + return $this->send(array('result' => 'success', 'message' => $this->lang->mr->mergeSuccess, 'locate' => helper::createLink('mr', 'browse', "random={$random}"))); + } + + /* The type of variable `$rawMR->message` is string. This is different with apiCreateMR. */ + if(isset($rawMR->message)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->mr->apiError->sudo, $rawMR->message), 'locate' => helper::createLink('mr', 'view', "mr={$MRID}"))); + + return $this->send(array('result' => 'fail', 'message' => $this->lang->mr->mergeFailed, 'locate' => helper::createLink('mr', 'view', "mr={$MRID}"))); + } + + /** + * AJAX: Get MR target projects. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return void + */ + public function ajaxGetMRTargetProjects($gitlabID, $projectID) + { + $this->loadModel('gitlab'); + + /* First step: get forks. Only get first level forks(not recursively). */ + $projects = $this->gitlab->apiGetForks($gitlabID, $projectID); + + /* Second step: get project itself. */ + $projects[] = $this->gitlab->apiGetSingleProject($gitlabID, $projectID); + + /* Last step: find its upstream recursively. */ + $project = $this->gitlab->apiGetUpstream($gitlabID, $projectID); + if(!empty($project)) $projects[] = $project; + + while(!empty($project) and isset($project->id)) + { + $project = $this->gitlab->apiGetUpstream($gitlabID, $project->id); + if(empty($project)) break; + $projects[] = $project; + } + + if(!$projects) return $this->send(array('message' => array())); + + $options = ""; + foreach($projects as $project) + { + $options .= ""; + } + + $this->send($options); + } + + /** + * View diff between MR source and target branches. + * + * @param int $MRID + * @access public + * @return void + */ + public function diff($MRID) + { + $MR = $this->mr->getByID($MRID); + $diffs = $this->mr->getDiffs($MR); + $arrange = $this->cookie->arrange ? $this->cookie->arrange : 'inline'; + + if($this->server->request_method == 'POST') + { + if($this->post->arrange) + { + $arrange = $this->post->arrange; + setcookie('arrange', $arrange); + } + if($this->post->encoding) $encoding = $this->post->encoding; + } + + if($arrange == 'appose') + { + foreach($diffs as $diffFile) + { + if(empty($diffFile->contents)) continue; + foreach($diffFile->contents as $content) + { + $old = array(); + $new = array(); + foreach($content->lines as $line) + { + if($line->type != 'new') $old[$line->oldlc] = $line->line; + if($line->type != 'old') $new[$line->newlc] = $line->line; + } + $content->old = $old; + $content->new = $new; + } + } + } + + $this->view->title = $this->lang->mr->viewDiff; + $this->view->diffs = $diffs; + $this->view->arrange = $arrange; + $this->display(); + } +} diff --git a/module/mr/css/diff.css b/module/mr/css/diff.css new file mode 100644 index 0000000000..5db97ecce4 --- /dev/null +++ b/module/mr/css/diff.css @@ -0,0 +1,22 @@ +body {padding-bottom: 0px;} +.w-code {width: 48%; word-break: break-all;} +td.code {color: #484848; padding: 0px 3px; white-space: pre-wrap;} +.none {background: #EAF2F5;} +table.diff {margin-bottom: 0px;} +.diff caption {border: 1px solid #e4e4e4; background: #edf3fe; margin: 0; padding: 6px 2px 6px 10px; text-align: left; font-weight: bold; font-size: 13px;} +.diff th, .diff td {border: none;} +.diff th {padding-top: 2px; padding-bottom: 2px;} +.diff .line-new, .diff .line-new {background: #CFC;} +.diff .line-old, .diff .line-old {background: #FCC;} +.diff .line-all, .diff .line-all {background: #FFF;} +.diff .w-num {width: 25px; border-right: 1px solid #E4E4E4; color: #999; border-left: 1px solid #E4E4E4; color: #999; font-weight: normal;} + +.repoCode .diff tr .comment-btn .icon-wrapper {left: -23px;} +.repoCode .diff tr.over td.line-all, .repoCode .diff tr.over td.line-all {background: #f8eec7;} +.repoCode .diff tr.over td.line-new, .repoCode .diff tr.over td.line-new {background: #8eff8e;} +.repoCode .diff tr.over td.line-old, .repoCode .diff tr.over td.line-old {background: #f6b2b2;} + +.repoCode form > .btn {margin-right: 10px;} +.label-exchange {background-color: #566F7C; cursor: pointer;} +.label-exchange i {padding: 0;} +.btn-download {border-right: none;} diff --git a/module/mr/js/create.js b/module/mr/js/create.js new file mode 100644 index 0000000000..2194a15d88 --- /dev/null +++ b/module/mr/js/create.js @@ -0,0 +1,56 @@ +$(function() +{ + $('#gitlabID').change(function() + { + gitlabID = $('#gitlabID').val(); + if(gitlabID == '') return false; + + url = createLink('repo', 'ajaxgetgitlabprojects', "gitlabID=" + gitlabID); + $.get(url, function(response) + { + $('#sourceProject').html('').append(response); + $('#sourceProject').chosen().trigger("chosen:updated");; + }); + }); + + $('#sourceProject,#targetProject').change(function() + { + sourceProject = $(this).val(); + var branchSelect = $(this).parents('td').find('select[name*=Branch]'); + branchUrl = createLink('gitlab', 'ajaxgetprojectbranches', "gitlabID=" + gitlabID + "&projectID=" + sourceProject); + $.get(branchUrl, function(response) + { + branchSelect.html('').append(response); + branchSelect.chosen().trigger("chosen:updated");; + }); + + }); + + $('#sourceProject').change(function() + { + sourceProject = $(this).val(); + projectUrl = createLink('mr', 'ajaxGetMRTargetProjects', "gitlabID=" + gitlabID + "&projectID=" + sourceProject); + $.get(projectUrl, function(response) + { + $('#targetProject').html('').append(response); + $('#targetProject').chosen().trigger("chosen:updated");; + }); + }); + + $('#targetProject').change(function() + { + targetProject = $(this).val(); + var assignee = $("#assignee").parents('td').find('select[name*=assignee]'); + var reviewer = $("#reviewer").parents('td').find('select[name*=reviewer]'); + usersUrl = createLink('gitlab', 'ajaxgetmruserpairs', "gitlabID=" + gitlabID + "&projectID=" + targetProject); + $.get(usersUrl, function(response) + { + assignee.html('').append(response); + assignee.chosen().trigger("chosen:updated");; + reviewer.html('').append(response); + reviewer.chosen().trigger("chosen:updated");; + }); + }); + + +}); diff --git a/module/mr/js/diff.js b/module/mr/js/diff.js new file mode 100644 index 0000000000..e44d3a65cb --- /dev/null +++ b/module/mr/js/diff.js @@ -0,0 +1,12 @@ +$(document).ready(function() +{ + $("#inline").click(function(){$('#arrange').val('inline');this.form.submit();}); + $("#appose").click(function(){$('#arrange').val('appose');this.form.submit();}); + $(".label-exchange").click(function(){ $('#exchange').submit();}); +}); + +function changeEncoding(encoding) +{ + $('#encoding').val(encoding); + $('#encoding').parents('form').submit(); +} diff --git a/module/mr/js/view.js b/module/mr/js/view.js new file mode 100644 index 0000000000..f75fc67687 --- /dev/null +++ b/module/mr/js/view.js @@ -0,0 +1,17 @@ +$(document).ready(function() + { + $('#mergeButton').click(function() + { + link = $(this).attr('href'); + $.getJSON(link, function(response) + { + if(response.result == 'success') + { + $.zui.messager.success(response.message); + setTimeout(function(){ location.href=response.locate }, 2500); + } + if(response.result == 'fail') $.zui.messager.danger(response.message); + }); + return false; + }); + }); diff --git a/module/mr/lang/en.php b/module/mr/lang/en.php new file mode 100644 index 0000000000..61e7e883d2 --- /dev/null +++ b/module/mr/lang/en.php @@ -0,0 +1,111 @@ +mr->common = "Merge Request"; +$lang->mr->create = "create"; +$lang->mr->browse = "browse"; +$lang->mr->list = "list"; +$lang->mr->edit = "edit"; +$lang->mr->delete = "delete"; +$lang->mr->view = "view"; +$lang->mr->source = 'source'; +$lang->mr->target = 'target'; +$lang->mr->viewDiff = 'view diff'; +$lang->mr->viewInGitlab = 'view in GitLab'; + +$lang->mr->id = 'ID'; +$lang->mr->mriid = "raw MR ID"; +$lang->mr->name = 'Name'; +$lang->mr->status = 'Status'; +$lang->mr->author = 'Author'; +$lang->mr->assignee = 'Assignee'; +$lang->mr->reviewer = 'Reviewer'; +$lang->mr->mergeStatus = 'Merge status'; +$lang->mr->commits = 'commits'; +$lang->mr->changes = 'changes'; + +$lang->mr->statusList = array(); +$lang->mr->statusList['opened'] = 'opened'; +$lang->mr->statusList['closed'] = 'closed'; +$lang->mr->statusList['merged'] = 'merged'; + +$lang->mr->mergeStatusList = array(); +$lang->mr->mergeStatusList['checking'] = 'checking'; +$lang->mr->mergeStatusList['can_be_merged'] = 'can be merged'; +$lang->mr->mergeStatusList['cannot_be_merged'] = 'cannot be merged'; + +$lang->mr->description = 'Description'; +$lang->mr->confirmDelete = 'Are you sure to delete this merge request?'; +$lang->mr->sourceProject = 'Source project'; +$lang->mr->sourceBranch = 'Source branch'; +$lang->mr->targetProject = 'Target project'; +$lang->mr->targetBranch = 'Target branch'; + +$lang->mr->usersTips = 'Tip: If you cannot choose the assignee and reviewer, please go to the GitLab page to bind the user first.'; +$lang->mr->notFound = "Merge Request does not exist!"; + +$lang->mr->apiError = new stdclass; +$lang->mr->apiError->createMR = "Failed to create a merge request through API. Reason: %s"; +$lang->mr->apiError->sudo = "Unable to operate with the GitLab account bound to the current user. Reason: %s"; + +$lang->mr->createFailedFromAPI = "Failed to create Merge Request."; +$lang->mr->accessGitlabFailed = "Unable to connect to the GitLab server."; + +$lang->mr->from = "from"; +$lang->mr->to = "to"; +$lang->mr->at = "at"; + +$lang->mr->pipeline = "Pipeline"; +$lang->mr->pipelineSuccess = "Success"; +$lang->mr->pipelineFailed = "Failed"; +$lang->mr->pipelineCancled = "Canceled"; +$lang->mr->pipelineUnknown = "Unknown"; + +$lang->mr->pipelineStatus = array(); +$lang->mr->pipelineStatus['success'] = "success"; +$lang->mr->pipelineStatus['failed'] = "failed"; +$lang->mr->pipelineStatus['canceled'] = "canceled"; + +$lang->mr->MRHasConflicts = "Merge Request has a conflict"; +$lang->mr->hasConflicts = "There are merge conflicts or wait for push"; +$lang->mr->hasNoConflict = "Can merge"; +$lang->mr->mergeByManual = "This merge request can be merged manually, please refer to"; +$lang->mr->commandLine = "Merge Request command"; +$lang->mr->acceptMR = "Accept Merge request "; +$lang->mr->mergeFailed = "Unable to merge request, please check the merge request status"; +$lang->mr->mergeSuccess = "Merge Request Successfully"; + +/** + * Merge Command Document. + * + * %s source_project::http_url_to_repo + * %s mr::source_branch + * %s source_project::path_with_namespace . '-' . mr::source_branch + * %s mr::target_branch + * %s source_project::path_with_namespace . '-' . mr::source_branch + * %s mr::target_branch + */ +$lang->mr->commandDocument = <<< EOD +
Check out, review and merge locally
+
+

Note: This merge request status will be changed after you merge locally and you will need to delete this merge request or submit new code.

+

+ step 1. Fetch and check out the branch for this merge request +

+    git fetch "%s" %s
+    git checkout -b "%s" FETCH_HEAD
+

+

+ step 2. Review the changes locally +

+

+ step 3. Merge the branch and fix any conflicts that come up +

+    git fetch origin
+    git checkout "%s"
+    git merge --no-ff "%s"
+

+

+ step 4. Push the result of the merge to GitLab +

 git push origin "%s" 
+

+
+EOD; diff --git a/module/mr/lang/zh-cn.php b/module/mr/lang/zh-cn.php new file mode 100644 index 0000000000..1f88c3e478 --- /dev/null +++ b/module/mr/lang/zh-cn.php @@ -0,0 +1,112 @@ +mr->common = "合并请求"; +$lang->mr->create = "创建{$lang->mr->common}"; +$lang->mr->browse = "浏览{$lang->mr->common}"; +$lang->mr->list = $lang->mr->browse; +$lang->mr->edit = "编辑{$lang->mr->common}"; +$lang->mr->delete = "删除{$lang->mr->common}"; +$lang->mr->view = "{$lang->mr->common}详情"; +$lang->mr->source = '源项目分支'; +$lang->mr->target = '目标项目分支'; +$lang->mr->viewDiff = '比对代码'; +$lang->mr->viewInGitlab = '在GitLab查看'; + +$lang->mr->id = 'ID'; +$lang->mr->mriid = "MR原始ID"; +$lang->mr->name = '名称'; +$lang->mr->status = '状态'; +$lang->mr->author = '创建人'; +$lang->mr->assignee = '指派给'; +$lang->mr->reviewer = '评审人'; +$lang->mr->mergeStatus = '是否可合并'; +$lang->mr->commits = '提交数'; +$lang->mr->changes = '更改数'; +$lang->mr->settings = '合并代码设置'; + +$lang->mr->statusList = array(); +$lang->mr->statusList['opened'] = '开放中'; +$lang->mr->statusList['closed'] = '已关闭'; +$lang->mr->statusList['merged'] = '已合并'; + +$lang->mr->mergeStatusList = array(); +$lang->mr->mergeStatusList['checking'] = '检查中'; +$lang->mr->mergeStatusList['can_be_merged'] = '可合并'; +$lang->mr->mergeStatusList['cannot_be_merged'] = '不可自动合并'; + +$lang->mr->description = '描述'; +$lang->mr->confirmDelete = '确认删除该合并请求吗?'; +$lang->mr->sourceProject = '源项目'; +$lang->mr->sourceBranch = '源分支'; +$lang->mr->targetProject = '目标项目'; +$lang->mr->targetBranch = '目标分支'; + +$lang->mr->usersTips = '提示:如果无法选择指派人和评审人,请先前往GitLab页面绑定用户。'; +$lang->mr->notFound = "此{$lang->mr->common}不存在。"; + +$lang->mr->apiError = new stdclass; +$lang->mr->apiError->createMR = "通过API创建合并请求失败,失败原因:%s"; +$lang->mr->apiError->sudo = "无法以当前用户绑定的GitLab账户进行操作,失败原因:%s"; + +$lang->mr->createFailedFromAPI = "创建合并请求失败。"; +$lang->mr->accessGitlabFailed = "当前无法连接到GitLab服务器。"; + +$lang->mr->from = "从"; +$lang->mr->to = "合并到"; +$lang->mr->at = "于"; + +$lang->mr->pipeline = "流水线"; +$lang->mr->pipelineSuccess = "已通过"; +$lang->mr->pipelineFailed = "未通过"; +$lang->mr->pipelineCancled = "已取消"; +$lang->mr->pipelineUnknown = "未知"; + +$lang->mr->pipelineStatus = array(); +$lang->mr->pipelineStatus['success'] = "已通过"; +$lang->mr->pipelineStatus['failed'] = "未通过"; +$lang->mr->pipelineStatus['canceled'] = "已取消"; + +$lang->mr->MRHasConflicts = "是否存在冲突"; +$lang->mr->hasConflicts = "存在冲突或等待提交"; +$lang->mr->hasNoConflict = "可以合并"; +$lang->mr->mergeByManual = "此合并请求可以手动合并,请使用以下"; +$lang->mr->commandLine = "合并命令"; +$lang->mr->acceptMR = "合并"; +$lang->mr->mergeFailed = "无法合并,请核对合并请求状态"; +$lang->mr->mergeSuccess = "已成功合并"; + +/** + * Merge Command Document. + * + * %s source_project::http_url_to_repo + * %s mr::source_branch + * %s source_project::path_with_namespace . '-' . mr::source_branch + * %s mr::target_branch + * %s source_project::path_with_namespace . '-' . mr::source_branch + * %s mr::target_branch + */ +$lang->mr->commandDocument = <<< EOD +
在本地检出、审核和手动合并
+
+

注意:您在本地合并后此合并请求将变为不可合并状态,需要删除此合并请求或者提交新的代码。

+

+ 第 1 步. 获取并查看此合并请求的分支 +

+    git fetch "%s" %s
+    git checkout -b "%s" FETCH_HEAD
+

+

+ 第 2 步. 在本地查看更改 +

+

+ 第 3 步. 合并分支并解决出现的任何冲突 +

+    git fetch origin
+    git checkout "%s"
+    git merge --no-ff "%s"
+

+

+ 第 4 步. 将合并结果推送到GitLab +

 git push origin "%s" 
+

+
+EOD; diff --git a/module/mr/model.php b/module/mr/model.php new file mode 100644 index 0000000000..fd4f449500 --- /dev/null +++ b/module/mr/model.php @@ -0,0 +1,490 @@ + + * @package mr + * @version $Id$ + * @link http://www.zentao.net + */ +class mrModel extends model +{ + /** + * The construct method, to do some auto things. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('gitlab'); + } + + /** + * Get a MR by id. + * + * @param int $id + * @access public + * @return object + */ + public function getByID($id) + { + return $this->dao->findByID($id)->from(TABLE_MR)->fetch(); + } + + /** + * Get MR list of gitlab project. + * + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getList($orderBy = 'id_desc', $pager = null) + { + $MRList = $this->dao->select('*') + ->from(TABLE_MR) + ->where('deleted')->eq('0') + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + + return $MRList; + } + + /** + * Get gitlab pairs. + * + * @access public + * @return array + */ + public function getPairs($repoID) + { + $MR = $this->dao->select('id,title') + ->from(TABLE_MR) + ->where('deleted')->eq('0') + ->AndWhere('repoID')->eq($repoID) + ->orderBy('id')->fetchPairs('id', 'title'); + return array('' => '') + $MR; + } + + /** + * Create MR function. + * + * @access public + * @return int|bool|object + */ + public function create() + { + $MR = fixer::input('post') + ->add('createdBy', $this->app->user->account) + ->add('createdDate', helper::now()) + ->get(); + + $this->dao->insert(TABLE_MR)->data($MR, $this->config->MR->create->skippedFields) + ->batchCheck($this->config->MR->create->requiredFields, 'notempty') + ->autoCheck() + ->exec(); + if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError()); + + $MRID = $this->dao->lastInsertId(); + + $MRObject = new stdclass; + $MRObject->target_project_id = $MR->targetProject; + $MRObject->source_branch = $MR->sourceBranch; + $MRObject->target_branch = $MR->targetBranch; + $MRObject->title = $MR->title; + $MRObject->description = $MR->description; + $MRObject->assignee_ids = $MR->assignee; + $MRObject->reviewer_ids = $MR->reviewer; + + $rawMR = $this->apiCreateMR($this->post->gitlabID, $this->post->sourceProject, $MRObject); + + /** + * Another open merge request already exists for this source branch. + * The type of variable `$rawMR->message` is array. + */ + if(isset($rawMR->message) and !isset($rawMR->iid)) + { + $this->dao->delete()->from(TABLE_MR)->where('id')->eq($MRID)->exec(); + return array('result' => 'fail', 'message' => sprintf($this->lang->mr->apiError->createMR, $rawMR->message[0])); + } + + /* Create MR failed. */ + if(!isset($rawMR->iid)) + { + $this->dao->delete()->from(TABLE_MR)->where('id')->eq($MRID)->exec(); + return array('result' => 'fail', 'message' => $this->lang->mr->createFailedFromAPI); + } + + $newMR = new stdclass; + $newMR->mriid = $rawMR->iid; + $newMR->status = $rawMR->state; + $newMR->mergeStatus = $rawMR->merge_status; + + /* Change gitlab user ID to zentao account. */ + $gitlabUsers = $this->gitlab->getUserIdAccountPairs($MR->gitlabID); + $newMR->assignee = zget($gitlabUsers, $MR->assignee, ''); + $newMR->reviewer = zget($gitlabUsers, $MR->reviewer, ''); + + /* Update MR in Zentao database. */ + $this->dao->update(TABLE_MR)->data($newMR) + ->where('id')->eq($MRID) + ->autoCheck() + ->exec(); + if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError()); + return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => helper::createLink('mr', 'browse')); + } + + /** + * Edit MR function. + * + * @access public + * @return void + */ + public function update($MRID) + { + $MR = fixer::input('post') + ->setDefault('editedBy', $this->app->user->account) + ->setDefault('editedDate', helper::now()) + ->get(); + + /* Update MR in GitLab. */ + $newMR = new stdclass; + $newMR->title = $MR->title; + $newMR->description = $MR->description; + $newMR->assignee_ids = $MR->assignee; + $newMR->reviewer_ids = $MR->reviewer; + $newMR->target_branch = $MR->targetBranch; + + $oldMR = $this->getByID($MRID); + + /* Known issue: `reviewer_ids` takes no effect. */ + $rawMR = $this->apiUpdateMR($oldMR->gitlabID, $oldMR->targetProject, $oldMR->mriid, $newMR); + + /* Change gitlab user ID to zentao account. */ + $gitlabUsers = $this->gitlab->getUserIdAccountPairs($oldMR->gitlabID); + $MR->assignee = zget($gitlabUsers, $MR->assignee, ''); + $MR->reviewer = zget($gitlabUsers, $MR->reviewer, ''); + + /* Update MR in Zentao database. */ + $this->dao->update(TABLE_MR)->data($MR) + ->where('id')->eq($MRID) + ->autoCheck() + ->exec(); + $MR = $this->getByID($MRID); + + if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError()); + return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => helper::createLink('mr', 'browse')); + } + + /** + * sync MR from GitLab API to Zentao database. + * + * @param object $MR + * @access public + * @return void + */ + public function apiSyncMR($MR) + { + $rawMR = $this->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid); + /* Sync MR in ZenTao database whatever status of MR in GitLab. */ + if(isset($rawMR->iid)) + { + $map = $this->config->MR->maps->sync; + $gitlabUsers = $this->gitlab->getUserIdAccountPairs($MR->gitlabID); + + $newMR = new stdclass; + foreach($map as $syncField => $config) + { + $value = ''; + list($field, $optionType, $options) = explode('|', $config); + + if($optionType == 'field') $value = $rawMR->$field; + if($optionType == 'userPairs') + { + $gitlabUserID = ''; + if(isset($rawMR->$field[0])) + { + $gitlabUserID = $rawMR->$field[0]->$options; + } + $value = zget($gitlabUsers, $gitlabUserID, ''); + } + + if($value) $newMR->$syncField = $value; + } + + /* Update MR in Zentao database. */ + $this->dao->update(TABLE_MR)->data($newMR) + ->where('id')->eq($MR->id) + ->exec(); + } + return $this->dao->findByID($MR->id)->from(TABLE_MR)->fetch(); + } + + /** + * Batch Sync GitLab MR Database. + * + * @param object $MRList + * @access public + * @return void + */ + public function batchSyncMR($MRList) + { + if(!empty($MRList)) foreach($MRList as $key => $MR) + { + if($MR->status != 'opened') continue; + $rawMR = $this->apiGetSingleMR($MR->gitlabID, $MR->targetProject, $MR->mriid); + if(isset($rawMR->iid)) + { + /* create gitlab mr todo to zentao todo */ + $this->batchSyncTodo($MR->gitlabID, $MR->targetProject); + + $map = $this->config->MR->maps->sync; + $gitlabUsers = $this->gitlab->getUserIdAccountPairs($MR->gitlabID); + + $newMR = new stdclass; + + foreach($map as $syncField => $config) + { + $value = ''; + list($field, $optionType, $options) = explode('|', $config); + + if($optionType == 'field') $value = $rawMR->$field; + if($optionType == 'userPairs') + { + $gitlabUserID = ''; + if(isset($rawMR->$field[0])) + { + $gitlabUserID = $rawMR->$field[0]->$options; + } + $value = zget($gitlabUsers, $gitlabUserID, ''); + } + + if($value) $newMR->$syncField = $value; + } + /* Update MR in Zentao database. */ + $this->dao->update(TABLE_MR)->data($newMR) + ->where('id')->eq($MR->id) + ->exec(); + + /* Refetch MR in Zentao database. */ + $MR = $this->dao->findByID($MR->id)->from(TABLE_MR)->fetch(); + $MRList[$key] = $MR; + } + } + + return $MRList; + } + + /** + * Sync GitLab Todo to ZenTao Todo. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return void + */ + public function batchSyncTodo($gitlabID, $projectID) + { + $todoList = $this->loadModel('gitlab')->apiTodoList($gitlabID, $projectID); + + if(!empty($todoList)) + { + foreach($todoList as $do) + { + $todoDesc = $this->dao->select('*') + ->from(TABLE_TODO) + ->where('idvalue')->eq($do->id) + ->fetch(); + if(empty($todoDesc)) + { + $todo = new stdClass; + $todo->account = $this->app->user->account; + $todo->assignedTo = $this->app->user->account; + $todo->assignedBy = $this->app->user->account; + $todo->date = $do->target->created_at; + $todo->assignedDate = $do->target->created_at; + $todo->begin = $do->target->created_at; + $todo->type = 'mrapprove'; + $todo->idvalue = $do->id; + $todo->pri = 1; + $todo->name = $do->target->title; + $todo->desc = $do->target->description . "
" . '' . $this->todoDescriptionLink($gitlabID, $projectID) .''; + $todo->finishedBy = $this->app->user->account; + $this->dao->insert(TABLE_TODO)->data($todo)->exec(); + } + } + } + } + + /** + * Get a list of to-do items. + * + * @param int $gitlabID + * @param int $projectID + * @access public + * @return object + */ + public function todoDescriptionLink($gitlabID, $projectID) + { + $gitlab = $this->loadModel('gitlab')->getByID($gitlabID); + if(!$gitlab) return ''; + return rtrim($gitlab->url, '/')."/dashboard/todos?project_id=$projectID&type=MergeRequest"; + } + + + /** + * Create MR by API. + * + * @docs https://docs.gitlab.com/ee/api/merge_requests.html#create-mr + * @param int $gitlabID + * @param int $projectID + * @param object $MR + * @access public + * @return object + */ + public function apiCreateMR($gitlabID, $projectID, $MR) + { + $url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests"); + return json_decode(commonModel::http($url, $MR)); + } + + /** + * Get MR list by API. + * + * @docs https://docs.gitlab.com/ee/api/merge_requests.html#list-project-merge-requests + * @param int $gitlabID + * @param int $projectID + * @access public + * @return object + */ + public function apiGetMRList($gitlabID, $projectID) + { + $url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests"); + return json_decode(commonModel::http($url)); + } + + /** + * Get single MR by API. + * + * @docs https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr + * @param int $gitlabID + * @param int $projectID targetProject + * @param int $MRID + * @access public + * @return object + */ + public function apiGetSingleMR($gitlabID, $projectID, $MRID) + { + $url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID"); + return json_decode(commonModel::http($url)); + } + + /** + * Update MR by API. + * + * @docs https://docs.gitlab.com/ee/api/merge_requests.html#update-mr + * @param int $gitlabID + * @param int $projectID + * @param int $MRID + * @param object $MR + * @access public + * @return object + */ + public function apiUpdateMR($gitlabID, $projectID, $MRID, $MR) + { + $url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID"); + return json_decode(commonModel::http($url, $MR, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT'))); + } + + /** + * Delete MR by API. + * + * @docs https://docs.gitlab.com/ee/api/merge_requests.html#delete-a-merge-request + * @param int $gitlabID + * @param int $projectID + * @param int $MRID + * @access public + * @return object + */ + public function apiDeleteMR($gitlabID, $projectID, $MRID) + { + $url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID"); + return json_decode(commonModel::http($url, null, array(CURLOPT_CUSTOMREQUEST => 'DELETE'))); + } + + /** + * Accept MR by API. + * + * @docs https://docs.gitlab.com/ee/api/merge_requests.html#accept-mr + * @param int $gitlabID + * @param int $projectID + * @param int $MRID + * @param string $sudo + * @access public + * @return object + */ + public function apiAcceptMR($gitlabID, $projectID, $MRID, $sudo = "") + { + $url = sprintf($this->gitlab->getApiRoot($gitlabID), "/projects/$projectID/merge_requests/$MRID/merge"); + if($sudo != "") return json_decode(commonModel::http($url, $data = null, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT'), $headers = array("sudo: {$sudo}"))); + return json_decode(commonModel::http($url, $data = null, $options = array(CURLOPT_CUSTOMREQUEST => 'PUT'))); + } + + /** + * Get MR diff versions by API. + * + * @docs https://docs.gitlab.com/ee/api/merge_requests.html#get-mr-diff-versions + * @param object $MR + * @access public + * @return object + */ + public function getDiffs($MR) + { + $gitlab = $this->gitlab->getByID($MR->gitlabID); + + $this->loadModel('repo'); + $repo = new stdclass; + $repo->SCM = 'GitLab'; + $repo->gitlab = $gitlab->id; + $repo->project = $MR->targetProject; + $repo->path = sprintf($this->config->repo->gitlab->apiPath, $gitlab->url, $MR->targetProject); + $repo->client = $gitlab->url; + $repo->password = $gitlab->token; + + $scm = $this->app->loadClass('scm'); + $scm->setEngine($repo); + + $encoding = empty($encoding) ? $repo->encoding : $encoding; + $encoding = strtolower(str_replace('_', '-', $encoding)); + return $scm->diff('', $MR->sourceBranch, $MR->targetBranch, $parse = true, $MR->sourceProject); + } + + /** + * Get sudo user ID in both GitLab and Project. + * Note: sudo parameter in GitLab API can be user ID or username. + * @param int $gitlabID + * @param int $projectID + * @access public + * @return int|string + */ + public function getSudoUsername($gitlabID, $projectID) + { + $zentaoUser = $this->app->user->account; + + /* Fetch user list both in Zentao and current GitLab project. */ + $bindedUsers = $this->gitlab->getUserAccountIdPairs($gitlabID); + $rawProjectUsers = $this->gitlab->apiGetProjectUsers($gitlabID, $projectID); + $users = array(); + foreach($rawProjectUsers as $rawProjectUser) + { + if(!empty($bindedUsers[$rawProjectUser->username])) $users[$rawProjectUser->username] = $bindedUsers[$rawProjectUser->username]; + } + if(!empty($users[$zentaoUser])) return $users[$zentaoUser]; + return ""; + } +} diff --git a/module/mr/view/browse.html.php b/module/mr/view/browse.html.php new file mode 100644 index 0000000000..45cc7ecb4c --- /dev/null +++ b/module/mr/view/browse.html.php @@ -0,0 +1,70 @@ + + + +
+ +
+

+ noData . $lang->mr->common;?> + + createLink('mr', 'create'), " " . $lang->mr->create, '', "class='btn btn-info'");?> + +

+
+ +
+ + + + recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}"; ?> + + + + + + + + + + + + + + + + + + + + + + + + +
mr->id); ?>mr->name); ?>mr->sourceProject); ?>mr->sourceBranch); ?>mr->targetProject); ?>mr->targetBranch); ?>mr->mergeStatus); ?>actions; ?>
id; ?>title; ?>loadModel('gitlab')->apiGetSingleProject($MR->gitlabID, $MR->sourceProject)->name_with_namespace; ?>sourceBranch;?>loadModel('gitlab')->apiGetSingleProject($MR->gitlabID, $MR->targetProject)->name_with_namespace; ?>targetBranch;?>status == 'merged') ? zget($lang->mr->statusList, $MR->status) : zget($lang->mr->mergeStatusList, $MR->mergeStatus); ?> + id}", '', '', "title='{$lang->mr->view}' class='btn btn-info'"); + common::printLink('mr', 'edit', "mr={$MR->id}", '', '', "title='{$lang->mr->edit}' class='btn btn-info'"); + /* Function diff is not ready yet. so comment it. */ + //common::printLink('mr', 'diff', "mr={$MR->id}", '', '', "title='{$lang->mr->viewDiff}' class='btn btn-info'"); + common::printLink('mr', 'delete', "mr={$MR->id}", '', '', "title='{$lang->mr->delete}' class='btn btn-info'"); + ?> +
+ +
+ +
+ diff --git a/module/mr/view/create.html.php b/module/mr/view/create.html.php new file mode 100644 index 0000000000..53489e179a --- /dev/null +++ b/module/mr/view/create.html.php @@ -0,0 +1,76 @@ + + +
+
+
+
+

mr->create;?>

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
gitlab->common;?>
mr->sourceProject;?> +
+ + mr->sourceBranch ?> + +
+
mr->targetProject;?> +
+ + mr->targetBranch ?> + +
+
mr->name;?>
mr->description; ?>
mr->assignee;?>
mr->reviewer;?>
mr->usersTips;?>
+ + goback, '', 'class="btn btn-wide"');?> +
+
+
+
+
+ diff --git a/module/mr/view/diff.html.php b/module/mr/view/diff.html.php new file mode 100644 index 0000000000..a4ff5ab6f9 --- /dev/null +++ b/module/mr/view/diff.html.php @@ -0,0 +1,147 @@ + + + + + + +
+
+
+
+ repo->viewDiffList['inline'], "id='inline'", $arrange == 'inline' ? 'active btn btn-sm' : 'btn btn-sm')?> + repo->viewDiffList['appose'], "id='appose'", $arrange == 'appose' ? 'active btn btn-sm' : 'btn btn-sm')?> +
+
+
+
+ repo->encodingList, $encoding, $lang->repo->encoding) . "", "data-toggle='dropdown'", 'btn dropdown-toggle btn-sm')?> + +
+
+
+ +
+
+ +
+ + + contents)) continue;?> + contents as $content):?> + oldStartLine; + $newCurrentLine = $content->newStartLine; + ?> + + + + + + + + + + + + lines as $line):?> + + + + + + + + lines as $line):?> + + type == 'old') + { + $oldlc = $line->oldlc; + $newlc = ''; + if(isset($content->new[$oldlc])) + { + $newlc = $line->oldlc; + $line->type = 'custom'; + } + } + else + { + $oldlc = $line->oldlc; + $newlc = $line->newlc; + if(!isset($content->new[$newlc])) continue; + } + ?> + + + + + old[$oldlc])) unset($content->old[$oldlc]); + if(isset($content->new[$newlc])) unset($content->new[$newlc]); + ?> + + + + +
fileName;?>
......
type != 'new') echo $line->oldlc?>type != 'old') echo $line->newlc?>line = $repo->SCM == 'Subversion' ? htmlspecialchars($line->line) : $line->line; + echo $line->type == 'old' ? preg_replace('/^\-/', '–', $line->line) : ($line->type == 'new' ? $line->line : ' ' . $line->line); + ?>
type?> type == 'custom') echo "line-old"?> code'>old[$oldlc])) $content->old[$oldlc] = ''; + $content->old[$oldlc] = $repo->SCM == 'Subversion' ? htmlspecialchars($content->old[$oldlc]) : $content->old[$oldlc]; + if(!empty($oldlc)) echo $line->type != 'all' ? preg_replace('/^\-/', '–', $content->old[$oldlc]) : ' ' . $content->old[$oldlc]; + ?>type?> type == 'custom') echo "line-new"?> code'>new[$newlc])) $content->new[$newlc] = ''; + $content->new[$newlc] = $repo->SCM == 'Subversion' ? htmlspecialchars($content->new[$newlc]) : $content->new[$newlc]; + if(!empty($newlc)) echo $line->type != 'all' ? $content->new[$newlc] : ' ' . $content->new[$newlc]; + ?>
+
+ +
+ + + diff --git a/module/mr/view/edit.html.php b/module/mr/view/edit.html.php new file mode 100644 index 0000000000..48541919ed --- /dev/null +++ b/module/mr/view/edit.html.php @@ -0,0 +1,78 @@ + + +
+
+
+
+

mr->edit;?>

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
gitlab->common;?>loadModel('gitlab')->getByID($MR->gitlabID)->name;?>
mr->sourceProject;?> +
+ + loadModel('gitlab')->apiGetSingleProject($MR->gitlabID, $MR->sourceProject)->name_with_namespace; ?>: + sourceBranch;?> + +
+
mr->targetProject;?> +
+ + loadModel('gitlab')->apiGetSingleProject($MR->gitlabID, $MR->targetProject)->name_with_namespace;?> + + targetBranch, "class='form-control chosen'");?> +
+
mr->name;?>title, "class='form-control'"); ?>
mr->description; ?>description, "rows='3' class='form-control'"); ?>
mr->assignee;?>
mr->reviewer;?>
mr->usersTips;?>
+ + goback, '', 'class="btn btn-wide"');?> +
+
+
+
+
+ diff --git a/module/mr/view/view.html.php b/module/mr/view/view.html.php new file mode 100644 index 0000000000..cbc02d1221 --- /dev/null +++ b/module/mr/view/view.html.php @@ -0,0 +1,67 @@ + +id)):?> +
+
+

+ mr->notFound;?> + + createLink('mr', 'create'), " " . $lang->mr->create, '', "class='btn btn-info'");?> + +

+
+
+ +
+ createLink('mr', 'browse'), ' ' . $lang->goback, '', "class='btn btn-secondary'");?> +
+
+ id?> + title;?> + web_url, $lang->mr->viewInGitlab, "_blank", "class='btn btn-link btn-active-text' style='color: blue'");?> +
+
+ +
+
+
+
+
+
mr->from . html::a($sourceProjectURL, $sourceProjectName . ":" . $MR->sourceBranch, "_blank", "class='btn btn-link btn-active-text' style='color: blue'") . $lang->mr->to . html::a($targetProjectURL, $targetProjectName . ":" . $MR->targetBranch, "_blank", "class='btn btn-link btn-active-text' style='color: blue'");?>
+
+
+ mr->status;?> + mr->statusList, $MR->status);?> +
+ head_pipeline->status)):?> +
+ mr->pipeline}{$lang->mr->status}";?> + mr->pipelineStatus, $rawMR->head_pipeline->status, $lang->mr->pipelineUnknown);?> +
+ + mr->mergeStatus;?> + mr->mergeStatusList, $rawMR->merge_status);?> +
+ mr->MRHasConflicts;?> + has_conflicts ? $lang->mr->hasConflicts : $lang->mr->hasNoConflict);?> +
+
+
+
+
+
mr->description;?>
+
+ description) ? $MR->description : "
" . $lang->noData . '
';?> +
+
+
+ state == 'opened'):?> +
mr->commandDocument, $httpRepoURL, $MR->sourceBranch, $branchPath, $MR->targetBranch, $branchPath, $MR->targetBranch);?>
+ +
+
+
+ state == 'opened' and !$rawMR->has_conflicts):?> + id"), ' ' . $lang->mr->acceptMR, '', "id='mergeButton' class='btn btn-wide btn-primary'");?> + + + diff --git a/module/repo/control.php b/module/repo/control.php index 086151ee48..7a804c818d 100644 --- a/module/repo/control.php +++ b/module/repo/control.php @@ -87,9 +87,9 @@ class repo extends control /* Pager. */ $this->app->loadClass('pager', $static = true); - $recTotal = count($repoList); - $pager = new pager($recTotal, $recPerPage, $pageID); - $repoList = array_chunk($repoList, $pager->recPerPage); + $recTotal = count($repoList); + $pager = new pager($recTotal, $recPerPage, $pageID); + $repoList = array_chunk($repoList, $pager->recPerPage); $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->browse; $this->view->position[] = $this->lang->repo->common; diff --git a/module/repo/view/maintain.html.php b/module/repo/view/maintain.html.php index 86f133c31f..a59d18a755 100644 --- a/module/repo/view/maintain.html.php +++ b/module/repo/view/maintain.html.php @@ -62,7 +62,7 @@ - + diff --git a/module/todo/lang/zh-cn.php b/module/todo/lang/zh-cn.php index 1e715a9363..7eae292cc4 100644 --- a/module/todo/lang/zh-cn.php +++ b/module/todo/lang/zh-cn.php @@ -96,12 +96,13 @@ $lang->todo->priList[2] = 2; $lang->todo->priList[3] = 3; $lang->todo->priList[4] = 4; -$lang->todo->typeList['custom'] = '自定义'; -$lang->todo->typeList['cycle'] = '周期'; -$lang->todo->typeList['bug'] = 'Bug'; -$lang->todo->typeList['task'] = '任务'; -$lang->todo->typeList['story'] = $lang->SRCommon; -$lang->todo->typeList['testtask'] = '测试单'; +$lang->todo->typeList['custom'] = '自定义'; +$lang->todo->typeList['cycle'] = '周期'; +$lang->todo->typeList['bug'] = 'Bug'; +$lang->todo->typeList['task'] = '任务'; +$lang->todo->typeList['story'] = $lang->SRCommon; +$lang->todo->typeList['testtask'] = '测试单'; +$lang->todo->typeList['mrapprove'] = '合并请求审批'; $lang->todo->confirmDelete = "您确定要删除这条待办吗?"; $lang->todo->thisIsPrivate = '这是一条私人事务。:)'; diff --git a/module/webhook/view/browse.html.php b/module/webhook/view/browse.html.php index 3450138f5f..f3fef5b1eb 100644 --- a/module/webhook/view/browse.html.php +++ b/module/webhook/view/browse.html.php @@ -50,7 +50,7 @@ - +