diff --git a/Makefile b/Makefile
index 3ada37c3e0..b025bd4662 100644
--- a/Makefile
+++ b/Makefile
@@ -69,6 +69,7 @@ zentaoxx:
cp -r xuan/xxb/module/common/view/marked.html.php zentaoxx/module/common/view
cp -r xuan/xxb/module/common/view/footer.modal.html.php zentaoxx/module/common/view
cp -r xuan/xxb/module/common/view/version.html.php zentaoxx/module/common/view
+ cp -r xuan/xxb/module/license zentaoxx/module/
mkdir -p zentaoxx/www/js/
cp -r xuan/xxb/www/js/markedjs zentaoxx/www/js/
cp -r xuan/xxb/www/js/version.js zentaoxx/www/js/
diff --git a/bin/init.sh b/bin/init.sh
index 5874a549ae..38b99f5fe0 100755
--- a/bin/init.sh
+++ b/bin/init.sh
@@ -141,7 +141,7 @@ echo $deletelog > $basePath/deletelog.sh
echo "deletelog.sh ok"
# cron
-if [ ! -d "$basePath/cron" ]; then
+if [ ! -d "$basePath/cron" ]; then
mkdir $basePath/cron
fi
echo "# system cron." > $basePath/cron/sys.cron
@@ -149,8 +149,8 @@ echo "#min hour day month week command." >> $basePath/cron/sys.cron
echo "0 1 * * * $basePath/dailyreminder.sh # dailyreminder." >> $basePath/cron/sys.cron
echo "1 1 * * * $basePath/backup.sh # backup database and file." >> $basePath/cron/sys.cron
echo "1 23 * * * $basePath/computeburn.sh # compute burndown chart." >> $basePath/cron/sys.cron
-echo "1-59/2 * * * * $basePath/syncsvn.sh # sync subversion." >> $basePath/cron/sys.cron
-echo "1-59/2 * * * * $basePath/syncgit.sh # sync git." >> $basePath/cron/sys.cron
+echo "1-59/5 * * * * $basePath/syncsvn.sh # sync subversion." >> $basePath/cron/sys.cron
+echo "1-59/5 * * * * $basePath/syncgit.sh # sync git." >> $basePath/cron/sys.cron
echo "1-59/5 * * * * $basePath/sendmail.sh # async send mail." >> $basePath/cron/sys.cron
echo "1-59/5 * * * * $basePath/sendwebhook.sh # async send webhook." >> $basePath/cron/sys.cron
echo "1 1 * * * $basePath/createcycle.sh # create cycle todo." >> $basePath/cron/sys.cron
diff --git a/config/zentaopms.php b/config/zentaopms.php
index 362d502d3b..4181858390 100644
--- a/config/zentaopms.php
+++ b/config/zentaopms.php
@@ -163,12 +163,15 @@ define('TABLE_TESTSUITE', '`' . $config->db->prefix . 'testsuite`');
define('TABLE_SUITECASE', '`' . $config->db->prefix . 'suitecase`');
define('TABLE_TESTREPORT', '`' . $config->db->prefix . 'testreport`');
-define('TABLE_ENTRY', '`' . $config->db->prefix . 'entry`');
-define('TABLE_WEBHOOK', '`' . $config->db->prefix . 'webhook`');
-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_ENTRY', '`' . $config->db->prefix . 'entry`');
+define('TABLE_WEBHOOK', '`' . $config->db->prefix . 'webhook`');
+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_JENKINS', '`' . $config->db->prefix . 'jenkins`');
+define('TABLE_INTEGRATION', '`' . $config->db->prefix . 'integration`');
+define('TABLE_COMPILE', '`' . $config->db->prefix . 'compile`');
define('TABLE_REPO', '`' . $config->db->prefix . 'repo`');
define('TABLE_REPOHISTORY', '`' . $config->db->prefix . 'repohistory`');
diff --git a/db/update11.7.sql b/db/update11.7.sql
index a18644bde1..f177f5c124 100644
--- a/db/update11.7.sql
+++ b/db/update11.7.sql
@@ -58,7 +58,7 @@ CREATE TABLE IF NOT EXISTS `zt_repofiles` (
KEY `revision` (`revision`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-ALTER TABLE `zt_bug` CHANGE `caseVersion` `caseVersion` smallint(6) NOT NULL AFTER `case`;
+ALTER TABLE `zt_bug` CHANGE `caseVersion` `caseVersion` smallint(6) NOT NULL DEFAULT 1 AFTER `case`;
ALTER TABLE `zt_bug` ADD `repo` mediumint(8) unsigned NOT NULL AFTER `result`;
ALTER TABLE `zt_bug` ADD `lines` varchar(10) COLLATE 'utf8_general_ci' NOT NULL AFTER `repo`;
ALTER TABLE `zt_bug` ADD `v1` varchar(40) COLLATE 'utf8_general_ci' NOT NULL AFTER `lines`;
diff --git a/db/update12.1.sql b/db/update12.1.sql
new file mode 100644
index 0000000000..52f61a8e19
--- /dev/null
+++ b/db/update12.1.sql
@@ -0,0 +1,68 @@
+CREATE TABLE `zt_jenkins` (
+ `id` smallint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `account` varchar(30) DEFAULT NULL,
+ `password` varchar(30) NOT NULL,
+ `encrypt` varchar(30) NOT NULL DEFAULT 'plain',
+ `token` varchar(255) DEFAULT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `zt_cijob` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `repo` mediumint(8) unsigned NOT NULL,
+ `jenkins` mediumint(8) unsigned NOT NULL,
+ `jenkinsJob` varchar(500) NOT NULL,
+ `triggerType` varchar(255) NOT NULL,
+ `scheduleType` varchar(255) NOT NULL,
+ `cronExpression` varchar(255) DEFAULT NULL,
+ `scheduleDay` varchar(255) DEFAULT NULL,
+ `scheduleTime` varchar(255) DEFAULT NULL,
+ `scheduleInterval` mediumint(8) DEFAULT NULL,
+ `tagKeywords` varchar(255) DEFAULT NULL,
+ `commentKeywords` varchar(255) DEFAULT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `lastExec` datetime DEFAULT NULL,
+ `lastStatus` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `zt_cibuild` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `cijob` mediumint(8) unsigned NOT NULL,
+ `queueItem` mediumint(8) NOT NULL,
+ `status` varchar(255) NOT NULL,
+ `logs` text,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `updateDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+ALTER TABLE `zt_cijob` ADD `svnFolder` varchar(255) COLLATE 'utf8_general_ci' NOT NULL AFTER `triggerType`;
+INSERT INTO `zt_cron` (`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`, `lastTime`) VALUES
+('1', '1', '*', '*', '*', 'moduleName=ci&methodName=buildTodayJob', '创建周期性任务', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
+('*/5', '*', '*', '*', '*', 'moduleName=ci&methodName=checkBuildStatus', '同步Jenkins任务状态', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
+('*/5', '*', '*', '*', '*', 'moduleName=ci&methodName=exec', '执行Jenkins任务', 'zentao', 1, 'normal', '0000-00-00 00:00:00');
+
+ALTER TABLE `zt_cibuild` RENAME TO `zt_compile`;
+ALTER TABLE `zt_cijob` RENAME TO `zt_integration`;
+
+ALTER TABLE `zt_integration`
+DROP `scheduleType`,
+DROP `cronExpression`,
+DROP `scheduleTime`,
+DROP `scheduleInterval`;
diff --git a/db/zentao.sql b/db/zentao.sql
index 152d2fe0d1..a318f90ce8 100644
--- a/db/zentao.sql
+++ b/db/zentao.sql
@@ -89,7 +89,7 @@ CREATE TABLE IF NOT EXISTS `zt_bug` (
`duplicateBug` mediumint(8) unsigned NOT NULL,
`linkBug` varchar(255) NOT NULL,
`case` mediumint(8) unsigned NOT NULL,
- `caseVersion` smallint(6) NOT NULL default '1',
+ `caseVersion` smallint(6) NOT NULL DEFAULT '1',
`result` mediumint(8) unsigned NOT NULL,
`repo` mediumint(8) unsigned NOT NULL,
`entry` varchar(255) NOT NULL,
@@ -1045,6 +1045,118 @@ CREATE TABLE `zt_score` (
KEY `method` (`method`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_repo`;
+CREATE TABLE IF NOT EXISTS `zt_repo` (
+ `id` mediumint(9) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `path` varchar(255) NOT NULL,
+ `prefix` varchar(100) NOT NULL,
+ `encoding` varchar(20) NOT NULL,
+ `SCM` varchar(10) NOT NULL,
+ `client` varchar(100) NOT NULL,
+ `commits` mediumint(8) unsigned NOT NULL,
+ `account` varchar(30) NOT NULL,
+ `password` varchar(30) NOT NULL,
+ `encrypt` varchar(30) NOT NULL DEFAULT 'plain',
+ `acl` text NOT NULL,
+ `synced` tinyint(1) NOT NULL DEFAULT '0',
+ `lastSync` datetime NOT NULL,
+ `desc` TEXT NULL,
+ `deleted` tinyint(1) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- DROP TABLE IF EXISTS `zt_repobranch`;
+CREATE TABLE IF NOT EXISTS `zt_repobranch` (
+ `repo` mediumint(8) unsigned NOT NULL,
+ `revision` mediumint(8) unsigned NOT NULL,
+ `branch` varchar(255) NOT NULL,
+ UNIQUE KEY `repo_revision_branch` (`repo`,`revision`,`branch`),
+ KEY `branch` (`branch`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- DROP TABLE IF EXISTS `zt_repohistory`;
+CREATE TABLE IF NOT EXISTS `zt_repohistory` (
+ `id` mediumint(9) NOT NULL AUTO_INCREMENT,
+ `repo` mediumint(9) NOT NULL,
+ `revision` varchar(40) NOT NULL,
+ `commit` mediumint(8) unsigned NOT NULL,
+ `comment` text NOT NULL,
+ `committer` varchar(100) NOT NULL,
+ `time` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `repo` (`repo`,`revision`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- DROP TABLE IF EXISTS `zt_repofiles`;
+CREATE TABLE IF NOT EXISTS `zt_repofiles` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `repo` mediumint(8) unsigned NOT NULL,
+ `revision` mediumint(8) unsigned NOT NULL,
+ `path` varchar(255) NOT NULL,
+ `parent` varchar(255) NOT NULL,
+ `type` varchar(20) NOT NULL,
+ `action` char(1) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `path` (`path`),
+ KEY `parent` (`parent`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `zt_jenkins` (
+ `id` smallint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `url` varchar(255) DEFAULT NULL,
+ `account` varchar(30) DEFAULT NULL,
+ `password` varchar(30) NOT NULL,
+ `encrypt` varchar(30) NOT NULL DEFAULT 'plain',
+ `token` varchar(255) DEFAULT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `zt_integration` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `repo` mediumint(8) unsigned NOT NULL,
+ `jenkins` mediumint(8) unsigned NOT NULL,
+ `jenkinsJob` varchar(500) NOT NULL,
+ `triggerType` varchar(255) NOT NULL,
+ `svnFolder` varchar(255) NOT NULL,
+ `scheduleType` varchar(255) NOT NULL,
+ `cronExpression` varchar(255) DEFAULT NULL,
+ `scheduleDay` varchar(255) DEFAULT NULL,
+ `scheduleTime` varchar(255) DEFAULT NULL,
+ `scheduleInterval` mediumint(8) DEFAULT NULL,
+ `tagKeywords` varchar(255) DEFAULT NULL,
+ `commentKeywords` varchar(255) DEFAULT NULL,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `editedBy` varchar(30) NOT NULL,
+ `editedDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ `lastExec` datetime DEFAULT NULL,
+ `lastStatus` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `zt_compile` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `cijob` mediumint(8) unsigned NOT NULL,
+ `queueItem` mediumint(8) NOT NULL,
+ `status` varchar(255) NOT NULL,
+ `logs` text,
+ `createdBy` varchar(30) NOT NULL,
+ `createdDate` datetime NOT NULL,
+ `updateDate` datetime NOT NULL,
+ `deleted` enum('0','1') NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
INSERT INTO `zt_cron` (`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type`, `buildin`, `status`, `lastTime`) VALUES
('*', '*', '*', '*', '*', '', '监控定时任务', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
('30', '23', '*', '*', '*', 'moduleName=project&methodName=computeburn', '更新燃尽图', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
@@ -1055,7 +1167,10 @@ INSERT INTO `zt_cron` (`m`, `h`, `dom`, `mon`, `dow`, `command`, `remark`, `type
('*/5', '*', '*', '*', '*', 'moduleName=mail&methodName=asyncSend', '异步发信', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
('*/5', '*', '*', '*', '*', 'moduleName=webhook&methodName=asyncSend', '异步发送Webhook', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
('*/5', '*', '*', '*', '*', 'moduleName=admin&methodName=deleteLog', '删除过期日志', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
-('1', '1', '*', '*', '*', 'moduleName=todo&methodName=createCycle', '生成周期性待办', 'zentao', 1, 'normal', '0000-00-00 00:00:00');
+('1', '1', '*', '*', '*', 'moduleName=todo&methodName=createCycle', '生成周期性待办', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
+('1', '1', '*', '*', '*', 'moduleName=ci&methodName=buildTodayJob', '创建周期性任务', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
+('*/5', '*', '*', '*', '*', 'moduleName=ci&methodName=checkBuildStatus', '同步Jenkins任务状态', 'zentao', 1, 'normal', '0000-00-00 00:00:00'),
+('*/5', '*', '*', '*', '*', 'moduleName=ci&methodName=exec', '执行Jenkins任务', 'zentao', 1, 'normal', '0000-00-00 00:00:00');
INSERT INTO `zt_group` (`id`, `name`, `role`, `desc`) VALUES
(1, 'ADMIN', 'admin', 'for administrator'),
diff --git a/doc/LICENSE.EN b/doc/LICENSE.EN
index 16cc3a8dce..65e91c9942 100644
--- a/doc/LICENSE.EN
+++ b/doc/LICENSE.EN
@@ -2,19 +2,19 @@ Z PUBLIC LICENSE 1.2
Authorization
-Z PUBLIC LICENSE, also known as ZPL Agreement, is drafted by QingDao Nature Easy Soft Network Technology Co,LTD. (,www.cnezsoft.com).
-Anyone can use the agreement to publish open source software, and modify the blank underlined part of the following text of the agreement accordingly.
-No other text of the agreement shall be changed. QingDao Nature Easy Soft Network Technology Co,LTD has the final authority to interpret the terms of the agreement.
+Z PUBLIC LICENSE, also known as ZPL Agreement, is drafted by EasyCorp(en.easycorp.ltd).
+Anyone can use the agreement to publish open source software, and modify the blank in the following text of the agreement accordingly.
+No other text of the agreement shall be changed. EasyCorp has the final interpretation of the terms in the agreement.
Preface
-ZenTaoPMS (Hereinafter referred to as "the software") developed by Nature EasySoft Network Tecnology Co.ltd, QingDao, China (www.cnezsoft.com) (hereinafter referred to I). I'm entitled to all copyright of the software.
+ZenTao ALM (Hereinafter referred to as "the software") developed by EasyCorp (en.easycorp.ltd) (hereinafter referred to I). I'm entitled to all copyright of the software.
The software is released as open source software. You are authorized to use the software as long as you are in compliance with this agreement.
By installation of the software, you agree that a contractual relationship between you and me is automatically established.
-You are obliged to fully comply with all the terms of this agreement unless you choose to stop using the software or you have signed additional contracts with me.
+You are obliged to fully comply with all the terms of this agreement unless you choose to stop using the software or you have signed additional agreement with me.
My Contact:
-Email: co@easysoft.ltd
+Email: renee@easysoft.ltd
Site: http://www.zentao.pm
We agree:
diff --git a/lib/scm/git.class.php b/lib/scm/parsegit.class.php
similarity index 85%
rename from lib/scm/git.class.php
rename to lib/scm/parsegit.class.php
index 52ece180bf..c0468db89a 100644
--- a/lib/scm/git.class.php
+++ b/lib/scm/parsegit.class.php
@@ -1,9 +1,20 @@
client} config core.quotepath false");
}
+ /**
+ * List files.
+ *
+ * @param string $path
+ * @param string $revision
+ * @access public
+ * @return array
+ */
public function ls($path, $revision = 'HEAD')
{
$path = ltrim($path, DIRECTORY_SEPARATOR);
@@ -29,7 +48,7 @@ class Git
$infos = array();
foreach($list as $entry)
- {
+ {
list($mod, $kind, $revision, $size, $name) = preg_split('/[\t ]+/', $entry);
/* Get commit info. */
@@ -70,6 +89,29 @@ class Git
return $infos;
}
+ /**
+ * Get tags
+ *
+ * @param string $path
+ * @param string $revision
+ * @access public
+ * @return array
+ */
+ public function tags($path, $revision = 'HEAD')
+ {
+ chdir($this->root);
+ $cmd = escapeCmd("$this->client tag --sort=taggerdate");
+ $list = execCmd($cmd . ' 2>&1', 'array', $result);
+ if($result) return array();
+ return $list;
+ }
+
+ /**
+ * Get branch
+ *
+ * @access public
+ * @return array
+ */
public function branch()
{
chdir($this->root);
@@ -92,6 +134,14 @@ class Git
return $branches;
}
+ /**
+ * Get last log.
+ *
+ * @param string $path
+ * @param int $count
+ * @access public
+ * @return array
+ */
public function getLastLog($path, $count = 10)
{
$path = ltrim($path, DIRECTORY_SEPARATOR);
@@ -104,6 +154,16 @@ class Git
return $logs;
}
+ /**
+ * Get logs
+ *
+ * @param string $path
+ * @param string $fromRevision
+ * @param string $toRevision
+ * @param int $count
+ * @access public
+ * @return array
+ */
public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0)
{
$path = ltrim($path, DIRECTORY_SEPARATOR);
@@ -116,7 +176,7 @@ class Git
$logs = array();
chdir($this->root);
- $list = execCmd(escapeCmd("$this->client log --stat=1024 --name-status -1 $fromRevision -- $path"), 'array');
+ $list = execCmd(escapeCmd("$this->client log --stat=1024 --name-status --stat-name-width=1000 -1 $fromRevision -- $path"), 'array');
$logs = $this->parseLog($list);
return $logs;
}
@@ -130,12 +190,20 @@ class Git
$revisions = "$fromRevision..$toRevision";
}
chdir($this->root);
- $list = execCmd(escapeCmd("$this->client log $count $revisions -- $path"), 'array');
+ $list = execCmd(escapeCmd("$this->client log --stat=1024 --name-status --stat-name-width=1000 $count $revisions -- $path"), 'array');
$logs = $this->parseLog($list);
return $logs;
}
+ /**
+ * Blame file
+ *
+ * @param string $path
+ * @param string $revision
+ * @access public
+ * @return array
+ */
public function blame($path, $revision)
{
$path = ltrim($path, DIRECTORY_SEPARATOR);
@@ -178,6 +246,15 @@ class Git
return $blames;
}
+ /**
+ * Diff file.
+ *
+ * @param string $path
+ * @param string $fromRevision
+ * @param string $toRevision
+ * @access public
+ * @return array
+ */
public function diff($path, $fromRevision, $toRevision)
{
$path = ltrim($path, DIRECTORY_SEPARATOR);
@@ -193,6 +270,14 @@ class Git
return $lines;
}
+ /**
+ * Cat file.
+ *
+ * @param string $entry
+ * @param string $revision
+ * @access public
+ * @return string
+ */
public function cat($entry, $revision = 'HEAD')
{
chdir($this->root);
@@ -203,6 +288,14 @@ class Git
return $content;
}
+ /**
+ * Get info.
+ *
+ * @param string $entry
+ * @param string $revision
+ * @access public
+ * @return object
+ */
public function info($entry, $revision = 'HEAD')
{
chdir($this->root);
@@ -235,6 +328,13 @@ class Git
return $info;
}
+ /**
+ * Parse diff.
+ *
+ * @param array $lines
+ * @access public
+ * @return array
+ */
public function parseDiff($lines)
{
if(empty($lines)) return array();
@@ -323,6 +423,14 @@ class Git
return $diffs;
}
+ /**
+ * Get commit count.
+ *
+ * @param int $commits
+ * @param string $lastVersion
+ * @access public
+ * @return int
+ */
public function getCommitCount($commits = 0, $lastVersion = '')
{
chdir($this->root);
@@ -330,6 +438,12 @@ class Git
return execCmd(escapeCmd("$this->client rev-list --count $revision -- ./"), 'string');
}
+ /**
+ * Get first revision.
+ *
+ * @access public
+ * @return string
+ */
public function getFirstRevision()
{
chdir($this->root);
@@ -337,6 +451,12 @@ class Git
return $list[0];
}
+ /**
+ * Get latest revision
+ *
+ * @access public
+ * @return string
+ */
public function getLatestRevision()
{
chdir($this->root);
@@ -345,6 +465,15 @@ class Git
return $list[0];
}
+ /**
+ * Get commits.
+ *
+ * @param string $version
+ * @param int $count
+ * @param string $branch
+ * @access public
+ * @return array
+ */
public function getCommits($version = '', $count = 0, $branch = '')
{
if($version == 'HEAD' and $branch) $version = $branch;
@@ -397,6 +526,13 @@ class Git
return $logs;
}
+ /**
+ * Parse log.
+ *
+ * @param array $logs
+ * @access public
+ * @return array
+ */
public function parseLog($logs)
{
$parsedLogs = array();
diff --git a/lib/scm/scm.class.php b/lib/scm/scm.class.php
index c2bdb6c48a..e41a57a566 100644
--- a/lib/scm/scm.class.php
+++ b/lib/scm/scm.class.php
@@ -3,38 +3,110 @@ class scm
{
public $engine;
+ /**
+ * Set engine.
+ *
+ * @param object $repo
+ * @access public
+ * @return void
+ */
public function setEngine($repo)
{
$className = $repo->SCM;
+ if($className == 'Git') $className = 'ParseGit';
if(!class_exists($className)) require(strtolower($className) . '.class.php');
$this->engine = new $className($repo->client, $repo->path, $repo->account, $repo->password, $repo->encoding);
}
+ /**
+ * List files.
+ *
+ * @param string $path
+ * @param string $revision
+ * @access public
+ * @return array
+ */
public function ls($path, $revision = 'HEAD')
{
return $this->engine->ls($path, $revision);
}
+ /**
+ * Get tags.
+ *
+ * @param string $path
+ * @param string $revision
+ * @param bool $onlyDir
+ * @access public
+ * @return array
+ */
+ public function tags($path, $revision, $onlyDir = true)
+ {
+ return $this->engine->tags($path, $revision, $onlyDir);
+ }
+
+ /**
+ * Get branch.
+ *
+ * @access public
+ * @return array
+ */
public function branch()
{
return $this->engine->branch();
}
-
+
+ /**
+ * Get log.
+ *
+ * @param string $path
+ * @param string $fromRevision
+ * @param string $toRevision
+ * @param int $count
+ * @access public
+ * @return array
+ */
public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0)
{
return $this->engine->log($path, $fromRevision, $toRevision);
}
+ /**
+ * Blame file.
+ *
+ * @param string $path
+ * @param string $revision
+ * @access public
+ * @return array
+ */
public function blame($path, $revision)
{
return $this->engine->blame($path, $revision);
}
+ /**
+ * Get last log.
+ *
+ * @param string $path
+ * @param int $count
+ * @access public
+ * @return array
+ */
public function getLastLog($path, $count = 10)
{
return $this->engine->getLastLog($path, $count);
}
+ /**
+ * Diff file.
+ *
+ * @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')
{
$diffs = $this->engine->diff($path, $fromRevision, $toRevision);
@@ -43,37 +115,89 @@ class scm
return $this->engine->parseDiff($diffs);
}
+ /**
+ * Cat file.
+ *
+ * @param string $entry
+ * @param string $revision
+ * @access public
+ * @return string
+ */
public function cat($entry, $revision = 'HEAD')
{
return $this->engine->cat($entry, $revision);
}
+ /**
+ * Get info.
+ *
+ * @param string $entry
+ * @param string $revision
+ * @access public
+ * @return object
+ */
public function info($entry, $revision = 'HEAD')
{
return $this->engine->info($entry, $revision);
}
+ /**
+ * Get commit count
+ *
+ * @param int $commits
+ * @param string $lastVersion
+ * @access public
+ * @return int
+ */
public function getCommitCount($commits = 0, $lastVersion = 0)
{
return $this->engine->getCommitCount($commits, $lastVersion);
}
+ /**
+ * Get latest revision.
+ *
+ * @access public
+ * @return string
+ */
public function getLatestRevision()
{
return $this->engine->getLatestRevision();
}
+ /**
+ * Get first revision.
+ *
+ * @access public
+ * @return string
+ */
public function getFirstRevision()
{
return $this->engine->getFirstRevision();
}
+ /**
+ * Get commits.
+ *
+ * @param string $version
+ * @param int $count
+ * @param string $branch
+ * @access public
+ * @return array
+ */
public function getCommits($version = '', $count = 0, $branch = '')
{
return $this->engine->getCommits($version, $count, $branch);
}
}
+/**
+ * Escape command.
+ *
+ * @param string $cmd
+ * @access public
+ * @return string
+ */
function escapeCmd($cmd)
{
$codes = array('#', '&', ';', '`', '|', '*', '?', '~', '<', '>', '^', '[', ']', '{', '}', '$', ',', '\x0A', '\xFF');
@@ -82,6 +206,16 @@ function escapeCmd($cmd)
return $cmd;
}
+/**
+ * Execute command.
+ *
+ * @param string $cmd
+ * @param string $return
+ * @param int $result
+ * @param string $type
+ * @access public
+ * @return array|string
+ */
function execCmd($cmd, $return = 'string', &$result = 0, $type = 'utf-8')
{
if(file_exists(dirname(__FILE__) . '/config.php')) include dirname(__FILE__) . '/config.php';
diff --git a/lib/scm/subversion.class.php b/lib/scm/subversion.class.php
index 943023e50d..6c52fbf48b 100644
--- a/lib/scm/subversion.class.php
+++ b/lib/scm/subversion.class.php
@@ -9,6 +9,17 @@ class Subversion
public $remote;
public $encoding;
+ /**
+ * Construct
+ *
+ * @param string $client
+ * @param string $root
+ * @param string $account
+ * @param string $password
+ * @param string $encoding
+ * @access public
+ * @return void
+ */
public function __construct($client, $root, $account, $password, $encoding = 'UTF-8')
{
putenv('LC_CTYPE=en_US.UTF-8');
@@ -22,6 +33,14 @@ class Subversion
if($this->encoding == 'utf-8') $this->encoding = 'gbk';
}
+ /**
+ * List files.
+ *
+ * @param string $path
+ * @param string $revision
+ * @access public
+ * @return array
+ */
public function ls($path, $revision = 'HEAD')
{
$resourcePath = $path;
@@ -65,11 +84,47 @@ class Subversion
return $infos;
}
+ /**
+ * Get tags.
+ *
+ * @param string $path
+ * @param string $revision
+ * @param bool $onlyDir
+ * @access public
+ * @return array
+ */
+ public function tags($path, $revision = 'HEAD', $onlyDir = true)
+ {
+ $infos = $this->ls($path, $revision);
+ $tags = array();
+ foreach($infos as $info)
+ {
+ if($onlyDir and $info->kind != 'dir') continue;
+ $tags[$info->name] = $info->name;
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Get branch.
+ *
+ * @access public
+ * @return array
+ */
public function branch()
{
return array();
}
+ /**
+ * Get last log.
+ *
+ * @param string $path
+ * @param int $count
+ * @access public
+ * @return array
+ */
public function getLastLog($path, $count = 10)
{
$resourcePath = $path;
@@ -110,6 +165,17 @@ class Subversion
return $logs;
}
+ /**
+ * Get log.
+ *
+ * @param string $path
+ * @param int $fromRevision
+ * @param string $toRevision
+ * @param int $count
+ * @param bool $quiet
+ * @access public
+ * @return array
+ */
public function log($path, $fromRevision = 0, $toRevision = 'HEAD', $count = 0, $quiet = false)
{
$resourcePath = $path;
@@ -165,6 +231,14 @@ class Subversion
return $logs;
}
+ /**
+ * Blame file.
+ *
+ * @param string $path
+ * @param int $revision
+ * @access public
+ * @return array
+ */
public function blame($path, $revision)
{
$resourcePath = $path;
@@ -225,6 +299,15 @@ class Subversion
return $blames;
}
+ /**
+ * Diff file.
+ *
+ * @param string $path
+ * @param int $fromRevision
+ * @param int $toRevision
+ * @access public
+ * @return array
+ */
public function diff($path, $fromRevision, $toRevision)
{
$resourcePath = $path;
@@ -241,6 +324,14 @@ class Subversion
return $lines;
}
+ /**
+ * Cat file.
+ *
+ * @param string $entry
+ * @param string $revision
+ * @access public
+ * @return string
+ */
public function cat($entry, $revision = 'HEAD')
{
$resourcePath = $entry;
@@ -256,16 +347,26 @@ class Subversion
return $content;
}
+ /**
+ * Get info.
+ *
+ * @param string $entry
+ * @param string $revision
+ * @access public
+ * @return object
+ */
public function info($entry, $revision = 'HEAD')
{
$resourcePath = $entry;
$entry = '"' . $this->root . '/' . str_replace('%2F', '/', urlencode($entry)) . '"';
$svnInfo = $this->replaceAuth(escapeCmd($this->buildCMD($entry, 'info', "-r $revision --xml")));
+
$svninfo = execCmd($svnInfo, 'string', $result);
if($result)
{
$entry = '"' . $this->root . '/' . $resourcePath . '"';
$svnInfo = $this->replaceAuth(escapeCmd($this->buildCMD($entry, 'info', "-r $revision --xml")));
+
$svninfo = execCmd($svnInfo, 'string', $result);
if($result) $svninfo = '';
}
@@ -285,6 +386,13 @@ class Subversion
return $info;
}
+ /**
+ * Parse diff.
+ *
+ * @param array $lines
+ * @access public
+ * @return array
+ */
public function parseDiff($lines)
{
if(empty($lines)) return array();
@@ -358,6 +466,14 @@ class Subversion
return $diffs;
}
+ /**
+ * Get commit count.
+ *
+ * @param int $commits
+ * @param int $lastVersion
+ * @access public
+ * @return int
+ */
public function getCommitCount($commits = 0, $lastVersion = 0)
{
if(empty($commits)) $commits = 0;
@@ -381,6 +497,12 @@ class Subversion
return $commits;
}
+ /**
+ * Get first revision.
+ *
+ * @access public
+ * @return int
+ */
public function getFirstRevision()
{
$logs = $this->log('', 0, 'HEAD', 1, $quiet = true);
@@ -389,12 +511,26 @@ class Subversion
return $firstLog->revision;
}
+ /**
+ * Get latest revision.
+ *
+ * @access public
+ * @return int
+ */
public function getLatestRevision()
{
$info = $this->info('');
return $info->cRevision;
}
+ /**
+ * Get commits.
+ *
+ * @param string $version
+ * @param int $count
+ * @access public
+ * @return array
+ */
public function getCommits($version = '', $count = 0)
{
$count = $count == 0 ? '' : "--limit $count";
@@ -443,11 +579,27 @@ class Subversion
return $logs;
}
+ /**
+ * Replace svn auth.
+ *
+ * @param string $cmd
+ * @access public
+ * @return string
+ */
public function replaceAuth($cmd)
{
return str_replace(array('@account@', '@password@'), array($this->account, $this->password), $cmd);
}
+ /**
+ * Build command.
+ *
+ * @param string $path
+ * @param string $action
+ * @param string $param
+ * @access public
+ * @return string
+ */
public function buildCMD($path, $action, $param)
{
if($this->ssh)
diff --git a/module/backup/control.php b/module/backup/control.php
index aaeda09f84..4621c6c4c8 100644
--- a/module/backup/control.php
+++ b/module/backup/control.php
@@ -143,21 +143,20 @@ class backup extends control
$backupFiles = glob("{$this->backupPath}*.*");
if(!empty($backupFiles))
{
- $time = time();
+ $time = time();
+ $zfile = $this->app->loadClass('zfile');
foreach($backupFiles as $file)
{
- if($time - filemtime($file) > $this->config->backup->holdDays * 24 * 3600) unlink($file);
+ if($time - filemtime($file) > $this->config->backup->holdDays * 24 * 3600)
+ {
+ $rmFunc = is_file($file) ? 'removeFile' : 'removeDir';
+ $zfile->{$rmFunc}($file);
+ }
}
}
- if($reload == 'yes')
- {
- die(js::reload('parent'));
- }
- else
- {
- echo $this->lang->backup->success->backup . "\n";
- }
+ if($reload == 'yes') die(js::reload('parent'));
+ echo $this->lang->backup->success->backup . "\n";
}
/**
diff --git a/module/bug/control.php b/module/bug/control.php
index 22b7e70f3c..ba672e10cc 100644
--- a/module/bug/control.php
+++ b/module/bug/control.php
@@ -541,10 +541,11 @@ class bug extends control
* View a bug.
*
* @param int $bugID
+ * @param string $form
* @access public
* @return void
*/
- public function view($bugID)
+ public function view($bugID, $from = 'bug')
{
/* Judge bug exits or not. */
$bug = $this->bug->getById($bugID, true);
@@ -555,7 +556,18 @@ class bug extends control
if($bug->assignedTo == $this->app->user->account) $this->loadModel('action')->read('bug', $bugID);
/* Set menu. */
- $this->bug->setMenu($this->products, $bug->product, $bug->branch);
+ if($from == 'bug')
+ {
+ $this->bug->setMenu($this->products, $bug->product, $bug->branch);
+ }
+ elseif($from == 'repo')
+ {
+ session_write_close();
+ $this->lang->set('menugroup.bug', 'repo');
+ $repos = $this->loadModel('repo')->getRepoPairs();
+ $this->repo->setMenu($repos);
+ $this->lang->bug->menu = $this->lang->repo->menu;
+ }
/* Get product info. */
$productID = $bug->product;
@@ -576,6 +588,7 @@ class bug extends control
$this->view->modulePath = $this->tree->getParents($bug->module);
$this->view->bugModule = empty($bug->module) ? '' : $this->tree->getById($bug->module);
$this->view->bug = $bug;
+ $this->view->from = $from;
$this->view->branchName = $this->session->currentProductType == 'normal' ? '' : zget($branches, $bug->branch, '');
$this->view->users = $this->user->getPairs('noletter');
$this->view->actions = $this->action->getList('bug', $bugID);
diff --git a/module/bug/lang/zh-cn.php b/module/bug/lang/zh-cn.php
index 3c19fd7444..dd8851bc5d 100644
--- a/module/bug/lang/zh-cn.php
+++ b/module/bug/lang/zh-cn.php
@@ -154,7 +154,7 @@ $lang->bug->lblLastEdited = '最后修改';
$lang->bug->lblResolved = '由谁解决';
$lang->bug->allUsers = '加载所有用户';
$lang->bug->allBuilds = '所有';
-$lang->bug->createBuild = '新建';
+$lang->bug->createBuild = '创建';
/* legend列表。*/
$lang->bug->legendBasicInfo = '基本信息';
diff --git a/module/bug/lang/zh-tw.php b/module/bug/lang/zh-tw.php
index 59a116c524..c459c001fc 100644
--- a/module/bug/lang/zh-tw.php
+++ b/module/bug/lang/zh-tw.php
@@ -154,7 +154,7 @@ $lang->bug->lblLastEdited = '最後修改';
$lang->bug->lblResolved = '由誰解決';
$lang->bug->allUsers = '加載所有用戶';
$lang->bug->allBuilds = '所有';
-$lang->bug->createBuild = '新建';
+$lang->bug->createBuild = '创建';
/* legend列表。*/
$lang->bug->legendBasicInfo = '基本信息';
diff --git a/module/bug/model.php b/module/bug/model.php
index 81701faa5c..88299ffae0 100644
--- a/module/bug/model.php
+++ b/module/bug/model.php
@@ -181,6 +181,7 @@ class bugModel extends model
{
$this->loadModel('action');
$branch = (int)$branch;
+ $productID = (int)$productID;
$now = helper::now();
$actions = array();
$data = fixer::input('post')->get();
diff --git a/module/caselib/model.php b/module/caselib/model.php
index 4ae71be856..1625001bec 100644
--- a/module/caselib/model.php
+++ b/module/caselib/model.php
@@ -579,6 +579,7 @@ class caselibModel extends model
$this->loadModel('action');
$now = helper::now();
+ $libID = (int)$libID;
$cases = fixer::input('post')->get();
$batchNum = count(reset($cases));
diff --git a/module/ci/control.php b/module/ci/control.php
new file mode 100644
index 0000000000..d5a9f9b119
--- /dev/null
+++ b/module/ci/control.php
@@ -0,0 +1,122 @@
+
+ * @package product
+ * @version $Id: ${FILE_NAME} 5144 2020/1/8 8:10 下午 chenqi@cnezsoft.com $
+ * @link http://www.zentao.net
+ */
+class ci extends control
+{
+ /**
+ * ci constructor.
+ * @param string $moduleName
+ * @param string $methodName
+ */
+ public function __construct($moduleName = '', $methodName = '')
+ {
+ parent::__construct($moduleName, $methodName);
+ $this->ci->setMenu();
+ }
+
+ /**
+ * Build today job.
+ *
+ * @access public
+ * @return void
+ */
+ public function buildTodayJob()
+ {
+ $scheduleJobs = $this->loadModel('integration')->getListByTriggerType('schedule');
+
+ $week = date('w');
+ $this->loadModel('compile');
+ foreach($scheduleJobs as $job)
+ {
+ if(strpos($job->scheduleDay, $week) !== false) $this->compile->createByIntegration($job->id);
+ }
+ echo 'success';
+ }
+
+ /**
+ * Exec compile.
+ *
+ * @access public
+ * @return void
+ */
+ public function exec()
+ {
+ $compiles = $this->loadModel('compile')->getUnexecutedList();
+ foreach($compiles as $compile) $this->compile->execByCompile($compile);
+
+ $integrations = $this->loadModel('integration')->getListByTriggerType('tag');
+
+ $repoIdList = array();
+ $repos = array();
+ foreach($integrations as $integration) $repoIdList[$integration->id] = $integration->id;
+ if($repoIdList) $repos = $this->loadModel('repo')->getByIdList($repoIdList);
+
+ foreach($integrations as $integration)
+ {
+ $repo = zget($repos, $integration->repo, null);
+ if(empty($repo)) continue;
+
+ $scm = $repo->SCM == 'Git' ? 'git' : 'svn';
+ $savedTag = $this->loadModel($scm)->getSavedTag($repo->id);
+
+ $tags = $this->$scm->getRepoTags($repo, $scm == 'svn' ? $integration->svnFolder : '');
+ if(!empty($tags))
+ {
+ $arriveLastTag = false;
+ foreach($tags as $tag)
+ {
+ /* Get the last build tag position */
+ if($scm == 'git')
+ {
+ if(!empty($savedTag) && !$arriveLastTag) continue;
+ if(!empty($savedTag) && $tag == $savedTag)
+ {
+ $arriveLastTag = true;
+ continue;
+ }
+ }
+ elseif($scm == 'svn')
+ {
+ if(isset($savedTag[$tag])) continue;
+ $tag = rtrim($repo->path , '/') . '/' . trim($integration->svnFolder, '/') . '/' . $tag;
+ }
+
+ $tagData = new stdclass();
+ $tagData->PARAM_TAG = $tag;
+ $this->compile->execByIntegration($integration->id, $tagData);
+ }
+
+ if($scm == 'svn') $tag = json_encode($tags);
+ $this->$scm->saveLastTag($tag, $repo->id);
+ }
+ }
+ echo 'success';
+ }
+
+ /**
+ * Send a request to jenkins to check build status.
+ *
+ * @access public
+ * @return void
+ */
+ public function checkBuildStatus()
+ {
+ $this->ci->checkBuildStatus();
+ if(dao::isError())
+ {
+ echo json_encode(dao::getError());
+ }
+ else
+ {
+ echo 'success';
+ }
+ }
+}
diff --git a/module/ci/lang/en.php b/module/ci/lang/en.php
new file mode 100644
index 0000000000..5c582d0e66
--- /dev/null
+++ b/module/ci/lang/en.php
@@ -0,0 +1,6 @@
+ci->common = 'CI';
+$lang->ci->at = ' at ';
+$lang->ci->job = 'Job';
+$lang->ci->task = 'Job';
+$lang->ci->history = 'Build';
diff --git a/module/ci/lang/zh-cn.php b/module/ci/lang/zh-cn.php
new file mode 100644
index 0000000000..6c0280971f
--- /dev/null
+++ b/module/ci/lang/zh-cn.php
@@ -0,0 +1,6 @@
+ci->common = '持续集成';
+$lang->ci->at = '于';
+$lang->ci->job = '构建';
+$lang->ci->task = '任务';
+$lang->ci->history = '历史';
diff --git a/module/ci/model.php b/module/ci/model.php
new file mode 100644
index 0000000000..127d685dd5
--- /dev/null
+++ b/module/ci/model.php
@@ -0,0 +1,129 @@
+
+ * @package product
+ * @version $Id: $
+ * @link http://www.zentao.net
+ */
+
+class ciModel extends model
+{
+ /**
+ * Set menu.
+ *
+ * @access public
+ * @return void
+ */
+ public function setMenu()
+ {
+ $repoID = $this->session->repoID;
+ $moduleName = $this->app->getModuleName();
+ foreach($this->lang->{$moduleName}->menu as $key => $menu) common::setMenuVars($this->lang->{$moduleName}->menu, $key, $repoID);
+ $this->lang->{$moduleName}->menuOrder = $this->lang->ci->menuOrder;
+ }
+
+ /**
+ * Send a request to jenkins to check build status.
+ *
+ * @access public
+ * @return bool
+ */
+ public function checkBuildStatus()
+ {
+ $compiles = $this->dao->select('t1.*, t2.jenkinsJob, t3.name jenkinsName,t3.serviceUrl,t3.account,t3.token,t3.password')
+ ->from(TABLE_COMPILE)->alias('t1')
+ ->leftJoin(TABLE_INTEGRATION)->alias('t2')->on('t1.cijob=t2.id')
+ ->leftJoin(TABLE_JENKINS)->alias('t3')->on('t2.jenkins=t3.id')
+ ->where('t1.status')->ne('success')
+ ->andWhere('t1.status')->ne('fail')
+ ->andWhere('t1.status')->ne('timeout')
+ ->andWhere('t1.createdDate')->gt(date(DT_DATETIME1, strtotime("-1 day")))
+ ->fetchAll();
+
+ foreach($compiles as $compile)
+ {
+ $jenkinsServer = $compile->serviceUrl;
+ $jenkinsUser = $compile->account;
+ $jenkinsPassword = $compile->token ? $compile->token : base64_decode($compile->password);
+
+ $jenkinsAuth = '://' . $jenkinsUser . ':' . $jenkinsPassword . '@';
+ $jenkinsServer = str_replace('://', $jenkinsAuth, $jenkinsServer);
+ $queueUrl = sprintf('%s/queue/item/%s/api/json', $jenkinsServer, $compile->queueItem);
+
+ $response = common::http($queueUrl);
+ if(strripos($response, "404") > -1)
+ {
+ /* Queue expired, use another api. */
+ $infoUrl = sprintf('%s/job/%s/%s/api/json', $jenkinsServer, $compile->jenkinsJob, $compile->queueItem);
+ $response = common::http($infoUrl);
+ $buildInfo = json_decode($response);
+ $result = strtolower($buildInfo->result);
+ $this->updateBuildStatus($compile, $result);
+
+ $logUrl = sprintf('%s/job/%s/%s/consoleText', $jenkinsServer, $compile->jenkinsJob, $compile->queueItem);
+ $response = common::http($logUrl);
+ $logs = json_decode($response);
+
+ $this->dao->update(TABLE_COMPILE)->set('logs')->eq($response)->where('id')->eq($compile->id)->exec();
+ }
+ else
+ {
+ $queueInfo = json_decode($response);
+ if(!empty($queueInfo->executable))
+ {
+ $buildUrl = $queueInfo->executable->url . 'api/json?pretty=true';
+ $buildUrl = str_replace('://', $jenkinsAuth, $buildUrl);
+
+ $response = common::http($buildUrl);
+ $buildInfo = json_decode($response);
+
+ if($buildInfo->building)
+ {
+ $this->updateBuildStatus($compile, 'building');
+ }
+ else
+ {
+ $result = strtolower($buildInfo->result);
+ $this->updateBuildStatus($compile, $result);
+
+ $logUrl = $buildInfo->url . 'logText/progressiveText/api/json';
+ $logUrl = str_replace('://', $jenkinsAuth, $logUrl);
+
+ $response = common::http($logUrl);
+ $logs = json_decode($response);
+
+ $this->dao->update(TABLE_COMPILE)->set('logs')->eq($response)->where('id')->eq($compile->id)->exec();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Update ci build status.
+ *
+ * @param object $build
+ * @param string $status
+ * @access public
+ * @return bool
+ */
+ public function updateBuildStatus($build, $status)
+ {
+ $this->dao->update(TABLE_COMPILE)->set('status')->eq($status)->where('id')->eq($build->id)->exec();
+ $this->dao->update(TABLE_INTEGRATION)->set('lastExec')->eq(helper::now())->set('lastStatus')->eq($status)->where('id')->eq($build->cijob)->exec();
+ }
+
+ /**
+ * @param $url
+ * @return false|mixed|string
+ */
+ public function sendRequest($url, $data)
+ {
+ if(!empty($data->PARAM_TAG)) $data->PARAM_REVISION = '';
+
+ $response = common::http($url, $data, true);
+ if(preg_match("!Location: .*item/(.*)/!", $response, $matches)) return $matches[1];
+ return 0;
+ }
+}
diff --git a/module/common/lang/en.php b/module/common/lang/en.php
index 4b340ce715..8620d479a5 100644
--- a/module/common/lang/en.php
+++ b/module/common/lang/en.php
@@ -101,6 +101,7 @@ $lang->loading = 'Loading...';
$lang->notFound = 'Not found!';
$lang->notPage = 'Sorry, the features you are visiting are in development!';
$lang->showAll = '[[Show All]]';
+$lang->selectedItems = 'Seleted {0} items';
$lang->future = 'Waiting';
$lang->year = 'Year';
@@ -122,7 +123,7 @@ $lang->menu->my = 'Dashboard|my|index';
$lang->menu->product = $lang->productCommon . '|product|index|locate=no';
$lang->menu->project = $lang->projectCommon . '|project|index|locate=no';
$lang->menu->qa = 'Test|qa|index';
-$lang->menu->repo = 'Code|repo|log';
+$lang->menu->ci = 'CI|repo|browse';
$lang->menu->doc = 'Doc|doc|index';
$lang->menu->report = 'Report|report|index';
$lang->menu->company = 'Company|company|index';
@@ -331,11 +332,17 @@ $lang->caselib->menu->testsuite = array('link' => 'Suite|testsuite|browse|');
$lang->caselib->menu->report = array('link' => 'Report|testreport|browse|');
$lang->caselib->menu->caselib = array('link' => 'Case Library|caselib|browse|libID=%s', 'alias' => 'create,createcase,view,edit,batchcreatecase,showimport', 'subModule' => 'tree,testcase');
-$lang->repo = new stdclass();
-$lang->repo->menu = new stdclass();
-$lang->repo->menu->browse = array('link' =>'Browse|repo|log|repoID=%s&entry=', 'alias' => 'diff,view,revision,showsynccomment');
-$lang->repo->menu->settings = 'Settings|repo|settings|repoID=%s';
-$lang->repo->menu->delete = array('link' => 'Delete|repo|delete|repoID=%s', 'target' => 'hiddenwin');
+$lang->ci = new stdclass();
+$lang->ci->menu = new stdclass();
+$lang->ci->menu->browse = array('link' =>'Code|repo|browse|repoID=%s', 'alias' => 'diff,view,revision,log,blame,showsynccomment');
+$lang->ci->menu->job = array('link' =>'Build|ci|browsejob', 'alias' => 'createjob,editjob,browsebuild,viewbuildlogs');
+$lang->ci->menu->maintain = array('link' =>'Repo|repo|maintain', 'alias' => 'create,edit');
+$lang->ci->menu->jenkins = array('link' =>'Jenkins|jenkins|browse', 'alias' => 'create,edit');
+
+$lang->repo = new stdclass();
+$lang->jenkins = new stdclass();
+$lang->repo->menu = $lang->ci->menu;
+$lang->jenkins->menu = $lang->ci->menu;
/* Doc menu settings. */
$lang->doc = new stdclass();
@@ -475,6 +482,9 @@ $lang->menugroup->entry = 'admin';
$lang->menugroup->webhook = 'admin';
$lang->menugroup->message = 'admin';
+$lang->menugroup->repo = 'ci';
+$lang->menugroup->jenkins = 'ci';
+
/* Error info. */
$lang->error = new stdclass();
$lang->error->companyNotFound = "The domain %s cannot be found!";
@@ -501,6 +511,7 @@ $lang->error->pasteImg = 'Images are not allowed to be pasted in your bro
$lang->error->noData = 'No data.';
$lang->error->editedByOther = 'This record might have been changed. Please refresh and try to edit again!';
$lang->error->tutorialData = 'No data can be imported in tutorial mode. Please quit tutorial first!';
+$lang->error->noCurlExt = 'No Curl module installed';
/* Page info. */
$lang->pager = new stdclass();
diff --git a/module/common/lang/menuOrder.php b/module/common/lang/menuOrder.php
index 559ae5d648..a92c4942b0 100644
--- a/module/common/lang/menuOrder.php
+++ b/module/common/lang/menuOrder.php
@@ -4,7 +4,7 @@ $lang->menuOrder[5] = 'my';
$lang->menuOrder[10] = 'product';
$lang->menuOrder[15] = 'project';
$lang->menuOrder[20] = 'qa';
-$lang->menuOrder[25] = 'repo';
+$lang->menuOrder[25] = 'ci';
$lang->menuOrder[30] = 'doc';
$lang->menuOrder[35] = 'report';
$lang->menuOrder[40] = 'company';
@@ -77,9 +77,14 @@ $lang->testsuite->menuOrder = $lang->testcase->menuOrder;
$lang->caselib->menuOrder = $lang->testcase->menuOrder;
$lang->testreport->menuOrder = $lang->testcase->menuOrder;
-$lang->repo->menuOrder[5] = 'browse';
-$lang->repo->menuOrder[15] = 'settings';
-$lang->repo->menuOrder[20] = 'delete';
+$lang->ci->menuOrder[5] = 'browse';
+$lang->ci->menuOrder[10] = 'job';
+$lang->ci->menuOrder[15] = 'maintain';
+$lang->ci->menuOrder[20] = 'jenkins';
+$lang->ci->menuOrder[25] = 'match';
+
+$lang->repo->menuOrder = $lang->ci->menuOrder;
+$lang->jenkins->menuOrder = $lang->ci->menuOrder;
/* doc menu order. */
$lang->doc->menuOrder[5] = 'list';
diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php
index 38f239887e..71e2229138 100644
--- a/module/common/lang/zh-cn.php
+++ b/module/common/lang/zh-cn.php
@@ -101,6 +101,7 @@ $lang->loading = '稍候...';
$lang->notFound = '抱歉,您访问的对象并不存在!';
$lang->notPage = '抱歉,您访问的功能正在开发中!';
$lang->showAll = '[[全部显示]]';
+$lang->selectedItems = '已选择 {0} 项';
$lang->future = '未来';
$lang->year = '年';
@@ -122,7 +123,7 @@ $lang->menu->my = ' 我的地盘|my|index';
$lang->menu->product = $lang->productCommon . '|product|index|locate=no';
$lang->menu->project = $lang->projectCommon . '|project|index|locate=no';
$lang->menu->qa = '测试|qa|index';
-$lang->menu->repo = '代码|repo|log';
+$lang->menu->ci = '集成|repo|browse';
$lang->menu->doc = '文档|doc|index';
$lang->menu->report = '统计|report|index';
$lang->menu->company = '组织|company|index';
@@ -331,11 +332,22 @@ $lang->caselib->menu->testsuite = array('link' => '套件|testsuite|browse|');
$lang->caselib->menu->report = array('link' => '报告|testreport|browse|');
$lang->caselib->menu->caselib = array('link' => '用例库|caselib|browse|libID=%s', 'alias' => 'create,createcase,view,edit,batchcreatecase,showimport', 'subModule' => 'tree,testcase');
-$lang->repo = new stdclass();
-$lang->repo->menu = new stdclass();
-$lang->repo->menu->browse = array('link' =>'浏览|repo|log|repoID=%s&entry=', 'alias' => 'diff,view,revision,showsynccomment');
-$lang->repo->menu->settings = '设置|repo|settings|repoID=%s';
-$lang->repo->menu->delete = array('link' => '删除|repo|delete|repoID=%s', 'target' => 'hiddenwin');
+$lang->ci = new stdclass();
+$lang->ci->menu = new stdclass();
+$lang->ci->menu->browse = array('link' =>'代码|repo|browse|repoID=%s', 'alias' => 'diff,view,revision,log,blame,showsynccomment');
+$lang->ci->menu->job = array('link' =>'构建|integration|browse', 'subModule' => 'compile,integration');
+$lang->ci->menu->maintain = array('link' =>'版本库|repo|maintain', 'alias' => 'create,edit');
+$lang->ci->menu->jenkins = array('link' =>'Jenkins|jenkins|browse', 'alias' => 'create,edit');
+$lang->ci->menu->match = array('link' =>'匹配设置|repo|setmatchcomment');
+
+$lang->repo = new stdclass();
+$lang->jenkins = new stdclass();
+$lang->compile = new stdclass();
+$lang->integration = new stdclass();
+$lang->repo->menu = $lang->ci->menu;
+$lang->jenkins->menu = $lang->ci->menu;
+$lang->compile->menu = $lang->ci->menu;
+$lang->integration->menu = $lang->ci->menu;
/* 文档视图菜单设置。*/
$lang->doc = new stdclass();
@@ -381,7 +393,7 @@ $lang->admin->menu = new stdclass();
$lang->admin->menu->index = array('link' => '首页|admin|index', 'alias' => 'register,certifytemail,certifyztmobile,ztcompany');
$lang->admin->menu->message = array('link' => '通知|message|index', 'subModule' => 'message,mail,webhook');
$lang->admin->menu->custom = array('link' => '自定义|custom|set', 'subModule' => 'custom');
-$lang->admin->menu->sso = array('link' => '集成|admin|sso');
+$lang->admin->menu->sso = array('link' => '集成|admin|sso', 'subModule' => '');
$lang->admin->menu->extension = array('link' => '插件|extension|browse', 'subModule' => 'extension');
$lang->admin->menu->dev = array('link' => '二次开发|dev|api', 'alias' => 'db', 'subModule' => 'dev,entry');
$lang->admin->menu->translate = array('link' => '翻译|dev|translate');
@@ -475,6 +487,11 @@ $lang->menugroup->entry = 'admin';
$lang->menugroup->webhook = 'admin';
$lang->menugroup->message = 'admin';
+$lang->menugroup->repo = 'ci';
+$lang->menugroup->jenkins = 'ci';
+$lang->menugroup->compile = 'ci';
+$lang->menugroup->integration = 'ci';
+
/* 错误提示信息。*/
$lang->error = new stdclass();
$lang->error->companyNotFound = "您访问的域名 %s 没有对应的公司。";
@@ -501,6 +518,7 @@ $lang->error->pasteImg = '您的浏览器不支持粘贴图片!';
$lang->error->noData = '没有数据';
$lang->error->editedByOther = '该记录可能已经被改动。请刷新页面重新编辑!';
$lang->error->tutorialData = '新手模式下不会插入数据,请退出新手模式操作';
+$lang->error->noCurlExt = '服务器未安装Curl模块。';
/* 分页信息。*/
$lang->pager = new stdclass();
diff --git a/module/common/model.php b/module/common/model.php
index f03e436704..81d73d047f 100644
--- a/module/common/model.php
+++ b/module/common/model.php
@@ -662,11 +662,8 @@ class commonModel extends model
echo '
' . html::a(helper::createLink('my', 'index'), $lang->zentaoPMS) . '';
if($moduleName != 'index')
{
- if(!isset($lang->menu->$mainMenu))
- {
- echo "";
- return;
- }
+ if(!isset($lang->menu->$mainMenu)) return print("");
+
$menuLink = $lang->menu->$mainMenu;
list($menuLabel, $module, $method) = explode('|', $menuLink);
echo '' . html::a(helper::createLink($module, $method), $menuLabel) . '';
@@ -1823,7 +1820,7 @@ EOD;
* @access public
* @return string
*/
- public static function http($url, $data = null)
+ public static function http($url, $data = null, $optHeader = false)
{
global $lang, $app;
if(!extension_loaded('curl')) return json_encode(array('result' => 'fail', 'message' => $lang->error->noCurlExt));
@@ -1844,6 +1841,7 @@ EOD;
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLINFO_HEADER_OUT, TRUE);
+ if($optHeader) curl_setopt($curl, CURLOPT_HEADER, true);
if(!empty($data))
{
curl_setopt($curl, CURLOPT_POST, true);
diff --git a/module/compile/control.php b/module/compile/control.php
new file mode 100644
index 0000000000..054b5879b6
--- /dev/null
+++ b/module/compile/control.php
@@ -0,0 +1,74 @@
+
+ * @package compile
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class compile extends control
+{
+ /**
+ * Construct
+ *
+ * @param string $moduleName
+ * @param string $methodName
+ * @access public
+ * @return void
+ */
+ public function __construct($moduleName = '', $methodName = '')
+ {
+ parent::__construct($moduleName, $methodName);
+ $this->loadModel('ci')->setMenu();
+ }
+
+ /**
+ * Browse jenkins build.
+ *
+ * @param string $orderBy
+ * @param int $recTotal
+ * @param int $recPerPage
+ * @param int $pageID
+ * @access public
+ * @return void
+ */
+ public function browse($jobID = 0, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
+ {
+ $this->app->loadClass('pager', $static = true);
+ $pager = new pager($recTotal, $recPerPage, $pageID);
+
+ $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->compile->browse;
+ $this->view->position[] = html::a($this->createLink('integration', 'browse'), $this->lang->ci->job);
+ $this->view->position[] = $this->lang->compile->browse;
+
+ $this->view->buildList = $this->compile->getList($jobID, $orderBy, $pager);
+ $this->view->job = $this->loadModel('integration')->getByID($jobID);
+ $this->view->orderBy = $orderBy;
+ $this->view->pager = $pager;
+ $this->display();
+ }
+
+ /**
+ * View jenkins build logs.
+ *
+ * @param int $buildID
+ * @access public
+ * @return void
+ */
+ public function logs($buildID)
+ {
+ $build = $this->compile->getByID($buildID);
+ $this->view->logs = str_replace("\r\n","
", $build->logs);
+ $this->view->build = $build;
+
+ $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->compile->logs;
+ $this->view->position[] = html::a($this->createLink('integration', 'browse'), $this->lang->ci->job);
+ $this->view->position[] = html::a($this->createLink('compile', 'browse', "jobID=" . $build->cijob), $this->lang->compile->browse);
+ $this->view->position[] = $this->lang->compile->logs;
+ $this->display();
+ }
+}
+
diff --git a/module/compile/lang/en.php b/module/compile/lang/en.php
new file mode 100644
index 0000000000..0f81e70164
--- /dev/null
+++ b/module/compile/lang/en.php
@@ -0,0 +1,15 @@
+compile->browse = 'Build Histories';
+$lang->compile->logs = 'Build Logs';
+
+$lang->compile->id = 'ID';
+$lang->compile->name = 'Name';
+$lang->compile->status = 'Build Status';
+$lang->compile->time = 'Build Time';
+
+$lang->compile->statusList['success'] = 'Success';
+$lang->compile->statusList['fail'] = 'Fail';
+$lang->compile->statusList['created'] = 'Created';
+$lang->compile->statusList['building'] = 'Building';
+$lang->compile->statusList['create_fail'] = 'Fail to create';
+$lang->compile->statusList['timeout'] = 'Exec Timeout';
diff --git a/module/compile/lang/zh-cn.php b/module/compile/lang/zh-cn.php
new file mode 100644
index 0000000000..2766e54c60
--- /dev/null
+++ b/module/compile/lang/zh-cn.php
@@ -0,0 +1,15 @@
+compile->browse = '构建历史';
+$lang->compile->logs = '构建日志';
+
+$lang->compile->id = 'ID';
+$lang->compile->name = '名称';
+$lang->compile->status = '构建状态';
+$lang->compile->time = '构建时间';
+
+$lang->compile->statusList['success'] = '成功';
+$lang->compile->statusList['fail'] = '失败';
+$lang->compile->statusList['created'] = '新建';
+$lang->compile->statusList['building'] = '构建中';
+$lang->compile->statusList['create_fail'] = '创建失败';
+$lang->compile->statusList['timeout'] = '执行超时';
diff --git a/module/compile/model.php b/module/compile/model.php
new file mode 100644
index 0000000000..adb7a1fca5
--- /dev/null
+++ b/module/compile/model.php
@@ -0,0 +1,153 @@
+
+ * @package compile
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class compileModel extends model
+{
+ /**
+ * Get build list.
+ *
+ * @param int $jobID
+ * @param string $orderBy
+ * @param object $pager
+ * @access public
+ * @return array
+ */
+ public function getList($jobID, $orderBy = 'id_desc', $pager = null)
+ {
+ return $this->dao->select('t1.id, t1.name, t1.status, t1.createdDate, t2.triggerType, t3.name as repoName, t4.name as jenkinsName')->from(TABLE_COMPILE)->alias('t1')
+ ->leftJoin(TABLE_INTEGRATION)->alias('t2')->on('t1.cijob=t2.id')
+ ->leftJoin(TABLE_REPO)->alias('t3')->on('t2.repo=t3.id')
+ ->leftJoin(TABLE_JENKINS)->alias('t4')->on('t2.jenkins=t4.id')
+ ->where('t1.deleted')->eq('0')
+ ->andWhere('t1.cijob')->eq($jobID)
+ ->orderBy($orderBy)
+ ->page($pager)
+ ->fetchAll('id');
+ }
+
+ /**
+ * Get by id
+ *
+ * @param int $buildID
+ * @access public
+ * @return object
+ */
+ public function getByID($buildID)
+ {
+ return $this->dao->select('*')->from(TABLE_COMPILE)->where('id')->eq($buildID)->fetch();
+ }
+
+ /**
+ * Get unexecuted list.
+ *
+ * @access public
+ * @return array
+ */
+ public function getUnexecutedList()
+ {
+ return $this->dao->select('*')->from(TABLE_COMPILE)->where('status')->eq('')->andWhere('deleted')->eq('0')->fetchAll();
+ }
+
+ /**
+ * Save build by job
+ *
+ * @param object $job
+ * @access public
+ * @return void
+ */
+ public function createByIntegration($integrationID)
+ {
+ $integration = $this->dao->select('id,name')->from(TABLE_INTEGRATION)->where('id')->eq($integrationID)->fetch();
+
+ $build = new stdClass();
+ $build->cijob = $integration->id;
+ $build->name = $integration->name;
+ $build->createdBy = $this->app->user->account;
+ $build->createdDate = helper::now();
+
+ $this->dao->insert(TABLE_COMPILE)->data($build)->exec();
+ }
+
+ /**
+ * Execute compile
+ *
+ * @param object $compile
+ * @access public
+ * @return bool
+ */
+ public function execByCompile($compile, $data = null)
+ {
+ $integration = $this->dao->select('t1.id as jobId,t1.name as jobName,t1.repo,t1.jenkinsJob,t2.name as jenkinsName,t2.serviceUrl,t2.account,t2.token,t2.password')
+ ->from(TABLE_INTEGRATION)->alias('t1')
+ ->leftJoin(TABLE_JENKINS)->alias('t2')->on('t1.jenkins=t2.id')
+ ->where('t1.id')->eq($compile->cijob)
+ ->fetch();
+
+ if(!$integration) return false;
+
+ $buildUrl = $this->getBuildUrl($integration);
+ $build = new stdclass();
+ $build->queueItem = $this->loadModel('ci')->sendRequest($buildUrl, $data);
+ $build->status = $build->queueItem ? 'created' : 'create_fail';
+ $this->dao->update(TABLE_COMPILE)->data($build)->where('id')->eq($compile->id)->exec();
+
+ return !dao::isError();
+ }
+
+ /**
+ * Execute by integration.
+ *
+ * @param int $compile
+ * @access public
+ * @return bool
+ */
+ public function execByIntegration($integrationID, $data = null)
+ {
+ $integration = $this->dao->select('t1.id as jobId,t1.name as jobName,t1.repo,t1.jenkinsJob,t2.name as jenkinsName,t2.serviceUrl,t2.account,t2.token,t2.password')
+ ->from(TABLE_INTEGRATION)->alias('t1')
+ ->leftJoin(TABLE_JENKINS)->alias('t2')->on('t1.jenkins=t2.id')
+ ->where('t1.id')->eq($integrationID)
+ ->fetch();
+
+ if(!$integration) return false;
+
+ $buildUrl = $this->getBuildUrl($integration);
+ $build = new stdClass();
+ $build->cijob = $integration->jobId;
+ $build->name = $integration->jobName;
+ $build->queueItem = $this->loadModel('ci')->sendRequest($buildUrl, $data);
+ $build->status = $build->queueItem ? 'created' : 'create_fail';
+ $build->createdBy = $this->app->user->account;
+ $build->createdDate = helper::now();
+ $this->dao->insert(TABLE_COMPILE)->data($build)->exec();
+
+ return !dao::isError();
+ }
+
+ /**
+ * Get build url.
+ *
+ * @param object $jenkins
+ * @access public
+ * @return string
+ */
+ public function getBuildUrl($jenkins)
+ {
+ $jenkinsServer = $jenkins->serviceUrl;
+ $jenkinsUser = $jenkins->account;
+ $jenkinsPassword = $jenkins->token ? $jenkins->token : base64_decode($jenkins->password);
+
+ $jenkinsAuth = '://' . $jenkinsUser . ':' . $jenkinsPassword . '@';
+ $jenkinsServer = str_replace('://', $jenkinsAuth, $jenkinsServer);
+ $buildUrl = sprintf('%s/job/%s/buildWithParameters/api/json', $jenkinsServer, $jenkins->jenkinsJob);
+ return $buildUrl;
+ }
+}
diff --git a/module/compile/view/browse.html.php b/module/compile/view/browse.html.php
new file mode 100644
index 0000000000..d4e8477276
--- /dev/null
+++ b/module/compile/view/browse.html.php
@@ -0,0 +1,63 @@
+
+ * @package compile
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+ createLink('integration', 'browse'), "{$lang->ci->task}", '', "class='btn btn-link'");
+ echo html::a($this->createLink('compile', 'browse'), "{$lang->ci->history}", '', "class='btn btn-link btn-active-text'");
+ ?>
+
+
+
+
+
diff --git a/module/compile/view/logs.html.php b/module/compile/view/logs.html.php
new file mode 100644
index 0000000000..7a0e262e81
--- /dev/null
+++ b/module/compile/view/logs.html.php
@@ -0,0 +1,27 @@
+
+ * @package compile
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+ cijob"), " ". $lang->goback, '', "class='btn btn-secondary'");?>
+
+
+
+
diff --git a/module/cron/control.php b/module/cron/control.php
index 81fbe3592c..7eae17551f 100644
--- a/module/cron/control.php
+++ b/module/cron/control.php
@@ -211,9 +211,12 @@ class cron extends control
}
/* Save log. */
- $log = '';
- $time = $now->format('G:i:s');
- $log = "$time task " . $id . " executed,\ncommand: $cron[command].\nreturn : $return.\noutput : $output\n";
+ $log = '';
+ $time = $now->format('G:i:s');
+ $output = "\n";
+ if(strlen($output) > 100) $output = "\n" . $output;
+
+ $log = "$time task " . $id . " executed,\ncommand: $cron[command].\nreturn : $return.\noutput : $output\n";
$this->cron->logCron($log);
unset($log);
}
diff --git a/module/file/control.php b/module/file/control.php
index f1a63316ad..d4c37047f0 100644
--- a/module/file/control.php
+++ b/module/file/control.php
@@ -149,8 +149,7 @@ class file extends control
/* Down the file. */
$fileName = $file->title;
if(!preg_match("/\.{$file->extension}$/", $fileName)) $fileName .= '.' . $file->extension;
- $fileData = file_get_contents($file->realPath);
- $this->sendDownHeader($fileName, $file->extension, $fileData);
+ $this->sendDownHeader($fileName, $file->extension, $file->realPath, 'file');
}
}
else
@@ -244,9 +243,9 @@ class file extends control
* @access public
* @return void
*/
- public function sendDownHeader($fileName, $fileType, $content)
+ public function sendDownHeader($fileName, $fileType, $content, $type = 'content')
{
- $this->file->sendDownHeader($fileName, $fileType, $content);
+ $this->file->sendDownHeader($fileName, $fileType, $content, $type);
}
/**
diff --git a/module/file/model.php b/module/file/model.php
index a48d187cab..ac16d9664a 100644
--- a/module/file/model.php
+++ b/module/file/model.php
@@ -869,7 +869,7 @@ class fileModel extends model
* @access public
* @return void
*/
- public function sendDownHeader($fileName, $fileType, $content)
+ public function sendDownHeader($fileName, $fileType, $content, $type = 'content')
{
/* Clean the ob content to make sure no space or utf-8 bom output. */
$obLevel = ob_get_level();
@@ -893,6 +893,14 @@ class fileModel extends model
header("Content-Disposition: attachment; filename=\"$fileName\"");
header("Pragma: no-cache");
header("Expires: 0");
- die($content);
+ if($type == 'content') die($content);
+ if($type == 'file' and file_exists($content))
+ {
+ $chunkSize = 1024 * 1024;
+ $handle = fopen($content, "r");
+ while(!feof($handle)) echo fread($handle, $chunkSize);
+ fclose($handle);
+ die();
+ }
}
}
diff --git a/module/git/config.php b/module/git/config.php
index 8033ef700f..a4abe2dafc 100644
--- a/module/git/config.php
+++ b/module/git/config.php
@@ -1,27 +1,2 @@
git->client = '/usr/bin/git'; // c:\git\git.exe
- * $config->git->repos['pms']['path'] = '/home/user/repo/pms'; // c:\repo\pms
- *
- */
-$config->git = new stdClass();
-$config->git->encodings = 'utf-8';
-$config->git->client = '';
-$i = 1;
-$config->git->repos[$i]['path'] = '';
-$config->git->repos[$i]['encoding'] = 'utf-8';
-
-/*
-$i ++;
-$config->git->repos[$i]['path'] = '';
-*/
diff --git a/module/git/control.php b/module/git/control.php
index f8c3c34228..b94baca7fe 100644
--- a/module/git/control.php
+++ b/module/git/control.php
@@ -37,10 +37,9 @@ class git extends control
$path = helper::safe64Decode($path);
if(common::hasPriv('repo', 'diff'))
{
- $repos = $this->loadModel('repo')->getAllRepos();
+ $repos = $this->loadModel('repo')->getListBySCM('Git', 'haspriv');
foreach($repos as $repo)
{
- if($repo->SCM != 'Git') continue;
if(strpos($path, $repo->path) === 0)
{
$entry = $this->repo->encodePath(str_replace($repo->path, '', $path));
@@ -72,10 +71,9 @@ class git extends control
$path = helper::safe64Decode($path);
if(common::hasPriv('repo', 'view'))
{
- $repos = $this->loadModel('repo')->getAllRepos();
+ $repos = $this->loadModel('repo')->getListBySCM('Git', 'haspriv');
foreach($repos as $repo)
{
- if($repo->SCM != 'Git') continue;
if(strpos($path, $repo->path) === 0)
{
$entry = $this->repo->encodePath(str_replace($repo->path, '', $path));
@@ -121,9 +119,11 @@ class git extends control
}
$parsedObjects = array('stories' => array(), 'tasks' => array(), 'bugs' => array());
+ $this->loadModel('repo');
foreach($parsedLogs as $log)
{
- $objects = $this->git->parseComment($log->msg);
+ $objects = $this->repo->parseComment($log->msg);
+
if($objects)
{
$this->git->saveAction2PMS($objects, $log, $repoRoot);
@@ -181,7 +181,8 @@ class git extends control
$parsedFiles[$action][] = ltrim($path, '/');
}
- $objects = $this->git->parseComment($message);
+ $objects = $this->loadModel('repo')->parseComment($message);
+
if($objects)
{
$log = new stdclass();
diff --git a/module/git/model.php b/module/git/model.php
index 844f2724ac..c5f0099467 100644
--- a/module/git/model.php
+++ b/module/git/model.php
@@ -71,6 +71,7 @@ class gitModel extends model
{
parent::__construct();
$this->loadModel('action');
+ $this->loadModel('repo');
}
/**
@@ -87,51 +88,59 @@ class gitModel extends model
$this->setLogRoot();
$this->setRestartFile();
- foreach($this->repos as $name => $repo)
+ foreach($this->repos as $repo)
{
- $this->printLog("begin repo $name");
- $repo = (object)$repo;
- $repo->name = $name;
+ $this->printLog("begin repo $repo->id");
if(!$this->setRepo($repo)) return false;
$savedRevision = $this->getSavedRevision();
$this->printLog("start from revision $savedRevision");
- $logs = $this->getRepoLogs($repo, $savedRevision);
- if(empty($logs)) continue;
-
- $this->printLog("get " . count($logs) . " logs");
- $this->printLog('begin parsing logs');
- $latestRevision = $logs[0]->revision;
- foreach($logs as $log)
+ $logs = $this->getRepoLogs($repo, $savedRevision);
+ $objects = array();
+ if(!empty($logs))
{
- $this->printLog("parsing log {$log->revision}");
- if($log->revision == $savedRevision)
+ $this->printLog("get " . count($logs) . " logs");
+ $this->printLog('begin parsing logs');
+ $latestRevision = $logs[0]->revision;
+
+ foreach($logs as $log)
{
- $this->printLog("{$log->revision} alread parsed, commit it");
- continue;
+ $this->printLog("parsing log {$log->revision}");
+ if($log->revision == $savedRevision)
+ {
+ $this->printLog("{$log->revision} alread parsed, commit it");
+ continue;
+ }
+
+ $this->printLog("comment is\n----------\n" . trim($log->msg) . "\n----------");
+
+ $objects = $this->repo->parseComment($log->msg);
+
+ if($objects)
+ {
+ $this->printLog('extract' .
+ ' story:' . join(' ', $objects['stories']) .
+ ' task:' . join(' ', $objects['tasks']) .
+ ' bug:' . join(',', $objects['bugs']));
+
+ $this->saveAction2PMS($objects, $log, $repo->encoding);
+ }
+ else
+ {
+ $this->printLog('no objects found' . "\n");
+ }
}
- $this->printLog("comment is\n----------\n" . trim($log->msg) . "\n----------");
- $objects = $this->parseComment($log->msg);
- if($objects)
- {
- $this->printLog('extract' .
- ' story:' . join(' ', $objects['stories']) .
- ' task:' . join(' ', $objects['tasks']) .
- ' bug:' . join(',', $objects['bugs']));
-
- $this->saveAction2PMS($objects, $log);
- }
- else
- {
- $this->printLog('no objects found' . "\n");
- }
+ $this->saveLastRevision($latestRevision);
+ $this->printLog("save revision $latestRevision");
+ $this->deleteRestartFile();
+ $this->printLog("\n\nrepo #" . $repo->id . ': ' . $repo->path . " finished");
}
- $this->saveLastRevision($latestRevision);
- $this->printLog("save revision $latestRevision");
- $this->deleteRestartFile();
- $this->printLog("\n\nrepo $name finished");
+ // exe ci jobs in log
+ $cijobIdList = zget($objects, 'integrations', array());
+ $this->loadModel('compile');
+ foreach($cijobIdList as $id) $this->compile->execByIntegration($id);
}
}
@@ -177,13 +186,46 @@ class gitModel extends model
*/
public function setRepos()
{
- if(!$this->config->git->repos)
+ $repos = $this->loadModel('repo')->getListBySCM('Git');
+ $gitRepos = array();
+ $paths = array();
+ foreach($repos as $repo)
{
- echo "You must set one git repo.\n";
- return false;
+ if(!isset($paths[$repo->path]))
+ {
+ unset($repo->acl);
+ unset($repo->desc);
+ $gitRepos[] = $repo;
+ $paths[$repo->path] = $repo->path;
+ }
}
- $this->repos = $this->config->git->repos;
+ if(isset($this->config->git->repos))
+ {
+ foreach($this->config->git->repos as $i => $repo)
+ {
+ $repoPath = $repo['path'];
+ if(empty($repoPath)) continue;
+ if(isset($paths[$repoPath])) continue;
+
+ $gitRepo = new stdclass();
+ $gitRepo->id = "c{$i}";
+ $gitRepo->client = $this->config->git->client;
+ $gitRepo->path = $repoPath;
+ $gitRepo->prefix = '';
+ $gitRepo->SCM = 'Git';
+ $gitRepo->account = '';
+ $gitRepo->password = '';
+ $gitRepo->encoding = zget($repo, 'encoding', $this->config->git->client);
+
+ $gitRepos[] = $gitRepo;
+ $paths[$repoPath] = $repoPath;
+ }
+ }
+
+ if(empty($gitRepos)) echo "You must set one git repo.\n";
+
+ $this->repos = $gitRepos;
return true;
}
@@ -195,15 +237,11 @@ class gitModel extends model
*/
public function getRepos()
{
- $repos = array();
- if(!$this->config->git->repos) return $repos;
+ $repos = $this->setRepos();
+ $repoPairs = array();
+ foreach($repos as $repo) $repoPairs[] = $repo->path;
- foreach($this->config->git->repos as $repo)
- {
- if(empty($repo['path'])) continue;
- $repos[] = $repo['path'];
- }
- return $repos;
+ return $repoPairs;
}
/**
@@ -218,40 +256,49 @@ class gitModel extends model
$this->setClient($repo);
if(empty($this->client)) return false;
- $this->setLogFile($repo->name);
+ $this->setLogFile($repo->id);
+ $this->setTagFile($repo->id);
$this->setRepoRoot($repo);
return true;
}
/**
* Set the git binary client of a repo.
- *
- * @param object $repo
+ *
+ * @param object $repo
* @access public
* @return bool
*/
+
public function setClient($repo)
{
- if($this->config->git->client == '')
- {
- echo "You must set the git client file.\n";
- return false;
- }
-
- $this->client = $this->config->git->client;
+ $this->client = $repo->client;
return true;
}
/**
* Set the log file of a repo.
- *
- * @param string $repoName
+ *
+ * @param string $repoId
* @access public
* @return void
*/
- public function setLogFile($repoName)
+ public function setLogFile($repoId)
{
- $this->logFile = $this->logRoot . $repoName;
+ $this->logFile = $this->logRoot . $repoId . '.log';
+ }
+
+ /**
+ * Set the tag file of a repo.
+ *
+ * @param string $repoId
+ * @access public
+ * @return void
+ */
+ public function setTagFile($repoId)
+ {
+ $this->setLogRoot();
+ $this->tagFile = $this->logRoot . $repoId . '.tag';
}
/**
@@ -266,6 +313,20 @@ class gitModel extends model
$this->repoRoot = $repo->path;
}
+ /**
+ * get tags histories for repo.
+ *
+ * @param object $repo
+ * @access public
+ * @return void
+ */
+ public function getRepoTags($repo)
+ {
+ $scm = $this->app->loadClass('scm');
+ $scm->setEngine($repo);
+ return $scm->tags('');
+ }
+
/**
* Get repo logs.
*
@@ -276,40 +337,22 @@ class gitModel extends model
*/
public function getRepoLogs($repo, $fromRevision)
{
- $parsedLogs = array();
+ $scm = $this->app->loadClass('scm');
+ $scm->setEngine($repo);
+ $logs = $scm->log('', $fromRevision);
+ if(empty($logs)) return false;
- /* The git log command. */
- chdir($this->repoRoot);
- exec("{$this->client} config core.quotepath false");
- if($fromRevision)
+ foreach($logs as $log)
{
- $cmd = "$this->client log --stat=1024 --stat-name-width=1000 --name-status $fromRevision..HEAD";
- }
- else
- {
- $cmd = "$this->client log --stat=1024 --stat-name-width=1000 --name-status";
- }
- exec($cmd, $list, $return);
+ $log->author = $log->committer;
+ $log->msg = $log->comment;
+ $log->date = $log->time;
- if(!$list and $return)
- {
- echo "Some error occers: \nThe command is $cmd\n";
- return false;
+ /* Process files. */
+ $log->files = array();
+ foreach($log->change as $file => $info) $log->files[$info['action']][] = $file;
}
- if(!$list and !$return) return array();
-
- /* Process logs. */
- $logs = array();
- $i = 0;
-
- foreach($list as $line)
- {
- if(strpos($line, 'commit ') === 0) $i++;
- $logs[$i][] = $line;
- }
-
- foreach($logs as $log) $parsedLogs[] = $this->convertLog($log);
- return $parsedLogs;
+ return $logs;
}
/**
@@ -354,63 +397,6 @@ class gitModel extends model
return $parsedLog;
}
- /**
- * Parse the comment of git, extract object id list from it.
- *
- * @param string $comment
- * @access public
- * @return array
- */
- public function parseComment($comment)
- {
- $stories = array();
- $tasks = array();
- $bugs = array();
-
- // bug|story|task(case insensitive) + some space + #|:|:(Chinese) + id lists(maybe join with space or ,)
- // $comment = "bug # 1,2,3,4 Bug:1 2 3 4 5 story:9999,1234566 story:456,1234566";
- $commonReg = "(?:\s){0,}(?:#|:|:){0,}([0-9, ]{1,})";
- $taskReg = '/task' . $commonReg . '/i';
- $storyReg = '/story' . $commonReg . '/i';
- $bugReg = '/bug' . $commonReg . '/i';
-
- if(preg_match_all($storyReg, $comment, $result)) $stories = join(' ', $result[1]);
- if(preg_match_all($taskReg, $comment, $result)) $tasks = join(' ', $result[1]);
- if(preg_match_all($bugReg, $comment, $result)) $bugs = join(' ', $result[1]);
-
- if($stories) $stories = array_unique(explode(' ', str_replace(',', ' ', $stories)));
- if($tasks) $tasks = array_unique(explode(' ', str_replace(',', ' ', $tasks)));
- if($bugs) $bugs = array_unique(explode(' ', str_replace(',', ' ', $bugs)));
-
- if(!$stories and !$tasks and !$bugs) return array();
- return array('stories' => $stories, 'tasks' => $tasks, 'bugs' => $bugs);
- }
-
- /**
- * Convert the comment to uft-8.
- *
- * @param string $comment
- * @access public
- * @return string
- */
- public function iconvComment($comment)
- {
- /* Get encodings. */
- $encodings = str_replace(' ', '', isset($this->config->git->encodings) ? $this->config->git->encodings : '');
- if($encodings == '') return $comment;
- $encodings = explode(',', $encodings);
-
- /* Try convert. */
- foreach($encodings as $encoding)
- {
- if($encoding == 'utf-8') continue;
- $result = helper::convertEncoding($comment, $encoding, 'utf-8');
- if($result) return $result;
- }
-
- return $comment;
- }
-
/**
* Diff a url.
*
@@ -520,13 +506,14 @@ class gitModel extends model
* @access public
* @return void
*/
- public function saveAction2PMS($objects, $log, $repoRoot = '')
+ public function saveAction2PMS($objects, $log, $repoRoot = '', $encodings = 'utf-8')
{
$action = new stdclass();
$action->actor = $log->author;
$action->action = 'gitcommited';
$action->date = $log->date;
- $action->comment = htmlspecialchars($this->iconvComment($log->msg));
+
+ $action->comment = htmlspecialchars($this->repo->iconvComment($log->msg, $encodings));
$action->extra = substr($log->revision, 0, 10);
$changes = $this->createActionChanges($log, $repoRoot);
@@ -738,7 +725,34 @@ class gitModel extends model
*/
public function saveLastRevision($revision)
{
- file_put_contents($this->logFile, $revision);
+ $ret = file_put_contents($this->logFile, $revision);
+ }
+
+ /**
+ * Get the saved tag.
+ *
+ * @access public
+ * @return int
+ */
+ public function getSavedTag($repoID = 0)
+ {
+ if($repoID) $this->setTagFile($repoID);
+ if(!file_exists($this->tagFile)) return 0;
+ if(file_exists($this->restartFile)) return 0;
+ return trim(file_get_contents($this->tagFile));
+ }
+
+ /**
+ * Save the last revision.
+ *
+ * @param int $tag
+ * @access public
+ * @return void
+ */
+ public function saveLastTag($tag, $repoId = 0)
+ {
+ if($repoId) $this->setTagFile($repoId);
+ file_put_contents($this->tagFile, $tag);
}
/**
@@ -753,6 +767,7 @@ class gitModel extends model
echo helper::now() . " $log\n";
}
+
/**
* Build URL.
*
diff --git a/module/group/lang/resource.php b/module/group/lang/resource.php
index cb90839a68..68aeada4ff 100644
--- a/module/group/lang/resource.php
+++ b/module/group/lang/resource.php
@@ -686,8 +686,9 @@ $lang->resource->repo->browse = 'browse';
$lang->resource->repo->view = 'view';
$lang->resource->repo->log = 'log';
$lang->resource->repo->revision = 'revisionAction';
-$lang->resource->repo->settings = 'settings';
-$lang->resource->repo->create = 'create';
+$lang->resource->repo->blame = 'blameAction';
+$lang->resource->repo->create = 'createAction';
+$lang->resource->repo->edit = 'editAction';
$lang->resource->repo->delete = 'delete';
$lang->resource->repo->showSyncComment = 'showSyncComment';
$lang->resource->repo->diff = 'diffAction';
diff --git a/module/install/lang/en.php b/module/install/lang/en.php
index a48f366334..bc4bce4ee2 100644
--- a/module/install/lang/en.php
+++ b/module/install/lang/en.php
@@ -165,6 +165,9 @@ $lang->install->cronList['moduleName=mail&methodName=asyncSend'] = 'Asynchr
$lang->install->cronList['moduleName=webhook&methodName=asyncSend'] = 'Asynchronize sending Webhook';
$lang->install->cronList['moduleName=admin&methodName=deleteLog'] = 'Delete overdue logs';
$lang->install->cronList['moduleName=todo&methodName=createCycle'] = 'Create recurring todos';
+$lang->install->cronList['moduleName=ci&methodName=buildTodayJob'] = 'Create recurring jenkins';
+$lang->install->cronList['moduleName=ci&methodName=checkBuildStatus'] = 'Synchronize Jenkins Status';
+$lang->install->cronList['moduleName=ci&methodName=exec'] = 'Execute Jenkins';
$lang->install->success = "Installed!";
$lang->install->login = 'Login ZenTao';
diff --git a/module/install/lang/zh-cn.php b/module/install/lang/zh-cn.php
index ad902e9657..074a934aed 100644
--- a/module/install/lang/zh-cn.php
+++ b/module/install/lang/zh-cn.php
@@ -165,6 +165,9 @@ $lang->install->cronList['moduleName=mail&methodName=asyncSend'] = '异步
$lang->install->cronList['moduleName=webhook&methodName=asyncSend'] = '异步发送Webhook';
$lang->install->cronList['moduleName=admin&methodName=deleteLog'] = '删除过期日志';
$lang->install->cronList['moduleName=todo&methodName=createCycle'] = '生成周期性待办';
+$lang->install->cronList['moduleName=ci&methodName=buildTodayJob'] = '创建周期性任务';
+$lang->install->cronList['moduleName=ci&methodName=checkBuildStatus'] = '同步Jenkins任务状态';
+$lang->install->cronList['moduleName=ci&methodName=exec'] = '执行Jenkins任务';
$lang->install->success = "安装成功";
$lang->install->login = '登录禅道管理系统';
diff --git a/module/integration/config.php b/module/integration/config.php
new file mode 100644
index 0000000000..dd9ddf2640
--- /dev/null
+++ b/module/integration/config.php
@@ -0,0 +1,6 @@
+integration = new stdclass();
+$config->integration->create = new stdclass();
+$config->integration->edit = new stdclass();
+$config->integration->create->requiredFields = 'name,repo,jenkins,jenkinsJob,triggerType';
+$config->integration->edit->requiredFields = 'name,repo,jenkins,jenkinsJob,triggerType';
diff --git a/module/integration/control.php b/module/integration/control.php
new file mode 100644
index 0000000000..6eb3ca7c43
--- /dev/null
+++ b/module/integration/control.php
@@ -0,0 +1,163 @@
+
+ * @package integration
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class integration extends control
+{
+ /**
+ * Construct
+ *
+ * @param string $moduleName
+ * @param string $methodName
+ * @access public
+ * @return void
+ */
+ public function __construct($moduleName = '', $methodName = '')
+ {
+ parent::__construct($moduleName, $methodName);
+ $this->loadModel('ci')->setMenu();
+ }
+
+ /**
+ * Browse ci job.
+ *
+ * @param string $orderBy
+ * @param int $recTotal
+ * @param int $recPerPage
+ * @param int $pageID
+ * @access public
+ * @return void
+ */
+ public function browse($orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
+ {
+ $this->app->loadClass('pager', $static = true);
+ $pager = new pager($recTotal, $recPerPage, $pageID);
+
+ $this->app->loadLang('compile');
+ $this->view->jobList = $this->integration->getList($orderBy, $pager);
+
+ $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->integration->browse;
+ $this->view->position[] = $this->lang->ci->job;
+ $this->view->position[] = $this->lang->integration->browse;
+
+ $this->view->orderBy = $orderBy;
+ $this->view->pager = $pager;
+ $this->display();
+ }
+
+ /**
+ * Create a ci job.
+ *
+ * @access public
+ * @return void
+ */
+ public function create()
+ {
+ if($_POST)
+ {
+ $this->integration->create();
+ if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse')));
+ }
+
+ $this->app->loadLang('action');
+
+ $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->integration->create;
+ $this->view->position[] = html::a(inlink('browse'), $this->lang->ci->job);
+ $this->view->position[] = $this->lang->integration->create;
+
+ $repoList = $this->loadModel('repo')->getList();
+ $repoPairs = array(0 => '');
+ $repoTypes = array();
+ foreach($repoList as $repo)
+ {
+ $repoPairs[$repo->id] = $repo->name;
+ $repoTypes[$repo->id] = $repo->SCM;
+ }
+ $this->view->repoPairs = $repoPairs;
+ $this->view->repoTypes = $repoTypes;
+ $this->view->jenkinsList = $this->loadModel('jenkins')->getPairs();
+
+ $this->display();
+ }
+
+ /**
+ * Edit a ci job.
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function edit($id)
+ {
+ $job = $this->integration->getByID($id);
+ if($_POST)
+ {
+ $this->integration->update($id);
+ if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse')));
+ }
+
+ $this->app->loadLang('action');
+
+ $this->view->title = $this->lang->ci->job . $this->lang->colon . $this->lang->integration->edit;
+ $this->view->position[] = html::a(inlink('browse'), $this->lang->ci->job);
+ $this->view->position[] = $this->lang->integration->edit;
+
+ $repo = $this->loadModel('repo')->getRepoByID($job->repo);
+ $repoList = $this->repo->getList();
+ $repoPairs = array(0 => '', $repo->id => $repo->name);
+ $repoTypes[$repo->id] = $repo->SCM;
+ foreach($repoList as $repo)
+ {
+ $repoPairs[$repo->id] = $repo->name;
+ $repoTypes[$repo->id] = $repo->SCM;
+ }
+
+ $this->view->repoPairs = $repoPairs;
+ $this->view->repoTypes = $repoTypes;
+ $this->view->job = $job;
+ $this->view->jenkinsList = $this->loadModel('jenkins')->getPairs();
+ $this->view->jenkinsJobs = $this->jenkins->getTasks($job->jenkins);
+
+ $this->display();
+ }
+
+ /**
+ * Delete a ci job.
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function delete($id, $confirm = 'no')
+ {
+ if($confirm != 'yes') die(js::confirm($this->lang->integration->confirmDelete, inlink('delete', "jobID=$id&confirm=yes")));
+
+ $this->integration->delete(TABLE_INTEGRATION, $id);
+ die(js::reload('parent'));
+ }
+
+ /**
+ * Exec a ci job.
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function exec($id)
+ {
+ $result = $this->integration->exec($id);
+ if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ if(!$result) $this->send(array('result' => 'fail', 'message' => 'not found'));
+
+ $this->send(array('result' => 'success'));
+ }
+}
diff --git a/module/integration/css/create.css b/module/integration/css/create.css
new file mode 100644
index 0000000000..f286553966
--- /dev/null
+++ b/module/integration/css/create.css
@@ -0,0 +1,4 @@
+.row.text-with-input .col{line-height: 32px;}
+.only-pick-time thead th, .only-pick-time tfoot th {color: transparent !important;}
+.checkbox-primary.checkbox-inline{display:inline-block !important;}
+.checkbox-primary.checkbox-inline label{padding-left:5px !important;}
diff --git a/module/integration/css/edit.css b/module/integration/css/edit.css
new file mode 100644
index 0000000000..5f526c2e30
--- /dev/null
+++ b/module/integration/css/edit.css
@@ -0,0 +1,4 @@
+.row.text-with-input .col {line-height: 32px;}
+.only-pick-time thead th, .only-pick-time tfoot th {color: transparent !important;}
+.checkbox-primary.checkbox-inline{display:inline-block !important;}
+.checkbox-primary.checkbox-inline label{padding-left:5px !important;}
diff --git a/module/integration/js/create.js b/module/integration/js/create.js
new file mode 100644
index 0000000000..06b63e32f5
--- /dev/null
+++ b/module/integration/js/create.js
@@ -0,0 +1,106 @@
+$('#repo').change(function()
+{
+ var repoID = $(this).val();
+ var type = 'Git';
+ if(typeof(repoTypes[repoID]) != 'undefined') type = repoTypes[repoID];
+ $('.svn-fields').toggleClass('hidden', type != 'Subversion');
+ $('#repoType').val(type);
+ if(type == 'Subversion')
+ {
+ $('#svnFolderBox').html("");
+ $.getJSON(createLink('repo', 'ajaxGetSVNTags', 'repoID=' + repoID), function(svnTags)
+ {
+ var tags = svnTags['tags'];
+ var parents = svnTags['parent'];
+
+ html = "';
+ $('#svnFolderBox').html(html);
+ $('#svnFolderBox #svnFolder').chosen();
+ })
+ }
+})
+
+$(document).on('change', '#svnFolder', function()
+{
+ var repoID = $('#repo').val();
+ var selectedTag = $(this).val();
+ var encodePath = $(this).find("option:selected").attr('data-encodePath');
+ $('#svnFolderBox').html("");
+ $.getJSON(createLink('repo', 'ajaxGetSVNTags', 'repoID=' + repoID + '&path=' + encodePath), function(svnTags)
+ {
+ var tags = svnTags['tags'];
+ var parents = svnTags['parent'];
+
+ html = "';
+ $('#svnFolderBox').html(html);
+ $('#svnFolderBox #svnFolder').val(selectedTag).chosen();
+ })
+})
+
+$('#triggerType').change(function()
+{
+ var type = $(this).val();
+ if(type == 'tag')
+ {
+ $('.comment-fields').addClass('hidden');
+ $('.custom-fields').addClass('hidden');
+ }
+ else if(type == 'commit')
+ {
+ $('.comment-fields').removeClass('hidden');
+ $('.custom-fields').addClass('hidden');
+ }
+ else if(type == 'schedule')
+ {
+ $('.comment-fields').addClass('hidden');
+ $('.custom-fields').removeClass('hidden');
+ }
+});
+
+$('#jenkins').change(function()
+{
+ var jenkinsID = $(this).val();
+ $('#jenkinsJobBox').html("");
+ $.getJSON(createLink('jenkins', 'ajaxGetTasks', 'jenkinsID=' + jenkinsID), function(tasks)
+ {
+ html = "';
+ $('#jenkinsJobBox').html(html);
+ $('#jenkinsJobBox #jenkinsJob').chosen();
+ })
+})
+
+$(function()
+{
+ $('#repo').change();
+ $('#triggerType').change();
+});
diff --git a/module/integration/js/edit.js b/module/integration/js/edit.js
new file mode 100644
index 0000000000..a772843d13
--- /dev/null
+++ b/module/integration/js/edit.js
@@ -0,0 +1,123 @@
+$('#repo').change(function()
+{
+ var repoID = $(this).val();
+ var type = 'Git';
+ if(typeof(repoTypes[repoID]) != 'undefined') type = repoTypes[repoID];
+ $('.svn-fields').toggleClass('hidden', type != 'Subversion');
+ $('#repoType').val(type);
+ if(type == 'Subversion')
+ {
+ $('#svnFolderBox').html("");
+ var params = 'repoID=' + repoID;
+ if(jobRepo == repoID) params = 'repoID=' + repoID + '&path=' + encodeSVNFolder;
+ $.getJSON(createLink('repo', 'ajaxGetSVNTags', params), function(svnTags)
+ {
+ var tags = svnTags['tags'];
+ var parents = svnTags['parent'];
+
+ html = "';
+ $('#svnFolderBox').html(html);
+ $('#svnFolderBox #svnFolder').val(svnFolder).chosen();
+ })
+ }
+})
+
+$(document).on('change', '#svnFolder', function()
+{
+ var repoID = $('#repo').val();
+ var selectedTag = $(this).val();
+ var encodePath = $(this).find("option:selected").attr('data-encodePath');
+ $('#svnFolderBox').html("");
+ $.getJSON(createLink('repo', 'ajaxGetSVNTags', 'repoID=' + repoID + '&path=' + encodePath), function(svnTags)
+ {
+ var tags = svnTags['tags'];
+ var parents = svnTags['parent'];
+
+ html = "';
+ $('#svnFolderBox').html(html);
+ $('#svnFolderBox #svnFolder').val(selectedTag).chosen();
+ })
+})
+
+$('#triggerType').change(function()
+{
+ var type = $(this).val();
+ if(type == 'tag')
+ {
+ $('.comment-fields').addClass('hidden');
+ $('.custom-fields').addClass('hidden');
+ }
+ else if(type == 'commit')
+ {
+ $('.comment-fields').removeClass('hidden');
+ $('.custom-fields').addClass('hidden');
+ }
+ else if(type == 'schedule')
+ {
+ $('.comment-fields').addClass('hidden');
+ $('.custom-fields').removeClass('hidden');
+ }
+});
+
+$('#jenkins').change(function()
+{
+ var jenkinsID = $(this).val();
+ $('#jenkinsJobBox').html("");
+ $.getJSON(createLink('jenkins', 'ajaxGetTasks', 'jenkinsID=' + jenkinsID), function(tasks)
+ {
+ html = "';
+ $('#jenkinsJobBox').html(html);
+ $('#jenkinsJobBox #jenkinsJob').chosen();
+ })
+})
+
+$(function()
+{
+ $('#repo').change();
+ $('#triggerType').change();
+});
+
+function execJob(id)
+{
+ $.ajax(
+ {
+ type: "POST",
+ url: createLink('integration', 'exec', 'id=' + id),
+ data: {},
+ datatype: "json",
+ success: function(data)
+ {
+ $('.exe-job-button').tooltip('show', sendExec);
+ }
+ });
+}
diff --git a/module/integration/lang/en.php b/module/integration/lang/en.php
new file mode 100644
index 0000000000..148eb157e0
--- /dev/null
+++ b/module/integration/lang/en.php
@@ -0,0 +1,31 @@
+integration->browse = 'Browse Integration';
+$lang->integration->create = 'Create Integration';
+$lang->integration->edit = 'Edit Integration';
+$lang->integration->execNow = 'Execute now';
+$lang->integration->delete = 'Delete Integration';
+$lang->integration->confirmDelete = 'Do you want to delete this Build?';
+
+$lang->integration->id = 'ID';
+$lang->integration->name = 'Name';
+$lang->integration->repo = 'Repo';
+$lang->integration->svnFolder = 'SVN Tag Watch Path';
+$lang->integration->jenkins = 'Jenkins Server';
+$lang->integration->buildType = 'Build Type';
+$lang->integration->jenkinsJob = 'Jenkins Task';
+$lang->integration->triggerType = 'Trigger';
+$lang->integration->scheduleDay = 'Custom Days';
+$lang->integration->lastExec = 'Last Executed';
+
+$lang->integration->example = 'e.g.';
+$lang->integration->commitEx = '%build% %integration% %id%15, to build Jenkins job that id is 15.';
+$lang->integration->cronSample = 'e.g. 0 0 2 * * 2-6/1 means 2:00 a.m. every weekday.';
+$lang->integration->sendExec = 'Send execute request success.';
+
+$lang->integration->buildTypeList['build'] = 'Only Build';
+$lang->integration->buildTypeList['buildAndDeploy'] = 'Build And Deploy';
+$lang->integration->buildTypeList['buildAndTest'] = 'Build And Test';
+
+$lang->integration->triggerTypeList['tag'] = 'Tag';
+$lang->integration->triggerTypeList['commit'] = 'Code Commit';
+$lang->integration->triggerTypeList['schedule'] = 'Schedule';
diff --git a/module/integration/lang/zh-cn.php b/module/integration/lang/zh-cn.php
new file mode 100644
index 0000000000..dc327991d9
--- /dev/null
+++ b/module/integration/lang/zh-cn.php
@@ -0,0 +1,33 @@
+integration->common = '构建任务';
+$lang->integration->browse = '浏览构建任务';
+$lang->integration->create = '创建构建任务';
+$lang->integration->start = '执行构建';
+$lang->integration->edit = '编辑构建任务';
+$lang->integration->execNow = '立即执行';
+$lang->integration->delete = '删除构建任务';
+$lang->integration->confirmDelete = '确认删除该构建任务吗?';
+
+$lang->integration->id = 'ID';
+$lang->integration->name = '名称';
+$lang->integration->repo = '代码库';
+$lang->integration->svnFolder = 'SVN Tag监控路径';
+$lang->integration->jenkins = 'Jenkins服务';
+$lang->integration->buildType = '构建类型';
+$lang->integration->jenkinsJob = 'Jenkins任务名';
+$lang->integration->triggerType = '触发方式';
+$lang->integration->scheduleDay = '自定义天数';
+$lang->integration->lastExec = '最后执行';
+
+$lang->integration->example = '举例';
+$lang->integration->commitEx = '%build% %integration% %id%15,其中15为Jenkins任务编号';
+$lang->integration->cronSample = '如 0 0 2 * * 2-6/1 表示每个工作日凌晨2点';
+$lang->integration->sendExec = '发送执行请求成功!';
+
+$lang->integration->buildTypeList['build'] = '仅构建';
+$lang->integration->buildTypeList['buildAndDeploy'] = '构建部署';
+$lang->integration->buildTypeList['buildAndTest'] = '构建测试';
+
+$lang->integration->triggerTypeList['tag'] = '打标签';
+$lang->integration->triggerTypeList['commit'] = '代码提交注释';
+$lang->integration->triggerTypeList['schedule'] = '定时计划';
diff --git a/module/integration/model.php b/module/integration/model.php
new file mode 100644
index 0000000000..1f446e07d2
--- /dev/null
+++ b/module/integration/model.php
@@ -0,0 +1,141 @@
+
+ * @package integration
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+class integrationModel extends model
+{
+ /**
+ * Get by id.
+ *
+ * @param int $id
+ * @access public
+ * @return object
+ */
+ public function getByID($id)
+ {
+ return $this->dao->select('*')->from(TABLE_INTEGRATION)->where('id')->eq($id)->fetch();
+ }
+
+ /**
+ * Get integration list.
+ *
+ * @param string $orderBy
+ * @param object $pager
+ * @access public
+ * @return array
+ */
+ public function getList($orderBy = 'id_desc', $pager = null)
+ {
+ return $this->dao->select('t1.*, t2.name as repoName, t3.name as jenkinsName')->from(TABLE_INTEGRATION)->alias('t1')
+ ->leftJoin(TABLE_REPO)->alias('t2')->on('t1.repo=t2.id')
+ ->leftJoin(TABLE_JENKINS)->alias('t3')->on('t1.jenkins=t3.id')
+ ->where('t1.deleted')->eq('0')
+ ->orderBy($orderBy)
+ ->page($pager)
+ ->fetchAll('id');
+ }
+
+ /**
+ * Get list by triggerType field
+ *
+ * @param string $triggerType
+ * @access public
+ * @return array
+ */
+ public function getListByTriggerType($triggerType)
+ {
+ return $this->dao->select('*')->from(TABLE_INTEGRATION)
+ ->where('deleted')->eq('0')
+ ->andWhere('triggerType')->eq($triggerType)
+ ->fetchAll('id');
+ }
+
+ /**
+ * Create integration
+ *
+ * @access public
+ * @return void
+ */
+ public function create()
+ {
+ $integration = fixer::input('post')
+ ->add('createdBy', $this->app->user->account)
+ ->add('createdDate', helper::now())
+ ->remove('repoType')
+ ->get();
+ if($integration->triggerType == 'schedule')
+ {
+ if(!isset($integration->scheduleDay)) $integration->scheduleDay = array();
+ $integration->scheduleDay = join(',', $integration->scheduleDay);
+ }
+ else
+ {
+ $integration->scheduleDay = '';
+ }
+
+ $this->dao->insert(TABLE_INTEGRATION)->data($integration)
+ ->batchCheck($this->config->integration->create->requiredFields, 'notempty')
+
+ ->batchCheckIF($integration->triggerType === 'schedule', "scheduleDay", 'notempty')
+ ->batchCheckIF($this->post->repoType == 'Subversion', "svnFolder", 'notempty')
+
+ ->autoCheck()
+ ->exec();
+
+ if($integration->triggerType == 'schedule' and strpos($integration->scheduleDay, date('w')) !== false) $this->loadModel('compile')->createByIntegration($integration->id);
+ return true;
+ }
+
+ /**
+ * Update integration
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function update($id)
+ {
+ $oldIntegration = $this->getById($id);
+ $integration = fixer::input('post')
+ ->add('editedBy', $this->app->user->account)
+ ->add('editedDate', helper::now())
+ ->remove('repoType')
+ ->get();
+ if($integration->triggerType == 'schedule')
+ {
+ if(!isset($integration->scheduleDay)) $integration->scheduleDay = array();
+ $integration->scheduleDay = join(',', $integration->scheduleDay);
+ }
+ else
+ {
+ $integration->scheduleDay = '';
+ }
+
+ $this->dao->update(TABLE_INTEGRATION)->data($integration)
+ ->batchCheck($this->config->integration->edit->requiredFields, 'notempty')
+
+ ->batchCheckIF($integration->triggerType === 'schedule', "scheduleDay", 'notempty')
+ ->batchCheckIF($this->post->repoType == 'Subversion', "svnFolder", 'notempty')
+
+ ->autoCheck()
+ ->where('id')->eq($id)
+ ->exec();
+
+ if($integration->triggerType == 'schedule')
+ {
+ $week = date('w');
+ if($integration->triggerType != $oldIntegration->triggerType or strpos($oldIntegration->scheduleDay, $week) === false)
+ {
+ if(strpos($integration->scheduleDay, $week) !== false) $this->loadModel('compile')->createByIntegration($integration->id);
+ }
+ }
+ return true;
+ }
+}
diff --git a/module/integration/view/browse.html.php b/module/integration/view/browse.html.php
new file mode 100644
index 0000000000..81e3381211
--- /dev/null
+++ b/module/integration/view/browse.html.php
@@ -0,0 +1,65 @@
+
+ * @package ci
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+ createLink('integration', 'browse'), "{$lang->ci->task}", '', "class='btn btn-link btn-active-text'");?>
+
+
+ " . $lang->integration->create, '', "class='btn btn-primary'");?>
+
+
+
+
diff --git a/module/integration/view/create.html.php b/module/integration/view/create.html.php
new file mode 100644
index 0000000000..40becea011
--- /dev/null
+++ b/module/integration/view/create.html.php
@@ -0,0 +1,70 @@
+
+ * @package integration
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+
+
+
+
+
+
integration->create; ?>
+
+
+
+
+
+
diff --git a/module/integration/view/edit.html.php b/module/integration/view/edit.html.php
new file mode 100644
index 0000000000..f75dd42000
--- /dev/null
+++ b/module/integration/view/edit.html.php
@@ -0,0 +1,76 @@
+
+ * @package integration
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+triggerType);?>
+repo);?>
+svnFolder);?>
+loadModel('repo')->encodePath($job->svnFolder));?>
+jenkinsJob);?>
+
+
+
+
+
+
integration->edit; ?>
+
+
+
+
+
+integration->sendExec);?>
+
diff --git a/module/jenkins/config.php b/module/jenkins/config.php
new file mode 100644
index 0000000000..7d2408526a
--- /dev/null
+++ b/module/jenkins/config.php
@@ -0,0 +1,5 @@
+jenkins->create = new stdclass();
+$config->jenkins->edit = new stdclass();
+$config->jenkins->create->requiredFields = 'name,serviceUrl,credentials';
+$config->jenkins->edit->requiredFields = 'name,serviceUrl,credentials';
diff --git a/module/jenkins/control.php b/module/jenkins/control.php
new file mode 100644
index 0000000000..9771be1d3b
--- /dev/null
+++ b/module/jenkins/control.php
@@ -0,0 +1,133 @@
+
+ * @package product
+ * @version $Id: ${FILE_NAME} 5144 2020/1/8 8:10 下午 chenqi@cnezsoft.com $
+ * @link http://www.zentao.net
+ */
+class jenkins extends control
+{
+ /**
+ * jenkins constructor.
+ * @param string $moduleName
+ * @param string $methodName
+ */
+ public function __construct($moduleName = '', $methodName = '')
+ {
+ parent::__construct($moduleName, $methodName);
+ $this->loadModel('ci')->setMenu();
+ }
+
+ /**
+ * Browse jenkinss.
+ *
+ * @param string $orderBy
+ * @param int $recTotal
+ * @param int $recPerPage
+ * @param int $pageID
+ * @access public
+ * @return void
+ */
+ public function browse($orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
+ {
+ if(common::hasPriv('jenkins', 'create')) $this->lang->modulePageActions = html::a(helper::createLink('jenkins', 'create'), " " . $this->lang->jenkins->create, '', "class='btn btn-primary'");
+
+ $this->app->loadClass('pager', $static = true);
+ $pager = new pager($recTotal, $recPerPage, $pageID);
+
+ $this->view->title = $this->lang->jenkins->common . $this->lang->colon . $this->lang->jenkins->browse;
+ $this->view->position[] = $this->lang->jenkins->common;
+ $this->view->position[] = $this->lang->jenkins->browse;
+
+ $this->view->jenkinsList = $this->jenkins->getList($orderBy, $pager);
+ $this->view->orderBy = $orderBy;
+ $this->view->pager = $pager;
+
+ $this->display();
+ }
+
+ /**
+ * Create a jenkins.
+ *
+ * @access public
+ * @return void
+ */
+ public function create()
+ {
+ if($_POST)
+ {
+ $this->jenkins->create();
+ if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse')));
+ }
+
+ $this->app->loadLang('action');
+
+ $this->view->title = $this->lang->jenkins->common . $this->lang->colon . $this->lang->jenkins->create;
+ $this->view->position[] = html::a(inlink('browse'), $this->lang->jenkins->common);
+ $this->view->position[] = $this->lang->jenkins->create;
+
+ $this->display();
+ }
+
+ /**
+ * Edit a jenkins.
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function edit($id)
+ {
+ $jenkins = $this->jenkins->getByID($id);
+ if($_POST)
+ {
+ $this->jenkins->update($id);
+ if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse')));
+ }
+
+ $this->app->loadLang('action');
+
+ $this->view->title = $this->lang->jenkins->common . $this->lang->colon . $this->lang->jenkins->edit;
+ $this->view->position[] = html::a(inlink('browse'), $this->lang->jenkins->common);
+ $this->view->position[] = $this->lang->jenkins->edit;
+
+ $this->view->jenkins = $jenkins;
+
+ $this->display();
+ }
+
+ /**
+ * Delete a jenkins.
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function delete($id, $confim = 'no')
+ {
+ if($confim != 'yes') die(js::confirm($this->lang->jenkins->confirmDelete, inlink('delete', "id=$id&confirm=yes")));
+
+ $this->jenkins->delete(TABLE_JENKINS, $id);
+ die(js::reload('parent'));
+ }
+
+ /**
+ * Ajax get tasks.
+ *
+ * @param int $id
+ * @access public
+ * @return void
+ */
+ public function ajaxGetTasks($id)
+ {
+ $tasks = $this->jenkins->getTasks($id);
+ die(json_encode($tasks));
+ }
+}
diff --git a/module/jenkins/lang/en.php b/module/jenkins/lang/en.php
new file mode 100644
index 0000000000..5c0a121107
--- /dev/null
+++ b/module/jenkins/lang/en.php
@@ -0,0 +1,18 @@
+jenkins->common = 'Jenkins';
+$lang->jenkins->browse = 'View';
+$lang->jenkins->create = 'Create';
+$lang->jenkins->edit = 'Edit';
+$lang->jenkins->delete = 'Delete';
+$lang->jenkins->confirmDelete = 'Do you want to delete this Jenkins server?';
+
+$lang->jenkins->id = 'ID';
+$lang->jenkins->name = 'Name';
+$lang->jenkins->serviceUrl = 'Service URL';
+$lang->jenkins->token = 'Token';
+$lang->jenkins->account = 'UserName';
+$lang->jenkins->password = 'Password';
+
+$lang->jenkins->desc = 'Description';
+$lang->jenkins->tokenFirst = 'Use token if not empty.';
+$lang->jenkins->tips = 'Cancel "Prevent Cross Site Request Forgery exploits" when using password.';
\ No newline at end of file
diff --git a/module/jenkins/lang/zh-cn.php b/module/jenkins/lang/zh-cn.php
new file mode 100644
index 0000000000..f2e95ec317
--- /dev/null
+++ b/module/jenkins/lang/zh-cn.php
@@ -0,0 +1,18 @@
+jenkins->common = 'Jenkins';
+$lang->jenkins->browse = '浏览';
+$lang->jenkins->create = '创建';
+$lang->jenkins->edit = '编辑';
+$lang->jenkins->delete = '删除';
+$lang->jenkins->confirmDelete = '确认删除该Jenkins吗?';
+
+$lang->jenkins->id = 'ID';
+$lang->jenkins->name = '名称';
+$lang->jenkins->serviceUrl = '服务地址';
+$lang->jenkins->token = 'Token';
+$lang->jenkins->account = '用户名';
+$lang->jenkins->password = '密码';
+
+$lang->jenkins->desc = '描述';
+$lang->jenkins->tokenFirst = 'Token不为空时,优先使用Token。';
+$lang->jenkins->tips = '使用密码时,请在Jenkins全局安全设置中禁用"防止跨站点请求伪造"选项。';
diff --git a/module/jenkins/model.php b/module/jenkins/model.php
new file mode 100644
index 0000000000..167a43f764
--- /dev/null
+++ b/module/jenkins/model.php
@@ -0,0 +1,138 @@
+
+ * @package product
+ * @version $Id: $
+ * @link http://www.zentao.net
+ */
+
+class jenkinsModel extends model
+{
+ /**
+ * Get a jenkins by id.
+ *
+ * @param int $id
+ * @access public
+ * @return object
+ */
+ public function getByID($id)
+ {
+ $jenkins = $this->dao->select('*')->from(TABLE_JENKINS)->where('id')->eq($id)->fetch();
+ $jenkins->password = base64_decode($jenkins->password);
+
+ return $jenkins;
+ }
+
+ /**
+ * Get jenkins list.
+ *
+ * @param string $orderBy
+ * @param object $pager
+ * @access public
+ * @return array
+ */
+ public function getList($orderBy = 'id_desc', $pager = null)
+ {
+ return $this->dao->select('*')->from(TABLE_JENKINS)
+ ->where('deleted')->eq('0')
+ ->orderBy($orderBy)
+ ->page($pager)
+ ->fetchAll('id');
+ }
+
+ /**
+ * Create a jenkins.
+ *
+ * @access public
+ * @return bool
+ */
+ public function create()
+ {
+ $jenkins = fixer::input('post')
+ ->add('createdBy', $this->app->user->account)
+ ->add('createdDate', helper::now())
+ ->skipSpecial('serviceUrl,token,account,password')
+ ->get();
+
+ $jenkins->password = base64_encode($jenkins->password);
+
+ $this->dao->insert(TABLE_JENKINS)->data($jenkins)
+ ->batchCheck($this->config->jenkins->create->requiredFields, 'notempty')
+ ->batchCheck("serviceUrl", 'URL')
+ ->autoCheck()
+ ->exec();
+ return !dao::isError();
+ }
+
+ /**
+ * Update a jenkins.
+ *
+ * @param int $id
+ * @access public
+ * @return bool
+ */
+ public function update($id)
+ {
+ $jenkins = fixer::input('post')
+ ->add('editedBy', $this->app->user->account)
+ ->add('editedDate', helper::now())
+ ->skipSpecial('serviceUrl,token,account,password')
+ ->get();
+
+ $jenkins->password = base64_encode($jenkins->password);
+
+ $this->dao->update(TABLE_JENKINS)->data($jenkins)
+ ->batchCheck($this->config->jenkins->edit->requiredFields, 'notempty')
+ ->batchCheck("serviceUrl", 'URL')
+ ->autoCheck()
+ ->where('id')->eq($id)
+ ->exec();
+ return !dao::isError();
+ }
+
+ /**
+ * list jenkins for ci task edit
+ *
+ * @return array
+ */
+ public function getPairs()
+ {
+ $jenkins = $this->dao->select('id,name')->from(TABLE_JENKINS)->where('deleted')->eq('0')->orderBy('id')->fetchPairs('id', 'name');
+ $jenkins = array('' => '') + $jenkins;
+ return $jenkins;
+ }
+
+ /**
+ * Get jenkins tasks.
+ *
+ * @param int $id
+ * @access public
+ * @return array
+ */
+ public function getTasks($id)
+ {
+ $jenkins = $this->getById($id);
+
+ $jenkinsServer = $jenkins->serviceUrl;
+ $jenkinsUser = $jenkins->account;
+ $jenkinsPassword = $jenkins->token ? $jenkins->token : $jenkins->password;
+
+ $jenkinsAuth = '://' . $jenkinsUser . ':' . $jenkinsPassword . '@';
+ $jenkinsServer = str_replace('://', $jenkinsAuth, $jenkinsServer);
+
+ $response = common::http($jenkinsServer . '/api/json/items/list');
+ $response = json_decode($response);
+
+ $tasks = array();
+ if(isset($response->jobs))
+ {
+ foreach($response->jobs as $job) $tasks[basename($job->url)] = $job->name;
+ }
+ return $tasks;
+
+ }
+}
diff --git a/module/jenkins/view/browse.html.php b/module/jenkins/view/browse.html.php
new file mode 100644
index 0000000000..5ec222dd2c
--- /dev/null
+++ b/module/jenkins/view/browse.html.php
@@ -0,0 +1,47 @@
+
+ * @package jenkins
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
diff --git a/module/jenkins/view/create.html.php b/module/jenkins/view/create.html.php
new file mode 100644
index 0000000000..3ec9f2737e
--- /dev/null
+++ b/module/jenkins/view/create.html.php
@@ -0,0 +1,58 @@
+
+ * @package jenkins
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+
+
jenkins->create; ?>
+
+
+
+
+
+
diff --git a/module/jenkins/view/edit.html.php b/module/jenkins/view/edit.html.php
new file mode 100644
index 0000000000..7b9a71c5f8
--- /dev/null
+++ b/module/jenkins/view/edit.html.php
@@ -0,0 +1,62 @@
+
+ * @package jenkins
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+
+
+
+
+
+
jenkins->edit; ?>
+
+
+
+
+
+
diff --git a/module/my/view/bug.html.php b/module/my/view/bug.html.php
index ec482e5f9d..56631222a1 100644
--- a/module/my/view/bug.html.php
+++ b/module/my/view/bug.html.php
@@ -17,7 +17,7 @@
$recTotalLabel = " {$pager->recTotal}";
echo html::a(inlink('bug', "type=assignedTo"), "{$lang->bug->assignedTo}" . ($type == 'assignedTo' ? $recTotalLabel : ''), '', "class='btn btn-link" . ($type == 'assignedTo' ? ' btn-active-text' : '') . "'");
echo html::a(inlink('bug', "type=openedBy"), "{$lang->bug->openedByMe}" . ($type == 'openedBy' ? $recTotalLabel : ''), '', "class='btn btn-link" . ($type == 'openedBy' ? ' btn-active-text' : '') . "'");
- echo html::a(inlink('bug', "type=resolvedBy"), "{$lang->bug->resolvedBy}" . ($type == 'resolvedBy' ? $recTotalLabel : ''), '', "class='btn btn-link" . ($type == 'resolvedBy' ? ' btn-active-text' : '') . "'");
+ echo html::a(inlink('bug', "type=resolvedBy"), "{$lang->bug->resolvedByMe}" . ($type == 'resolvedBy' ? $recTotalLabel : ''), '', "class='btn btn-link" . ($type == 'resolvedBy' ? ' btn-active-text' : '') . "'");
echo html::a(inlink('bug', "type=closedBy"), "{$lang->bug->closedByMe}" . ($type == 'closedBy' ? $recTotalLabel : ''), '', "class='btn btn-link" . ($type == 'closedBy' ? ' btn-active-text' : '') . "'");
?>
diff --git a/module/my/view/task.html.php b/module/my/view/task.html.php
index 8ce0a1b8b3..8989ae4ee1 100644
--- a/module/my/view/task.html.php
+++ b/module/my/view/task.html.php
@@ -48,12 +48,11 @@
task->project);?> |
task->name);?> |
openedByAB);?> |
- task->assignedTo);?> |
+ task->assignedTo);?> |
task->finishedBy);?> |
- task->estimateAB);?> |
- task->consumedAB);?> |
- task->leftAB);?> |
- task->deadlineAB);?> |
+ task->estimateAB);?> |
+ task->consumedAB);?> |
+ task->leftAB);?> |
statusAB);?> |
actions;?> |
@@ -83,7 +82,6 @@
estimate;?> |
consumed;?> |
left;?> |
- '>deadline, 0, 4) > 0) echo $task->deadline;?> |
storyStatus) and $task->storyStatus == 'active' and $task->latestStoryVersion > $task->storyVersion and !in_array($task->status, array('cancel', 'closed')));?>
{$this->lang->story->changed}") : print(" " . $this->processStatus('task', $task) . "");?>
diff --git a/module/repo/config.php b/module/repo/config.php
index d8ea864336..7a0c7ed085 100644
--- a/module/repo/config.php
+++ b/module/repo/config.php
@@ -26,3 +26,34 @@ $config->repo->syncTime = 10;
$config->repo->batchNum = 100;
$config->repo->images = '|png|gif|jpg|ico|jpeg|bmp|';
$config->repo->binary = '|pdf|';
+
+$config->repo->editor = new stdclass();
+$config->repo->editor->create = array('id' => 'desc', 'tools' => 'simpleTools');
+$config->repo->editor->edit = array('id' => 'desc', 'tools' => 'simpleTools');
+$config->repo->editor->view = array('id' => 'commentText', 'tools' => 'simpleTools');
+$config->repo->editor->diff = array('id' => 'commentText', 'tools' => 'simpleTools');
+
+$config->repo->create = new stdclass();
+$config->repo->create->requiredFields = 'SCM,name,path,encoding,client';
+$config->repo->edit = new stdclass();
+$config->repo->edit->requiredFields = 'SCM,name,path,encoding,client';
+$config->repo->svn = new stdclass();
+$config->repo->svn->requiredFields = 'account,password';
+
+$config->repo->matchComment['module']['story'] = 'Story';
+$config->repo->matchComment['module']['task'] = 'Task';
+$config->repo->matchComment['module']['bug'] = 'Bug';
+$config->repo->matchComment['module']['integration'] = 'Build';
+$config->repo->matchComment['task']['start'] = 'Start';
+$config->repo->matchComment['task']['finish'] = 'Finish';
+$config->repo->matchComment['task']['cancel'] = 'Cancel';
+$config->repo->matchComment['task']['consumed'] = 'Cost';
+$config->repo->matchComment['task']['left'] = 'Left';
+$config->repo->matchComment['bug']['resolve'] = 'Fix';
+$config->repo->matchComment['bug']['resolvedBuild'] = 'Build';
+$config->repo->matchComment['integration']['start'] = 'Start';
+$config->repo->matchComment['id']['mark'] = '#';
+$config->repo->matchComment['id']['split'] = ',';
+$config->repo->matchComment['mark']['consumed'] = ':';
+$config->repo->matchComment['mark']['left'] = ':';
+$config->repo->matchComment['mark']['resolvedBuild'] = '#';
diff --git a/module/repo/control.php b/module/repo/control.php
index 8975ec3a08..4e368fffc6 100644
--- a/module/repo/control.php
+++ b/module/repo/control.php
@@ -29,7 +29,6 @@ class repo extends control
$this->scm = $this->app->loadClass('scm');
$this->repos = $this->repo->getRepoPairs();
- if(common::hasPriv('repo', 'create')) $this->lang->modulePageActions = html::a(helper::createLink('repo', 'create'), " " . $this->lang->repo->create, '', "class='btn btn-primary'");
if(empty($this->repos) and $this->methodName != 'create') die(js::locate($this->repo->createLink('create')));
/* Unlock session for wait to get data of repo. */
@@ -37,54 +36,104 @@ class repo extends control
}
/**
- * Create repo.
- *
+ * List all repo.
+ *
+ * @param string $orderBy
+ * @param int $recTotal
+ * @param int $recPerPage
+ * @param int $pageID
+ * @access public
+ * @return void
+ */
+ public function maintain($orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
+ {
+ $repoID = $this->session->repoID;
+ $this->repo->setMenu($this->repos, $repoID, false);
+ if(common::hasPriv('repo', 'create')) $this->lang->modulePageActions = html::a(helper::createLink('repo', 'create'), " " . $this->lang->repo->create, '', "class='btn btn-primary'");
+
+ $this->app->loadClass('pager', $static = true);
+ $pager = new pager($recTotal, $recPerPage, $pageID);
+
+ $this->view->repoList = $this->repo->getList($orderBy, $pager);
+
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->browse;
+ $this->view->position[] = $this->lang->repo->common;
+ $this->view->position[] = $this->lang->repo->browse;
+
+ $this->view->repoID = $repoID;
+ $this->view->orderBy = $orderBy;
+ $this->view->pager = $pager;
+
+ $this->display();
+ }
+
+ /**
+ * Create a repo.
+ *
* @access public
* @return void
*/
public function create()
{
- $this->repo->setMenu($this->repos);
- if(!empty($_POST))
+ if($_POST)
{
$repoID = $this->repo->create();
+
if(dao::isError()) die(js::error(dao::getError()));
- die(js::locate($this->repo->createLink('showSyncComment', "repoID=$repoID"), 'parent'));
+
+ $link = $this->repo->createLink('showSyncComment', "repoID=$repoID");
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $link));
}
- $this->view->title = $this->lang->repo->create;
+ $this->repo->setMenu($this->repos, '', false);
+ $this->app->loadLang('action');
+
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted');
+
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->create;
+ $this->view->position[] = html::a(inlink('maintain'), $this->lang->repo->common);
+ $this->view->position[] = $this->lang->repo->create;
+
$this->display();
}
/**
- * Set repo.
- *
- * @param int $repoID
+ * Edit a repo.
+ *
+ * @param int $repoID
* @access public
* @return void
*/
- public function settings($repoID = 0)
+ public function edit($repoID)
{
- $this->repo->setMenu($this->repos, $repoID);
- if($repoID == 0) $repoID = $this->session->repoID;
- if(!empty($_POST))
+ $repo = $this->repo->getRepoByID($repoID);
+ if($_POST)
{
- $needSync = $this->repo->saveSettings($repoID);
+ $noNeedSync = $this->repo->update($repoID);
if(dao::isError()) die(js::error(dao::getError()));
- if(!$needSync)
+ if(!$noNeedSync)
{
- die(js::locate($this->repo->createLink('showSyncComment', "repoID=$repoID"), 'parent'));
+ $link = $this->repo->createLink('showSyncComment', "repoID=$repoID");
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => $link));
}
- die(js::locate($this->repo->createLink('log', "repoID=$repoID"), 'parent'));
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('maintain')));
}
- $this->view->title = $this->lang->repo->settings;
- $this->view->repo = $this->repo->getRepoByID($repoID);
+ $this->repo->setMenu($this->repos, $repo->id, false);
+ $this->app->loadLang('action');
+
+ $repo->repoType = $repo->id . '-' . $repo->SCM;
+ $this->view->repo = $repo;
+ $this->view->repoID = $repoID;
$this->view->groups = $this->loadModel('group')->getPairs();
- $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted', !empty($repo->acl->users) ? $repo->acl->users : '');
- $this->display();
+ $this->view->users = $this->loadModel('user')->getPairs('noletter|noempty|nodeleted');
+
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->edit;
+ $this->view->position[] = html::a(inlink('maintain'), $this->lang->repo->common);
+ $this->view->position[] = $this->lang->repo->edit;
+
+ $this->display();
}
/**
@@ -101,12 +150,14 @@ class repo extends control
{
die(js::confirm($this->lang->repo->notice->delete, $this->repo->createLink('delete', "repoID=$repoID&confirm=yes")));
}
+
$this->dao->delete()->from(TABLE_REPO)->where('id')->eq($repoID)->exec();
$this->dao->delete()->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->exec();
$this->dao->delete()->from(TABLE_REPOFILES)->where('repo')->eq($repoID)->exec();
$this->dao->delete()->from(TABLE_REPOBRANCH)->where('repo')->eq($repoID)->exec();
- echo js::alert($this->lang->repo->notice->successDelete);
- die(js::locate($this->repo->createLink('log'), 'parent'));
+
+ if(dao::isError()) die(js::error(dao::getError()));
+ die(js::reload('parent'));
}
/**
@@ -193,6 +244,9 @@ class repo extends control
$this->view->logType = $logType;
$this->view->info = $info;
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->view;
+ $this->view->position[] = $this->lang->repo->common;
+ $this->view->position[] = $this->lang->repo->view;
$this->display();
}
@@ -302,7 +356,7 @@ class repo extends control
}
else
{
- $change['view'] = $viewPriv ? html::a($this->repo->createLink('log', "repoID=$repoID&entry=&revision=$revision", "entry=$encodePath"), $this->lang->repo->log) : '';
+ $change['view'] = $viewPriv ? html::a($this->repo->createLink('browse', "repoID=$repoID&path=&revision=$revision", "path=$encodePath"), $this->lang->repo->browse) : '';
if($change['action'] == 'M') $change['diff'] = $diffPriv ? html::a($this->repo->createLink('diff', "repoID=$repoID&entry=&oldRevision=$oldRevision&newRevision=$revision", "entry=$encodePath"), $this->lang->repo->diffAB) : '';
}
$changes[$path] = $change;
@@ -330,6 +384,55 @@ class repo extends control
$this->view->oldRevision = $oldRevision;
$this->view->preAndNext = $this->repo->getPreAndNext($repo, $root, $revision, $type, 'revision');
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->viewRevision;
+ $this->view->position[] = $this->lang->repo->common;
+ $this->view->position[] = $this->lang->repo->viewRevision;
+
+ $this->display();
+ }
+
+ /**
+ * Blame repo file.
+ *
+ * @param int $repoID
+ * @param string $entry
+ * @param string $revision
+ * @param string $encoding
+ * @access public
+ * @return void
+ */
+ public function blame($repoID, $entry, $revision = 'HEAD', $encoding = '')
+ {
+ if($this->get->entry) $entry = $this->get->entry;
+ $this->repo->setMenu($this->repos, $repoID);
+ if($repoID == 0) $repoID = $this->session->repoID;
+ $repo = $this->repo->getRepoByID($repoID);
+ $file = $entry;
+ $entry = $this->repo->decodePath($entry);
+
+ $this->scm->setEngine($repo);
+ $encoding = empty($encoding) ? $repo->encoding : $encoding;
+ $encoding = strtolower(str_replace('_', '-', $encoding));
+ $blames = $this->scm->blame($entry, $revision);
+ $revisions = array();
+ foreach($blames as $i => $blame)
+ {
+ if(isset($blame['revision'])) $revisions[$blame['revision']] = $blame['revision'];
+ if($encoding != 'utf-8') $blames[$i]['content'] = helper::convertEncoding($blame['content'], $encoding);
+ }
+
+ $log = $repo->SCM == 'Git' ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->eq($revision)->andWhere('repo')->eq($repo->id)->fetch() : '';
+
+ $this->view->title = $this->lang->repo->common;
+ $this->view->repoID = $repoID;
+ $this->view->repo = $repo;
+ $this->view->revision = $revision;
+ $this->view->entry = $entry;
+ $this->view->file = $file;
+ $this->view->encoding = str_replace('-', '_', $encoding);
+ $this->view->historys = $repo->SCM == 'Git' ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->andWhere('repo')->eq($repo->id)->fetchPairs() : '';
+ $this->view->revisionName = ($log and $repo->SCM == 'Git') ? $this->repo->getGitRevisionName($log->revision, $log->commit) : $revision;
+ $this->view->blames = $blames;
$this->display();
}
@@ -420,9 +523,6 @@ class repo extends control
}
}
- $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->diff;
- $this->view->position[] = $this->lang->repo->diff;
-
$this->view->type = 'diff';
$this->view->showBug = $showBug;
$this->view->entry = urldecode($entry);
@@ -439,6 +539,10 @@ class repo extends control
$this->view->historys = $repo->SCM == 'Git' ? $this->dao->select('revision,commit')->from(TABLE_REPOHISTORY)->where('revision')->in("$oldRevision,$newRevision")->andWhere('repo')->eq($repo->id)->fetchPairs() : '';
$this->view->info = $info;
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->diff;
+ $this->view->position[] = $this->lang->repo->common;
+ $this->view->position[] = $this->lang->repo->diff;
+
$this->display();
}
@@ -466,6 +570,37 @@ class repo extends control
$this->fetch('file', 'sendDownHeader', array("fileName" => $fileName, "fileType" => $extension, "content" => $content));
}
+ /**
+ * Set matchComment.
+ *
+ * @access public
+ * @return void
+ */
+ public function setMatchComment($module = '')
+ {
+ if($_POST)
+ {
+ $module = $this->post->selectModule;
+ unset($_POST['selectModule']);
+ $this->loadModel('setting')->setItem('system.repo.matchComment', json_encode($this->post->matchComment));
+ $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => inlink('setMatchComment', "module={$module}")));
+ }
+
+ $this->repo->setMenu($this->repos, $this->session->repoID, false);
+
+ $this->app->loadLang('task');
+ $this->app->loadLang('bug');
+ $this->app->loadLang('story');
+ $this->app->loadLang('integration');
+ if(is_string($this->config->repo->matchComment)) $this->config->repo->matchComment = json_decode($this->config->repo->matchComment, true);
+
+ $this->view->title = $this->lang->repo->common . $this->lang->colon . $this->lang->repo->setMatchComment;
+ $this->view->position[] = $this->lang->repo->setMatchComment;
+
+ $this->view->selectModule = $module;
+ $this->display();
+ }
+
/**
* Show sync comment.
*
@@ -628,4 +763,67 @@ class repo extends control
die('norecords');
}
+
+ /**
+ * Ajax get svn tags
+ *
+ * @param int $repoID
+ * @param string $path
+ * @param string $revision
+ * @access public
+ * @return void
+ */
+ public function ajaxGetSVNTags($repoID, $path = '', $revision = 'HEAD')
+ {
+ if($this->get->path) $entry = $this->get->path;
+ $repo = $this->repo->getRepoByID($repoID);
+ if($repo->SCM != 'Subversion') die(json_encode(array()));
+
+ $this->scm->setEngine($repo);
+ $path = $this->repo->decodePath($path);
+ $tags = $this->scm->tags($path, $revision);
+
+ $parentInfos = array();
+ $parentPath = '';
+
+ $info = array();
+ $info['path'] = '/';
+ $info['url'] = $repo->path . $info['path'];
+ $info['encodePath'] = $this->repo->encodePath($info['path']);
+ $parentInfos['/'] = $info;
+
+ foreach(explode('/', $path) as $parent)
+ {
+ if(empty($parent)) continue;
+ $parentPath .= '/' . $parent;
+
+ $info = array();
+ $info['path'] = $parentPath;
+ $info['url'] = $repo->path . $info['path'];
+
+ $info['encodePath'] = $this->repo->encodePath($info['path']);
+
+ $parentInfos[$parent] = $info;
+ }
+
+ $tagInfos = array();
+ foreach($tags as $tag)
+ {
+ $info = array();
+ $info['path'] = '/';
+ if($path) $info['path'] .= $path . '/';
+ $info['path'] .= $tag;
+ $info['url'] = $repo->path . $info['path'];
+
+ $info['encodePath'] = $this->repo->encodePath($info['path']);
+
+ $tagInfos[$tag] = $info;
+ }
+
+ $svnTags = array();
+ $svnTags['parent'] = $parentInfos;
+ $svnTags['tags'] = $tagInfos;
+
+ die(json_encode($svnTags));
+ }
}
diff --git a/module/repo/css/common.css b/module/repo/css/common.css
index 95d97fd170..5b76cbfba2 100644
--- a/module/repo/css/common.css
+++ b/module/repo/css/common.css
@@ -1,6 +1,6 @@
a{color:#169;}
a:hover, a:active{ text-decoration:underline; color:#C61A1A;}
-h2,h3 {font-size:20px; padding:0 0 10px; margin: 0; clear: both;}
+h2,h3 {font-size:20px; margin: 0; clear: both;}
h3 {font-size:16px;}
.revision{font-size:12px; line-height:20px; text-align:right; padding-right:8px;}
.directory{ background-image:url('theme/default/images/repo/dir.png')}
@@ -76,11 +76,11 @@ h3 {font-size:16px;}
.repoCode tr.commented .comment-btn {display: block;}
.repoCode tr.commented .comment-btn .icon-wrapper {background: none; border: none; width: 24px; line-height: 18px; height: 18px; left: -6px; color: #4183c4}
.repoCode tr.commented .comment-btn .icon-wrapper:hover {border-color: #169; color: #169}
-.repoCode tr.commented .comment-btn .icon-wrapper > i:before {content: '\e750'; font-size: 18px; transform: scale(-1, 1); display: inline-block;}
+.repoCode tr.commented .comment-btn .icon-wrapper > i:before {font-size: 18px; transform: scale(-1, 1); display: inline-block;}
.repoCode tr.commented .comment-btn .icon-wrapper:before {display: none}
.repoCode tr.over.commented .comment-btn .icon-wrapper, .repoCode tr.selected.commented .comment-btn .icon-wrapper {line-height: 20px; height: 20px; background: #4183C4; color: #fff; left: -6px}
-.repoCode tr.over.commented .comment-btn .icon-wrapper > i:before {content: "\e661"; font-size: 14px;}
+.repoCode tr.over.commented .comment-btn .icon-wrapper > i:before {font-size: 14px;}
.repoCode tr.selected.commented .comment-btn .icon-wrapper > i:before {font-size: 14px;}
.repoCode tr.over.commented .comment-btn .icon-wrapper:before, .repoCode tr.selected.commented .comment-btn .icon-wrapper:before {display: block;}
diff --git a/module/repo/js/create.js b/module/repo/js/create.js
new file mode 100644
index 0000000000..808e0ec8dd
--- /dev/null
+++ b/module/repo/js/create.js
@@ -0,0 +1,20 @@
+$(function()
+{
+ scmChanged(scm);
+});
+
+function scmChanged(scm) {
+ if(scm == 'Git')
+ {
+ $('.account-fields').addClass('hidden');
+
+ $('.tips-git').removeClass('hidden');
+ $('.tips-svn').addClass('hidden');
+ } else
+ {
+ $('.account-fields').removeClass('hidden');
+
+ $('.tips-git').addClass('hidden');
+ $('.tips-svn').removeClass('hidden');
+ }
+}
\ No newline at end of file
diff --git a/module/repo/js/edit.js b/module/repo/js/edit.js
new file mode 100644
index 0000000000..808e0ec8dd
--- /dev/null
+++ b/module/repo/js/edit.js
@@ -0,0 +1,20 @@
+$(function()
+{
+ scmChanged(scm);
+});
+
+function scmChanged(scm) {
+ if(scm == 'Git')
+ {
+ $('.account-fields').addClass('hidden');
+
+ $('.tips-git').removeClass('hidden');
+ $('.tips-svn').addClass('hidden');
+ } else
+ {
+ $('.account-fields').removeClass('hidden');
+
+ $('.tips-git').addClass('hidden');
+ $('.tips-svn').removeClass('hidden');
+ }
+}
\ No newline at end of file
diff --git a/module/repo/lang/de.php b/module/repo/lang/de.php
deleted file mode 100644
index f666a0368d..0000000000
--- a/module/repo/lang/de.php
+++ /dev/null
@@ -1,150 +0,0 @@
-repo->common = 'Repo';
-$lang->repo->create = 'Create Repo';
-$lang->repo->settings = 'Settings';
-$lang->repo->browse = 'View Repo';
-$lang->repo->delete = 'Delete Repo';
-$lang->repo->showSyncComment = 'Display Synchronization';
-$lang->repo->ajaxSyncComment = 'Interface: Ajax Sync Note';
-$lang->repo->download = 'Download File';
-$lang->repo->downloadDiff = 'Download Diff';
-$lang->repo->diffAction = 'Revision Diff';
-$lang->repo->revisionAction = 'Revision Detail';
-$lang->repo->blameAction = 'Repo Blame';
-$lang->repo->addBug = 'Add Review';
-$lang->repo->editBug = 'Edit Bug';
-$lang->repo->deleteBug = 'Delete Bug';
-$lang->repo->addComment = 'Add Comment';
-$lang->repo->editComment = 'Edit Comment';
-$lang->repo->deleteComment = 'Delete Comment';
-
-$lang->repo->submit = 'Submit';
-$lang->repo->cancel = 'Cancel';
-$lang->repo->addComment = 'Add Comment';
-
-$lang->repo->product = $lang->productCommon;
-$lang->repo->module = 'Module';
-$lang->repo->project = $lang->projectCommon;
-$lang->repo->type = 'Type';
-$lang->repo->assign = 'AssignTo';
-$lang->repo->title = 'Title';
-$lang->repo->detile = 'Detail';
-$lang->repo->lines = 'Lines';
-$lang->repo->line = 'Line';
-$lang->repo->expand = 'Unfold';
-$lang->repo->collapse = 'Fold';
-
-$lang->repo->id = 'ID';
-$lang->repo->SCM = 'Type';
-$lang->repo->name = 'Name';
-$lang->repo->path = 'Path';
-$lang->repo->prefix = 'Prefix';
-$lang->repo->config = 'Config';
-$lang->repo->account = 'Username';
-$lang->repo->password = 'Password';
-$lang->repo->encoding = 'Encoding';
-$lang->repo->client = 'Client Path';
-$lang->repo->size = 'Size';
-$lang->repo->revision = 'Revision';
-$lang->repo->revisionA = 'Revision';
-$lang->repo->revisions = 'Revision';
-$lang->repo->time = 'Date';
-$lang->repo->committer = 'Committer';
-$lang->repo->commits = 'Commits';
-$lang->repo->synced = 'Initialize Sync';
-$lang->repo->lastSync = 'Last Sync';
-$lang->repo->deleted = 'Deleted';
-$lang->repo->commit = 'Commit';
-$lang->repo->comment = 'Comment';
-$lang->repo->view = 'View File';
-$lang->repo->viewA = 'View';
-$lang->repo->log = 'Revision Log';
-$lang->repo->blame = 'Blame';
-$lang->repo->date = 'Date';
-$lang->repo->diff = 'Diff';
-$lang->repo->diffAB = 'Diff';
-$lang->repo->diffAll = 'Diff All';
-$lang->repo->viewDiff = 'View diff';
-$lang->repo->allLog = 'All Revisions';
-$lang->repo->location = 'Location';
-$lang->repo->file = 'File';
-$lang->repo->action = 'Action';
-$lang->repo->code = 'Code';
-$lang->repo->review = 'Repo Review';
-$lang->repo->acl = 'Privilege';
-$lang->repo->group = 'Group';
-$lang->repo->user = 'User';
-$lang->repo->info = 'Version Info';
-
-$lang->repo->title = 'Title';
-$lang->repo->status = 'Status';
-$lang->repo->openedBy = 'CreatedBy';
-$lang->repo->assignedTo = 'AssignedTo';
-$lang->repo->openedDate = 'CreatedDate';
-
-$lang->repo->latestRevision = 'Latest Revision';
-$lang->repo->actionInfo = "Add by %s in %s";
-$lang->repo->changes = "Change Log";
-$lang->repo->reviewLocation = "File: %s@%s, line:%s - %s";
-$lang->repo->commentEdit = '';
-$lang->repo->commentDelete = '';
-$lang->repo->allChanges = "Other Changes";
-$lang->repo->commitTitle = "The %sth Commit";
-
-$lang->repo->viewDiffList['inline'] = 'Inline';
-$lang->repo->viewDiffList['appose'] = 'Parallel';
-
-$lang->repo->encryptList['plain'] = 'No encryption';
-$lang->repo->encryptList['base64'] = 'BASE64';
-
-$lang->repo->logStyles['A'] = 'Add';
-$lang->repo->logStyles['M'] = 'Modification';
-$lang->repo->logStyles['D'] = 'Delete';
-
-$lang->repo->encodingList['utf_8'] = 'UTF-8';
-$lang->repo->encodingList['gbk'] = 'GBK';
-
-$lang->repo->scmList['Subversion'] = 'Subversion';
-$lang->repo->scmList['Git'] = 'Git';
-
-$lang->repo->notice = new stdclass();
-$lang->repo->notice->syncing = 'Synchronizing. Please wait ...';
-$lang->repo->notice->syncComplete = 'Synchronized. Now redirecting ...';
-$lang->repo->notice->syncedCount = 'The number of records synchronized is ';
-$lang->repo->notice->delete = 'Are you sure delete this repo?';
-$lang->repo->notice->successDelete = 'Repository is removed.';
-$lang->repo->notice->commentContent = 'Comment';
-$lang->repo->notice->deleteBug = 'Are you sure to delete this bug?';
-$lang->repo->notice->deleteComment = 'Are you sure to delete this comment?';
-$lang->repo->notice->lastSyncTime = 'Last Sync:';
-
-$lang->repo->error = new stdclass();
-$lang->repo->error->useless = 'Your server disabled exec and shell_exec, so it cannot be applied.';
-$lang->repo->error->connect = 'Connection to the repo failed. Please enter username, password and repo address correctly!';
-$lang->repo->error->version = 'Version 1.8+ of https and svn protocol is required. Please update to latest version! Go to http://subversion.apache.org/';
-$lang->repo->error->path = 'Repo address is the file path, e.g. /home/test.';
-$lang->repo->error->cmd = 'Client Error!';
-$lang->repo->error->diff = 'Two versions must be selected.';
-$lang->repo->error->product = "Please select {$lang->productCommon}!";
-$lang->repo->error->commentText = 'Please enter content for review!';
-$lang->repo->error->comment = 'Please enter content!';
-$lang->repo->error->title = 'Please enter title!';
-$lang->repo->error->accessDenied = 'You do not have the privilege to access the repository.';
-$lang->repo->error->noFound = 'The repo is not found.';
-$lang->repo->error->noFile = '%s does not exist.';
-$lang->repo->error->noPriv = 'The program does not have the privilege to switch to %s';
-$lang->repo->error->output = "The command is: %s\nThe error is(%s): %s\n";
-$lang->repo->error->clientVersion = "Client version is too low, please upgrade or change SVN client";
-$lang->repo->error->encoding = "The encoding maybe wrong. Please change the encoding and try again.";
-
-$lang->repo->example = new stdclass();
-$lang->repo->example->client = "For example, /usr/bin/svn, C:\subversion\svn.exe, /usr/bin/git";
-$lang->repo->example->path = "For example, SVN: http://example.googlecode.com/svn/, GIT: /home/test";
-$lang->repo->example->config = "Config directory is required in https. Use '--config-dir' to generate config dir.";
-$lang->repo->example->encoding = "input encoding of files";
-
-$lang->repo->typeList['standard'] = 'Standard';
-$lang->repo->typeList['performance'] = 'Performance';
-$lang->repo->typeList['security'] = 'Security';
-$lang->repo->typeList['redundancy'] = 'Redundancy';
-$lang->repo->typeList['logicError'] = 'Logic Error';
diff --git a/module/repo/lang/en.php b/module/repo/lang/en.php
index f666a0368d..aa36fd26c4 100644
--- a/module/repo/lang/en.php
+++ b/module/repo/lang/en.php
@@ -1,11 +1,15 @@
repo->common = 'Repo';
-$lang->repo->create = 'Create Repo';
-$lang->repo->settings = 'Settings';
-$lang->repo->browse = 'View Repo';
+$lang->repo->browse = 'View';
+$lang->repo->viewRevision = 'View Revision';
+$lang->repo->create = 'Create';
+$lang->repo->createAction = 'Create Repo';
+$lang->repo->edit = 'Edit';
+$lang->repo->editAction = 'Edit Repo';
$lang->repo->delete = 'Delete Repo';
$lang->repo->showSyncComment = 'Display Synchronization';
$lang->repo->ajaxSyncComment = 'Interface: Ajax Sync Note';
+$lang->repo->setMatchComment = 'Set match comment';
$lang->repo->download = 'Download File';
$lang->repo->downloadDiff = 'Download Diff';
$lang->repo->diffAction = 'Revision Diff';
@@ -17,6 +21,7 @@ $lang->repo->deleteBug = 'Delete Bug';
$lang->repo->addComment = 'Add Comment';
$lang->repo->editComment = 'Edit Comment';
$lang->repo->deleteComment = 'Delete Comment';
+$lang->repo->selectModule = 'Select Module';
$lang->repo->submit = 'Submit';
$lang->repo->cancel = 'Cancel';
@@ -40,6 +45,7 @@ $lang->repo->name = 'Name';
$lang->repo->path = 'Path';
$lang->repo->prefix = 'Prefix';
$lang->repo->config = 'Config';
+$lang->repo->desc = 'Describe';
$lang->repo->account = 'Username';
$lang->repo->password = 'Password';
$lang->repo->encoding = 'Encoding';
@@ -90,6 +96,8 @@ $lang->repo->commentEdit = '';
$lang->repo->commentDelete = '';
$lang->repo->allChanges = "Other Changes";
$lang->repo->commitTitle = "The %sth Commit";
+$lang->repo->mark = "Mark Tag";
+$lang->repo->split = "Split Mark";
$lang->repo->viewDiffList['inline'] = 'Inline';
$lang->repo->viewDiffList['appose'] = 'Parallel';
@@ -104,8 +112,8 @@ $lang->repo->logStyles['D'] = 'Delete';
$lang->repo->encodingList['utf_8'] = 'UTF-8';
$lang->repo->encodingList['gbk'] = 'GBK';
-$lang->repo->scmList['Subversion'] = 'Subversion';
$lang->repo->scmList['Git'] = 'Git';
+$lang->repo->scmList['Subversion'] = 'Subversion';
$lang->repo->notice = new stdclass();
$lang->repo->notice->syncing = 'Synchronizing. Please wait ...';
@@ -118,6 +126,12 @@ $lang->repo->notice->deleteBug = 'Are you sure to delete this bug?';
$lang->repo->notice->deleteComment = 'Are you sure to delete this comment?';
$lang->repo->notice->lastSyncTime = 'Last Sync:';
+$lang->repo->matchComment = new stdclass();
+$lang->repo->matchComment->exampleLabel = "Comment Example";
+$lang->repo->matchComment->example['task']['start'] = "%start% %task% %id%1%split%2 %cost%%consumedmark%1 %left%%leftmark%3";
+$lang->repo->matchComment->example['task']['finish'] = "%finish% %task% %id%1%split%2 %cost%%consumedmark%10";
+$lang->repo->matchComment->example['bug']['resolve'] = "%resolve% %bug% %id%1%split%2 %resolvedBuild% %buildmark%10";
+
$lang->repo->error = new stdclass();
$lang->repo->error->useless = 'Your server disabled exec and shell_exec, so it cannot be applied.';
$lang->repo->error->connect = 'Connection to the repo failed. Please enter username, password and repo address correctly!';
@@ -137,11 +151,18 @@ $lang->repo->error->output = "The command is: %s\nThe error is(%s): %s\n"
$lang->repo->error->clientVersion = "Client version is too low, please upgrade or change SVN client";
$lang->repo->error->encoding = "The encoding maybe wrong. Please change the encoding and try again.";
-$lang->repo->example = new stdclass();
-$lang->repo->example->client = "For example, /usr/bin/svn, C:\subversion\svn.exe, /usr/bin/git";
-$lang->repo->example->path = "For example, SVN: http://example.googlecode.com/svn/, GIT: /home/test";
-$lang->repo->example->config = "Config directory is required in https. Use '--config-dir' to generate config dir.";
-$lang->repo->example->encoding = "input encoding of files";
+$lang->repo->synTips = 'You may find the reference about how to set Git sync from here.';
+$lang->repo->encodingsTips = "The encodings of commit comments, can be comma separated values,e.g. utf-8";
+
+$lang->repo->example = new stdclass();
+$lang->repo->example->client = new stdclass();
+$lang->repo->example->path = new stdclass();
+$lang->repo->example->client->git = "e.g. /usr/bin/git";
+$lang->repo->example->client->svn = "e.g. /usr/bin/svn";
+$lang->repo->example->path->git = "e.g. /homt/user/myproject";
+$lang->repo->example->path->svn = "e.g. http://example.googlecode.com/svn/trunk/myproject";
+$lang->repo->example->config = "Config directory is required in https. Use '--config-dir' to generate config dir.";
+$lang->repo->example->encoding = "input encoding of files";
$lang->repo->typeList['standard'] = 'Standard';
$lang->repo->typeList['performance'] = 'Performance';
diff --git a/module/repo/lang/fr.php b/module/repo/lang/fr.php
deleted file mode 100644
index f666a0368d..0000000000
--- a/module/repo/lang/fr.php
+++ /dev/null
@@ -1,150 +0,0 @@
-repo->common = 'Repo';
-$lang->repo->create = 'Create Repo';
-$lang->repo->settings = 'Settings';
-$lang->repo->browse = 'View Repo';
-$lang->repo->delete = 'Delete Repo';
-$lang->repo->showSyncComment = 'Display Synchronization';
-$lang->repo->ajaxSyncComment = 'Interface: Ajax Sync Note';
-$lang->repo->download = 'Download File';
-$lang->repo->downloadDiff = 'Download Diff';
-$lang->repo->diffAction = 'Revision Diff';
-$lang->repo->revisionAction = 'Revision Detail';
-$lang->repo->blameAction = 'Repo Blame';
-$lang->repo->addBug = 'Add Review';
-$lang->repo->editBug = 'Edit Bug';
-$lang->repo->deleteBug = 'Delete Bug';
-$lang->repo->addComment = 'Add Comment';
-$lang->repo->editComment = 'Edit Comment';
-$lang->repo->deleteComment = 'Delete Comment';
-
-$lang->repo->submit = 'Submit';
-$lang->repo->cancel = 'Cancel';
-$lang->repo->addComment = 'Add Comment';
-
-$lang->repo->product = $lang->productCommon;
-$lang->repo->module = 'Module';
-$lang->repo->project = $lang->projectCommon;
-$lang->repo->type = 'Type';
-$lang->repo->assign = 'AssignTo';
-$lang->repo->title = 'Title';
-$lang->repo->detile = 'Detail';
-$lang->repo->lines = 'Lines';
-$lang->repo->line = 'Line';
-$lang->repo->expand = 'Unfold';
-$lang->repo->collapse = 'Fold';
-
-$lang->repo->id = 'ID';
-$lang->repo->SCM = 'Type';
-$lang->repo->name = 'Name';
-$lang->repo->path = 'Path';
-$lang->repo->prefix = 'Prefix';
-$lang->repo->config = 'Config';
-$lang->repo->account = 'Username';
-$lang->repo->password = 'Password';
-$lang->repo->encoding = 'Encoding';
-$lang->repo->client = 'Client Path';
-$lang->repo->size = 'Size';
-$lang->repo->revision = 'Revision';
-$lang->repo->revisionA = 'Revision';
-$lang->repo->revisions = 'Revision';
-$lang->repo->time = 'Date';
-$lang->repo->committer = 'Committer';
-$lang->repo->commits = 'Commits';
-$lang->repo->synced = 'Initialize Sync';
-$lang->repo->lastSync = 'Last Sync';
-$lang->repo->deleted = 'Deleted';
-$lang->repo->commit = 'Commit';
-$lang->repo->comment = 'Comment';
-$lang->repo->view = 'View File';
-$lang->repo->viewA = 'View';
-$lang->repo->log = 'Revision Log';
-$lang->repo->blame = 'Blame';
-$lang->repo->date = 'Date';
-$lang->repo->diff = 'Diff';
-$lang->repo->diffAB = 'Diff';
-$lang->repo->diffAll = 'Diff All';
-$lang->repo->viewDiff = 'View diff';
-$lang->repo->allLog = 'All Revisions';
-$lang->repo->location = 'Location';
-$lang->repo->file = 'File';
-$lang->repo->action = 'Action';
-$lang->repo->code = 'Code';
-$lang->repo->review = 'Repo Review';
-$lang->repo->acl = 'Privilege';
-$lang->repo->group = 'Group';
-$lang->repo->user = 'User';
-$lang->repo->info = 'Version Info';
-
-$lang->repo->title = 'Title';
-$lang->repo->status = 'Status';
-$lang->repo->openedBy = 'CreatedBy';
-$lang->repo->assignedTo = 'AssignedTo';
-$lang->repo->openedDate = 'CreatedDate';
-
-$lang->repo->latestRevision = 'Latest Revision';
-$lang->repo->actionInfo = "Add by %s in %s";
-$lang->repo->changes = "Change Log";
-$lang->repo->reviewLocation = "File: %s@%s, line:%s - %s";
-$lang->repo->commentEdit = '';
-$lang->repo->commentDelete = '';
-$lang->repo->allChanges = "Other Changes";
-$lang->repo->commitTitle = "The %sth Commit";
-
-$lang->repo->viewDiffList['inline'] = 'Inline';
-$lang->repo->viewDiffList['appose'] = 'Parallel';
-
-$lang->repo->encryptList['plain'] = 'No encryption';
-$lang->repo->encryptList['base64'] = 'BASE64';
-
-$lang->repo->logStyles['A'] = 'Add';
-$lang->repo->logStyles['M'] = 'Modification';
-$lang->repo->logStyles['D'] = 'Delete';
-
-$lang->repo->encodingList['utf_8'] = 'UTF-8';
-$lang->repo->encodingList['gbk'] = 'GBK';
-
-$lang->repo->scmList['Subversion'] = 'Subversion';
-$lang->repo->scmList['Git'] = 'Git';
-
-$lang->repo->notice = new stdclass();
-$lang->repo->notice->syncing = 'Synchronizing. Please wait ...';
-$lang->repo->notice->syncComplete = 'Synchronized. Now redirecting ...';
-$lang->repo->notice->syncedCount = 'The number of records synchronized is ';
-$lang->repo->notice->delete = 'Are you sure delete this repo?';
-$lang->repo->notice->successDelete = 'Repository is removed.';
-$lang->repo->notice->commentContent = 'Comment';
-$lang->repo->notice->deleteBug = 'Are you sure to delete this bug?';
-$lang->repo->notice->deleteComment = 'Are you sure to delete this comment?';
-$lang->repo->notice->lastSyncTime = 'Last Sync:';
-
-$lang->repo->error = new stdclass();
-$lang->repo->error->useless = 'Your server disabled exec and shell_exec, so it cannot be applied.';
-$lang->repo->error->connect = 'Connection to the repo failed. Please enter username, password and repo address correctly!';
-$lang->repo->error->version = 'Version 1.8+ of https and svn protocol is required. Please update to latest version! Go to http://subversion.apache.org/';
-$lang->repo->error->path = 'Repo address is the file path, e.g. /home/test.';
-$lang->repo->error->cmd = 'Client Error!';
-$lang->repo->error->diff = 'Two versions must be selected.';
-$lang->repo->error->product = "Please select {$lang->productCommon}!";
-$lang->repo->error->commentText = 'Please enter content for review!';
-$lang->repo->error->comment = 'Please enter content!';
-$lang->repo->error->title = 'Please enter title!';
-$lang->repo->error->accessDenied = 'You do not have the privilege to access the repository.';
-$lang->repo->error->noFound = 'The repo is not found.';
-$lang->repo->error->noFile = '%s does not exist.';
-$lang->repo->error->noPriv = 'The program does not have the privilege to switch to %s';
-$lang->repo->error->output = "The command is: %s\nThe error is(%s): %s\n";
-$lang->repo->error->clientVersion = "Client version is too low, please upgrade or change SVN client";
-$lang->repo->error->encoding = "The encoding maybe wrong. Please change the encoding and try again.";
-
-$lang->repo->example = new stdclass();
-$lang->repo->example->client = "For example, /usr/bin/svn, C:\subversion\svn.exe, /usr/bin/git";
-$lang->repo->example->path = "For example, SVN: http://example.googlecode.com/svn/, GIT: /home/test";
-$lang->repo->example->config = "Config directory is required in https. Use '--config-dir' to generate config dir.";
-$lang->repo->example->encoding = "input encoding of files";
-
-$lang->repo->typeList['standard'] = 'Standard';
-$lang->repo->typeList['performance'] = 'Performance';
-$lang->repo->typeList['security'] = 'Security';
-$lang->repo->typeList['redundancy'] = 'Redundancy';
-$lang->repo->typeList['logicError'] = 'Logic Error';
diff --git a/module/repo/lang/zh-cn.php b/module/repo/lang/zh-cn.php
index 14c089439a..c680f543ff 100644
--- a/module/repo/lang/zh-cn.php
+++ b/module/repo/lang/zh-cn.php
@@ -1,11 +1,15 @@
repo->common = '代码';
-$lang->repo->create = '创建版本库';
-$lang->repo->settings = '版本库设置';
$lang->repo->browse = '浏览';
+$lang->repo->viewRevision = '查看修订';
+$lang->repo->create = '创建';
+$lang->repo->createAction = '创建版本库';
+$lang->repo->edit = '编辑';
+$lang->repo->editAction = '编辑版本库';
$lang->repo->delete = '删除版本库';
$lang->repo->showSyncComment = '显示同步进度';
$lang->repo->ajaxSyncComment = '接口:AJAX同步注释';
+$lang->repo->setMatchComment = '注释指令配置';
$lang->repo->download = '下载';
$lang->repo->downloadDiff = '下载Diff';
$lang->repo->diffAction = '版本对比';
@@ -17,6 +21,7 @@ $lang->repo->deleteBug = '删除评审';
$lang->repo->addComment = '添加备注';
$lang->repo->editComment = '编辑备注';
$lang->repo->deleteComment = '删除备注';
+$lang->repo->selectModule = '选择模块';
$lang->repo->submit = '提交';
$lang->repo->cancel = '取消';
@@ -40,6 +45,7 @@ $lang->repo->name = '名称';
$lang->repo->path = '地址';
$lang->repo->prefix = '地址扩展';
$lang->repo->config = '配置目录';
+$lang->repo->desc = '描述';
$lang->repo->account = '用户名';
$lang->repo->password = '密码';
$lang->repo->encoding = '编码';
@@ -90,6 +96,8 @@ $lang->repo->commentEdit = '';
$lang->repo->commentDelete = '';
$lang->repo->allChanges = "其他改动";
$lang->repo->commitTitle = "第%s次提交";
+$lang->repo->mark = "匹配标记";
+$lang->repo->split = "分割匹配";
$lang->repo->viewDiffList['inline'] = '直列';
$lang->repo->viewDiffList['appose'] = '并排';
@@ -104,8 +112,8 @@ $lang->repo->logStyles['D'] = '删除';
$lang->repo->encodingList['utf_8'] = 'UTF-8';
$lang->repo->encodingList['gbk'] = 'GBK';
-$lang->repo->scmList['Subversion'] = 'Subversion';
$lang->repo->scmList['Git'] = 'Git';
+$lang->repo->scmList['Subversion'] = 'Subversion';
$lang->repo->notice = new stdclass();
$lang->repo->notice->syncing = '正在同步中, 请稍等...';
@@ -118,6 +126,14 @@ $lang->repo->notice->deleteBug = '确认删除该Bug?';
$lang->repo->notice->deleteComment = '确认删除该回复?';
$lang->repo->notice->lastSyncTime = '最后更新于:';
+$lang->repo->matchComment = new stdclass();
+$lang->repo->matchComment->exampleLabel = "注释示例";
+$lang->repo->matchComment->example['story']['common'] = "%story% %id%1%split%2";
+$lang->repo->matchComment->example['task']['start'] = "%start% %task% %id%1%split%2 %cost%%consumedmark%1 %left%%leftmark%3";
+$lang->repo->matchComment->example['task']['finish'] = "%finish% %task% %id%1%split%2 %cost%%consumedmark%10";
+$lang->repo->matchComment->example['bug']['resolve'] = "%resolve% %bug% %id%1%split%2 %resolvedBuild% %buildmark%10";
+$lang->repo->matchComment->example['integration']['start'] = "%build% %integration% %id%1%split%2";
+
$lang->repo->error = new stdclass();
$lang->repo->error->useless = '你的服务器禁用了exec,shell_exec方法,无法使用该功能';
$lang->repo->error->connect = '连接版本库失败,请填写正确的用户名、密码和版本库地址!';
@@ -137,11 +153,18 @@ $lang->repo->error->output = "执行命令:%s\n错误结果(%s): %s\n
$lang->repo->error->clientVersion = "客户端版本过低,请升级或更换SVN客户端";
$lang->repo->error->encoding = "编码可能错误,请更换编码重试。";
-$lang->repo->example = new stdclass();
-$lang->repo->example->client = "例如:/usr/bin/svn, C:\subversion\svn.exe, /usr/bin/git";
-$lang->repo->example->path = "例如:SVN: http://example.googlecode.com/svn/, GIT: /homt/test";
-$lang->repo->example->config = "https需要填写配置目录的位置,通过config-dir选项生成配置目录";
-$lang->repo->example->encoding = "填写版本库中文件的编码";
+$lang->repo->synTips = '请参照这里,设置版本库定时同步。';
+$lang->repo->encodingsTips = "提交日志的编码,可以用逗号连接起来的多个,比如utf-8。";
+
+$lang->repo->example = new stdclass();
+$lang->repo->example->client = new stdclass();
+$lang->repo->example->path = new stdclass();
+$lang->repo->example->client->git = "例如:/usr/bin/git";
+$lang->repo->example->client->svn = "例如:/usr/bin/svn";
+$lang->repo->example->path->git = "例如:/homt/user/myproject";
+$lang->repo->example->path->svn = "例如:http://example.googlecode.com/svn/trunk/myproject";
+$lang->repo->example->config = "https需要填写配置目录的位置,通过config-dir选项生成配置目录";
+$lang->repo->example->encoding = "填写版本库中文件的编码";
$lang->repo->typeList['standard'] = '规范';
$lang->repo->typeList['performance'] = '性能';
diff --git a/module/repo/lang/zh-tw.php b/module/repo/lang/zh-tw.php
deleted file mode 100644
index dac611fd5a..0000000000
--- a/module/repo/lang/zh-tw.php
+++ /dev/null
@@ -1,150 +0,0 @@
-repo->common = '代碼';
-$lang->repo->create = '創建版本庫';
-$lang->repo->settings = '版本庫設置';
-$lang->repo->browse = '瀏覽';
-$lang->repo->delete = '刪除版本庫';
-$lang->repo->showSyncComment = '顯示同步進度';
-$lang->repo->ajaxSyncComment = '介面:AJAX同步註釋';
-$lang->repo->download = '下載';
-$lang->repo->downloadDiff = '下載Diff';
-$lang->repo->diffAction = '版本對比';
-$lang->repo->revisionAction = '版本詳情';
-$lang->repo->blameAction = '版本追溯';
-$lang->repo->addBug = '添加評審';
-$lang->repo->editBug = '編輯評審';
-$lang->repo->deleteBug = '刪除評審';
-$lang->repo->addComment = '添加備註';
-$lang->repo->editComment = '編輯備註';
-$lang->repo->deleteComment = '刪除備註';
-
-$lang->repo->submit = '提交';
-$lang->repo->cancel = '取消';
-$lang->repo->addComment = '添加評論';
-
-$lang->repo->product = $lang->productCommon;
-$lang->repo->module = '模組';
-$lang->repo->project = $lang->projectCommon;
-$lang->repo->type = '類型';
-$lang->repo->assign = '指派';
-$lang->repo->title = '標題';
-$lang->repo->detile = '詳情';
-$lang->repo->lines = '代碼行';
-$lang->repo->line = '行';
-$lang->repo->expand = '點擊展開';
-$lang->repo->collapse = '點擊摺疊';
-
-$lang->repo->id = '編號';
-$lang->repo->SCM = '類型';
-$lang->repo->name = '名稱';
-$lang->repo->path = '地址';
-$lang->repo->prefix = '地址擴展';
-$lang->repo->config = '配置目錄';
-$lang->repo->account = '用戶名';
-$lang->repo->password = '密碼';
-$lang->repo->encoding = '編碼';
-$lang->repo->client = '客戶端';
-$lang->repo->size = '大小';
-$lang->repo->revision = '查看版本';
-$lang->repo->revisionA = '版本';
-$lang->repo->revisions = '版本';
-$lang->repo->time = '提交時間';
-$lang->repo->committer = '作者';
-$lang->repo->commits = '提交數';
-$lang->repo->synced = '初始化同步';
-$lang->repo->lastSync = '最後同步時間';
-$lang->repo->deleted = '已刪除';
-$lang->repo->commit = '提交';
-$lang->repo->comment = '註釋';
-$lang->repo->view = '查看檔案';
-$lang->repo->viewA = '查看';
-$lang->repo->log = '版本歷史';
-$lang->repo->blame = '追溯';
-$lang->repo->date = '日期';
-$lang->repo->diff = '比較差異';
-$lang->repo->diffAB = '比較';
-$lang->repo->diffAll = '全部比較';
-$lang->repo->viewDiff = '查看差異';
-$lang->repo->allLog = '所有版本';
-$lang->repo->location = '位置';
-$lang->repo->file = '檔案';
-$lang->repo->action = '操作';
-$lang->repo->code = '代碼';
-$lang->repo->review = '評審';
-$lang->repo->acl = '權限';
-$lang->repo->group = '分組';
-$lang->repo->user = '用戶';
-$lang->repo->info = '版本信息';
-
-$lang->repo->title = '標題';
-$lang->repo->status = '狀態';
-$lang->repo->openedBy = '創建者';
-$lang->repo->assignedTo = '指派給';
-$lang->repo->openedDate = '創建日期';
-
-$lang->repo->latestRevision = '最近修訂版本';
-$lang->repo->actionInfo = "由%s在%s添加";
-$lang->repo->changes = "修改記錄";
-$lang->repo->reviewLocation = "%s@%s,%s行 - %s行";
-$lang->repo->commentEdit = '';
-$lang->repo->commentDelete = '';
-$lang->repo->allChanges = "其他改動";
-$lang->repo->commitTitle = "第%s次提交";
-
-$lang->repo->viewDiffList['inline'] = '直列';
-$lang->repo->viewDiffList['appose'] = '並排';
-
-$lang->repo->encryptList['plain'] = '不加密';
-$lang->repo->encryptList['base64'] = 'BASE64';
-
-$lang->repo->logStyles['A'] = '添加';
-$lang->repo->logStyles['M'] = '修改';
-$lang->repo->logStyles['D'] = '刪除';
-
-$lang->repo->encodingList['utf_8'] = 'UTF-8';
-$lang->repo->encodingList['gbk'] = 'GBK';
-
-$lang->repo->scmList['Subversion'] = 'Subversion';
-$lang->repo->scmList['Git'] = 'Git';
-
-$lang->repo->notice = new stdclass();
-$lang->repo->notice->syncing = '正在同步中, 請稍等...';
-$lang->repo->notice->syncComplete = '同步完成,正在跳轉...';
-$lang->repo->notice->syncedCount = '已經同步記錄條數';
-$lang->repo->notice->delete = '是否要刪除該版本庫?';
-$lang->repo->notice->successDelete = '已經成功刪除版本庫。';
-$lang->repo->notice->commentContent = '輸入回覆內容';
-$lang->repo->notice->deleteBug = '確認刪除該Bug?';
-$lang->repo->notice->deleteComment = '確認刪除該回覆?';
-$lang->repo->notice->lastSyncTime = '最後更新于:';
-
-$lang->repo->error = new stdclass();
-$lang->repo->error->useless = '你的伺服器禁用了exec,shell_exec方法,無法使用該功能';
-$lang->repo->error->connect = '連接版本庫失敗,請填寫正確的用戶名、密碼和版本庫地址!';
-$lang->repo->error->version = "https和svn協議需要1.8及以上版本的客戶端,請升級到最新版本!詳情訪問:http://subversion.apache.org/";
-$lang->repo->error->path = '版本庫地址直接填寫檔案路徑,如:/home/test。';
-$lang->repo->error->cmd = '客戶端錯誤!';
-$lang->repo->error->diff = '必須選擇兩個版本';
-$lang->repo->error->product = "請選擇{$lang->productCommon}!";
-$lang->repo->error->commentText = '請填寫評審內容';
-$lang->repo->error->comment = '請填寫內容';
-$lang->repo->error->title = '請填寫標題';
-$lang->repo->error->accessDenied = '你沒有權限訪問該版本庫';
-$lang->repo->error->noFound = '你訪問的版本庫不存在';
-$lang->repo->error->noFile = '目錄 %s 不存在';
-$lang->repo->error->noPriv = '程序沒有權限切換到目錄 %s';
-$lang->repo->error->output = "執行命令:%s\n錯誤結果(%s): %s\n";
-$lang->repo->error->clientVersion = "客戶端版本過低,請升級或更換SVN客戶端";
-$lang->repo->error->encoding = "編碼可能錯誤,請更換編碼重試。";
-
-$lang->repo->example = new stdclass();
-$lang->repo->example->client = "例如:/usr/bin/svn, C:\subversion\svn.exe, /usr/bin/git";
-$lang->repo->example->path = "例如:SVN: http://example.googlecode.com/svn/, GIT: /homt/test";
-$lang->repo->example->config = "https需要填寫配置目錄的位置,通過config-dir選項生成配置目錄";
-$lang->repo->example->encoding = "填寫版本庫中檔案的編碼";
-
-$lang->repo->typeList['standard'] = '規範';
-$lang->repo->typeList['performance'] = '性能';
-$lang->repo->typeList['security'] = '安全';
-$lang->repo->typeList['redundancy'] = '冗餘';
-$lang->repo->typeList['logicError'] = '邏輯錯誤';
diff --git a/module/repo/model.php b/module/repo/model.php
index 72a4e7db16..36f15ce624 100644
--- a/module/repo/model.php
+++ b/module/repo/model.php
@@ -32,7 +32,7 @@ class repoModel extends model
* @access public
* @return void
*/
- public function setMenu($repos, $repoID = '')
+ public function setMenu($repos, $repoID = '', $showRepoSeletion = true)
{
if(empty($repoID)) $repoID = $this->session->repoID ? $this->session->repoID : key($repos);
if(!isset($repos[$repoID])) $repoID = key($repos);
@@ -54,7 +54,7 @@ class repoModel extends model
}
}
- if(!empty($repos))
+ if($showRepoSeletion && !empty($repos))
{
$repoIndex = '';
$repoIndex .= $this->select($repos, $repoID);
@@ -131,14 +131,21 @@ class repoModel extends model
}
/**
- * Get all repos.
- *
+ * Get repo list.
+ *
+ * @param string $orderBy
+ * @param object $pager
+ * @param bool $decode
* @access public
* @return array
*/
- public function getAllRepos()
+ public function getList($orderBy = 'id_desc', $pager = null)
{
- $repos = $this->dao->select('*')->from(TABLE_REPO)->where('deleted')->eq(0)->fetchAll();
+ $repos = $this->dao->select('*')->from(TABLE_REPO)->where('deleted')->eq('0')
+ ->orderBy($orderBy)
+ ->page($pager)
+ ->fetchAll('id');
+
foreach($repos as $i => $repo)
{
$repo->acl = json_decode($repo->acl);
@@ -148,6 +155,109 @@ class repoModel extends model
return $repos;
}
+ /**
+ * Get list by SCM.
+ *
+ * @param strint $scm
+ * @param string $type
+ * @access public
+ * @return array
+ */
+ public function getListBySCM($scm, $type = 'all')
+ {
+ $repos = $this->dao->select('*')->from(TABLE_REPO)->where('deleted')->eq('0')
+ ->andWhere('SCM')->eq($scm)
+ ->orderBy('id')
+ ->fetchAll();
+
+ foreach($repos as $i => $repo)
+ {
+ if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password);
+ $repo->acl = json_decode($repo->acl);
+ if($type == 'haspriv' and !$this->checkPriv($repo)) unset($repos[$i]);
+ }
+
+ return $repos;
+ }
+
+ /**
+ * Create a repo.
+ *
+ * @access public
+ * @return bool
+ */
+ public function create()
+ {
+ $this->checkConnection();
+ $data = fixer::input('post')->skipSpecial('path,client,account,password')->get();
+ $data->acl = empty($data->acl) ? '' : json_encode($data->acl);
+ if(empty($data->client)) $data->client = 'svn';
+
+ if($data->SCM == 'Subversion')
+ {
+ $scm = $this->app->loadClass('scm');
+ $scm->setEngine($data);
+ $info = $scm->info('');
+ $data->prefix = empty($info->root) ? '' : trim(str_ireplace($info->root, '', str_replace('\\', '/', $data->path)), '/');
+ if($data->prefix) $data->prefix = '/' . $data->prefix;
+ }
+
+ if($data->encrypt == 'base64') $data->password = base64_encode($data->password);
+ $this->dao->insert(TABLE_REPO)->data($data)
+ ->batchCheck($this->config->repo->create->requiredFields, 'notempty')
+ ->checkIF($data->SCM == 'Subversion', $this->config->repo->svn->requiredFields, 'notempty')
+ ->autoCheck()
+ ->exec();
+ return $this->dao->lastInsertID();
+ }
+
+ /**
+ * Update a repo.
+ *
+ * @param int $id
+ * @access public
+ * @return bool
+ */
+ public function update($id)
+ {
+ $this->checkConnection();
+ $data = fixer::input('post')->skipSpecial('path,client,account,password')->get();
+ $data->acl = empty($data->acl) ? '' : json_encode($data->acl);
+
+ if(empty($data->client)) $data->client = 'svn';
+ $repo = $this->getRepoByID($id);
+ $data->prefix = $repo->prefix;
+ if($data->SCM == 'Subversion' and $data->path != $repo->path)
+ {
+ $scm = $this->app->loadClass('scm');
+ $scm->setEngine($data);
+ $info = $scm->info('');
+ $data->prefix = empty($info->root) ? '' : trim(str_ireplace($info->root, '', str_replace('\\', '/', $data->path)), '/');
+ if($data->prefix) $data->prefix = '/' . $data->prefix;
+ }
+ elseif($data->SCM != $repo->SCM and $data->SCM == 'Git')
+ {
+ $data->prefix = '';
+ }
+
+ if($data->path != $repo->path) $data->synced = 0;
+
+ if($data->encrypt == 'base64') $data->password = base64_encode($data->password);
+ $this->dao->update(TABLE_REPO)->data($data)
+ ->batchCheck($this->config->repo->edit->requiredFields, 'notempty')
+ ->checkIF($data->SCM == 'Subversion', $this->config->repo->svn->requiredFields, 'notempty')
+ ->autoCheck()
+ ->where('id')->eq($id)->exec();
+
+ if($repo->path != $data->path)
+ {
+ $this->dao->delete()->from(TABLE_REPOHISTORY)->where('repo')->eq($id)->exec();
+ $this->dao->delete()->from(TABLE_REPOFILES)->where('repo')->eq($id)->exec();
+ return false;
+ }
+ return true;
+ }
+
/**
* Get repo pairs.
*
@@ -185,6 +295,25 @@ class repoModel extends model
return $repo;
}
+ /**
+ * Get by id list.
+ *
+ * @param array $idList
+ * @access public
+ * @return array
+ */
+ public function getByIdList($idList)
+ {
+ $repos = $this->dao->select('*')->from(TABLE_REPO)->where('deleted')->eq(0)->andWhere('id')->in($idList)->fetchAll();
+ foreach($repos as $i => $repo)
+ {
+ if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password);
+ $repo->acl = json_decode($repo->acl);
+ }
+
+ return $repos;
+ }
+
/**
* Get git branches.
*
@@ -296,6 +425,36 @@ class repoModel extends model
return $lastComment;
}
+ /**
+ * Get revisions from db.
+ *
+ * @param int $repoID
+ * @param string $limit
+ * @param string $maxRevision
+ * @param string $minRevision
+ * @access public
+ * @return array
+ */
+ public function getRevisionsFromDB($repoID, $limit = '', $maxRevision = '', $minRevision = '')
+ {
+ $revisions = $this->dao->select('DISTINCT t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
+ ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
+ ->where('t1.repo')->eq($repoID)
+ ->beginIF(!empty($maxRevision))->andWhere('t1.revision')->le($maxRevision)->fi()
+ ->beginIF(!empty($minRevision))->andWhere('t1.revision')->ge($minRevision)->fi()
+ ->beginIF($this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
+ ->orderBy('t1.revision desc')
+ ->beginIF(!empty($limit))->limit($limit)->fi()
+ ->fetchAll('revision');
+ $commiters = $this->loadModel('user')->getCommiters();
+ foreach($revisions as $revision)
+ {
+ $revision->comment = $this->replaceCommentLink($revision->comment);
+ $revision->committer = isset($commiters[$revision->committer]) ? $commiters[$revision->committer] : $revision->committer;
+ }
+ return $revisions;
+ }
+
/**
* Get git revisionName.
*
@@ -310,75 +469,6 @@ class repoModel extends model
return substr($revision, 0, 10) . ' (' . $commit . ') ';
}
- /**
- * create
- *
- * @access public
- * @return int
- */
- public function create()
- {
- $this->checkConnection();
- $data = fixer::input('post')->skipSpecial('path,client,account,password')->get();
- $data->acl = empty($data->acl) ? '' : json_encode($data->acl);
- if(empty($data->client)) $data->client = 'svn';
-
- if($data->SCM == 'Subversion')
- {
- $scm = $this->app->loadClass('scm');
- $scm->setEngine($data);
- $info = $scm->info('');
- $data->prefix = empty($info->root) ? '' : trim(str_ireplace($info->root, '', str_replace('\\', '/', $data->path)), '/');
- if($data->prefix) $data->prefix = '/' . $data->prefix;
- }
-
- if($data->encrypt == 'base64') $data->password = base64_encode($data->password);
- $this->dao->insert(TABLE_REPO)->data($data)->exec();
- return $this->dao->lastInsertID();
- }
-
- /**
- * Save settings.
- *
- * @param int $repoID
- * @access public
- * @return bool
- */
- public function saveSettings($repoID)
- {
- $this->checkConnection();
- $data = fixer::input('post')->skipSpecial('path,client,account,password')->get();
- $data->acl = empty($data->acl) ? '' : json_encode($data->acl);
-
- if(empty($data->client)) $data->client = 'svn';
- $repo = $this->getRepoByID($repoID);
- $data->prefix = $repo->prefix;
- if($data->SCM == 'Subversion' and $data->path != $repo->path)
- {
- $scm = $this->app->loadClass('scm');
- $scm->setEngine($data);
- $info = $scm->info('');
- $data->prefix = empty($info->root) ? '' : trim(str_ireplace($info->root, '', str_replace('\\', '/', $data->path)), '/');
- if($data->prefix) $data->prefix = '/' . $data->prefix;
- }
- elseif($data->SCM != $repo->SCM and $data->SCM == 'Git')
- {
- $data->prefix = '';
- }
-
- if($data->path != $repo->path) $data->synced = 0;
- if($data->encrypt == 'base64') $data->password = base64_encode($data->password);
- $this->dao->update(TABLE_REPO)->data($data)->where('id')->eq($repoID)->exec();
- if($repo->path != $data->path)
- {
- $this->dao->delete()->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->exec();
- $this->dao->delete()->from(TABLE_REPOFILES)->where('repo')->eq($repoID)->exec();
- $this->dao->delete()->from(TABLE_REPOBRANCH)->where('repo')->eq($repoID)->exec();
- return false;
- }
- return true;
- }
-
/**
* Save commit.
*
@@ -676,6 +766,7 @@ class repoModel extends model
*/
public function encodePath($path = '')
{
+ if(empty($path)) return $path;
return helper::safe64Encode(urlencode($path));
}
@@ -688,6 +779,7 @@ class repoModel extends model
*/
public function decodePath($path = '')
{
+ if(empty($path)) return $path;
return trim(urldecode(helper::safe64Decode($path)), '/');
}
@@ -801,7 +893,7 @@ class repoModel extends model
$stories = array();
$tasks = array();
$bugs = array();
- $commonReg = "(?:\s){0,}((?:#|:|:){0,})([0-9, ]{1,})";
+ $commonReg = "(?:\s){0,}((?:#|:|��){0,})([0-9, ]{1,})";
$taskReg = '/task' . $commonReg . '/i';
$storyReg = '/story' . $commonReg . '/i';
$bugReg = '/bug' . $commonReg . '/i';
@@ -848,4 +940,128 @@ class repoModel extends model
}
return $replaceLines;
}
+
+ /**
+ * Parse the comment of git, extract object id list from it.
+ *
+ * @param string $comment
+ * @access public
+ * @return array
+ */
+ public function parseComment($comment)
+ {
+ if(is_string($this->config->repo->matchComment)) $this->config->repo->matchComment = json_decode($this->config->repo->matchComment, true);
+ $matchComment = $this->config->repo->matchComment;
+
+ $matches = array();
+ $stories = array();
+ $tasks = array();
+ $bugs = array();
+ $integrations = array();
+ $actions = array();
+ while(true)
+ {
+ $found = preg_match("/{$matchComment['id']['mark']}[0-9]+({$matchComment['id']['split']}[0-9]+)*/", $comment, $matches);
+ if(empty($found)) break;
+ if($found)
+ {
+ $position = strpos($comment, $matches[0]);
+ $subComment = substr($comment, 0, $position + strlen($matches[0]));
+ $comment = substr($comment, $position + strlen($matches[0]) + 1);
+
+ $idList = explode($matchComment['id']['mark'], str_replace($matchComment['id']['split'], '', $matches[0]));
+ foreach($matchComment['module'] as $module => $moduleMatch)
+ {
+ if(stripos($subComment, $moduleMatch) !== false)
+ {
+ if($module == 'story')
+ {
+ foreach($idList as $id) $stories[$id] = $id;
+ }
+ elseif($module == 'task')
+ {
+ foreach($idList as $id) $tasks[$id] = $id;
+ foreach($matchComment['task'] as $method => $match)
+ {
+ if(strpos($subComment, $match) !== false)
+ {
+ $consumed = 0;
+ preg_match("/{$matchComment['task']['consumed']}{$matchComment['mark']['consumed']}([0-9]+)/", $comment, $costMatch);
+ if($costMatch) $consumed = $costMatch[1];
+
+ $left = 0;
+ preg_match("/{$matchComment['task']['left']}{$matchComment['mark']['left']}([0-9]+)/", $comment, $leftMatch);
+ if($leftMatch) $left = $leftMatch[1];
+
+ foreach($idList as $id)
+ {
+ $actions['task'][$id]['action'] = $method;
+ $actions['task'][$id]['consumed'] = $consumed;
+ $actions['task'][$id]['left'] = $left;
+ }
+ }
+ }
+ }
+ elseif($module == 'bug')
+ {
+ foreach($idList as $id) $bugs[$id] = $id;
+ foreach($matchComment['bug'] as $method => $match)
+ {
+ if(strpos($subComment, $match) !== false)
+ {
+ $buildID = 0;
+ preg_match("/{$matchComment['bug']['resolvedBuild']}{$matchComment['mark']['resolvedBuild']}([0-9]+)/", $comment, $buildMatch);
+ if($buildMatch) $buildID = $buildMatch[1];
+
+ foreach($idList as $id)
+ {
+ $actions['bug'][$id]['action'] = $method;
+ $actions['bug'][$id]['resolvedBuild'] = $buildID;
+ }
+ }
+ }
+ }
+ elseif($module == 'integration')
+ {
+ foreach($idList as $id) $integrations[$id] = $id;
+ foreach($matchComment['integration'] as $method => $match)
+ {
+ if(strpos($subComment, $match) !== false)
+ {
+ foreach($idList as $id) $actions['integration'][$id]['action'] = $method;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return array('stories' => $stories, 'tasks' => $tasks, 'bugs' => $bugs, 'integrations' => $integrations, 'actions' => $actions);
+ }
+
+ /**
+ * Iconv Comment.
+ *
+ * @param string $comment
+ * @param string $encodings
+ * @access public
+ * @return string
+ */
+ public function iconvComment($comment, $encodings)
+ {
+ /* Get encodings. */
+ if($encodings == '') return $comment;
+ $encodings = explode(',', $encodings);
+
+ /* Try convert. */
+ foreach($encodings as $encoding)
+ {
+ if($encoding == 'utf-8') continue;
+ $result = helper::convertEncoding($comment, $encoding);
+ if($result) return $result;
+ }
+
+ return $comment;
+ }
}
diff --git a/module/repo/view/blame.html.php b/module/repo/view/blame.html.php
new file mode 100644
index 0000000000..9f3b5413b5
--- /dev/null
+++ b/module/repo/view/blame.html.php
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+ repo->encodePath($entry);?>
+
+
+ repo->encodingList, $encoding, $lang->repo->encoding) . "", "id='encoding' data-toggle='dropdown'", 'btn dropdown-toggle')?>
+
+
+
+
+
+
+
diff --git a/module/repo/view/create.html.php b/module/repo/view/create.html.php
index 8b133ea902..5907f079fa 100644
--- a/module/repo/view/create.html.php
+++ b/module/repo/view/create.html.php
@@ -9,15 +9,88 @@
*/
?>
+
- |