Skip to content
Snippets Groups Projects
Select Git revision
  • 8133244e64bea890baf8d0c0ad7076f4026b52dd
  • master default
  • disable-new-requests
  • fix-bulletin-view-missing-notes-error
  • add-missing-queue-managers
  • projects-task-53
  • projects-task-51
  • projects-task-43
  • projects-task-24
  • projects-task-31
  • projects-task-32
  • projects-task-8
  • project-setup-docs
  • projects-task-28
  • projects-task-27
  • projects-task-9
  • projects-task-7
  • mass-update-course-codes-in-sections
  • wdn-four
  • learning-outcomes
  • additional-bulletin-pages
  • svn-redesign
  • svn-popups
  • svn-trunk
  • svn-performance
  • svn-tim
26 results

RepositoryModel.php

Blame
  • RepositoryModel.php 13.11 KiB
    <?php
    
    class Bulletin_RepositoryModel
    {
        static protected $_instance;
    
        static public function getInstance()
        {
            if (!self::$_instance) {
                self::$_instance = new self();
            }
            return self::$_instance;
        }
    
        protected $_repoPath;
    
        protected $_lockFile;
    
        // Array of options loaded from application.ini
        protected $_options;
    
        public function getFileList()
        {
            foreach (glob($this->_repoPath . '/data/20*') as $yearDirectory) {
                $relativePath = substr($yearDirectory, strlen($this->_repoPath));
                $year = basename($relativePath);
    
                foreach (glob($yearDirectory . '/majors/*') as $majorFile) {
                    $files[$year]['majors'][basename($majorFile)] = basename($majorFile, '.xhtml');
                }
                foreach (glob($yearDirectory . '/colleges/*') as $collegeFile) {
                    $files[$year]['colleges'][basename($collegeFile)] = basename($collegeFile, '.xhtml');
                }
                foreach (glob($yearDirectory . '/other/*') as $otherFile) {
                    $files[$year]['other'][basename($otherFile)] = basename($otherFile, '.xhtml');
                }
            }
            return $files;
        }
    
        public function getFileContents($path, $branch = 'master')
        {
            $this->_checkout($branch);
            $contents = file_get_contents($this->_repoPath . '/' . $path);
            $this->_checkout('master');
    
            return $contents;
        }
    
        public function saveFileContents($path, $branch, Auth_UserModel $user, $contents, $message = 'Saving Changes')
        {
            if ($branch == 'master') {
                throw new Exception('Saving to the master branch is not allowed.');
            }
            $this->_checkout($branch);
            file_put_contents($this->_repoPath . '/' . $path, $contents);
            $author = $user->getFirstName() . ' ' . $user->getLastName() . ' <' . $user->getEmail() . '>';
            $this->_git_exec('commit --allow-empty -a -m ' . escapeshellarg($message) . ' --author=' . escapeshellarg($author));
            $this->_checkout('master');
    
            $this->push();
        }
    
        public function getFileNotes($path, $branch = 'master')
        {
            $object = $note = '';
            $filepath = $this->_repoPath . '/' . $path;
    
            $this->_checkout($branch);
            if(file_exists($filepath)){
                $this->_git_exec('hash-object ' . escapeshellarg($filepath), $object);
                $this->_git_exec('notes --ref=creq show ' .  escapeshellarg($object), $note);
            }
            $this->_checkout('master');
    
            return $note;
        }
    
        public function saveFileNotes($path, $branch, $contents)
        {
            $tmpPath = tempnam(sys_get_temp_dir(), 'creq_');
            file_put_contents($tmpPath, $contents);
    
            if ($branch == 'master') {
                throw new Exception('Saving to the master branch is not allowed');
            }
            $this->_checkout($branch);
            $this->_git_exec('hash-object ' . escapeshellarg($this->_repoPath . '/' . $path), $object);
            $this->_git_exec(
                'notes --ref=creq ' .
                ' add ' . escapeshellarg($object) .
                ' -f -F ' . escapeshellarg($tmpPath)
            );
            $this->_checkout('master');
    
            unlink($tmpPath);
        }
    
        public function moveFile($current_file_path, $new_file_path, $branch, Auth_UserModel $user, $message = 'Renaming File')
        {
            if ($branch == 'master') {
                throw new Exception('Saving to the master branch is not allowed.');
            }
            $this->_checkout($branch);
            $this->_git_exec("mv " . escapeshellarg($current_file_path) . " " . escapeshellarg($new_file_path));
            $author = $user->getFirstName() . ' ' . $user->getLastName() . ' <' . $user->getEmail() . '>';
            $this->_git_exec('commit --allow-empty -a -m ' . escapeshellarg($message) . ' --author=' . escapeshellarg($author));
            $this->_checkout('master');
        }
    
        public function deleteFile($file_path, $branch, Auth_UserModel $user, $message = 'Removing File')
        {
            if ($branch == 'master') {
                throw new Exception('Saving to the master branch is not allowed.');
            }
            $this->_checkout($branch);
            $this->_git_exec("rm " . escapeshellarg($file_path));
            $author = $user->getFirstName() . ' ' . $user->getLastName() . ' <' . $user->getEmail() . '>';
            $this->_git_exec('commit --allow-empty -a -m ' . escapeshellarg($message) . ' --author=' . escapeshellarg($author));
            $this->_checkout('master');
        }
    
        public function mergeBranchToMaster($branch)
        {
            if ($branch == 'master') {
                throw new Exception('Supplied branch name cannot be master.');
            }
            $this->_checkout('master');
            $this->_git_exec("merge " . $branch);
    
            $this->push();
        }
    
        protected function __construct()
        {
            $this->_repoPath = APPLICATION_PATH . '/bulletin.git';
    
            $front = Zend_Controller_Front::getInstance();
    
            $options = $front->getParam('bootstrap')->getOptions();
            $this->_options = (isset($options['bulletin']) ? $options['bulletin'] : array());
    
            if (!file_exists($this->_repoPath) || !is_dir($this->_repoPath)) {
                throw new Exception('The repository directory does not exist.');
            }
    
            if (!is_writable($this->_repoPath)) {
                throw new Exception('The repository is not writable by the web user.');
            }
    
            if (!file_exists($this->_repoPath . '/.git')) {
                $originParts = parse_url($this->_options['github']['origin']);
                $origin = $originParts['scheme'] . '://'
                        . urlencode($this->_options['github']['username']) . ':'
                        . urlencode($this->_options['github']['password']) . '@'
                        . $originParts['host'] . $originParts['path'];
    
                $this->_git_exec('clone ' . escapeshellarg($origin) . ' .');
                $this->_git_exec('remote add upstream ' . escapeshellarg($this->_options['github']['upstream']));
                $this->_git_exec('config --add remote.origin.fetch "refs/notes/*:refs/notes/*"');
                $this->_git_exec('config --add remote.origin.push refs/notes/creq');
                $this->_git_exec('fetch');
                file_put_contents($this->_repoPath . '/.git/info/exclude', '*.xhtml.diff' . PHP_EOL);
            }
    
            $this->lockFile = fopen($this->_repoPath . '/.lock', 'a');
            flock($this->lockFile, LOCK_EX);
    
            $this->_checkout('master');
        }
    
        protected function __destruct()
        {
            flock($this->lockFile, LOCK_UN);
        }
    
        protected function _git_exec($command, &$output = array()) {
            $cwd = getcwd();
            chdir($this->_repoPath);
    
            $return = NULL;
            $output = array();
            exec('git ' . $command, $output, $return);
            $output = implode(PHP_EOL, $output);
    
            if ($return != 0) {
                throw new Exception('An error occurred while running a git command.', $return);
            }
    
            chdir($cwd);
            return $return;
        }
    
        protected function _checkout($branch)
        {
            try {
                $this->_git_exec('checkout ' . escapeshellarg($branch));
            } catch (Exception $e) {
                $this->_git_exec('checkout -b ' . escapeshellarg($branch) . ' master');
            }
    
        }
    
        public function pull()
        {
            $this->_checkout('master');
            $this->_git_exec('fetch');
            $this->_git_exec('fetch upstream');
            $this->_git_exec('merge --ff-only upstream/master');
        }
    
        public function push()
        {
            $this->_git_exec('push origin --all');
        }
    
        public function pullRequest($branch, $title, $body = '', &$pullRequestNumber = -1)
        {
            $remotePath = explode('/', parse_url($this->_options['github']['upstream'], PHP_URL_PATH));
            $remoteHost = parse_url($this->_options['github']['upstream'], PHP_URL_HOST);
            $remoteUser = $remotePath[1];
            $remoteRepo = basename($remotePath[2], '.git');
    
            $originPath = explode('/', parse_url($this->_options['github']['origin'], PHP_URL_PATH));
            $originUser = $originPath[1];
            $originRepo = basename($originPath[2], '.git');
    
            $uri = 'https://api.' . $remoteHost . '/repos/' . $remoteUser . '/' . $remoteRepo . '/pulls';
    
            $data = array(
                'title' => $title,
                'head'  => $originUser . ':' . $branch,
                //'base'  => $remoteUser . ':' . 'master', // github changed the way this works (04/2014)
                'base'  => 'master',
            );
            if ($body) {
                $data['body'] = $body;
            }
            $data = json_encode($data);
    
            $http = new Zend_Http_Client();
            $http->setUri($uri);
            $http->setAuth($this->_options['github']['username'], $this->_options['github']['password']);
            $http->setMethod(Zend_Http_Client::POST);
            $http->setRawData($data);
            $http->request();
    
            if ($http->getLastResponse()->getStatus() == 201) {
                $response = json_decode($http->getLastResponse()->getBody(), true);
                $pullRequestNumber = $response['number'];
    
                return $http->getLastResponse()->getHeader('Location');
            }
            if ($branch != 'master') {
                print_r($http->getLastResponse()); exit;
    
                throw new Exception('An error occurred while creating the pull request.');
            } else {
                // let's send an email off to admins, just so this is noted, but should be ok to ignore
                // this error should happen only when processing mulitiple major name changes at same time, adding to existing pull request
                NotifyEmailModel::NotifyAdmins("An error occured when trying to create a bulletin pull request. This error is expected when a pull request for the given branch already exists, it will automatically add files/changes to this existing pull request. This error should be safe to ignore, but we are notifying you anyway just in case.\n\n".print_r($http->getLastResponse(),true));
    
                // ignore error on master, pull request already exists, it will automatically add to existing pull request
                return $http->getLastResponse()->getHeader('Location');
            }
        }
    
        public function getFilePathForSection(Bulletin_SectionModel $section)
        {
            return $this->getFilePathForCollegeMajorYear(
                $section->getCollegeLong(),
                $section->getMajor(),
                $section->getYear()
            );
        }
    
        public function getParentCommitForSection(Bulletin_SectionModel $section)
        {
            $command = 'merge-base '
                     . escapeshellarg('request-' . $section->getRequest()) . ' '
                     . 'master '
                     ;
            if ($this->_git_exec($command, $data) == 0) {
                return $data;
            }
    
            throw new Exception('No parent commit could be found!');
        }
    
        public function getFilePathForCollegeMajorYear($college, $major, $year)
        {
            $year = intval($year);
    
            $xhtmlFiles = array();
            foreach ($this->getFileList() as $yearDir => $sections) {
                if ($year != $yearDir) {
                    continue;
                }
                foreach ($sections as $section => $files) {
                    foreach ($files as $file => $name) {
                        if (substr($file, -6) != '.xhtml') {
                            continue;
                        }
                        $xhtmlFiles[$section][$name] = $yearDir . DIRECTORY_SEPARATOR . $section . DIRECTORY_SEPARATOR . $file;
                    }
                }
            }
            require_once 'UNL/Autoload.php';
            spl_autoload_register('Unl_Autoload');
    
            require_once $this->_repoPath . '/data/' . $year . '/edition.config.inc.php';
            $nameMap = array_flip(UNL_UndergraduateBulletin_Major_Description::getEpubToTitleMap());
    
            if ($major) {
                if (array_key_exists($major, $nameMap)) {
                    $major = $nameMap[$major];
                }
                if ($college == 'Other University Programs') {
                    $path = $xhtmlFiles['other'][$major];
                } else {
                    $path = $xhtmlFiles['majors'][$major];
                }
            } else {
                $path = $xhtmlFiles['colleges'][$college . ' Main Page'];
            }
            return $path;
        }
    
        /**
         * Checks if a pull request has been merged
         * @param $requestNumber Int Pull request number to check
         * @return bool TRUE if merged, FALSE otherwise
         */
        public function pullRequestHasBeenMerged($requestNumber)
        {
            $remotePath = explode('/', parse_url($this->_options['github']['upstream'], PHP_URL_PATH));
            $remoteHost = parse_url($this->_options['github']['upstream'], PHP_URL_HOST);
            $remoteUser = $remotePath[1];
            $remoteRepo = basename($remotePath[2], '.git');
    
            $requestNumber = intval($requestNumber);
    
            $uri = 'https://api.' . $remoteHost . '/repos/' . $remoteUser . '/' . $remoteRepo . '/pulls/' . $requestNumber;
    
            $http = new Zend_Http_Client();
            $http->setUri($uri);
            $http->setMethod(Zend_Http_Client::GET);
            $http->request();
    
            if ($http->getLastResponse()->getStatus() == 200) {
                $response = json_decode($http->getLastResponse()->getBody(), true);
                $status = $response['state'];
                if ($status === "closed") {
                    return true;
                }
            }
            return false;
        }
    }