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 @@
+
+
+
+
+ " . $lang->mr->create, '', "class='btn btn-primary'");?>
+
+
+
+
+
+
+ noData . $lang->mr->common;?>
+
+ createLink('mr', 'create'), " " . $lang->mr->create, '', "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 @@
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+ fileName;?>
+ contents)) continue;?>
+ contents as $content):?>
+ oldStartLine;
+ $newCurrentLine = $content->newStartLine;
+ ?>
+
+
+ | ... |
+
+ |
+
+ ... |
+ |
+
+
+
+ lines as $line):?>
+
+ | 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);
+ ?> |
+
+
+
+ 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;
+ }
+ ?>
+ |
+ 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];
+ ?> |
+ old[$oldlc])) unset($content->old[$oldlc]);
+ if(isset($content->new[$newlc])) unset($content->new[$newlc]);
+ ?>
+
+
+
+
+
+
+
+
+
+ SCM, 'Subversion') === false)
+ {
+ $oldRevision = $oldRevision == '^' ? "$newRevision" : $oldRevision;
+ echo " " . substr($oldRevision, 0, 10) . " : " . substr($newRevision, 0, 10) . ' (' . $historys[$oldRevision] . ' : ' . $historys[$newRevision] . ')';
+ }
+ else
+ {
+ $oldRevision = $oldRevision == '^' ? $newRevision - 1 : $oldRevision;
+ echo " $oldRevision : $newRevision";
+ }
+ ?>
+
+
+
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 @@
+
+
+
+
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'");?>
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
-
+