| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183 |
- <?php
- /**
- * The model file of repo module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
- * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Yanyi Cao <caoyanyi@cnezsoft.com>
- * @package repo
- * @property repoTao $repoTao
- * @link https://www.zentao.net
- */
- class repoModel extends model
- {
- /**
- * 检查代码库的权限。
- * Check repo priv.
- *
- * @param object $repo
- * @access public
- * @return bool
- */
- public function checkPriv($repo)
- {
- $account = $this->app->user->account;
- $acl = !empty($repo->acl->acl) ? $repo->acl->acl : 'custom';
- if(empty($repo->acl)) $repo->acl = new stdclass();
- if(empty($repo->acl->users)) $repo->acl->users = array();
- if(empty($repo->acl->groups)) $repo->acl->groups = array();
- if(strpos(",{$this->app->company->admins},", ",$account,") !== false || $acl == 'open') return true;
- if($acl == 'custom' && empty(array_filter($repo->acl->groups)) && empty(array_filter($repo->acl->users))) return true;
- if($acl == 'private')
- {
- $userProducts = explode(',', $this->app->user->view->products);
- $repoProducts = explode(',', $repo->product);
- if(array_intersect($userProducts, $repoProducts)) return true;
- }
- if(!empty($repo->acl->groups))
- {
- foreach($this->app->user->groups as $group)
- {
- if(in_array($group, $repo->acl->groups)) return true;
- }
- }
- if(!empty($repo->acl->users) and in_array($account, $repo->acl->users)) return true;
- return false;
- }
- /**
- * 设置菜单链接信息。
- * Set menu.
- *
- * @param array $repos
- * @param int $repoID
- * @access public
- * @return void
- */
- public function setMenu($repos, $repoID = 0)
- {
- if(empty($repoID)) $repoID = $this->session->repoID ? $this->session->repoID : key($repos);
- if(!isset($repos[$repoID])) $repoID = key($repos);
- $repoID = (int)$repoID;
- /* Check the privilege. */
- if($repoID)
- {
- $repo = $this->getByID($repoID);
- if(!$repo || !$this->checkPriv($repo)) $repoID = 0;
- if(!$repo || !in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) unset($this->lang->devops->menu->mr);
- if(!$repo || !in_array($repo->SCM, $this->config->repo->notSyncSCM))
- {
- unset($this->lang->devops->menu->tag);
- unset($this->lang->devops->menu->branch);
- }
- }
- if(!in_array($this->app->methodName, $this->config->repo->notSetMenuVars)) common::setMenuVars('devops', $repoID);
- if(!session_id()) session_start();
- $this->session->set('repoID', $repoID);
- session_write_close();
- }
- /**
- * 获取代码库列表。
- * Get repo list.
- *
- * @param int $projectID
- * @param string $SCM Subversion|Git|Gitlab
- * @param string $orderBy
- * @param object $pager
- * @param bool $getCodePath
- * @access public
- * @return array
- * @param bool $lastSubmitTime
- * @param string $type
- * @param int $param
- */
- public function getList($projectID = 0, $SCM = '', $orderBy = 'id_desc', $pager = null, $getCodePath = false, $lastSubmitTime = false, $type = '', $param = 0)
- {
- $repoQuery = $type == 'bySearch' ? $this->repoTao->processSearchQuery($param) : '';
- $repos = $this->getListByCondition($repoQuery, $SCM, $orderBy, $pager);
- /* Get products. */
- $productIdList = $this->loadModel('product')->getProductIDByProject($projectID, false);
- foreach($repos as $i => $repo)
- {
- $repo->acl = json_decode($repo->acl);
- $repo->codePath = $repo->path;
- if(!$this->checkPriv($repo))
- {
- unset($repos[$i]);
- }
- elseif($projectID)
- {
- $hasPriv = false;
- foreach(explode(',', $repo->product) as $productID)
- {
- if(isset($productIdList[$productID])) $hasPriv = true;
- }
- if(!$hasPriv) unset($repos[$i]);
- }
- if($lastSubmitTime) $repo->lastSubmitTime = $repo->lastCommit ? $repo->lastCommit : $this->repoTao->getLastRevision($repo->id);
- if(in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) $repo = $this->processGitService($repo, $getCodePath);
- }
- return $repos;
- }
- /**
- * 根据SCM和权限获取代码库列表。
- * Get list by SCM.
- *
- * @param string $scm
- * @param string $type all|haspriv
- * @access public
- * @return array
- */
- public function getListBySCM($scm, $type = 'all')
- {
- $repos = $this->dao->select('*,acl')->from(TABLE_REPO)->where('deleted')->eq('0')
- ->andWhere('SCM')->in($scm)
- ->andWhere('synced')->eq(1)
- ->fetchAll('id', false);
- foreach($repos as $i => $repo)
- {
- if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password);
- $repo->acl = json_decode($repo->acl);
- $repo->codePath = $repo->path;
- if($type == 'haspriv' and !$this->checkPriv($repo)) unset($repos[$i]);
- if(in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) $repo = $this->processGitService($repo);
- }
- return $repos;
- }
- /**
- * 创建版本库。
- * Create a repo.
- *
- * @param object $repo
- * @param bool $isPipelineServer
- * @access public
- * @return int|false
- */
- public function create($repo, $isPipelineServer)
- {
- $this->dao->insert(TABLE_REPO)->data($repo, 'serviceToken')
- ->batchCheck($this->config->repo->create->requiredFields, 'notempty')
- ->batchCheckIF(!in_array($repo->SCM, $this->config->repo->notSyncSCM), 'path,client', 'notempty')
- ->batchCheckIF($isPipelineServer, 'serviceHost,serviceProject', 'notempty')
- ->batchCheckIF($repo->SCM == 'Subversion', $this->config->repo->svn->requiredFields, 'notempty')
- ->check('name', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->SCM))
- ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->SCM) . " and `serviceHost` = " . $this->dao->sqlobj->quote($repo->serviceHost))
- ->autoCheck()
- ->exec();
- if(dao::isError()) return false;
- $repoID = $this->dao->lastInsertID();
- $repo = $this->getByID($repoID);
- if(in_array($repo->SCM, $this->config->repo->notSyncSCM))
- {
- $token = uniqid();
- $res = $this->loadModel($repo->SCM)->addPushWebhook($repo, $token);
- if($res !== true)
- {
- $this->dao->delete()->from(TABLE_REPO)->where('id')->eq($repoID)->exec();
- dao::$errors['webhook'][] = isset($res['message']) ? $res['message'] : $this->lang->gitlab->failCreateWebhook;
- return false;
- }
- else
- {
- $this->dao->update(TABLE_REPO)->set('password')->eq($token)->where('id')->eq($repoID)->exec();
- }
- }
- $this->rmClientVersionFile();
- return $repoID;
- }
- /**
- * 创建远程版本库。
- * Create a repo.
- *
- * @param object $repo
- * @access public
- * @return int|false
- */
- public function createRepo($repo)
- {
- $check = $this->checkName($repo->name);
- if(!$check)
- {
- dao::$errors['name'] = $this->lang->repo->error->repoNameInvalid;
- return false;
- }
- $response = $this->createGitlabRepo($repo, $repo->namespace);
- $this->loadModel('gitlab');
- if(!empty($response->id))
- {
- $repo->path = $response->path;
- $repo->serviceProject = $response->serviceProject;
- $repo->extra = $response->extra;
- $repo->SCM = 'Gitlab';
- unset($repo->namespace);
- $repoID = $this->create($repo, false);
- if(dao::isError())
- {
- $this->gitlab->apiDeleteProject($repo->serviceHost, $response->id);
- return false;
- }
- return $repoID;
- }
- return $this->gitlab->apiErrorHandling($response);
- }
- /**
- * 创建gitlab远程版本库。
- * Create gitlab repo.
- *
- * @param object $repo
- * @param string $namespace
- * @access public
- * @return object|false
- */
- public function createGitlabRepo($repo, $namespace)
- {
- $project = new stdclass();
- $project->name = $repo->name;
- $project->path = $repo->name;
- $project->description = $repo->desc;
- $project->namespace_id = (int)$namespace;
- $project->initialize_with_readme = true;
- $response = $this->loadModel('gitlab')->apiCreateProject($repo->serviceHost, $project);
- if(empty($response->id)) return $response;
- $result = new stdclass();
- $result->id = $response->id;
- $result->path = $response->web_url;
- $result->serviceProject = $response->id;
- $result->extra = $response->id;
- return $result;
- }
- /**
- * 批量创建版本库。
- * Batch create repos.
- *
- * @param array $repos
- * @param int $serviceHost
- * @param string $scm
- * @access public
- * @return bool
- */
- public function batchCreate($repos, $serviceHost, $scm)
- {
- $this->loadModel('instance');
- foreach($repos as $index => $repo)
- {
- if(empty($repo->product)) continue;
- if(empty($repo->name))
- {
- dao::$errors["name[$index]"] = sprintf($this->lang->error->notempty, $this->lang->repo->name);
- return false;
- }
- $repo->serviceHost = $serviceHost;
- $repo->SCM = $scm;
- $this->dao->insert(TABLE_REPO)->data($repo)
- ->batchCheck($this->config->repo->create->requiredFields, 'notempty')
- ->check('serviceHost,serviceProject', 'notempty')
- ->check('name', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->SCM))
- ->check('serviceProject', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($repo->SCM) . " and `serviceHost` = " . $this->dao->sqlobj->quote($repo->serviceHost))
- ->autoCheck()
- ->exec();
- if(dao::isError()) return false;
- $repoID = $this->dao->lastInsertID();
- if(in_array($repo->SCM, $this->config->repo->notSyncSCM))
- {
- /* Add webhook. */
- $repo = $this->getByID($repoID);
- $this->loadModel($repo->SCM)->addPushWebhook($repo);
- $this->{$repo->SCM}->updateCodePath($repo->serviceHost, (int)$repo->serviceProject, (int)$repo->id);
- }
- $this->loadModel('action')->create('repo', $repoID, 'created');
- if(method_exists($this->instance, 'saveWaitSyncData')) $this->instance->saveWaitSyncData('repo', $repoID, 'add', false);
- }
- return true;
- }
- /**
- * 更新版本库。
- * Update a repo.
- *
- * @param object $data
- * @param object $repo
- * @param bool $isPipelineServer
- * @access public
- * @return bool
- */
- public function update($data, $repo, $isPipelineServer)
- {
- if(($repo->serviceHost != $data->serviceHost || $repo->serviceProject != $data->serviceProject) && $data->SCM == 'Gitlab')
- {
- $repo->gitService = $data->serviceHost;
- $repo->project = $data->serviceProject;
- $token = uniqid();
- $res = $this->loadModel('gitlab')->addPushWebhook($repo, $token);
- if($res !== true)
- {
- dao::$errors['webhook'][] = isset($res['message']) ? $res['message'] : $this->lang->gitlab->failCreateWebhook;
- return false;
- }
- else
- {
- $data->password = $token;
- }
- }
- if($data->SCM == 'Subversion' && $data->path != $repo->path)
- {
- $data->synced = 0;
- $data->lastSync = null;
- $data->lastCommit = null;
- }
- if($data->encrypt == 'base64') $data->password = base64_encode((string)$data->password);
- $this->dao->update(TABLE_REPO)->data($data, 'serviceToken')
- ->batchCheck($this->config->repo->edit->requiredFields, 'notempty')
- ->batchCheckIF($data->SCM != 'Gitlab', 'path,client', 'notempty')
- ->batchCheckIF($isPipelineServer, 'serviceHost,serviceProject', 'notempty')
- ->batchCheckIF($data->SCM == 'Subversion', $this->config->repo->svn->requiredFields, 'notempty')
- ->check('name', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($data->SCM) . " and `id` != $repo->id")
- ->checkIF(!$isPipelineServer, 'path', 'unique', "`SCM` = " . $this->dao->sqlobj->quote($data->SCM) . " and `serviceHost` = " . $this->dao->sqlobj->quote($data->serviceHost) . " and `id` != $repo->id")
- ->autoCheck()
- ->where('id')->eq($repo->id)->exec();
- $this->rmClientVersionFile();
- if(in_array($data->SCM, $this->config->repo->notSyncSCM))
- {
- $this->loadModel($data->SCM)->updateCodePath($data->serviceHost, (int)$data->serviceProject, $repo->id);
- $data->path = $this->getByID($repo->id)->path;
- $this->updateCommitDate($repo->id);
- }
- if(($repo->serviceHost != $data->serviceHost || $repo->serviceProject != $data->serviceProject || $repo->SCM == 'Subversion') && $repo->path != $data->path)
- {
- $this->repoTao->deleteInfoByID($repo->id);
- return false;
- }
- return true;
- }
- /**
- * 代码提交关联任务、需求、Bug。
- * Link commit to story, bug, task.
- *
- * @param int $repoID
- * @param string $revision
- * @param string $type
- * @param string $from repo|commit
- * @access public
- * @return void
- */
- public function link($repoID, $revision, $type = 'story', $from = 'repo')
- {
- $this->loadModel('action');
- if($type == 'story') $links = $objects['stories'] = $this->post->stories;
- if($type == 'bug') $links = $objects['bugs'] = $this->post->bugs;
- if($type == 'task') $links = $objects['task'] = $this->post->tasks;
- $revisionInfo = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($revision)->fetch();
- if(empty($revisionInfo))
- {
- $repo = $this->getByID($repoID);
- if(in_array($repo->SCM, $this->config->repo->notSyncSCM))
- {
- $scm = $this->app->loadClass('scm');
- $scm->setEngine($repo);
- $logs = $scm->getCommits($revision, 1);
- $this->saveCommit($repoID, $logs, 0);
- }
- else
- {
- $this->updateCommit($repoID);
- }
- }
- $revisionInfo = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($revision)->fetch();
- if(empty($revisionInfo))
- {
- dao::$errors = $this->lang->fail;
- return false;
- }
- $revisionID = $revisionInfo->id;
- $committer = $this->dao->select('account')->from(TABLE_USER)->where('commiter')->eq($revisionInfo->committer)->fetch('account');
- if(empty($committer)) $committer = $revisionInfo->committer;
- if($from == 'repo') $committer = $this->app->user->account;
- foreach($links as $linkID)
- {
- $relation = new stdclass;
- $relation->AType = 'revision';
- $relation->AID = $revisionID;
- $relation->relation = 'commit';
- $relation->BType = $type;
- $relation->BID = $linkID;
- $relation->product = 0;
- /* record module related information. */
- $this->loadModel($type)->updateLinkedCommits((int)$linkID, $repoID, [$revisionID]);
- $this->dao->replace(TABLE_RELATION)->data($relation)->exec();
- $this->action->create($type, (int)$linkID, 'linked2revision', '', substr($revisionInfo->revision, 0, 10), $committer);
- }
- return !dao::isError();
- }
- /**
- * 删除一个版本库。
- * Delete a repo.
- *
- * @param int $repoID
- * @access public
- * @return bool
- */
- public function deleteRepo($repoID)
- {
- $this->repoTao->deleteInfoByID($repoID);
- $this->dao->delete()->from(TABLE_REPO)->where('id')->eq($repoID)->exec();
- if(dao::isError()) return false;
- $this->loadModel('action')->create('repo', $repoID, 'deleted', '');
- return true;
- }
- /**
- * 取消代码提交关联的任务、需求、Bug。
- * Unlink object and commit revision.
- *
- * @param int $repoID
- * @param string $revision
- * @param string $objectType story|bug|task
- * @param int $objectID
- * @access public
- * @return bool
- */
- public function unlink($repoID, $revision, $objectType, $objectID)
- {
- $revisionID = $this->dao->select('id')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($revision)->fetch('id');
- $this->dao->delete()->from(TABLE_RELATION)
- ->where('AID')->eq($revisionID)
- ->andWhere('AType')->eq('revision')
- ->andWhere('relation')->eq('commit')
- ->andWhere('BType')->eq($objectType)
- ->andWhere('BID')->eq($objectID)->exec();
- $this->dao->delete()->from(TABLE_RELATION)
- ->where('AType')->eq($objectType)
- ->andWhere('AID')->eq($objectID)
- ->andWhere('BType')->eq('commit')
- ->andWhere('BID')->eq($revisionID)
- ->andWhere('relation')->eq('completedin')->exec();
- $this->dao->delete()->from(TABLE_RELATION)
- ->where('AType')->eq('commit')
- ->andWhere('AID')->eq($revisionID)
- ->andWhere('BType')->eq('story')
- ->andWhere('BID')->eq($objectID)
- ->andWhere('relation')->eq('completedfrom')->exec();
- if(!dao::isError()) $this->loadModel('action')->create($objectType, $objectID, 'unlinkedfromrevision', '', substr($revision, 0, 10));
- return !dao::isError();
- }
- /**
- * 设置代码库id。
- * Save repo state.
- *
- * @param int $repoID
- * @param int $objectID
- * @access public
- * @return int
- */
- public function saveState($repoID = 0, $objectID = 0)
- {
- if(session_id()) session_write_close();
- if(!defined('RUN_MODE') || RUN_MODE != 'test') session_start();
- if($repoID > 0) $this->session->set('repoID', (int)$repoID);
- $repos = $this->getRepoPairs($this->app->tab, $objectID);
- if($repoID == 0 && $this->session->repoID == '') $this->session->set('repoID', key($repos));
- if(!isset($repos[$this->session->repoID])) $this->session->set('repoID', key($repos));
- $repoID = (int)$this->session->repoID;
- session_write_close();
- return $repoID;
- }
- /**
- * 获取代码库列表键值对。
- * Get repo pairs.
- *
- * @param string $type project|execution|repo
- * @param int $projectID
- * @param bool $showScm
- * @access public
- * @return array
- */
- public function getRepoPairs($type, $projectID = 0, $showScm = true)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getRepoPairs();
- $repos = $this->dao->select('*,acl')->from(TABLE_REPO)
- ->where('deleted')->eq(0)
- ->fetchAll('id', false);
- /* Get products. */
- $productIdList = ($type == 'project' or $type == 'execution') ? $this->loadModel('product')->getProductIDByProject($projectID, false) : array();
- $repoPairs = array();
- foreach($repos as $repo)
- {
- $repo->acl = json_decode($repo->acl);
- $scm = '';
- if($showScm) $scm = $repo->SCM == 'Subversion' ? '[svn] ' : '[' . strtolower($repo->SCM) . '] ';
- if($this->checkPriv($repo))
- {
- if(($type == 'project' or $type == 'execution') and $projectID)
- {
- foreach($productIdList as $productID)
- {
- if(strpos(",$repo->product,", ",$productID,") !== false) $repoPairs[$repo->id] = $scm . $repo->name;
- }
- }
- else
- {
- $repoPairs[$repo->id] = $scm . $repo->name;
- }
- }
- }
- return $repoPairs;
- }
- /**
- * 根据应用获取代码库分组。
- * Get repos group by repo type.
- *
- * @param string $type
- * @param int $projectID
- * @param array $scmList
- * @access public
- * @return array
- */
- public function getRepoGroup($type, $projectID = 0, $scmList = array())
- {
- $repos = $this->getList(0, implode(',', $scmList));
- $productIds = $productItems = array();
- if($projectID)
- {
- $productIds = $this->loadModel('product')->getProductIDByProject($projectID, false);
- }
- else
- {
- foreach($repos as $repo) $productIds = array_merge($productIds, explode(',', $repo->product));
- }
- $products = $this->loadModel('product')->getByIdList(array_unique($productIds));
- foreach($products as $productID => $product)
- {
- $productItem = array();
- $productItem['pid'] = $productID;
- $productItem['type'] = $product->shadow ? $this->lang->project->common : 'product';
- $productItem['text'] = $product->name;
- $productItem['items'] = array();
- $productItems[$productID] = $productItem;
- }
- /* Get project products. */
- $projectProductIds = in_array($type, array('project', 'execution')) ? $this->loadModel('product')->getProductIDByProject($projectID, false) : array();
- /* Get repo data for dropmenu. */
- $repoPairs = array();
- foreach($repos as $repo)
- {
- $repo->acl = is_string($repo->acl) ? json_decode($repo->acl) : $repo->acl;
- if($this->checkPriv($repo))
- {
- $repoItem = array();
- $repoItem['id'] = $repo->id;
- $repoItem['text'] = $repo->name;
- $repoItem['keys'] = zget(common::convert2Pinyin(array($repo->name)), $repo->name, '');
- $repoItem['data-app'] = $this->app->tab;
- $repoProducts = explode(',', $repo->product);
- foreach($repoProducts as $productID)
- {
- if(in_array($type, array('project', 'execution')) && $projectID && !in_array($productID, $projectProductIds)) continue;
- if(strpos(",$repo->product,", ",$productID,") !== false)
- {
- if(!isset($repoPairs[$productID])) $repoPairs[$productID] = $productItems[$productID];
- $repoPairs[$productID]['items'][] = $repoItem;
- }
- }
- }
- }
- return $repoPairs;
- }
- /**
- * 根据ID获取代码库。
- * Get repo by id.
- *
- * @param int $repoID
- * @access public
- * @return object|false
- */
- public function getByID($repoID)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getRepo();
- if(empty($repoID)) return false;
- $repo = $this->dao->select('*')->from(TABLE_REPO)->where('id')->eq($repoID)->fetch();
- if(!$repo) return false;
- /* Update repo table for old version. */
- if(empty($repo->serviceHost) && in_array($repo->SCM, $this->config->repo->gitServiceTypeList))
- {
- $repo->serviceHost = $repo->client;
- $repo->serviceProject = $repo->extra;
- $this->dao->update(TABLE_REPO)->data(array('serviceHost' => $repo->serviceHost, 'serviceProject' => $repo->serviceProject))->where('id')->eq($repoID)->exec();
- /* Add webhook. */
- if($repo->SCM == 'Gitlab') $this->loadModel('gitlab')->updateCodePath((int)$repo->serviceHost, (int)$repo->serviceProject, $repo->id);
- }
- if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password);
- $repo->codePath = $repo->path;
- if(in_array(strtolower($repo->SCM), $this->config->repo->gitServiceList)) $repo = $this->processGitService($repo);
- $repo->acl = json_decode($repo->acl);
- if(empty($repo->acl)) $repo->acl = new stdclass();
- if(empty($repo->acl->acl)) $repo->acl->acl = 'custom';
- $repo->serviceHost = (int)$repo->serviceHost;
- $repo->gitService = $repo->serviceHost;
- $repo->serviceProject = $repo->SCM == 'Gitlab' ? (int)$repo->serviceProject : $repo->serviceProject;
- return $repo;
- }
- /**
- * 根据URL获取代码库。
- * Get repo by url.
- *
- * @param string $url
- * @access public
- * @return array
- */
- public function getRepoByUrl($url)
- {
- if(empty($url)) return array('result' => 'fail', 'message' => 'Url is empty.');
- $matches = $this->repoTao->getMatchedReposByUrl($url);
- if(empty($matches)) return array('result' => 'fail', 'message' => 'No matched gitlab.');
- $conditions = array();
- foreach($matches as $matched) $conditions[] = "(`serviceHost`='{$matched['gitlab']}' and `serviceProject`='{$matched['project']}')";
- $matchedRepos = $this->getListByCondition('(' . implode(' OR ', $conditions). ')', 'Gitlab');
- if(empty($matchedRepos)) return array('result' => 'fail', 'message' => 'No matched gitlab.');
- $matchedRepo = new stdclass();
- foreach($matchedRepos as $repo)
- {
- if(!empty($repo->preMerge))
- {
- $matchedRepo = $repo;
- break;
- }
- }
- if(empty($matchedRepo)) return array('result' => 'fail', 'message' => 'Matched gitlab is not open pre merge.');
- if(empty($matchedRepo->job)) return array('result' => 'fail', 'message' => 'No linked job.');
- $job = $this->dao->select('*')->from(TABLE_JOB)->where('id')->eq($matchedRepo->job)->andWhere('deleted')->eq(0)->fetch();
- if(empty($job)) return array('result' => 'fail', 'message' => 'Linked job is not exists.');
- $matchedRepo->job = $job;
- return array('result' => 'success', 'data' => $matchedRepo);
- }
- /**
- * 根据URL获取代码库列表。
- * Get repo list by url.
- *
- * @param string $url
- * @access public
- * @return array
- */
- public function getRepoListByUrl($url = '')
- {
- if(empty($url)) return array('status' => 'fail', 'message' => 'Url is empty.');
- $matches = $this->repoTao->getMatchedReposByUrl($url);
- if(empty($matches)) return array('status' => 'fail', 'message' => 'No matched gitlab.');
- $conditions = array();
- foreach($matches as $matched) $conditions[] = "(`serviceHost`='{$matched['gitlab']}' and `serviceProject`='{$matched['project']}')";
- $matchedRepos = $this->getListByCondition('(' . implode(' OR ', $conditions). ')', 'Gitlab');
- foreach($matchedRepos as $key => $repo)
- {
- if(!$this->checkPriv($repo)) unset($matchedRepos[$key]);
- }
- if(empty($matchedRepos)) return array('status' => 'fail', 'message' => 'No matched gitlab.');
- return array('status' => 'success', 'repos' => $matchedRepos);
- }
- /**
- * 根据ID列表获取代码库列表。
- * 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('id', false);
- foreach($repos as $repo)
- {
- if($repo->encrypt == 'base64') $repo->password = base64_decode($repo->password);
- $repo->acl = json_decode($repo->acl);
- }
- return $repos;
- }
- /**
- * 获取代码库的分支列表。
- * Get git branches.
- *
- * @param object $repo
- * @param bool $printLabel
- * @param string $source select current repo's branches from scm or database.
- * @access public
- * @return array
- */
- public function getBranches($repo, $printLabel = false, $source = 'scm')
- {
- if($source == 'database')
- {
- $branches = $this->dao->select('branch')->from(TABLE_REPOBRANCH)
- ->where('repo')->eq($repo->id)
- ->fetchPairs();
- }
- else
- {
- $this->scm = $this->app->loadClass('scm');
- $this->scm->setEngine($repo);
- $branches = $this->scm->branch();
- }
- if($printLabel)
- {
- foreach($branches as &$branch) $branch = 'Branch::' . $branch;
- }
- return $branches;
- }
- /**
- * 根据提交ID获取提交信息。
- * Get commit by id.
- *
- * @param array $revisions
- * @access public
- * @return void
- */
- public function getCommitsByRevisions($revisions)
- {
- return $this->dao->select('id')->from(TABLE_REPOHISTORY)->where('revision')->in($revisions)->fetchPairs('id');
- }
- /**
- * 获取代码库的提交列表。
- * Get commits.
- *
- * @param object $repo
- * @param string $entry
- * @param string $revision
- * @param string $type
- * @param object $pager
- * @param string $begin
- * @param string $end
- * @param mixed $query
- * @access public
- * @return array
- */
- public function getCommits($repo, $entry, $revision = 'HEAD', $type = 'dir', $pager = null, $begin = '', $end = '', $query = null)
- {
- if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCommits();
- if(!isset($repo->id)) return array();
- if(in_array($repo->SCM, $this->config->repo->notSyncSCM)) return $this->loadModel('gitlab')->getCommits($repo, $entry, $pager, $begin, $end, $query);
- $entry = ltrim($entry, '/');
- $entry = empty($entry) ? '' : '/' . $entry;
- $revisionTime = $this->repoTao->getLatestCommitTime($repo->id, $revision, $repo->SCM == 'Subversion' ? '' : (string)$this->cookie->repoBranch);
- $hasBranch = $repo->SCM != 'Subversion' && $this->cookie->repoBranch;
- $historyIdList = array();
- if($entry != '/' && !empty($entry))
- {
- $historyIdList = $this->dao->select('DISTINCT t2.id,t2.`time`')->from(TABLE_REPOFILES)->alias('t1')
- ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.revision=t2.id')
- ->beginIF($hasBranch)->leftJoin(TABLE_REPOBRANCH)->alias('t3')->on('t2.id=t3.revision')->fi()
- ->where('t1.repo')->eq($repo->id)
- ->beginIF($begin)->andWhere('t2.`time`')->ge($begin)->fi()
- ->beginIF($end)->andWhere('t2.`time`')->le($end)->fi()
- ->beginIF($revisionTime)->andWhere('t2.`time`')->le($revisionTime)->fi()
- ->andWhere('left(t2.`comment`, 12)')->ne('Merge branch')
- ->beginIF($hasBranch)->andWhere('t3.branch')->eq($this->cookie->repoBranch)->fi()
- ->beginIF($type == 'dir')
- ->andWhere('t1.parent', true)->like(rtrim($entry, '/') . "/%")
- ->orWhere('t1.parent')->eq(rtrim($entry, '/'))
- ->markRight(1)
- ->fi()
- ->beginIF($type == 'file')->andWhere('t1.path')->eq("$entry")->fi()
- ->orderBy('t2.`time` desc')
- ->page($pager, 't2.id')
- ->fetchPairs('id', 'id');
- }
- $comments = $this->dao->select('DISTINCT t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
- ->beginIF($hasBranch)->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')->fi()
- ->where('t1.repo')->eq($repo->id)
- ->andWhere('left(t1.`comment`, 12)')->ne('Merge branch')
- ->beginIF($revisionTime)->andWhere('t1.`time`')->le($revisionTime)->fi()
- ->beginIF($begin)->andWhere('t1.`time`')->ge($begin)->fi()
- ->beginIF($end)->andWhere('t1.`time`')->le($end)->fi()
- ->beginIF($query)->andWhere($query)->fi()
- ->beginIF($hasBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
- ->beginIF($entry != '/' && !empty($entry))->andWhere('t1.id')->in($historyIdList)->fi()
- ->beginIF($begin)->andWhere('t1.time')->ge($begin)->fi()
- ->beginIF($end)->andWhere('t1.time')->le($end)->fi()
- ->orderBy('time desc');
- if($entry == '/' || empty($entry)) $comments->page($pager, 't1.id');
- $comments = $comments->fetchAll('revision');
- foreach($comments as $repoComment)
- {
- $repoComment->originalComment = $repoComment->comment;
- $repoComment->comment = $this->replaceCommentLink($repoComment->comment);
- }
- return $comments;
- }
- /**
- * 获取最后一次提交的信息。
- * Get latest commit.
- *
- * @param int $repoID
- * @param bool $checkCount
- * @access public
- * @return object|false
- */
- public function getLatestCommit($repoID, $checkCount = true)
- {
- $repo = $this->fetchByID($repoID);
- $branchID = (string)$this->cookie->repoBranch;
- $lastComment = $this->dao->select('t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
- ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
- ->where('t1.repo')->eq($repoID)
- ->beginIF($repo->SCM != 'Subversion' && $branchID)->andWhere('t2.branch')->eq($branchID)->fi()
- ->beginIF($repo->SCM == 'Subversion')->andWhere('t1.time')->ne('1970-01-01 08:00:00')->fi()
- ->orderBy('t1.`time` desc')
- ->fetch();
- if(empty($lastComment)) return false;
- $lastComment->svnRevision = intval($lastComment->revision);
- if(!$checkCount) return $lastComment;
- $count = $this->dao->select('count(DISTINCT t1.id) as count')->from(TABLE_REPOHISTORY)->alias('t1')
- ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
- ->where('t1.repo')->eq($repoID)
- ->beginIF($repo->SCM != 'Subversion' && $branchID)->andWhere('t2.branch')->eq($branchID)->fi()
- ->fetch('count');
- if($repo->SCM == 'Git' && $lastComment->commit != $count)
- {
- $this->fixCommit($repo->id);
- $lastComment->commit = $count;
- }
- return $lastComment;
- }
- /**
- * 从数据库中获取提交记录。
- * Get revisions from db.
- *
- * @param int $repoID
- * @param int $limit
- * @param string $maxRevision
- * @param string $minRevision
- * @access public
- * @return array
- */
- public function getRevisionsFromDB($repoID, $limit = 0, $maxRevision = '', $minRevision = '')
- {
- $revisions = $this->dao->select('DISTINCT t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
- ->beginIF($this->cookie->repoBranch)->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')->fi()
- ->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 history.
- *
- * @param int $repoID
- * @param array $revisions
- * @access public
- * @return array
- */
- public function getHistory($repoID, $revisions)
- {
- return $this->dao->select('DISTINCT t1.*')->from(TABLE_REPOHISTORY)->alias('t1')
- ->beginIF($this->cookie->repoBranch)->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')->fi()
- ->where('t1.repo')->eq($repoID)
- ->andWhere('t1.revision')->in($revisions)
- ->beginIF($this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
- ->fetchAll('revision');
- }
- /**
- * 查询提交记录的名称。
- * Get git revisionName.
- *
- * @param string $revision
- * @param int $commit
- * @access public
- * @return string
- */
- public function getGitRevisionName($revision, $commit)
- {
- if(empty($commit)) return substr($revision, 0, 10);
- return substr($revision, 0, 10) . '<span title="' . sprintf($this->lang->repo->commitTitle, $commit) . '"> (' . $commit . ') </span>';
- }
- /**
- * 获取缓存文件位置。
- * Get cache file.
- *
- * @param int $repoID
- * @param string $path
- * @param string $revision
- * @access public
- * @return string
- */
- public function getCacheFile($repoID, $path, $revision)
- {
- $cachePath = $this->app->getCacheRoot() . '/repo/' . $repoID;
- if(!is_dir($cachePath)) mkdir($cachePath, 0777, true);
- if(!is_writable($cachePath)) return false;
- return $cachePath . '/' . md5("{$this->cookie->repoBranch}-$path-$revision");
- }
- /**
- * 查询代码库关联的产品列表。
- * Get products by repoID.
- *
- * @param int $repoID
- * @access public
- * @return array
- */
- public function getProductsByRepo($repoID)
- {
- $repo = $this->getByID($repoID);
- if(empty($repo->id)) return array();
- return $this->dao->select('id,name')->from(TABLE_PRODUCT)
- ->where('id')->in($repo->product)
- ->andWhere('deleted')->eq(0)
- ->fetchPairs();
- }
- /**
- * 保存代码提交信息并返回保存数量。
- * Save commit.
- *
- * @param int $repoID
- * @param array $logs
- * @param int $version
- * @param string $branch
- * @access public
- * @return int
- */
- public function saveCommit($repoID, $logs, $version, $branch = '')
- {
- $count = 0;
- if(empty($logs)) return $count;
- foreach($logs['commits'] as $i => $commit)
- {
- $existsRevision = $this->dao->select('id,revision')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($commit->revision)->fetch();
- if($existsRevision)
- {
- if($branch) $this->dao->replace(TABLE_REPOBRANCH)->set('repo')->eq($repoID)->set('revision')->eq($existsRevision->id)->set('branch')->eq($branch)->exec();
- continue;
- }
- $commit->repo = $repoID;
- $commit->commit = $version;
- $commit->comment = htmlSpecialString($commit->comment);
- $this->dao->insert(TABLE_REPOHISTORY)->data($commit)->exec();
- if(!dao::isError())
- {
- $commitID = $this->dao->lastInsertID();
- if($branch) $this->dao->replace(TABLE_REPOBRANCH)->set('repo')->eq($repoID)->set('revision')->eq($commitID)->set('branch')->eq($branch)->exec();
- if(!empty($logs['files']))
- {
- foreach($logs['files'][$i] as $file)
- {
- $parentPath = dirname($file->path);
- $copyfromPath = !empty($file->copyfromPath) ? $file->copyfromPath : '';
- $copyfromRev = !empty($file->copyfromRev) ? $file->copyfromRev : '';
- unset($file->copyfromPath);
- unset($file->copyfromRev);
- $file->parent = $parentPath == '\\' ? '/' : $parentPath;
- $file->revision = $commitID;
- $file->repo = $repoID;
- $this->dao->insert(TABLE_REPOFILES)->data($file)->exec();
- if($file->action == 'R' && !empty($file->oldPath))
- {
- $file->path = $file->oldPath;
- $file->parent = dirname($file->path);
- $file->oldPath = '';
- $file->action = 'D';
- $this->dao->insert(TABLE_REPOFILES)->data($file)->exec();
- }
- if(!empty($copyfromPath) && !empty($copyfromRev)) $this->repoTao->copySvnDir($repoID, $copyfromPath, $copyfromRev, $file->path);
- }
- }
- $revisionPairs[$commit->revision] = $commit->revision;
- $version ++;
- $count ++;
- }
- dao::$errors = array();
- }
- return $count;
- }
- /**
- * 保存单个提交信息。
- * Save One Commit.
- *
- * @param int $repoID
- * @param object $commit
- * @param int $version
- * @param string $branch
- * @access public
- * @return int
- */
- public function saveOneCommit($repoID, $commit, $version, $branch = '')
- {
- $existsRevision = $this->dao->select('id,revision')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('revision')->eq($commit->revision)->fetch();
- if($existsRevision)
- {
- if($branch) $this->dao->replace(TABLE_REPOBRANCH)->set('repo')->eq($repoID)->set('revision')->eq($existsRevision->id)->set('branch')->eq($branch)->exec();
- return $version;
- }
- $history = new stdclass();
- $history->repo = $repoID;
- $history->commit = $version;
- $history->revision = $commit->revision;
- $history->comment = htmlSpecialString($commit->comment);
- $history->committer = $commit->committer;
- $history->time = $commit->time;
- $this->dao->insert(TABLE_REPOHISTORY)->data($history)->exec();
- if(!dao::isError())
- {
- $commitID = $this->dao->lastInsertID();
- if($branch) $this->dao->replace(TABLE_REPOBRANCH)->set('repo')->eq($repoID)->set('revision')->eq($commitID)->set('branch')->eq($branch)->exec();
- foreach($commit->change as $file => $info)
- {
- $parentPath = dirname($file);
- $copyfromPath = !empty($info['copyfrom-path']) ? $info['copyfrom-path'] : '';
- $copyfromRev = !empty($info['copyfrom-rev']) ? $info['copyfrom-rev']: '';
- $repoFile = new stdclass();
- $repoFile->repo = $repoID;
- $repoFile->revision = $commitID;
- $repoFile->path = $file;
- $repoFile->parent = $parentPath == '\\' ? '/' : $parentPath;
- $repoFile->type = $info['kind'];
- $repoFile->action = $info['action'];
- $repoFile->oldPath = empty($info['oldPath']) ? '' : $info['oldPath'];
- $this->dao->insert(TABLE_REPOFILES)->data($repoFile)->exec();
- if($repoFile->oldPath and $repoFile->action == 'R')
- {
- $parentPath = dirname($repoFile->oldPath);
- $repoFile->path = $repoFile->oldPath;
- $repoFile->parent = $parentPath == '\\' ? '/' : $parentPath;
- $repoFile->type = $info['kind'];
- $repoFile->action = 'D';
- $repoFile->oldPath = '';
- $this->dao->insert(TABLE_REPOFILES)->data($repoFile)->exec();
- }
- if(!empty($copyfromPath) && !empty($copyfromRev)) $this->repoTao->copySvnDir($repoID, $copyfromPath, $copyfromRev, $repoFile->path);
- }
- $version ++;
- }
- dao::$errors = array();
- return $version;
- }
- /**
- * 保存已存在的分支日志。
- * Save exists log branch.
- *
- * @param int $repoID
- * @param string $branch
- * @access public
- * @return bool
- */
- public function saveExistCommits4Branch($repoID, $branch)
- {
- $lastBranchLog = $this->dao->select('t1.time')->from(TABLE_REPOHISTORY)->alias('t1')
- ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
- ->where('t1.repo')->eq($repoID)
- ->andWhere('t2.branch')->eq($branch)
- ->orderBy('time')
- ->limit(1)
- ->fetch();
- if(empty($lastBranchLog)) return false;
- $stmt = $this->dao->select('*')->from(TABLE_REPOHISTORY)->where('repo')->eq($repoID)->andWhere('time')->lt($lastBranchLog->time)->query();
- while($log = $stmt->fetch())
- {
- $this->dao->REPLACE(TABLE_REPOBRANCH)->set('repo')->eq($repoID)->set('revision')->eq($log->id)->set('branch')->eq($branch)->exec();
- }
- return !dao::isError();
- }
- /**
- * 更新代码库的提交次数。
- * Update commit count.
- *
- * @param int $repoID
- * @param int $count
- * @access public
- * @return bool
- */
- public function updateCommitCount($repoID, $count)
- {
- $this->dao->update(TABLE_REPO)->set('commits')->eq($count)->where('id')->eq($repoID)->exec();
- return !dao::isError();
- }
- /**
- * 获取未同步的提交。
- * Get unsync commits.
- *
- * @param object $repo
- * @access public
- * @return array
- */
- public function getUnsyncedCommits($repo)
- {
- $repoID = $repo->id;
- $lastInDB = $this->getLatestCommit($repoID);
- $scm = $this->app->loadClass('scm');
- $scm->setEngine($repo);
- $logs = $scm->log('', $lastInDB ? $lastInDB->revision : 0);
- if(empty($logs)) return array();
- /* Process logs. */
- $logs = array_reverse($logs, true);
- foreach($logs as $i => $log)
- {
- if(isset($lastInDB->revision) and $lastInDB->revision == $log->revision)
- {
- unset($logs[$i]);
- continue;
- }
- $log->author = $log->committer;
- $log->msg = $log->comment;
- $log->date = $log->time;
- /* Process files. */
- $log->files = array();
- foreach($log->change as $file => $info) $log->files[$info['action']][] = $file;
- }
- return $logs;
- }
- /**
- * 生成链接。
- * Create link for repo.
- *
- * @param string $method
- * @param string $params
- * @param string $viewType
- * @access public
- * @return string
- */
- public function createLink($method, $params = '', $viewType = '')
- {
- if($this->config->requestType == 'GET') return helper::createLink('repo', $method, $params, $viewType);
- $parsedParams = array();
- parse_str($params, $parsedParams);
- $pathParams = '';
- $pathKey = 'path';
- if(isset($parsedParams['entry'])) $pathKey = 'entry';
- if(isset($parsedParams['file'])) $pathKey = 'file';
- if(isset($parsedParams['root'])) $pathKey = 'root';
- if(isset($parsedParams[$pathKey]))
- {
- $pathParams = 'repoPath=' . $parsedParams[$pathKey];
- $parsedParams[$pathKey] = '';
- }
- $params = http_build_query($parsedParams);
- $link = helper::createLink('repo', $method, $params, $viewType);
- if(empty($pathParams)) return $link;
- $link .= strpos($link, '?') === false ? '?' : '&';
- $link .= $pathParams;
- return $link;
- }
- /**
- * 更新代码库的同步状态。
- * Mark synced status.
- *
- * @param int $repoID
- * @access public
- * @return bool
- */
- public function markSynced($repoID)
- {
- $this->fixCommit($repoID);
- $this->dao->update(TABLE_REPO)->set('synced')->eq(1)->where('id')->eq($repoID)->exec();
- return !dao::isError();
- }
- /**
- * 更新提交记录的排序。
- * Fix commit.
- *
- * @param int $repoID
- * @access public
- * @return bool
- */
- public function fixCommit($repoID)
- {
- $historyList = $this->dao->select('DISTINCT t1.id,t1.`time`')->from(TABLE_REPOHISTORY)->alias('t1')
- ->leftJoin(TABLE_REPOBRANCH)->alias('t2')->on('t1.id=t2.revision')
- ->where('t1.repo')->eq($repoID)
- ->beginIF($this->cookie->repoBranch)->andWhere('t2.branch')->eq($this->cookie->repoBranch)->fi()
- ->orderBy('time')
- ->query();
- foreach($historyList as $i => $repoHistory)
- {
- $i++;
- $this->dao->update(TABLE_REPOHISTORY)->set('`commit`')->eq($i)->where('id')->eq($repoHistory->id)->exec();
- }
- return !dao::isError();
- }
- /**
- * 转义代码库文件路径。
- * Encode repo path.
- *
- * @param string $path
- * @access public
- * @return string
- */
- public function encodePath($path = '')
- {
- if(empty($path)) return $path;
- return helper::safe64Encode(urlencode($path));
- }
- /**
- * 解析代码库文件路径。
- * Decode repo path.
- *
- * @param string $path
- * @access public
- * @return string
- */
- public function decodePath($path = '')
- {
- if(empty($path)) return $path;
- return trim(urldecode(helper::safe64Decode($path)), '/');
- }
- /**
- * 删除客户端代码工具生成的版本文件。
- * remove client version file.
- *
- * @access public
- * @return void
- */
- public function rmClientVersionFile()
- {
- $clientVersionFile = $this->session->clientVersionFile;
- if($clientVersionFile)
- {
- if(!session_id()) session_start();
- $this->session->set('clientVersionFile', '');
- session_write_close();
- if(file_exists($clientVersionFile)) @unlink($clientVersionFile);
- }
- }
- /**
- * 替换提交记录中的链接。
- * Replace comment link.
- *
- * @param string $comment
- * @access public
- * @return string
- */
- public function replaceCommentLink($comment)
- {
- $rules = $this->processRules();
- $storyReg = '/' . $rules['storyReg'] . '/i';
- $taskReg = '/' . $rules['taskReg'] . '/i';
- $bugReg = '/' . $rules['bugReg'] . '/i';
- if(preg_match_all($taskReg, $comment, $result))
- {
- $taskLinks = $this->addLink($result, 'task');
- foreach($taskLinks as $search => $replace) $comment = str_replace($search, $replace, $comment);
- }
- if(preg_match_all($bugReg, $comment, $result))
- {
- $bugLinks = $this->addLink($result, 'bug');
- foreach($bugLinks as $search => $replace) $comment = str_replace($search, $replace, $comment);
- }
- if(preg_match_all($storyReg, $comment, $result))
- {
- $storyLinks = $this->addLink($result, 'story');
- foreach($storyLinks as $search => $replace) $comment = str_replace($search, $replace, $comment);
- }
- return $comment;
- }
- /**
- * 解析提交记录中的链接。
- * Add link.
- *
- * @param array $matches
- * @param string $method
- * @access public
- * @return array
- */
- public function addLink($matches, $method)
- {
- if(empty($matches)) return array();
- $replaceLines = array();
- foreach($matches[3] as $i => $idList)
- {
- $links = $matches[2][$i] . ' ' . $matches[4][$i];
- preg_match_all('/\d+/', $idList, $idMatches);
- foreach($idMatches[0] as $id)
- {
- $links .= html::a(helper::createLink($method, 'view', "id=$id"), $id) . $matches[6][$i];
- }
- $replaceLines[$matches[0][$i]] = rtrim($links, $matches[6][$i]);
- }
- return $replaceLines;
- }
- /**
- * 解析git和svn的注释,从中提取对象id列表。
- * Parse the comment of git and svn, extract object id list from it.
- *
- * @param string $comment
- * @access public
- * @return array
- */
- public function parseComment($comment)
- {
- $rules = $this->processRules();
- $stories = array();
- $actions = array();
- $designs = array();
- $tasks = $this->repoTao->parseTaskComment($comment, $rules, $actions);
- $bugs = $this->repoTao->parseBugComment($comment, $rules, $actions);
- preg_match_all("/{$rules['taskReg']}/i", $comment, $matches);
- if($matches[0])
- {
- foreach($matches[3] as $idList)
- {
- preg_match_all('/\d+/', $idList, $idMatches);
- foreach($idMatches[0] as $id) $tasks[$id] = $id;
- }
- }
- preg_match_all("/{$rules['bugReg']}/i", $comment, $matches);
- if($matches[0])
- {
- foreach($matches[3] as $idList)
- {
- preg_match_all('/\d+/', $idList, $idMatches);
- foreach($idMatches[0] as $id) $bugs[$id] = $id;
- }
- }
- preg_match_all("/{$rules['storyReg']}/i", $comment, $matches);
- if($matches[0])
- {
- foreach($matches[3] as $idList)
- {
- preg_match_all('/\d+/', $idList, $idMatches);
- foreach($idMatches[0] as $id) $stories[$id] = $id;
- }
- }
- preg_match_all("/{$rules['designReg']}/i", $comment, $matches);
- if($matches[0])
- {
- $designs = implode(' ', $matches[1]);
- if($designs) $designs = array_unique(explode(' ', str_replace(',', ' ', $designs)));
- }
- return array('stories' => $stories, 'tasks' => $tasks, 'bugs' => $bugs, 'actions' => $actions, 'designs' => $designs);
- }
- /**
- * 转码提交注释信息。
- * Convert encoding of comment.
- *
- * @param string $comment
- * @param string $encodings
- * @access public
- * @return string
- */
- public function iconvComment($comment, $encodings)
- {
- /* Get encodings. */
- if($encodings == '') return $comment;
- /* Try convert. */
- $encodings = explode(',', $encodings);
- foreach($encodings as $encoding)
- {
- if($encoding == 'utf-8') continue;
- $result = helper::convertEncoding($comment, $encoding);
- if($result) return $result;
- }
- return $comment;
- }
- /**
- * 解析提交指令规则。
- * Process rules to REG.
- *
- * @access public
- * @return array
- */
- public function processRules()
- {
- if(is_string($this->config->repo->rules)) $this->config->repo->rules = json_decode($this->config->repo->rules, true);
- $rules = $this->config->repo->rules;
- $idMarks = str_replace(';', '|', preg_replace('/([^;])/', '\\\\\1', trim($rules['id']['mark'], ';')));
- $idSplits = str_replace(';', '|', preg_replace('/([^;])/', '\\\\\1', trim($rules['id']['split'], ';')));
- $costs = str_replace(';', '|', trim($rules['task']['consumed'], ';'));
- $costMarks = str_replace(';', '|', preg_replace('/([^;])/', '\\\\\1', trim($rules['mark']['consumed'], ';')));
- $lefts = str_replace(';', '|', trim($rules['task']['left'], ';'));
- $leftMarks = str_replace(';', '|', preg_replace('/([^;])/', '\\\\\1', trim($rules['mark']['left'], ';')));
- $storyModule = str_replace(';', '|', trim($rules['module']['story'], ';'));
- $taskModule = str_replace(';', '|', trim($rules['module']['task'], ';'));
- $bugModule = str_replace(';', '|', trim($rules['module']['bug'], ';'));
- $costUnit = str_replace(';', '|', trim($rules['unit']['consumed'], ';'));
- $leftUnit = str_replace(';', '|', trim($rules['unit']['left'], ';'));
- $startAction = str_replace(';', '|', trim($rules['task']['start'], ';'));
- $finishAction = str_replace(';', '|', trim($rules['task']['finish'], ';'));
- $effortAction = str_replace(';', '|', trim($rules['task']['logEfforts'], ';'));
- $resolveAction = str_replace(';', '|', trim($rules['bug']['resolve'], ';'));
- $storyReg = "(($storyModule) *(({$idMarks})[0-9]+(({$idSplits})[0-9]+)*))";
- $taskReg = "(($taskModule) *(({$idMarks})[0-9]+(({$idSplits})[0-9]+)*))";
- $bugReg = "(($bugModule) *(({$idMarks})[0-9]+(({$idSplits})[0-9]+)*))";
- $costReg = "($costs) *(($costMarks)([0-9]+(\.?[0-9]+)?)($costUnit))";
- $leftReg = "($lefts) *(($leftMarks)([0-9]+(\.?[0-9]+)?)($leftUnit))";
- $startTaskReg = "({$startAction}) *{$taskReg}.*$costReg.*$leftReg";
- $effortTaskReg = "({$effortAction}) *{$taskReg}.*$costReg.*$leftReg";
- $finishTaskReg = "({$finishAction}) *{$taskReg}.*$costReg";
- $resolveBugReg = "({$resolveAction}) *{$bugReg}";
- $reg = array();
- $reg['storyReg'] = $storyReg;
- $reg['taskReg'] = $taskReg;
- $reg['bugReg'] = $bugReg;
- $reg['costReg'] = $costReg;
- $reg['leftReg'] = $leftReg;
- $reg['startTaskReg'] = $startTaskReg;
- $reg['effortTaskReg'] = $effortTaskReg;
- $reg['finishTaskReg'] = $finishTaskReg;
- $reg['resolveBugReg'] = $resolveBugReg;
- $reg['designReg'] = 'design(?:\s){0,}(?:#|:|:){0,}([0-9, ]{1,})';
- return $reg;
- }
- /**
- * 保存提交信息到系统。
- * Save action to pms.
- *
- * @param array $objects
- * @param object $log
- * @param string $repoRoot
- * @param string $encodings
- * @param string $scm
- * @param array $gitlabAccountPairs
- * @access public
- * @return bool
- */
- public function saveAction2PMS($objects, $log, $repoRoot = '', $encodings = 'utf-8', $scm = 'svn', $gitlabAccountPairs = array())
- {
- $committers = $this->loadModel('user')->getCommiters('account');
- $log->author = zget($gitlabAccountPairs, $log->author, zget($committers, $log->author));
- if(isset($this->app->user))
- {
- $account = $this->app->user->account;
- $this->app->user->account = $log->author;
- }
- $action = new stdclass();
- $action->actor = $log->author;
- $action->date = $log->date;
- $action->extra = $scm == 'svn' ? $log->revision : substr($log->revision, 0, 10);
- $action->comment = $this->lang->repo->revisionA . ': #' . $action->extra . "<br />" . htmlSpecialString($this->iconvComment($log->msg, $encodings));
- $this->loadModel('action');
- $actions = $objects['actions'];
- $changes = $this->createActionChanges($log, $repoRoot, $scm);
- if(isset($actions['task']))
- {
- $this->loadModel('task');
- $productsAndExecutions = $this->getTaskProductsAndExecutions($objects['tasks']);
- foreach($actions['task'] as $taskID => $taskActions)
- {
- $task = $this->task->getById($taskID);
- if(empty($task)) continue;
- $action->objectType = 'task';
- $action->objectID = $taskID;
- $action->product = $productsAndExecutions[$taskID]['product'];
- $action->execution = $productsAndExecutions[$taskID]['execution'];
- $this->setTaskByCommit($task, $taskActions, $action, $changes, $scm);
- unset($objects['tasks'][$taskID]);
- dao::$errors = array();
- }
- }
- if(isset($actions['bug'])) $objects['bugs'] = $this->setBugStatusByCommit($objects['bugs'], $actions, $action, $changes);
- $action->action = $scm == 'svn' ? 'svncommited' : 'gitcommited';
- $this->saveObjectToPms($objects, $action, $changes);
- if(isset($this->app->user)) $this->app->user->account = $account;
- return !dao::isError();
- }
- /**
- * 保存commit触发的操作日志信息。
- * Save an action to pms.
- *
- * @param object $action
- * @param array $changes
- * @access public
- * @return bool
- */
- public function saveRecord($action, $changes)
- {
- /* Remove sql error. */
- dao::getError();
- $record = $this->dao->select('*')->from(TABLE_ACTION)
- ->where('objectType')->eq($action->objectType)
- ->andWhere('objectID')->eq($action->objectID)
- ->andWhere('extra')->eq($action->extra)
- ->andWhere('action')->eq($action->action)
- ->beginIf(!empty($action->comment))->andWhere('comment')->eq(zget($action, 'comment', ''))->fi()
- ->fetch();
- if($record)
- {
- $this->dao->update(TABLE_ACTION)->data($action)->where('id')->eq($record->id)->exec();
- if($changes)
- {
- $historyIdList = $this->dao->findByAction($record->id)->from(TABLE_HISTORY)->fetchPairs('id', 'id');
- if($historyIdList) $this->dao->delete()->from(TABLE_HISTORY)->where('id')->in($historyIdList)->exec();
- $this->loadModel('action')->logHistory($record->id, $changes);
- }
- }
- else
- {
- $this->dao->insert(TABLE_ACTION)->data($action)->autoCheck()->exec();
- if($changes)
- {
- $actionID = $this->dao->lastInsertID();
- $this->loadModel('action')->logHistory($actionID, $changes);
- }
- }
- return !dao::isError();
- }
- /**
- * 从日志中为设置变更信息。
- * Create changes for action from a log.
- *
- * @param object $log
- * @param string $repoRoot
- * @access public
- * @return array
- * @param string $scm
- */
- public function createActionChanges($log, $repoRoot, $scm = 'svn')
- {
- if(empty($log->files)) return array();
- $oldSelf = $this->server->PHP_SELF;
- $this->server->set('PHP_SELF', $this->config->webRoot, '', false, true);
- $diff = '';
- foreach($log->files as $action => $actionFiles)
- {
- foreach($actionFiles as $file)
- {
- $catLink = trim(html::a($this->buildURL('cat', $repoRoot . $file, (string) $log->revision, $scm), 'view', '', "data-toggle='modal' data-size='{\"width\": 800, \"height\": 500}'"));
- $diffLink = trim(html::a($this->buildURL('diff', $repoRoot . $file, (string) $log->revision, $scm), 'diff', '', "data-toggle='modal' data-size='{\"width\": 800, \"height\": 500}'"));
- $catLink = str_replace('+', '%2B', $catLink);
- $diffLink = str_replace('+', '%2B', $diffLink);
- $diff .= $action . " " . $file . " $catLink ";
- $diff .= $action == 'M' ? "$diffLink\n" : "\n" ;
- }
- }
- $this->server->set('PHP_SELF', $oldSelf);
- $change = new stdclass();
- $change->field = $scm == 'svn' ? 'subversion' : 'git';
- $change->old = '';
- $change->new = '';
- $change->diff = trim($diff);
- return array($change);
- }
- /**
- * 根据任务列表获取产品和执行。
- * Get products and executions of tasks.
- *
- * @param array $tasks
- * @access public
- * @return array
- */
- public function getTaskProductsAndExecutions($tasks)
- {
- $records = array();
- $products = $this->dao->select('t1.id,t1.execution,t2.product')->from(TABLE_TASK)->alias('t1')
- ->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.execution = t2.project')
- ->where('t1.id')->in($tasks)
- ->fetchGroup('id','product');
- $executions = $this->dao->select('id, execution')->from(TABLE_TASK)->where('id')->in($tasks)->fetchPairs();
- foreach($executions as $taskID => $executionID)
- {
- $record = array();
- $record['execution'] = $executionID;
- $record['product'] = isset($products[$taskID]) ? "," . implode(',', array_keys($products[$taskID])) . "," : ",0,";
- $records[$taskID] = $record;
- }
- return $records;
- }
- /**
- * 根据bug列表获取产品和执行。
- * Get products and executions of bugs.
- *
- * @param array $bugs
- * @access public
- * @return array
- */
- public function getBugProductsAndExecutions($bugs)
- {
- $records = $this->dao->select('id, execution, product')->from(TABLE_BUG)->where('id')->in($bugs)->fetchAll('id');
- foreach($records as $record) $record->product = ",{$record->product},";
- return $records;
- }
- /**
- * 构造git和svn的展示链接。
- * Build url for git and svn.
- *
- * @param string $methodName
- * @param string $url
- * @param string $revision
- * @access public
- * @return string
- * @param string $scm
- */
- public function buildURL($methodName, $url, $revision, $scm = 'svn')
- {
- $buildedURL = helper::createLink($scm, $methodName, "url=&revision=$revision", 'html');
- $buildedURL .= strpos($buildedURL, '?') === false ? '?' : '&';
- $buildedURL .= 'repoUrl=' . helper::safe64Encode($url);
- return $buildedURL;
- }
- /**
- * 处理代码库信息,增加代码路径和api路径。
- * Process git service, add code path and api path.
- *
- * @param object $repo
- * @param bool $getCodePath
- * @access public
- * @return object
- */
- public function processGitService($repo, $getCodePath = false)
- {
- $service = $this->loadModel('pipeline')->getByID((int)$repo->serviceHost);
- if(!$service) return $repo;
- if(in_array($repo->SCM, $this->config->repo->notSyncSCM))
- {
- if($getCodePath)
- {
- if($repo->SCM == 'Gitlab') $repo->serviceProject = (int)$repo->serviceProject;
- $project = $this->loadModel($repo->SCM)->apiGetSingleProject((int)$repo->serviceHost, $repo->serviceProject);
- if(isset($project->web_url) && $repo->path != $project->web_url)
- {
- $repo->path = $project->web_url;
- $this->dao->update(TABLE_REPO)->set('path')->eq($repo->path)->where('id')->eq($repo->id)->exec();
- }
- }
- $repo->path = (!$repo->path && $service) ? sprintf($this->config->repo->{$service->type}->apiPath, $service->url, $repo->serviceProject) : $repo->path;
- $repo->apiPath = sprintf($this->config->repo->{$service->type}->apiPath, $service->url, $repo->serviceProject);
- $repo->client = $service ? $service->url : '';
- $repo->password = $service ? $service->token : '';
- $repo->codePath = isset($project->web_url) ? $project->web_url : $repo->path;
- }
- else
- {
- if(!is_dir($repo->path) && !is_writable(dirname($repo->path)))
- {
- $path = $this->app->getAppRoot() . "www/data/repo/{$repo->name}_{$repo->SCM}";
- $repo->path = $path;
- $this->dao->update(TABLE_REPO)->set('path')->eq($repo->path)->where('id')->eq($repo->id)->exec();
- }
- $repo->codePath = $service ? "{$service->url}/{$repo->serviceProject}" : $repo->path;
- }
- $repo->gitService = (int)$repo->serviceHost;
- return $repo;
- }
- /**
- * 处理webhook请求。
- * Handle received GitLab webhook.
- *
- * @param string $event
- * @param object $data
- * @param object $repotime
- * @access public
- * @return bool
- * @param object $repo
- */
- public function handleWebhook($event, $data, $repo)
- {
- if(!in_array($event, array('Push Hook', 'Merge Request Hook', 'branch_updated'))) return false;
- if(empty($data->commits)) return false;
- /* Update code commit history. */
- $commentGroup = $this->loadModel('job')->getTriggerGroup('commit', array($repo->id));
- if(!in_array($repo->SCM, $this->config->repo->notSyncSCM)) return $this->loadModel('git')->updateCommit($repo, $commentGroup, false);
- $scm = $this->app->loadClass('scm');
- $scm->setEngine($repo);
- $jobs = zget($commentGroup, $repo->id, array());
- $accountPairs = array();
- $userList = $this->loadModel($repo->SCM)->apiGetUsers($repo->gitService);
- $accountIDPairs = $this->loadModel('pipeline')->getUserBindedPairs($repo->gitService, strtolower($repo->SCM), 'openID,account');
- foreach($userList as $gitlabUser) $accountPairs[$gitlabUser->realname] = zget($accountIDPairs, $gitlabUser->id, '');
- foreach($data->commits as $commit)
- {
- $time = zget($commit, 'timestamp', '');
- if(isset($commit->author->when)) $time = $commit->author->when;
- $log = new stdclass();
- $log->revision = isset($commit->id) ? $commit->id : $commit->sha;
- $log->msg = $commit->message;
- $log->author = isset($commit->author->identity->name) ? $commit->author->identity->name : $commit->author->name;
- $log->date = date("Y-m-d H:i:s", strtotime($time));
- $log->files = array();
- $log->repo = $repo;
- if(!isset($commit->added))
- {
- $diffs = $scm->engine->getFilesByCommit($log->revision);
- if(!empty($diffs))
- {
- foreach($diffs as $diff) $log->files[$diff->action][] = $diff->path;
- }
- }
- else
- {
- foreach($commit->added as $file) $log->files['A'][] = $file;
- foreach($commit->removed as $file) $log->files['D'][] = $file;
- foreach($commit->modified as $file) $log->files['M'][] = $file;
- }
- $objects = $this->parseComment($log->msg);
- $this->saveAction2PMS($objects, $log, $repo->path, $repo->encoding, 'git', $accountPairs);
- foreach($jobs as $job)
- {
- foreach(explode(',', $job->comment) as $comment)
- {
- if(strpos($log->msg, $comment) !== false)
- {
- $this->loadModel('job')->exec($job->id, array(), 'commit');
- continue 2;
- }
- }
- }
- if(!empty($objects['stories']) || !empty($objects['tasks']) || !empty($objects['bugs']))
- {
- $historyLog = new stdclass();
- $historyLog->committer = $log->author;
- $historyLog->revision = $log->revision;
- $historyLog->comment = $commit->message;
- $historyLog->time = date("Y-m-d H:i:s", strtotime($time));
- $this->saveCommit($repo->id, array('commits' => [$historyLog]), 0);
- $revisions = $this->dao->select('id')->from(TABLE_REPOHISTORY)
- ->where('revision')->in($log->revision)
- ->andWhere('repo')->eq($repo->id)
- ->fetchPairs('id');
- foreach (array('stories' => 'story', 'tasks' => 'task', 'bugs' => 'bug') as $objectType=>$modelType)
- {
- if(!empty($objects[$objectType]))
- {
- foreach($objects[$objectType] as $modelID)
- {
- $this->loadModel($modelType)->updateLinkedCommits((int)$modelID, $repo->id, $revisions);
- }
- }
- }
- }
- }
- return !dao::isError();
- }
- /**
- * Get execution pairs.
- *
- * @param int $product
- * @param int $branch
- * @access public
- * @return array
- */
- public function getExecutionPairs($product, $branch = 0)
- {
- $pairs = array();
- $executions = $this->loadModel('execution')->getList(0, 'all', 'undone', 0, $product, $branch);
- $parents = $this->dao->select('distinct parent,parent')->from(TABLE_EXECUTION)->where('type')->eq('stage')->andWhere('grade')->gt(1)->andWhere('deleted')->eq(0)->fetchPairs();
- foreach($executions as $execution)
- {
- if(!empty($parents[$execution->id]) or ($execution->type == 'stage' and in_array($execution->attribute, array('request', 'design', 'review')))) continue;
- if($execution->type == 'stage' and $execution->grade > 1)
- {
- $parentExecutions = $this->dao->select('id,name')->from(TABLE_EXECUTION)->where('id')->in(trim($execution->path, ','))->andWhere('type')->in('stage,kanban,sprint')->orderBy('grade')->fetchPairs();
- $execution->name = implode('/', $parentExecutions);
- }
- $pairs[$execution->id] = $execution->name;
- }
- return $pairs;
- }
- /**
- * 获取代码库的clone地址。
- * Get clone url.
- *
- * @param object $repo
- * @access public
- * @return object
- */
- public function getCloneUrl($repo)
- {
- if(empty($repo->id)) return new stdclass();
- $url = new stdClass();
- if($repo->SCM == 'Subversion')
- {
- $url->svn = $repo->path;
- }
- elseif($repo->SCM == 'Gitlab')
- {
- $project = $this->loadModel('gitlab')->apiGetSingleProject($repo->gitService, (int)$repo->serviceProject);
- if(isset($project->id))
- {
- $url->http = $project->http_url_to_repo ?? '';
- $url->ssh = $project->ssh_url_to_repo ?? '';
- }
- }
- elseif($repo->SCM == 'Gitea')
- {
- $project = $this->loadModel('gitea')->apiGetSingleProject($repo->gitService, (string)$repo->serviceProject);
- if(isset($project->id))
- {
- $url->http = $project->clone_url;
- $url->ssh = $project->ssh_url;
- }
- }
- elseif($repo->SCM == 'Gogs')
- {
- $project = $this->loadModel('gogs')->apiGetSingleProject($repo->gitService, (string)$repo->serviceProject);
- if(isset($project->id))
- {
- $url->http = $project->clone_url;
- $url->ssh = $project->ssh_url;
- }
- }
- else
- {
- $this->scm = $this->app->loadClass('scm');
- $this->scm->setEngine($repo);
- $url = $this->scm->getCloneUrl();
- }
- return $url;
- }
- /**
- * 获取代码文件的提交信息。
- * Get file commits.
- *
- * @param object $repo
- * @param string $branch
- * @param string $parent
- * @access public
- * @return array
- */
- public function getFileCommits($repo, $branch, $parent = '')
- {
- /* Get file commits by repo. */
- if($repo->SCM != 'Subversion' && empty($branch)) $branch = $this->cookie->repoBranch;
- $parent = '/' . ltrim($parent, '/');
- if($repo->prefix) $parent = rtrim($repo->prefix . $parent, '/');
- $fileCommits = $this->dao->select('t1.id,t1.path,t1.type,t1.action,t1.oldPath,t1.parent,t2.revision,t2.comment,t2.committer,t2.time')->from(TABLE_REPOFILES)->alias('t1')
- ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.revision=t2.id')
- ->beginIF($repo->SCM != 'Subversion' && $branch)->leftJoin(TABLE_REPOBRANCH)->alias('t3')->on('t2.id=t3.revision')->fi()
- ->where('t1.repo')->eq($repo->id)
- ->andWhere('left(t2.`comment`, 12)')->ne('Merge branch')
- ->beginIF($repo->SCM != 'Subversion' && $branch)->andWhere('t3.branch')->eq($branch)->fi()
- ->beginIF($repo->SCM == 'Subversion')->andWhere('t1.parent')->eq("$parent")->fi()
- ->beginIF($repo->SCM != 'Subversion')->andWhere('t1.parent')->like("$parent%")->fi()
- ->orderBy('t2.`time` asc')
- ->fetchAll('path');
- $files = $folders = $fileSort = $dirSort = array();
- $existsFiles = array();
- foreach($fileCommits as $fileCommit)
- {
- if($fileCommit->action != 'D' && strpos($fileCommit->path, $parent) === 0) $existsFiles[$fileCommit->path] = true;
- if($fileCommit->action == 'R' && isset($existsFiles[$fileCommit->oldPath])) unset($existsFiles[$fileCommit->oldPath]);
- }
- foreach($fileCommits as $fileCommit)
- {
- /* Filter by parent. */
- if(!isset($existsFiles[$fileCommit->path])) continue;
- $pathList = explode('/', ltrim($fileCommit->path, '/'));
- $file = new stdclass();
- $file->revision = $fileCommit->revision;
- $file->comment = $fileCommit->comment;
- $file->account = $fileCommit->committer;
- $file->date = $fileCommit->time;
- $file->kind = 'file';
- $file->name = end($pathList);
- if($fileCommit->parent == $parent && $fileCommit->type == 'file')
- {
- $files[] = $file;
- $fileSort[] = $file->name;
- }
- else
- {
- $childPath = explode('/', ltrim(substr($fileCommit->path, strlen($parent)), '/'));
- $fileName = $fileCommit->type == 'dir' ? end($pathList) : $childPath[0];
- if(in_array($fileName, $dirSort)) continue;
- $file->name = $fileName;
- $file->kind = 'dir';
- $folders[] = $file;
- $dirSort[] = $fileName;
- }
- }
- array_multisort($fileSort, SORT_ASC, $files);
- array_multisort($dirSort, SORT_ASC, $folders);
- return array_merge($folders, $files);
- }
- /**
- * 获取目录树。
- * Get html for file tree.
- *
- * @param object $repo
- * @param string $branch
- * @param array $diffs
- * @access public
- * @return array
- */
- public function getFileTree($repo, $branch = '', $diffs = null)
- {
- set_time_limit(0);
- $allFiles = array();
- if(is_null($diffs))
- {
- if($repo->SCM != 'Subversion' && empty($branch)) $branch = $this->cookie->repoBranch;
- $files = $this->dao->select('t1.path,t2.time,t1.action')->from(TABLE_REPOFILES)->alias('t1')
- ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.revision=t2.id')
- ->leftJoin(TABLE_REPOBRANCH)->alias('t3')->on('t2.id=t3.revision')
- ->where('t1.repo')->eq($repo->id)
- ->andWhere('t1.type')->eq('file')
- ->andWhere('left(t2.`comment`, 12)')->ne('Merge branch')
- ->beginIF($repo->SCM != 'Subversion' && $branch)->andWhere('t3.branch')->eq($branch)->fi()
- ->orderBy('t2.`time` asc')
- ->fetchAll('path');
- $removeDirs = array();
- if($repo->SCM == 'Subversion')
- {
- $removeDirs = $this->dao->select('t2.time,t1.path')->from(TABLE_REPOFILES)->alias('t1')
- ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.revision=t2.id')
- ->where('t1.repo')->eq($repo->id)
- ->andWhere('t1.type')->eq('dir')
- ->andWhere('t1.action')->eq('D')
- ->fetchPairs();
- }
- foreach($files as $file)
- {
- foreach($removeDirs as $removeTime => $dir)
- {
- if(strpos($file->path, $dir . '/') === 0 and $file->time <= $removeTime)
- {
- $file->action = 'D';
- break;
- }
- }
- if($file->action != 'D') $allFiles[] = $file->path;
- }
- }
- else
- {
- foreach($diffs as $diff) $allFiles[] = $diff->fileName;
- }
- return $this->repoTao->buildFileTree($allFiles);
- }
- /**
- * 根据提交获取关联信息。
- * Get relation by commit.
- *
- * @param int $repoID
- * @param string $commit
- * @param string $type story|bug|task
- * @access public
- * @return array
- */
- public function getRelationByCommit($repoID, $commit, $type = '')
- {
- $relationList = $this->dao->select('t1.BID as id, t1.BType as type')->from(TABLE_RELATION)->alias('t1')
- ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.AID = t2.id')
- ->where('t2.revision')->eq($commit)
- ->andWhere('t2.repo')->eq($repoID)
- ->andWhere('t1.AType')->eq('revision')
- ->beginIF($type)->andWhere('t1.BType')->eq($type)->fi()
- ->fetchGroup('type', 'id');
- $stories = empty($relationList['story']) ? array() : $this->loadModel('story')->getByList(array_keys($relationList['story']));
- $bugs = empty($relationList['bug']) ? array() : $this->loadModel('bug')->getByIdList(array_keys($relationList['bug']));
- $tasks = empty($relationList['task']) ? array() : $this->loadModel('task')->getByIdList(array_keys($relationList['task']));
- $index = 0;
- $titleList = array();
- foreach($relationList as $objectType => $objects)
- {
- foreach($objects as $object)
- {
- $titleList[$index] = array(
- 'id' => $object->id,
- 'type' => $objectType,
- 'title' => "#$object->id "
- );
- if($objectType == 'story')
- {
- $story = zget($stories, $object->id, array());
- $titleList[$index]['title'] .= zget($story, 'title', '');
- }
- elseif($objectType == 'bug')
- {
- $bug = zget($bugs, $object->id, array());
- $titleList[$index]['title'] .= zget($bug, 'title', '');
- }
- elseif($objectType == 'task')
- {
- $task = zget($tasks, $object->id, array());
- $titleList[$index]['title'] .= zget($task, 'name', '');
- }
- $index ++;
- }
- }
- return $type ? $titleList : array_values($titleList);
- }
- /**
- * 根据关联对象获取提交。
- * Get relation commit.
- *
- * @param int $objectID
- * @param string $objectType story|bug|task
- * @access public
- * @return array
- */
- public function getCommitsByObject($objectID, $objectType)
- {
- return $this->dao->select('t2.*')->from(TABLE_RELATION)->alias('t1')
- ->leftJoin(TABLE_REPOHISTORY)->alias('t2')->on('t1.AID = t2.id')
- ->where('t1.BID')->eq($objectID)
- ->andWhere('t1.BType')->eq($objectType)
- ->andWhere('t1.AType')->eq('revision')
- ->andWhere('t1.relation')->eq('commit')
- ->fetchAll('', false);
- }
- /*
- * 移除没有权限的项目。
- * Remove projects without privileges.
- *
- * @param array $productIDList
- * @param array $projectIDList
- * @access public
- * @return array
- */
- /**
- * @param mixed[] $productIDList
- * @param mixed[] $projectIDList
- */
- public function filterProject($productIDList, $projectIDList = array())
- {
- /* Get all projects that can be accessed. */
- $accessProjects = array();
- foreach($productIDList as $productID)
- {
- $projects = $this->loadModel('product')->getProjectPairsByProduct((int)$productID);
- $accessProjects = $accessProjects + $projects;
- }
- /* Get linked projects. */
- $linkedProjects = $this->dao->select('id,name')->from(TABLE_PROJECT)->where('id')->in($projectIDList)->fetchPairs('id', 'name');
- return $accessProjects + $linkedProjects; // Merge projects can be accessed and exists.
- }
- /**
- * 更新代码提交历史。
- * Update commit history.
- *
- * @param int $repoID
- * @param int $objectID
- * @param string $branchID
- * @access public
- * @return bool
- */
- public function updateCommit($repoID, $objectID = 0, $branchID = '')
- {
- $repo = $this->getByID($repoID);
- if($repo->SCM == 'Gitlab') return true;
- /* Update code commit history. */
- $commentGroup = $this->loadModel('job')->getTriggerGroup('commit', array($repoID));
- if(in_array($repo->SCM, $this->config->repo->gitTypeList))
- {
- $branch = $this->cookie->repoBranch;
- if($branchID)
- {
- $currentBranches = $this->getBranches($repo, false, 'database');
- if(!in_array($branch, $currentBranches))
- {
- $link = $this->createLink('showSyncCommit', "repoID=$repoID&objectID=$objectID&branch=$branchID", '', false);
- return $this->app->control->locate($link);
- }
- }
- $this->loadModel('git')->updateCommit($repo, $commentGroup, false);
- $_COOKIE['repoBranch'] = $branch;
- }
- if($repo->SCM == 'Subversion') $this->loadModel('svn')->updateCommit($repo, $commentGroup, false);
- return !dao::isError();
- }
- /**
- * 移除已经删除的分支。
- * Delete the deleted branch.
- *
- * @param int $repoID
- * @param array $latestBranches
- * @access public
- * @return bool
- */
- public function checkDeletedBranches($repoID, $latestBranches)
- {
- if(empty($latestBranches)) return false;
- $currentBranches = $this->dao->select('branch')->from(TABLE_REPOBRANCH)->where('repo')->eq($repoID)->groupBy('branch')->fetchPairs('branch');
- $deletedBranches = array_diff($currentBranches, $latestBranches);
- foreach($deletedBranches as $deletedBranch)
- {
- if($deletedBranch == 'master') continue;
- $revisionIds = $this->dao->select('revision')->from(TABLE_REPOBRANCH)->where('repo')->eq($repoID)->andWhere('branch')->eq($deletedBranch)->fetchPairs('revision');
- $branchRevisionIds = $this->dao->select('revision,count(branch) as count')->from(TABLE_REPOBRANCH)->where('revision')->in($revisionIds)->groupBy('revision')->having('count')->eq(1)->fetchPairs('revision', 'revision');
- $fileIds = $this->dao->select('id')->from(TABLE_REPOFILES)->where('revision')->in($branchRevisionIds)->fetchPairs('id');
- $this->dao->delete()->from(TABLE_REPOHISTORY)->where('id')->in($branchRevisionIds)->exec();
- $this->dao->delete()->from(TABLE_REPOFILES)->where('id')->in($fileIds)->exec();
- $this->dao->delete()->from(TABLE_REPOBRANCH)->where('repo')->eq($repoID)->andWhere('branch')->eq($deletedBranch)->exec();
- }
- return true;
- }
- /**
- * 判断按钮是否可点击。
- * Judge an action is clickable or not.
- *
- * @param object $repo
- * @param string $action
- * @access public
- * @return bool
- */
- public static function isClickable($repo, $action)
- {
- $action = strtolower($action);
- if($action == 'execjob') return common::hasPriv('sonarqube', $action) && !$repo->exec;
- if($action == 'reportview') return common::hasPriv('sonarqube', $action) && !$repo->report;
- if(!commonModel::hasPriv('repo', $action)) return false;
- return true;
- }
- /**
- * 获取gitlab项目列表。
- * Get gitlab projects.
- *
- * @param int $gitlabID
- * @param string $projectFilter
- * @access public
- * @return array
- */
- public function getGitlabProjects($gitlabID, $projectFilter = '')
- {
- if($this->app->user->admin || ($projectFilter == 'ALL' && common::hasPriv('repo', 'create')))
- {
- $projects = $this->loadModel('gitlab')->apiGetProjects($gitlabID, 'true', 0, 0, false);
- }
- else
- {
- $gitlabUser = $this->loadModel('pipeline')->getOpenIdByAccount($gitlabID, 'gitlab', $this->app->user->account);
- if(!$gitlabUser) return array();
- $projects = $this->loadModel('gitlab')->apiGetProjects($gitlabID, $projectFilter ? 'false' : 'true');
- $groupIDList = array(0 => 0);
- $groups = $this->gitlab->apiGetGroups($gitlabID, 'name_asc', 'developer');
- foreach($groups as $group) $groupIDList[] = $group->id;
- if($projectFilter == 'IS_DEVELOPER')
- {
- foreach($projects as $key => $project)
- {
- if(!$this->gitlab->checkUserAccess($gitlabID, 0, $project, $groupIDList, 'developer')) unset($projects[$key]);
- }
- }
- }
- $importedProjects = $this->getImportedProjects($gitlabID);
- $projects = array_filter($projects, function($project) use ($importedProjects) { return !in_array($project->id, $importedProjects); });
- return $projects;
- }
- /**
- * Get repo groups.
- *
- * @param int $serverID
- * @param int $groupID
- * @access public
- * @return string|array|false
- */
- public function getGroups($serverID, $groupID = 0)
- {
- $server = $this->loadModel('pipeline')->getByID($serverID);
- if(empty($server->type)) return false;
- $getGroupFunc = 'get' . $server->type . 'Groups';
- $groups = $this->$getGroupFunc($serverID);
- if($groupID !== 0)
- {
- foreach($groups as $group)
- {
- if($group['value'] == $groupID) return $group['text'];
- }
- return false;
- }
- return $groups;
- }
- /**
- * Get gitlab groups.
- *
- * @param int $gitlabID
- * @access public
- * @return void
- */
- public function getGitlabGroups($gitlabID)
- {
- $groups = $this->loadModel('gitlab')->apiGetGroups($gitlabID, 'name_asc');
- $options = array();
- foreach($groups as $group)
- {
- $options[] = array('text' => $group->name, 'value' => $group->id);
- }
- return $options;
- }
- /**
- * Get gitea groups.
- *
- * @param int $giteaID
- * @access public
- * @return array
- */
- public function getGiteaGroups($giteaID)
- {
- $groups = $this->loadModel('gitea')->apiGetGroups($giteaID);
- $options = array();
- foreach($groups as $group)
- {
- $options[] = array('text' => $group->username, 'value' => $group->id);
- }
- return $options;
- }
- /**
- * 更新版本库最后提交时间。
- * Update repo last commited date.
- *
- * @param int $repoID
- * @access public
- * @return void
- */
- public function updateCommitDate($repoID)
- {
- $repo = $this->getByID($repoID);
- if(empty($repo->id)) return;
- if(in_array($repo->SCM, $this->config->repo->notSyncSCM))
- {
- $scm = $this->app->loadClass('scm');
- $scm->setEngine($repo);
- $commits = $scm->engine->getCommitsByPath('', '', 'HEAD', 1, 1);
- if(empty($commits)) return;
- $commitDate = $repo->SCM == 'Gitlab' ? $commits[0]->committed_date : $commits[0]->author->when;
- if(!empty($commitDate))
- {
- $lastCommitDate = date('Y-m-d H:i:s', strtotime($commitDate));
- $this->dao->update(TABLE_REPO)->set('lastCommit')->eq($lastCommitDate)->where('id')->eq($repoID)->exec();
- }
- }
- }
- /**
- * 检查gitea连接。
- * Check gitea connection.
- *
- * @param string $scm
- * @param string $name
- * @param int|string $serviceHost
- * @param int|string $serviceProject
- * @access public
- * @return string|false
- */
- public function checkGiteaConnection($scm, $name, $serviceHost, $serviceProject)
- {
- if($name != '' and $serviceProject != '')
- {
- $module = strtolower($scm);
- $project = $this->loadModel($module)->apiGetSingleProject($serviceHost, $serviceProject);
- if(isset($project->tokenCloneUrl))
- {
- $path = $this->app->getAppRoot() . 'www/data/repo/' . $name . '_' . $module;
- if(!realpath($path))
- {
- $cmd = 'git clone --progress -v "' . $project->tokenCloneUrl . '" "' . $path . '" > "' . $this->app->getTmpRoot() . "log/clone.progress.$module.{$name}.log\" 2>&1 &";
- if(PHP_OS == 'WINNT') $cmd = "start /b $cmd";
- exec($cmd);
- }
- return $path;
- }
- else
- {
- dao::$errors['serviceProject'] = $this->lang->repo->error->noCloneAddr;
- return false;
- }
- }
- return false;
- }
- /**
- * 根据路径获取gitlab文件列表。
- * Get gitlab files by path.
- *
- * @param object $repo
- * @param string $path
- * @param string $branch
- * @access public
- * @return array
- */
- public function getGitlabFilesByPath($repo, $path = '', $branch = '')
- {
- $fileList = $this->getTreeByGraphql($repo, $path, $branch, 'blobs');
- $folderList = $this->getTreeByGraphql($repo, $path, $branch, 'trees');
- if(empty($fileList) && empty($folderList)) return array();
- $files = array();
- $folders = array();
- $fileSort = $dirSort = array(); // Use it to sort array.
- foreach($fileList as $file)
- {
- if(in_array($file->name, $fileSort)) continue;
- $base64Name = $this->encodePath($file->path);
- $fileInfo = new stdclass();
- $fileInfo->id = $base64Name;
- $fileInfo->name = $file->name;
- $fileInfo->text = $file->name;
- $fileInfo->path = $file->path;
- $fileInfo->key = $base64Name;
- $fileInfo->kind = 'file';
- $files[] = $fileInfo;
- $fileSort[] = $file->name;
- }
- foreach($folderList as $dir)
- {
- if(in_array($dir->name, $dirSort)) continue;
- $base64Name = $this->encodePath($dir->path);
- $folder = new stdclass();
- $folder->id = $base64Name;
- $folder->name = $dir->name;
- $folder->text = $dir->name;
- $folder->path = $dir->path;
- $folder->key = $base64Name;
- $folder->kind = 'dir';
- $folder->items = array('url' => helper::createLink('repo', 'ajaxGetFiles', "repoID={$repo->id}&branch={$branch}&path=" . helper::safe64Encode($dir->path)));
- $folders[] = $folder;
- $dirSort[] = $dir->name;
- }
- array_multisort($fileSort, SORT_ASC, $files);
- array_multisort($dirSort, SORT_ASC, $folders);
- return array_merge($folders, $files);
- }
- /**
- * 通过Graphql获取GitLab文件列表。
- * Get GitLab files by Graphql.
- *
- * @param object $repo
- * @param string $path
- * @param string $branch
- * @param string $type
- * @access public
- * @return array
- */
- public function getTreeByGraphql($repo, $path = '', $branch = '', $type = 'blobs')
- {
- if(!$branch) $branch = $this->cookie->branch;
- $this->loadModel('gitlab');
- $fileList = array();
- $endCursor = '';
- $hasNextPage = true;
- while($hasNextPage)
- {
- $query = 'query { project(fullPath: "%s") {repository {tree(path: "' . trim($path, '/') . '", ref: "' . $branch . '") {' . $type . '(after: "' . $endCursor . '") {pageInfo {endCursor hasNextPage} nodes {sha name path}}}}}}';
- $response = $this->gitlab->apiGetByGraphql($repo, $query);
- if(!$endCursor && !isset($response->data->project->repository)) return array();
- if(empty($response->data->project->repository->tree)) break;
- $fileList = array_merge($fileList, $response->data->project->repository->tree->{$type}->nodes);
- $hasNextPage = $response->data->project->repository->tree->{$type}->pageInfo->hasNextPage;
- $endCursor = $response->data->project->repository->tree->{$type}->pageInfo->endCursor;
- }
- return $fileList;
- }
- /**
- * 查询提交记录的版本号。
- * Get history revision.
- *
- * @param int $repoID
- * @param string $revision
- * @param bool $withCommit
- * @param string $condition
- * @access public
- * @return string|object|false
- */
- public function getHistoryRevision($repoID, $revision, $withCommit = false, $condition = 'eq')
- {
- $field = $withCommit ? 'revision, commit' : 'revision';
- return $this->dao->select($field)->from(TABLE_REPOHISTORY)
- ->where('repo')->eq($repoID)
- ->beginIF($condition != 'lt')->andWhere('revision')->eq($revision)->fi()
- ->beginIF($condition == 'lt')->andWhere('revision')->lt($revision)->fi()
- ->orderBy('id desc')
- ->fetch($withCommit ? '' : 'revision');
- }
- /**
- * 通过指令开始任务。
- * Start task by commit.
- *
- * @param object $task
- * @param array $params
- * @param object $action
- * @param array $changes
- * @access private
- * @return bool
- */
- private function startTask($task, $params, $action, $changes)
- {
- $now = helper::now();
- $newTask = new stdclass();
- $newTask->id = $task->id;
- $newTask->status = 'doing';
- $newTask->left = $params['left'];
- $newTask->consumed = $params['consumed'] + $task->consumed;
- $newTask->realStarted = $now;
- $newTask->lastEditedBy = $this->app->user->account;
- $newTask->lastEditedDate = $now;
- if($newTask->left == 0 && empty($task->team))
- {
- $newTask->status = 'done';
- $newTask->finishedBy = $this->app->user->account;
- $newTask->finishedDate = $now;
- $newTask->assignedTo = $task->openedBy;
- }
- $this->loadModel('task');
- $currentTeam = !empty($task->team) ? $this->task->getTeamByAccount($task->team) : array();
- $effort = new stdclass();
- $effort->date = helper::today();
- $effort->task = $newTask->id;
- $effort->consumed = zget($newTask, 'consumed', 0);
- $effort->left = zget($newTask, 'left', 0);
- $effort->account = $this->app->user->account;
- $effort->work = $this->lang->action->label->started . $this->lang->task->task . " : " . $task->name;
- $effort->consumed = !empty($task->team) && $currentTeam ? $effort->consumed - $currentTeam->consumed : $effort->consumed - $task->consumed;
- if($effort->consumed > 0) $effortID = $this->task->addTaskEffort($effort);
- if($task->mode == 'linear' && !empty($effortID)) $this->task->updateEffortOrder($effortID, $currentTeam->order);
- $taskChanges = $this->task->start($task, $newTask);
- if($taskChanges)
- {
- $taskChanges = array_merge($taskChanges, $changes);
- $action->action = $newTask->left == 0 ? 'finished' : 'started';
- $this->saveRecord($action, $taskChanges);
- $this->task->afterStart($task, array(), (float)$newTask->left, array());
- }
- return !dao::isError();
- }
- /**
- * 根据指令完成任务。
- * Finish task by commit.
- *
- * @param object $task
- * @param array $params
- * @param object $action
- * @param array $changes
- * @access private
- * @return bool
- */
- private function finishTask($task, $params, $action, $changes)
- {
- $now = helper::now();
- $newTask = new stdclass();
- $newTask->status = 'done';
- $newTask->left = zget($params, 'left', 0);
- $newTask->consumed = $params['consumed'] + $task->consumed;
- $newTask->assignedTo = $task->openedBy;
- $newTask->realStarted = $task->realStarted ? $task->realStarted : $now;
- $newTask->finishedDate = $now;
- $newTask->lastEditedDate = $now;
- $newTask->assignedDate = $now;
- $newTask->finishedBy = $this->app->user->account;
- $newTask->lastEditedBy = $this->app->user->account;
- $this->loadModel('task');
- if(empty($task->team))
- {
- $consumed = $params['consumed'];
- }
- else
- {
- $currentTeam = $this->task->getTeamByAccount($task->team);
- $consumed = $currentTeam ? $task->consumed - $currentTeam->consumed : $newTask->consumed;
- }
- $effort = new stdclass();
- $effort->date = helper::today();
- $effort->task = $task->id;
- $effort->left = 0;
- $effort->account = $this->app->user->account;
- $effort->consumed = $consumed > 0 ? $consumed : 0;
- $effort->work = $this->lang->action->label->finished . $this->lang->task->task . " : " . $task->name;
- if($effort->consumed > 0) $effortID = $this->task->addTaskEffort($effort);
- if($task->mode == 'linear' && !empty($effortID)) $this->task->updateEffortOrder($effortID, $currentTeam->order);
- $taskChanges = $this->task->finish($task, $newTask);
- if($taskChanges)
- {
- $taskChanges = array_merge($taskChanges, $changes);
- $action->action = 'finished';
- $this->saveRecord($action, $taskChanges);
- $this->task->afterStart($task, array(), 0, array());
- }
- return !dao::isError();
- }
- /**
- * 根据提交信息设置任务信息。
- * Set task by commit.
- *
- * @param object $task
- * @param array $taskActions
- * @param object $action
- * @param array $changes
- * @param string $scm
- * @access public
- * @return bool
- */
- public function setTaskByCommit($task, $taskActions, $action, $changes, $scm)
- {
- foreach($taskActions as $taskAction => $params)
- {
- if($taskAction == 'start' && $task->status == 'wait')
- {
- $this->startTask($task, $params, $action, $changes);
- dao::$errors = array();
- }
- elseif($taskAction == 'effort' && in_array($task->status, array('wait', 'pause', 'doing')))
- {
- $action->action = $scm == 'svn' ? 'svncommited' : 'gitcommited';
- $this->saveEffortForCommit($task->id, $params, $action, $changes);
- }
- elseif($taskAction == 'finish' and in_array($task->status, array('wait', 'pause', 'doing')))
- {
- $this->finishTask($task, $params, $action, $changes);
- }
- }
- return !dao::isError();
- }
- /**
- * 根据提交信息设置工时。
- * Set effort by commit message.
- *
- * @param int $taskID
- * @param array $params
- * @param object $action
- * @param array $changes
- * @access public
- * @return bool
- */
- public function saveEffortForCommit($taskID, $params, $action, $changes)
- {
- unset($_POST['consumed']);
- unset($_POST['left']);
- $_POST['date'][1] = date('Y-m-d');
- $_POST['consumed'][1] = $params['consumed'];
- $_POST['left'][1] = $params['left'];
- $_POST['work'][1] = str_replace('<br />', "\n", $action->comment);
- $this->loadModel('task');
- $workhour = form::batchData($this->config->task->form->recordWorkhour)->get();
- $this->task->recordWorkhour($taskID, $workhour);
- $this->saveRecord($action, $changes);
- return !dao::isError();
- }
- /**
- * 根据提交信息设置Bug状态。
- * Set bug status by commit.
- *
- * @param array $bugs
- * @param array $actions
- * @param object $action
- * @param array $changes
- * @access public
- * @return array
- */
- public function setBugStatusByCommit($bugs, $actions, $action, $changes)
- {
- global $app;
- $productsAndExecutions = $this->loadModel('bug')->getByIdList($bugs);
- foreach($actions['bug'] as $bugID => $bugActions)
- {
- $app->rawModule = 'bug';
- $bug = $this->bug->getByID($bugID);
- if(empty($bug)) continue;
- $action->objectType = 'bug';
- $action->objectID = $bugID;
- $action->product = ",{$productsAndExecutions[$bugID]->product},";
- $action->execution = $productsAndExecutions[$bugID]->execution;
- foreach($bugActions as $bugAction => $params)
- {
- $_POST = array();
- if($bugAction == 'resolve' && $bug->status == 'active')
- {
- $app->rawMethod = 'resolve';
- $this->post->set('resolvedBuild', 'trunk');
- $this->post->set('resolution', 'fixed');
- $newBug = form::data($this->config->bug->form->resolve)
- ->setDefault('assignedTo', $bug->openedBy)
- ->add('id', $bug->id)
- ->add('execution', $bug->execution)
- ->get();
- $changes = array();
- $result = $this->bug->resolve($newBug);
- if($result)
- {
- $newBug = $this->bug->getByID($bugID);
- $changes = common::createChanges($bug, $newBug);
- }
- foreach($changes as $change) $changes[] = $change;
- if($changes)
- {
- $action->action = 'resolved';
- $action->extra = 'fixed';
- $this->saveRecord($action, $changes);
- }
- }
- }
- dao::$errors = array();
- unset($bugs[$bugID]);
- }
- return $bugs;
- }
- /**
- * 保存提交信息关联的日志。
- * Save commit linkage log.
- *
- * @param array $objects
- * @param object $action
- * @param array $changes
- * @access public
- * @return bool
- */
- public function saveObjectToPms($objects, $action, $changes)
- {
- $singular = array('stories' => 'story', 'tasks' => 'task', 'bugs' => 'bug', 'designs' => 'design');
- foreach(array_keys($objects) as $objectType)
- {
- if($objectType == 'actions') continue;
- if($objects[$objectType])
- {
- $objectList = array();
- if($objectType == 'stories')
- {
- $objectList = $this->loadModel('story')->getByList($objects[$objectType]);
- }
- elseif($objectType == 'bugs')
- {
- $objectList = $this->getBugProductsAndExecutions($objects[$objectType]);
- }
- elseif($objectType != 'designs')
- {
- $objectList = $this->getTaskProductsAndExecutions($objects[$objectType]);
- }
- foreach($objects[$objectType] as $objectID)
- {
- $objectID = (int)$objectID;
- if(!isset($objectList[$objectID])) continue;
- $action->objectType = $singular[$objectType];
- $action->objectID = $objectID;
- if($objectType != 'designs')
- {
- $action->product = in_array($objectType, array('stories', 'bugs')) ? $objectList[$objectID]->product : $objectList[$objectID]['product'];
- $action->execution = in_array($objectType, array('stories', 'bugs')) ? 0 : $objectList[$objectID]['execution'];
- }
- $this->saveRecord($action, $changes);
- }
- }
- }
- return !dao::isError();
- }
- /**
- * 获取并列展示的对比信息。
- * Get appose diff.
- *
- * @param array $diffs
- * @access public
- * @return array
- */
- public function getApposeDiff($diffs)
- {
- foreach($diffs as $diffFile)
- {
- if(empty($diffFile->contents)) continue;
- foreach($diffFile->contents as $content)
- {
- $old = array();
- $new = array();
- foreach($content->lines as $line)
- {
- if($line->type != 'new') $old[$line->oldlc] = $line->line;
- if($line->type != 'old') $new[$line->newlc] = $line->line;
- }
- $content->old = $old;
- $content->new = $new;
- }
- }
- return $diffs;
- }
- /**
- * 根据条件获取版本库列表。
- * Get repo list by condition.
- *
- * @param string $repoQuery
- * @param string $SCM
- * @param string $orderBy
- * @param object $pager
- * @access public
- * @return array
- */
- public function getListByCondition($repoQuery, $SCM, $orderBy = 'id_desc', $pager = null)
- {
- return $this->dao->select('*')->from(TABLE_REPO)
- ->where('deleted')->eq('0')
- ->beginIF(!empty($repoQuery))->andWhere($repoQuery)->fi()
- ->beginIF($SCM)->andWhere('SCM')->in($SCM)->fi()
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('id', false);
- }
- /*
- * 保存对象和分支的关联关系。
- * Save object and branch relation.
- *
- * @param int $repoID
- * @param string $branch
- * @param int $objectID
- * @param string $objectType
- * @param string $relation
- * @access public
- * @return bool
- */
- /**
- * @param int $repoID
- * @param string $branch
- * @param int $objectID
- * @param string $objectType
- * @param string $relation
- */
- public function saveRelation($repoID, $branch, $objectID, $objectType, $relation = 'linkrepobranch')
- {
- $relate = new stdclass();
- $relate->product = 0;
- $relate->AType = $objectType;
- $relate->AID = $objectID;
- $relate->BType = $branch;
- $relate->BID = $repoID;
- $relate->relation = $relation;
- $this->dao->replace(TABLE_RELATION)->data($relate)->exec();
- return !dao::isError();
- }
- /**
- * 获取对象关联的代码分支。
- * Get linked branch of object.
- *
- * @param int $objectID
- * @param string $objectType
- * @access public
- * @return array
- * @param int $repoID
- */
- public function getLinkedBranch($objectID = 0, $objectType = '', $repoID = 0)
- {
- return $this->dao->select('BID,BType,AType')->from(TABLE_RELATION)
- ->where('relation')->eq('linkrepobranch')
- ->beginIF($objectType)->andWhere('AType')->eq($objectType)->fi()
- ->beginIF($repoID)->andWhere('BID')->eq($repoID)->fi()
- ->beginIF($objectID)->andWhere('AID')->eq($objectID)->fi()
- ->fetchAll();
- }
- /**
- * 移除对象关联的代码分支。
- * Get linked branch of object.
- *
- * @param int $objectID
- * @param string $objectType
- * @param int $repoID
- * @param string $branch
- * @access public
- * @return array
- */
- public function unlinkObjectBranch($objectID, $objectType, $repoID, $branch)
- {
- $this->dao->delete()->from(TABLE_RELATION)
- ->where('AType')->eq($objectType)
- ->andWhere('relation')->eq('linkrepobranch')
- ->andWhere('AID')->eq($objectID)
- ->andWhere('BID')->eq($repoID)
- ->andWhere('BType')->eq($branch)
- ->exec();
- return !dao::isError();
- }
- /**
- * 通过产品ID和代码库类型获取代码库列表。
- * Get repo list by product id.
- *
- * @param int $productID
- * @param string $scm
- * @param int $limit
- * @access public
- * @return array
- */
- public function getListByProduct($productID, $scm = '', $limit = 0)
- {
- return $this->dao->select('*')->from(TABLE_REPO)
- ->where('deleted')->eq('0')
- ->andWhere("FIND_IN_SET({$productID}, `product`)")
- ->beginIF($scm)->andWhere('SCM')->in($scm)->fi()
- ->beginIF($limit)->limit($limit)->fi()
- ->fetchAll('id');
- }
- /**
- * 获取代码库服务器已经导入的项目/代码库。
- * Get the imported projects/repositories by service host id.
- *
- * @param int $hostID
- * @return array
- */
- public function getImportedProjects($hostID)
- {
- $importedProjects = $this->dao->select('serviceProject')->from(TABLE_REPO)
- ->where('serviceHost')->eq($hostID)
- ->andWhere('deleted')->eq('0')
- ->fetchAll('serviceProject');
- if(dao::isError()) return array();
- return array_keys($importedProjects);
- }
- /**
- * 隐藏DevOps菜单,执行和项目模块使用。
- * Hide DevOps menu.
- *
- * @param int $objectID
- * @access public
- * @return int
- */
- public function setHideMenu($objectID)
- {
- $menuGroup = $this->app->tab == 'project' ? array('project', 'waterfall') : array('execution');
- $repoPairs = $this->loadModel('repo')->getRepoPairs($this->app->tab, $objectID);
- $showMR = false;
- $showTag = false;
- $showBranch = false;
- $showCommit = false;
- $hasTagSCM = array_map('strtolower', $this->config->repo->notSyncSCM);
- foreach($repoPairs as $repoID => $repoName)
- {
- preg_match('/^\[(\w+)\]/', $repoName, $matches);
- $result = isset($matches[1]) ? $matches[1] : '';
- if($repoID == $this->session->repoID && in_array($result, $hasTagSCM))
- {
- $showTag = true;
- $showBranch = true;
- }
- if(in_array($result, $this->config->repo->gitServiceList)) $showMR = true;
- }
- $showMR = $showMR && common::hasPriv('mr', 'browse');
- $showTag = $showTag && common::hasPriv('repo', 'browsetag');
- $showBranch = $showBranch && common::hasPriv('repo', 'browsebranch');
- $showReview = $repoPairs && common::hasPriv('repo', 'review');
- $showCommit = $repoPairs && common::hasPriv('repo', 'log');
- foreach($menuGroup as $module)
- {
- if(!isset($this->lang->{$module}->menu->devops['subMenu'])) continue;
- if(!$showMR) unset($this->lang->{$module}->menu->devops['subMenu']->mr);
- if(!$showTag) unset($this->lang->{$module}->menu->devops['subMenu']->tag);
- if(!$showBranch) unset($this->lang->{$module}->menu->devops['subMenu']->branch);
- if(!$showReview) unset($this->lang->{$module}->menu->devops['subMenu']->review);
- if(!$showCommit) unset($this->lang->{$module}->menu->devops['subMenu']->commit);
- if(count((array)$this->lang->{$module}->menu->devops['subMenu']) < 2) unset($this->lang->{$module}->menu->devops['subMenu']);
- }
- return $objectID;
- }
- /**
- * Check repo name.
- *
- * @param string $name
- * @access public
- * @return bool
- */
- public function checkName($name)
- {
- $pattern = "/^[a-z_]{1}[a-z0-9_\-\.]*$/i";
- return preg_match($pattern, $name);
- }
- }
|