/** * Gets the hash paths for a tree * * @param GitPHP_Tree $tree tre * @return array array of treepath and hashpath arrays */ public function LoadHashPaths($tree) { if (!$tree) { return; } $treePaths = array(); $blobPaths = array(); $args = array(); $args[] = '--full-name'; $args[] = '-r'; $args[] = '-t'; $args[] = $tree->GetHash(); $lines = explode("\n", $this->exe->Execute($tree->GetProject()->GetPath(), GIT_LS_TREE, $args)); foreach ($lines as $line) { if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)\$/", $line, $regs)) { switch ($regs[2]) { case 'tree': $treePaths[trim($regs[4])] = $regs[3]; break; case 'blob': $blobPaths[trim($regs[4])] = $regs[3]; break; } } } return array($treePaths, $blobPaths); }
/** * Load blob size using git * * @param GitPHP_Blob $blob blob * @return int blob size */ protected function LoadSize($blob) { if (!$blob) { return; } $args = array(); $args[] = '-s'; $args[] = $blob->GetHash(); return $this->exe->Execute($blob->GetProject()->GetPath(), GIT_CAT_FILE, $args); }
/** * Reads the tree diff data */ private function ReadData() { $this->dataRead = true; $this->fileDiffs = array(); $args = array(); $args[] = '-r'; if ($this->renames) { $args[] = '-M'; } if (empty($this->fromHash)) { $args[] = '--root'; } else { $args[] = $this->fromHash; } $args[] = $this->toHash; $diffTreeLines = explode("\n", $this->exe->Execute($this->GetProject()->GetPath(), GIT_DIFF_TREE, $args)); foreach ($diffTreeLines as $line) { $trimmed = trim($line); if (strlen($trimmed) > 0 && substr_compare($trimmed, ':', 0, 1) === 0) { try { $this->fileDiffs[] = $this->GetProject()->GetObjectManager()->GetFileDiff($trimmed); } catch (Exception $e) { } } } }
/** * Searches file contents for matches */ private function SearchFileContents() { $args = array(); $args[] = '-I'; $args[] = '--full-name'; $args[] = '--ignore-case'; $args[] = '-n'; $args[] = '-e'; $args[] = '"' . addslashes($this->search) . '"'; $args[] = $this->treeHash; $lines = explode("\n", $this->exe->Execute($this->project->GetPath(), GIT_GREP, $args)); foreach ($lines as $line) { if (preg_match('/^[^:]+:([^:]+):([0-9]+):(.+)$/', $line, $regs)) { if (isset($this->allResults[$regs[1]])) { $result = $this->allResults[$regs[1]]; $matchingLines = $result->GetMatchingLines(); $matchingLines[(int) $regs[2]] = trim($regs[3], "\n\r\v"); $result->SetMatchingLines($matchingLines); } else { $tree = $this->GetTree(); $hash = $tree->PathToHash($regs[1]); if ($hash) { $blob = $this->project->GetObjectManager()->GetBlob($hash); $blob->SetPath($regs[1]); $result = new GitPHP_FileSearchResult($this->project, $blob, $regs[1]); $matchingLines = array(); $matchingLines[(int) $regs[2]] = trim($regs[3], "\n\r\v"); $result->SetMatchingLines($matchingLines); $this->allResults[$regs[1]] = $result; } } } } }
/** * Execute rev-list command * * @param GitPHP_Project $project project * @param string $hash hash to look back from * @param int $count number to return (0 for all) * @param int $skip number of items to skip * @param array $args extra arguments */ public function RevList($project, $hash, $count, $skip = 0, $args = array()) { if (!$project || empty($hash)) { return; } $canSkip = true; if ($skip > 0) { $canSkip = $this->exe->CanSkip(); } $extraargs = array(); if ($canSkip) { if ($count > 0) { $extraargs[] = '--max-count=' . $count; } if ($skip > 0) { $extraargs[] = '--skip=' . $skip; } } else { if ($count > 0) { $extraargs[] = '--max-count=' . ($count + $skip); } } $extraargs[] = $hash; if (count($args) > 0) { $endarg = array_search('--', $args); if ($endarg !== false) { array_splice($args, $endarg, 0, $extraargs); } else { $args = array_merge($args, $extraargs); } } else { $args = $extraargs; } $revlist = explode("\n", $this->exe->Execute($project->GetPath(), GIT_REV_LIST, $args)); if (!$revlist[count($revlist) - 1]) { /* the last newline creates a null entry */ array_splice($revlist, -1, 1); } if ($skip > 0 && !$canSkip) { if ($count > 0) { return array_slice($revlist, $skip, $count); } else { return array_slice($revlist, $skip); } } return $revlist; }
/** * Gets the containing tag for a commit * * @param GitPHP_Commit $commit commit * @return string containing tag */ public function LoadContainingTag($commit) { if (!$commit) { return; } $args = array(); $args[] = '--tags'; $args[] = $commit->GetHash(); $revs = explode("\n", $this->exe->Execute($commit->GetProject()->GetPath(), GIT_NAME_REV, $args)); foreach ($revs as $revline) { if (preg_match('/^([0-9a-fA-F]{40})\\s+tags\\/(.+)(\\^[0-9]+|\\~[0-9]+)$/', $revline, $regs)) { if ($regs[1] == $commit->GetHash()) { return $regs[2]; } } } }
/** * Loads the history data */ protected function LoadData() { $this->dataLoaded = true; $args = array(); $args[] = $this->hash; $args[] = '--no-merges'; $canSkip = true; if ($this->skip > 0) { $canSkip = $this->exe->CanSkip(); } if ($canSkip) { if ($this->limit > 0) { $args[] = '--max-count=' . $this->limit; } if ($this->skip > 0) { $args[] = '--skip=' . $this->skip; } } else { if ($this->limit > 0) { $args[] = '--max-count=' . ($this->limit + $this->skip); } } $args[] = '--'; $args[] = $this->path; $args[] = '|'; $args[] = $this->exe->GetBinary(); $args[] = '--git-dir=' . escapeshellarg($this->project->GetPath()); $args[] = GIT_DIFF_TREE; $args[] = '-r'; $args[] = '--stdin'; $args[] = '--'; $args[] = $this->path; $historylines = explode("\n", $this->exe->Execute($this->project->GetPath(), GIT_REV_LIST, $args)); $commitHash = null; foreach ($historylines as $line) { if (preg_match('/^([0-9a-fA-F]{40})/', $line, $regs)) { $commitHash = $regs[1]; } else { if ($commitHash) { try { $this->history[] = array('diffline' => $line, 'commithash' => $commitHash); } catch (Exception $e) { } $commitHash = null; } } } if ($this->skip > 0 && !$canSkip) { if ($this->limit > 0) { $this->history = array_slice($this->history, $this->skip, $this->limit); } else { $this->history = array_slice($this->history, $this->skip); } } }
/** * Loads the blame data */ private function LoadData() { $this->dataLoaded = true; $args = array(); $args[] = '-s'; $args[] = '-l'; $args[] = $this->commitHash; $args[] = '--'; $args[] = $this->path; $blamelines = explode("\n", $this->exe->Execute($this->project->GetPath(), GIT_BLAME, $args)); $lastcommit = ''; foreach ($blamelines as $line) { if (preg_match('/^([0-9a-fA-F]{40})(\\s+.+)?\\s+([0-9]+)\\)/', $line, $regs)) { if ($regs[1] != $lastcommit) { $this->blame[(int) $regs[3]] = $regs[1]; $lastcommit = $regs[1]; } } } }
/** * Abbreviate a hash * * @param GitPHP_Project $project project * @param string $hash hash to abbreviate * @return string abbreviated hash */ public function AbbreviateHash($project, $hash) { if (!$project) { return $hash; } if (!preg_match('/[0-9A-Fa-f]{40}/', $hash)) { return $hash; } $args = array(); $args[] = '-1'; $args[] = '--format=format:%h'; $args[] = $hash; $abbrevData = explode("\n", $this->exe->Execute($project->GetPath(), GIT_REV_LIST, $args)); if (empty($abbrevData[0])) { return $hash; } if (substr_compare(trim($abbrevData[0]), 'commit', 0, 6) !== 0) { return $hash; } if (empty($abbrevData[1])) { return $hash; } return trim($abbrevData[1]); }
/** * Get refs in a specific order * * @param GitPHP_RefList $refList ref list * @param string $type type of ref * @param string $order order to use * @param int $count limit the number of results * @param int $skip skip a number of results * @return array array of refs */ protected function GetOrderedRefs($refList, $type, $order, $count = 0, $skip = 0) { if (!$refList) { return; } if (empty($type) || empty($order)) { return null; } $args = array(); $args[] = '--sort=' . $order; $args[] = '--format="%(refname)"'; if ($count > 0) { if ($skip > 0) { $args[] = '--count=' . ($count + $skip); } else { $args[] = '--count=' . $count; } } $args[] = '--'; $args[] = 'refs/' . $type; $ret = $this->exe->Execute($refList->GetProject()->GetPath(), GIT_FOR_EACH_REF, $args); $lines = explode("\n", $ret); $prefix = 'refs/' . $type . '/'; $prefixLen = strlen($prefix); $refs = array(); foreach ($lines as $ref) { $ref = substr($ref, $prefixLen); if (!empty($ref)) { $refs[] = $ref; } } if ($skip > 0) { $refs = array_slice($refs, $skip); } return $refs; }
/** * FindHash * * Looks up the hash for the ref * * @access protected * @throws Exception if hash is not found */ protected function FindHash() { $exe = new GitPHP_GitExe($this->GetProject()); $args = array(); $args[] = '--hash'; $args[] = '--verify'; $args[] = $this->GetRefPath(); $hash = trim($exe->Execute(GIT_SHOW_REF, $args)); if (empty($hash)) { throw new Exception('Invalid ref ' . $this->GetRefPath()); } $this->SetHash($hash); }
/** * ReadCommit * * Attempts to dereference the commit for this tag * * @access private */ private function ReadCommit() { $exe = new GitPHP_GitExe($this->GetProject()); $args = array(); $args[] = '--tags'; $args[] = '--dereference'; $args[] = $this->refName; $ret = $exe->Execute(GIT_SHOW_REF, $args); unset($exe); $lines = explode("\n", $ret); foreach ($lines as $line) { if (preg_match('/^([0-9a-fA-F]{40}) refs\\/tags\\/' . preg_quote($this->refName) . '(\\^{})$/', $line, $regs)) { $this->commit = $this->GetProject()->GetCommit($regs[1]); return; } } GitPHP_Cache::GetObjectCacheInstance()->Set($this->GetCacheKey(), $this); }
/** * GetDiffSplit * * construct the side by side diff data from the git data * The result is an array of ternary arrays with 3 elements each: * First the mode ("" or "-added" or "-deleted" or "-modified"), * then the first column, then the second. * * @author Mattias Ulbrich * * @access public * @return an array of line elements (see above) */ public function GetDiffSplit() { if ($this->diffDataSplitRead) { return $this->diffDataSplit; } $this->diffDataSplitRead = true; $exe = new GitPHP_GitExe($this->project); $fromBlob = $this->GetFromBlob(); $blob = $fromBlob->GetData(true); $diffLines = ''; if (function_exists('xdiff_string_diff')) { $diffLines = explode("\n", $this->GetXDiff(0, false)); } else { $diffLines = explode("\n", $exe->Execute(GIT_DIFF, array("-U0", $this->fromHash, $this->toHash))); } unset($exe); // // parse diffs $diffs = array(); $currentDiff = FALSE; foreach ($diffLines as $d) { if (strlen($d) == 0) { continue; } switch ($d[0]) { case '@': if ($currentDiff) { if (count($currentDiff['left']) == 0 && count($currentDiff['right']) > 0) { $currentDiff['line']++; } // HACK to make added blocks align correctly $diffs[] = $currentDiff; } $comma = strpos($d, ","); $line = -intval(substr($d, 2, $comma - 2)); $currentDiff = array("line" => $line, "left" => array(), "right" => array()); break; case '+': if ($currentDiff) { $currentDiff["right"][] = substr($d, 1); } break; case '-': if ($currentDiff) { $currentDiff["left"][] = substr($d, 1); } break; case ' ': echo "should not happen!"; if ($currentDiff) { $currentDiff["left"][] = substr($d, 1); $currentDiff["right"][] = substr($d, 1); } break; } } if ($currentDiff) { if (count($currentDiff['left']) == 0 && count($currentDiff['right']) > 0) { $currentDiff['line']++; } // HACK to make added blocks align correctly $diffs[] = $currentDiff; } // // iterate over diffs $output = array(); $idx = 0; foreach ($diffs as $d) { while ($idx + 1 < $d['line']) { $h = $blob[$idx]; $output[] = array('', $h, $h); $idx++; } if (count($d['left']) == 0) { $mode = 'added'; } elseif (count($d['right']) == 0) { $mode = 'deleted'; } else { $mode = 'modified'; } for ($i = 0; $i < count($d['left']) || $i < count($d['right']); $i++) { $left = $i < count($d['left']) ? $d['left'][$i] : FALSE; $right = $i < count($d['right']) ? $d['right'][$i] : FALSE; $output[] = array($mode, $left, $right); } $idx += count($d['left']); } while ($idx < count($blob)) { $h = $blob[$idx]; $output[] = array('', $h, $h); $idx++; } $this->diffDataSplit = $output; return $output; }
/** * ReadData * * Reads the tree diff data * * @access private */ private function ReadData() { $this->dataRead = true; $this->fileDiffs = array(); $exe = new GitPHP_GitExe($this->project); $args = array(); $args[] = '-r'; if ($this->renames) { $args[] = '-M'; } if (empty($this->fromHash)) { $args[] = '--root'; } else { $args[] = $this->fromHash; } $args[] = $this->toHash; $diffTreeLines = explode("\n", $exe->Execute(GIT_DIFF_TREE, $args)); foreach ($diffTreeLines as $line) { $trimmed = trim($line); if (strlen($trimmed) > 0 && substr_compare($trimmed, ':', 0, 1) === 0) { try { $this->fileDiffs[] = new GitPHP_FileDiff($this->project, $trimmed); } catch (Exception $e) { } } } unset($exe); }
/** * ReadBlame * * Read blame info * * @access private */ private function ReadBlame() { $this->blameRead = true; $exe = new GitPHP_GitExe($this->GetProject()); $args = array(); $args[] = '-s'; $args[] = '-l'; if ($this->commit) { $args[] = $this->commit->GetHash(); } else { $args[] = 'HEAD'; } $args[] = '--'; $args[] = $this->GetPath(); $blamelines = explode("\n", $exe->Execute(GIT_BLAME, $args)); $lastcommit = ''; foreach ($blamelines as $line) { if (preg_match('/^([0-9a-fA-F]{40})(\\s+.+)?\\s+([0-9]+)\\)/', $line, $regs)) { if ($regs[1] != $lastcommit) { $this->blame[(int) $regs[3]] = $this->GetProject()->GetCommit($regs[1]); $lastcommit = $regs[1]; } } } }
/** * ReadContentsGit * * Reads the tree contents using the git executable * * @access private */ private function ReadContentsGit() { $exe = new GitPHP_GitExe($this->GetProject()); $args = array(); $args[] = '--full-name'; if ($exe->CanShowSizeInTree()) { $args[] = '-l'; } $args[] = '-t'; $args[] = $this->hash; $lines = explode("\n", $exe->Execute(GIT_LS_TREE, $args)); foreach ($lines as $line) { if (preg_match("/^([0-9]+) (.+) ([0-9a-fA-F]{40})(\\s+[0-9]+|\\s+-)?\t(.+)\$/", $line, $regs)) { switch ($regs[2]) { case 'tree': $t = $this->GetProject()->GetTree($regs[3]); $t->SetMode($regs[1]); $path = $regs[5]; if (!empty($this->path)) { $path = $this->path . '/' . $path; } $t->SetPath($path); if ($this->commit) { $t->SetCommit($this->commit); } $this->contents[] = $t; break; case 'blob': $b = $this->GetProject()->GetBlob($regs[3]); $b->SetMode($regs[1]); $path = $regs[5]; if (!empty($this->path)) { $path = $this->path . '/' . $path; } $b->SetPath($path); $size = trim($regs[4]); if (!empty($size)) { $b->SetSize($regs[4]); } if ($this->commit) { $b->SetCommit($this->commit); } $this->contents[] = $b; break; } } } }
/** * SearchFileContents * * Searches for a pattern in file contents * * @access public * @param string $pattern pattern to search for * @return array multidimensional array of results */ public function SearchFileContents($pattern) { if (empty($pattern)) { return; } $exe = new GitPHP_GitExe($this->GetProject()); $args = array(); $args[] = '-I'; $args[] = '--full-name'; $args[] = '--ignore-case'; $args[] = '-n'; $args[] = '-e'; $args[] = '\'' . preg_quote($pattern) . '\''; $args[] = $this->hash; $lines = explode("\n", $exe->Execute(GIT_GREP, $args)); $results = array(); foreach ($lines as $line) { if (preg_match('/^[^:]+:([^:]+):([0-9]+):(.+)$/', $line, $regs)) { if (!isset($results[$regs[1]]['object'])) { $hash = $this->PathToHash($regs[1]); if (!empty($hash)) { $obj = $this->GetProject()->GetBlob($hash); $obj->SetCommit($this); $results[$regs[1]]['object'] = $obj; } } $results[$regs[1]]['lines'][(int) $regs[2]] = $regs[3]; } } return $results; }
/** * RevList * * Common code for using rev-list command * * @access private * @param string $hash hash to list from * @param integer $count number of results to get * @param integer $skip number of results to skip * @param array $args args to give to rev-list * @return array array of hashes */ private function RevList($hash, $count = 50, $skip = 0, $args = array()) { if ($count < 1) { return; } $exe = new GitPHP_GitExe($this); $canSkip = true; if ($skip > 0) { $canSkip = $exe->CanSkip(); } if ($canSkip) { $args[] = '--max-count=' . $count; if ($skip > 0) { $args[] = '--skip=' . $skip; } } else { $args[] = '--max-count=' . ($count + $skip); } $args[] = $hash; $revlist = explode("\n", $exe->Execute(GIT_REV_LIST, $args)); if (!$revlist[count($revlist) - 1]) { /* the last newline creates a null entry */ array_splice($revlist, -1, 1); } if ($skip > 0 && !$exe->CanSkip()) { return array_slice($revlist, $skip, $count); } return $revlist; }
/** * Gets the data for a tag * * @param GitPHP_Tag $tag tag * @return array array of tag data */ public function Load($tag) { if (!$tag) { return; } $type = null; $object = null; $commitHash = null; $tagger = null; $taggerEpoch = null; $taggerTimezone = null; $comment = array(); $result = $this->exe->GetObjectData($tag->GetProject()->GetPath(), $tag->GetHash()); if ($result['type'] === 'commit') { /* light tag */ $object = $tag->GetHash(); $commitHash = $tag->GetHash(); $type = 'commit'; return array($type, $object, $commitHash, $tagger, $taggerEpoch, $taggerTimezone, $comment); } /* get data from tag object */ $result = $this->exe->GetObjectData($tag->GetProject()->GetPath(), $tag->GetName()); $lines = explode("\n", $result['contents']); if (!isset($lines[0])) { return; } $objectHash = null; $readInitialData = false; foreach ($lines as $i => $line) { if (!$readInitialData) { if (preg_match('/^object ([0-9a-fA-F]{40})$/', $line, $regs)) { $objectHash = $regs[1]; continue; } else { if (preg_match('/^type (.+)$/', $line, $regs)) { $type = $regs[1]; continue; } else { if (preg_match('/^tag (.+)$/', $line, $regs)) { continue; } else { if (preg_match('/^tagger (.*) ([0-9]+) (.*)$/', $line, $regs)) { $tagger = $regs[1]; $taggerEpoch = $regs[2]; $taggerTimezone = $regs[3]; continue; } } } } } $trimmed = trim($line); if (strlen($trimmed) > 0 || $readInitialData === true) { $comment[] = $line; } $readInitialData = true; } switch ($type) { case 'commit': $object = $objectHash; $commitHash = $objectHash; break; case 'tag': $args = array(); $args[] = 'tag'; $args[] = $objectHash; $ret = $this->exe->Execute($tag->GetProject()->GetPath(), GIT_CAT_FILE, $args); $lines = explode("\n", $ret); foreach ($lines as $i => $line) { if (preg_match('/^tag (.+)$/', $line, $regs)) { $name = trim($regs[1]); $object = $name; } } break; case 'blob': $object = $objectHash; break; } return array($type, $object, $commitHash, $tagger, $taggerEpoch, $taggerTimezone, $comment); }