Пример #1
0
 protected function doLock($wait)
 {
     $conn = $this->conn;
     if (!$conn) {
         // Try to reuse a connection from the connection pool.
         $conn = array_pop(self::$pool);
     }
     if (!$conn) {
         // NOTE: Using the 'repository' database somewhat arbitrarily, mostly
         // because the first client of locks is the repository daemons. We must
         // always use the same database for all locks, but don't access any
         // tables so we could use any valid database. We could build a
         // database-free connection instead, but that's kind of messy and we
         // might forget about it in the future if we vertically partition the
         // application.
         $dao = new PhabricatorRepository();
         // NOTE: Using "force_new" to make sure each lock is on its own
         // connection.
         $conn = $dao->establishConnection('w', $force_new = true);
         // NOTE: Since MySQL will disconnect us if we're idle for too long, we set
         // the wait_timeout to an enormous value, to allow us to hold the
         // connection open indefinitely (or, at least, for 24 days).
         $max_allowed_timeout = 2147483;
         queryfx($conn, 'SET wait_timeout = %d', $max_allowed_timeout);
     }
     $result = queryfx_one($conn, 'SELECT GET_LOCK(%s, %f)', 'phabricator:' . $this->lockname, $wait);
     $ok = head($result);
     if (!$ok) {
         throw new PhutilLockException($this->getName());
     }
     $this->conn = $conn;
 }
 private function checkIfRepositoryIsFullyImported(PhabricatorRepository $repository)
 {
     // Check if the repository has the "Importing" flag set. We want to clear
     // the flag if we can.
     $importing = $repository->getDetail('importing');
     if (!$importing) {
         // This repository isn't marked as "Importing", so we're done.
         return;
     }
     // Look for any commit which hasn't imported.
     $unparsed_commit = queryfx_one($repository->establishConnection('r'), 'SELECT * FROM %T WHERE repositoryID = %d AND (importStatus & %d) != %d
     LIMIT 1', id(new PhabricatorRepositoryCommit())->getTableName(), $repository->getID(), PhabricatorRepositoryCommit::IMPORTED_ALL, PhabricatorRepositoryCommit::IMPORTED_ALL);
     if ($unparsed_commit) {
         // We found a commit which still needs to import, so we can't clear the
         // flag.
         return;
     }
     // Clear the "importing" flag.
     $repository->openTransaction();
     $repository->beginReadLocking();
     $repository = $repository->reload();
     $repository->setDetail('importing', false);
     $repository->save();
     $repository->endReadLocking();
     $repository->saveTransaction();
 }
 protected function executeChecks()
 {
     if (phutil_is_windows()) {
         $bin_name = 'where';
     } else {
         $bin_name = 'which';
     }
     if (!Filesystem::binaryExists($bin_name)) {
         $message = pht("Without '%s', Phabricator can not test for the availability " . "of other binaries.", $bin_name);
         $this->raiseWarning($bin_name, $message);
         // We need to return here if we can't find the 'which' / 'where' binary
         // because the other tests won't be valid.
         return;
     }
     if (!Filesystem::binaryExists('diff')) {
         $message = pht("Without 'diff', Phabricator will not be able to generate or render " . "diffs in multiple applications.");
         $this->raiseWarning('diff', $message);
     } else {
         $tmp_a = new TempFile();
         $tmp_b = new TempFile();
         $tmp_c = new TempFile();
         Filesystem::writeFile($tmp_a, 'A');
         Filesystem::writeFile($tmp_b, 'A');
         Filesystem::writeFile($tmp_c, 'B');
         list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_b);
         if ($err) {
             $this->newIssue('bin.diff.same')->setName(pht("Unexpected 'diff' Behavior"))->setMessage(pht("The 'diff' binary on this system has unexpected behavior: " . "it was expected to exit without an error code when passed " . "identical files, but exited with code %d.", $err));
         }
         list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_c);
         if (!$err) {
             $this->newIssue('bin.diff.diff')->setName(pht("Unexpected 'diff' Behavior"))->setMessage(pht("The 'diff' binary on this system has unexpected behavior: " . "it was expected to exit with a nonzero error code when passed " . "differing files, but did not."));
         }
     }
     $table = new PhabricatorRepository();
     $vcses = queryfx_all($table->establishConnection('r'), 'SELECT DISTINCT versionControlSystem FROM %T', $table->getTableName());
     foreach ($vcses as $vcs) {
         switch ($vcs['versionControlSystem']) {
             case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                 $binary = 'git';
                 break;
             case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                 $binary = 'svn';
                 break;
             case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                 $binary = 'hg';
                 break;
             default:
                 $binary = null;
                 break;
         }
         if (!$binary) {
             continue;
         }
         if (!Filesystem::binaryExists($binary)) {
             $message = pht('You have at least one repository configured which uses this ' . 'version control system. It will not work without the VCS binary.');
             $this->raiseWarning($binary, $message);
         }
     }
 }
 private static function lookupPaths(array $paths)
 {
     $repository = new PhabricatorRepository();
     $conn_w = $repository->establishConnection('w');
     $result_map = array();
     foreach (array_chunk($paths, 128) as $path_chunk) {
         $chunk_map = queryfx_all($conn_w, 'SELECT path, id FROM %T WHERE path IN (%Ls)', PhabricatorRepository::TABLE_PATH, $path_chunk);
         foreach ($chunk_map as $row) {
             $result_map[$row['path']] = $row['id'];
         }
     }
     return $result_map;
 }
Пример #5
0
 public function loadPathIDs()
 {
     $repository = new PhabricatorRepository();
     $path_normal_map = array();
     foreach ($this->paths as $path) {
         $normal = self::normalizePath($path);
         $path_normal_map[$normal][] = $path;
     }
     $paths = queryfx_all($repository->establishConnection('r'), 'SELECT * FROM %T WHERE pathHash IN (%Ls)', PhabricatorRepository::TABLE_PATH, array_map('md5', array_keys($path_normal_map)));
     $paths = ipull($paths, 'id', 'path');
     $result = array();
     foreach ($path_normal_map as $normal => $originals) {
         foreach ($originals as $original) {
             $result[$original] = idx($paths, $normal);
         }
     }
     return $result;
 }
 private function writeCommitChanges(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, array $changes)
 {
     $repository_id = (int) $repository->getID();
     $commit_id = (int) $commit->getID();
     // NOTE: This SQL is being built manually instead of with qsprintf()
     // because some SVN changes affect an enormous number of paths (millions)
     // and this showed up as significantly slow on a profile at some point.
     $changes_sql = array();
     foreach ($changes as $change) {
         $values = array($repository_id, (int) $change->getPathID(), $commit_id, nonempty((int) $change->getTargetPathID(), 'null'), nonempty((int) $change->getTargetCommitID(), 'null'), (int) $change->getChangeType(), (int) $change->getFileType(), (int) $change->getIsDirect(), (int) $change->getCommitSequence());
         $changes_sql[] = '(' . implode(', ', $values) . ')';
     }
     $conn_w = $repository->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE commitID = %d', PhabricatorRepository::TABLE_PATHCHANGE, $commit_id);
     foreach (PhabricatorLiskDAO::chunkSQL($changes_sql) as $chunk) {
         queryfx($conn_w, 'INSERT INTO %T
       (repositoryID, pathID, commitID, targetPathID, targetCommitID,
         changeType, fileType, isDirect, commitSequence)
       VALUES %Q', PhabricatorRepository::TABLE_PATHCHANGE, $chunk);
     }
 }
 protected function isBadCommit($full_commit_name)
 {
     $repository = new PhabricatorRepository();
     $bad_commit = queryfx_one($repository->establishConnection('w'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, $full_commit_name);
     return (bool) $bad_commit;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $shortcuts = id(new PhabricatorRepositoryShortcut())->loadAll();
     if ($shortcuts) {
         $shortcuts = msort($shortcuts, 'getSequence');
         $rows = array();
         foreach ($shortcuts as $shortcut) {
             $rows[] = array(phutil_render_tag('a', array('href' => $shortcut->getHref()), phutil_escape_html($shortcut->getName())), phutil_escape_html($shortcut->getDescription()));
         }
         $shortcut_table = new AphrontTableView($rows);
         $shortcut_table->setHeaders(array('Link', ''));
         $shortcut_table->setColumnClasses(array('pri', 'wide'));
         $shortcut_panel = new AphrontPanelView();
         $shortcut_panel->setHeader('Shortcuts');
         $shortcut_panel->appendChild($shortcut_table);
     } else {
         $shortcut_panel = null;
     }
     $repository = new PhabricatorRepository();
     $repositories = $repository->loadAll();
     foreach ($repositories as $key => $repo) {
         if (!$repo->isTracked()) {
             unset($repositories[$key]);
         }
     }
     $repository_ids = mpull($repositories, 'getID');
     $summaries = array();
     $commits = array();
     if ($repository_ids) {
         $summaries = queryfx_all($repository->establishConnection('r'), 'SELECT * FROM %T WHERE repositoryID IN (%Ld)', PhabricatorRepository::TABLE_SUMMARY, $repository_ids);
         $summaries = ipull($summaries, null, 'repositoryID');
         $commit_ids = array_filter(ipull($summaries, 'lastCommitID'));
         if ($commit_ids) {
             $commit = new PhabricatorRepositoryCommit();
             $commits = $commit->loadAllWhere('id IN (%Ld)', $commit_ids);
             $commits = mpull($commits, null, 'getRepositoryID');
         }
     }
     $rows = array();
     foreach ($repositories as $repository) {
         $id = $repository->getID();
         $commit = idx($commits, $id);
         $size = idx(idx($summaries, $id, array()), 'size', 0);
         $date = '-';
         $time = '-';
         if ($commit) {
             $date = phabricator_date($commit->getEpoch(), $user);
             $time = phabricator_time($commit->getEpoch(), $user);
         }
         $rows[] = array(phutil_render_tag('a', array('href' => '/diffusion/' . $repository->getCallsign() . '/'), phutil_escape_html($repository->getName())), phutil_escape_html($repository->getDetail('description')), PhabricatorRepositoryType::getNameForRepositoryType($repository->getVersionControlSystem()), $size ? number_format($size) : '-', $commit ? DiffusionView::linkCommit($repository, $commit->getCommitIdentifier()) : '-', $date, $time);
     }
     $repository_tool_uri = PhabricatorEnv::getProductionURI('/repository/');
     $repository_tool = phutil_render_tag('a', array('href' => $repository_tool_uri), 'repository tool');
     $no_repositories_txt = 'This instance of Phabricator does not have any ' . 'configured repositories. ';
     if ($user->getIsAdmin()) {
         $no_repositories_txt .= 'To setup one or more repositories, visit the ' . $repository_tool . '.';
     } else {
         $no_repositories_txt .= 'Ask an administrator to setup one or more ' . 'repositories via the ' . $repository_tool . '.';
     }
     $table = new AphrontTableView($rows);
     $table->setNoDataString($no_repositories_txt);
     $table->setHeaders(array('Repository', 'Description', 'VCS', 'Commits', 'Last', 'Date', 'Time'));
     $table->setColumnClasses(array('pri', 'wide', '', 'n', 'n', '', 'right'));
     $panel = new AphrontPanelView();
     $panel->setHeader('Browse Repositories');
     $panel->appendChild($table);
     $crumbs = $this->buildCrumbs();
     return $this->buildStandardPageResponse(array($crumbs, $shortcut_panel, $panel), array('title' => 'Diffusion'));
 }
 protected function executeChecks()
 {
     if (phutil_is_windows()) {
         $bin_name = 'where';
     } else {
         $bin_name = 'which';
     }
     if (!Filesystem::binaryExists($bin_name)) {
         $message = pht("Without '%s', Phabricator can not test for the availability " . "of other binaries.", $bin_name);
         $this->raiseWarning($bin_name, $message);
         // We need to return here if we can't find the 'which' / 'where' binary
         // because the other tests won't be valid.
         return;
     }
     if (!Filesystem::binaryExists('diff')) {
         $message = pht("Without '%s', Phabricator will not be able to generate or render " . "diffs in multiple applications.", 'diff');
         $this->raiseWarning('diff', $message);
     } else {
         $tmp_a = new TempFile();
         $tmp_b = new TempFile();
         $tmp_c = new TempFile();
         Filesystem::writeFile($tmp_a, 'A');
         Filesystem::writeFile($tmp_b, 'A');
         Filesystem::writeFile($tmp_c, 'B');
         list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_b);
         if ($err) {
             $this->newIssue('bin.diff.same')->setName(pht("Unexpected '%s' Behavior", 'diff'))->setMessage(pht("The '%s' binary on this system has unexpected behavior: " . "it was expected to exit without an error code when passed " . "identical files, but exited with code %d.", 'diff', $err));
         }
         list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_c);
         if (!$err) {
             $this->newIssue('bin.diff.diff')->setName(pht("Unexpected 'diff' Behavior"))->setMessage(pht("The '%s' binary on this system has unexpected behavior: " . "it was expected to exit with a nonzero error code when passed " . "differing files, but did not.", 'diff'));
         }
     }
     $table = new PhabricatorRepository();
     $vcses = queryfx_all($table->establishConnection('r'), 'SELECT DISTINCT versionControlSystem FROM %T', $table->getTableName());
     foreach ($vcses as $vcs) {
         switch ($vcs['versionControlSystem']) {
             case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                 $binary = 'git';
                 break;
             case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                 $binary = 'svn';
                 break;
             case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                 $binary = 'hg';
                 break;
             default:
                 $binary = null;
                 break;
         }
         if (!$binary) {
             continue;
         }
         if (!Filesystem::binaryExists($binary)) {
             $message = pht('You have at least one repository configured which uses this ' . 'version control system. It will not work without the VCS binary.');
             $this->raiseWarning($binary, $message);
             continue;
         }
         $version = null;
         switch ($vcs['versionControlSystem']) {
             case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                 $minimum_version = null;
                 $bad_versions = array();
                 list($err, $stdout, $stderr) = exec_manual('git --version');
                 $version = trim(substr($stdout, strlen('git version ')));
                 break;
             case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                 $minimum_version = '1.5';
                 $bad_versions = array('1.7.1' => pht('This version of Subversion has a bug where `%s` does not work ' . 'for files added in rN (Subversion issue #2873), fixed in 1.7.2.', 'svn diff -c N'));
                 list($err, $stdout, $stderr) = exec_manual('svn --version --quiet');
                 $version = trim($stdout);
                 break;
             case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                 $minimum_version = '1.9';
                 $bad_versions = array('2.1' => pht('This version of Mercurial returns a bad exit code ' . 'after a successful pull.'), '2.2' => pht('This version of Mercurial has a significant memory leak, fixed ' . 'in 2.2.1. Pushing fails with this version as well; see %s.', 'T3046#54922'));
                 $version = PhabricatorRepositoryVersion::getMercurialVersion();
                 break;
         }
         if ($version === null) {
             $this->raiseUnknownVersionWarning($binary);
         } else {
             if ($minimum_version && version_compare($version, $minimum_version, '<')) {
                 $this->raiseMinimumVersionWarning($binary, $minimum_version, $version);
             }
             foreach ($bad_versions as $bad_version => $details) {
                 if ($bad_version === $version) {
                     $this->raiseBadVersionWarning($binary, $bad_version);
                 }
             }
         }
     }
 }
<?php

$table_w = new PhabricatorRepository();
$conn_w = $table_w->establishConnection('w');
// Repository and Project share a database.
$conn_r = $table_w->establishConnection('r');
$projects_table = 'repository_arcanistproject';
$raw_projects_data = queryfx_all($conn_r, 'SELECT * FROM %T', $projects_table);
$raw_projects_data = ipull($raw_projects_data, null, 'id');
$repository_ids = ipull($raw_projects_data, 'repositoryID');
if (!$repository_ids) {
    return;
}
$repositories = id(new PhabricatorRepositoryQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withIDs($repository_ids)->execute();
$projects_to_repo_ids_map = ipull($raw_projects_data, 'repositoryID', 'phid');
$projects_to_repos_map = array();
foreach ($projects_to_repo_ids_map as $projectPHID => $repositoryID) {
    $repo = idx($repositories, $repositoryID);
    if ($repo) {
        $projects_to_repos_map[$projectPHID] = $repo->getPHID();
    }
}
foreach ($raw_projects_data as $project_row) {
    $repositoryID = idx($project_row, 'repositoryID');
    $repo = idx($repositories, $repositoryID);
    if (!$repo) {
        continue;
    }
    echo pht("Migrating symbols configuration for '%s' project...\n", idx($project_row, 'name', '???'));
    $symbol_index_projects = $project_row['symbolIndexProjects'];
    $symbol_index_projects = nonempty($symbol_index_projects, '[]');
 private function recordCommit(PhabricatorRepository $repository, $commit_identifier, $epoch, $close_immediately, array $parents)
 {
     $commit = new PhabricatorRepositoryCommit();
     $commit->setRepositoryID($repository->getID());
     $commit->setCommitIdentifier($commit_identifier);
     $commit->setEpoch($epoch);
     if ($close_immediately) {
         $commit->setImportStatus(PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE);
     }
     $data = new PhabricatorRepositoryCommitData();
     $conn_w = $repository->establishConnection('w');
     try {
         // If this commit has parents, look up their IDs. The parent commits
         // should always exist already.
         $parent_ids = array();
         if ($parents) {
             $parent_rows = queryfx_all($conn_w, 'SELECT id, commitIdentifier FROM %T
         WHERE commitIdentifier IN (%Ls) AND repositoryID = %d', $commit->getTableName(), $parents, $repository->getID());
             $parent_map = ipull($parent_rows, 'id', 'commitIdentifier');
             foreach ($parents as $parent) {
                 if (empty($parent_map[$parent])) {
                     throw new Exception(pht('Unable to identify parent "%s"!', $parent));
                 }
                 $parent_ids[] = $parent_map[$parent];
             }
         } else {
             // Write an explicit 0 so we can distinguish between "really no
             // parents" and "data not available".
             if (!$repository->isSVN()) {
                 $parent_ids = array(0);
             }
         }
         $commit->openTransaction();
         $commit->save();
         $data->setCommitID($commit->getID());
         $data->save();
         foreach ($parent_ids as $parent_id) {
             queryfx($conn_w, 'INSERT IGNORE INTO %T (childCommitID, parentCommitID)
           VALUES (%d, %d)', PhabricatorRepository::TABLE_PARENTS, $commit->getID(), $parent_id);
         }
         $commit->saveTransaction();
         $this->insertTask($repository, $commit);
         queryfx($conn_w, 'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
       VALUES (%d, 1, %d, %d)
       ON DUPLICATE KEY UPDATE
         size = size + 1,
         lastCommitID =
           IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID),
         epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)', PhabricatorRepository::TABLE_SUMMARY, $repository->getID(), $commit->getID(), $epoch);
         if ($this->repairMode) {
             // Normally, the query should throw a duplicate key exception. If we
             // reach this in repair mode, we've actually performed a repair.
             $this->log(pht('Repaired commit "%s".', $commit_identifier));
         }
         PhutilEventEngine::dispatchEvent(new PhabricatorEvent(PhabricatorEventType::TYPE_DIFFUSION_DIDDISCOVERCOMMIT, array('repository' => $repository, 'commit' => $commit)));
     } catch (AphrontDuplicateKeyQueryException $ex) {
         $commit->killTransaction();
         // Ignore. This can happen because we discover the same new commit
         // more than once when looking at history, or because of races or
         // data inconsistency or cosmic radiation; in any case, we're still
         // in a good state if we ignore the failure.
     }
 }
 private function writeBrowse(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit, array $effects, array $path_map)
 {
     $conn_w = $repository->establishConnection('w');
     $sql = array();
     foreach ($effects as $effect) {
         $type = $effect['changeType'];
         if (!$effect['rawDirect']) {
             if ($type == DifferentialChangeType::TYPE_COPY_AWAY) {
                 // Don't write COPY_AWAY to the filesystem table if it isn't a direct
                 // event.
                 continue;
             }
             if ($type == DifferentialChangeType::TYPE_CHILD) {
                 // Don't write CHILD to the filesystem table. Although doing these
                 // writes has the nice property of letting you see when a directory's
                 // contents were last changed, it explodes the table tremendously
                 // and makes Diffusion far slower.
                 continue;
             }
         }
         if ($effect['rawPath'] == '/') {
             // Don't write any events on '/' to the filesystem table; in
             // particular, it doesn't have a meaningful parentID.
             continue;
         }
         $existed = !DifferentialChangeType::isDeleteChangeType($type);
         $sql[] = qsprintf($conn_w, '(%d, %d, %d, %d, %d, %d)', $repository->getID(), $path_map[$this->getParentPath($effect['rawPath'])], $commit->getCommitIdentifier(), $path_map[$effect['rawPath']], $existed ? 1 : 0, $effect['fileType']);
     }
     queryfx($conn_w, 'DELETE FROM %T WHERE repositoryID = %d AND svnCommit = %d', PhabricatorRepository::TABLE_FILESYSTEM, $repository->getID(), $commit->getCommitIdentifier());
     foreach (array_chunk($sql, 512) as $sql_chunk) {
         queryfx($conn_w, 'INSERT INTO %T
       (repositoryID, parentID, svnCommit, pathID, existed, fileType)
       VALUES %Q', PhabricatorRepository::TABLE_FILESYSTEM, implode(', ', $sql_chunk));
     }
 }
 protected function loadPage()
 {
     $table = new PhabricatorRepository();
     $conn_r = $table->establishConnection('r');
     $data = queryfx_all($conn_r, 'SELECT * FROM %T r %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r));
     $repositories = $table->loadAllFromArray($data);
     if ($this->needCommitCounts) {
         $sizes = ipull($data, 'size', 'id');
         foreach ($repositories as $id => $repository) {
             $repository->attachCommitCount(nonempty($sizes[$id], 0));
         }
     }
     if ($this->needMostRecentCommits) {
         $commit_ids = ipull($data, 'lastCommitID', 'id');
         $commit_ids = array_filter($commit_ids);
         if ($commit_ids) {
             $commits = id(new DiffusionCommitQuery())->setViewer($this->getViewer())->withIDs($commit_ids)->execute();
         } else {
             $commits = array();
         }
         foreach ($repositories as $id => $repository) {
             $commit = null;
             if (idx($commit_ids, $id)) {
                 $commit = idx($commits, $commit_ids[$id]);
             }
             $repository->attachMostRecentCommit($commit);
         }
     }
     return $repositories;
 }
 protected function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     $full_name = 'r' . $repository->getCallsign() . $commit->getCommitIdentifier();
     echo "Parsing {$full_name}...\n";
     if ($this->isBadCommit($full_name)) {
         echo "This commit is marked bad!\n";
         return;
     }
     $local_path = $repository->getDetail('local-path');
     list($raw) = execx('(cd %s && git log -n1 -M -C -B --find-copies-harder --raw -t ' . '--abbrev=40 --pretty=format: %s)', $local_path, $commit->getCommitIdentifier());
     $changes = array();
     $move_away = array();
     $copy_away = array();
     $lines = explode("\n", $raw);
     foreach ($lines as $line) {
         if (!strlen(trim($line))) {
             continue;
         }
         list($old_mode, $new_mode, $old_hash, $new_hash, $more_stuff) = preg_split('/ +/', $line);
         // We may only have two pieces here.
         list($action, $src_path, $dst_path) = array_merge(explode("\t", $more_stuff), array(null));
         // Normalize the paths for consistency with the SVN workflow.
         $src_path = '/' . $src_path;
         if ($dst_path) {
             $dst_path = '/' . $dst_path;
         }
         $old_mode = intval($old_mode, 8);
         $new_mode = intval($new_mode, 8);
         $file_type = DifferentialChangeType::FILE_NORMAL;
         if ($new_mode & 040000) {
             $file_type = DifferentialChangeType::FILE_DIRECTORY;
         } else {
             if ($new_mode & 0120000) {
                 $file_type = DifferentialChangeType::FILE_SYMLINK;
             }
         }
         // TODO: We can detect binary changes as git does, through a combination
         // of running 'git check-attr' for stuff like 'binary', 'merge' or 'diff',
         // and by falling back to inspecting the first 8,000 characters of the
         // buffer for null bytes (this is seriously git's algorithm, see
         // buffer_is_binary() in xdiff-interface.c).
         $change_type = null;
         $change_path = $src_path;
         $change_target = null;
         $is_direct = true;
         switch ($action[0]) {
             case 'A':
                 $change_type = DifferentialChangeType::TYPE_ADD;
                 break;
             case 'D':
                 $change_type = DifferentialChangeType::TYPE_DELETE;
                 break;
             case 'C':
                 $change_type = DifferentialChangeType::TYPE_COPY_HERE;
                 $change_path = $dst_path;
                 $change_target = $src_path;
                 $copy_away[$change_target][] = $change_path;
                 break;
             case 'R':
                 $change_type = DifferentialChangeType::TYPE_MOVE_HERE;
                 $change_path = $dst_path;
                 $change_target = $src_path;
                 $move_away[$change_target][] = $change_path;
                 break;
             case 'T':
                 // Type of the file changed, fall through and treat it as a
                 // modification. Not 100% sure this is the right thing to do but it
                 // seems reasonable.
             // Type of the file changed, fall through and treat it as a
             // modification. Not 100% sure this is the right thing to do but it
             // seems reasonable.
             case 'M':
                 if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
                     $change_type = DifferentialChangeType::TYPE_CHILD;
                     $is_direct = false;
                 } else {
                     $change_type = DifferentialChangeType::TYPE_CHANGE;
                 }
                 break;
                 // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible
                 // in theory but shouldn't appear here.
             // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible
             // in theory but shouldn't appear here.
             default:
                 throw new Exception("Failed to parse line '{$line}'.");
         }
         $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $file_type, 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => $change_target, 'targetCommitID' => $change_target ? $commit->getID() : null);
     }
     // Add a change to '/' since git doesn't mention it.
     $changes['/'] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => '/', 'changeType' => DifferentialChangeType::TYPE_CHILD, 'fileType' => DifferentialChangeType::FILE_DIRECTORY, 'isDirect' => false, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null);
     foreach ($copy_away as $change_path => $destinations) {
         if (isset($move_away[$change_path])) {
             $change_type = DifferentialChangeType::TYPE_MULTICOPY;
             $is_direct = true;
             unset($move_away[$change_path]);
         } else {
             $change_type = DifferentialChangeType::TYPE_COPY_AWAY;
             $is_direct = false;
         }
         $reference = $changes[reset($destinations)];
         $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $reference['fileType'], 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null);
     }
     foreach ($move_away as $change_path => $destinations) {
         $reference = $changes[reset($destinations)];
         $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => DifferentialChangeType::TYPE_MOVE_AWAY, 'fileType' => $reference['fileType'], 'isDirect' => true, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null);
     }
     $paths = array();
     foreach ($changes as $change) {
         $paths[$change['path']] = true;
         if ($change['targetPath']) {
             $paths[$change['targetPath']] = true;
         }
     }
     $path_map = $this->lookupOrCreatePaths(array_keys($paths));
     foreach ($changes as $key => $change) {
         $changes[$key]['pathID'] = $path_map[$change['path']];
         if ($change['targetPath']) {
             $changes[$key]['targetPathID'] = $path_map[$change['targetPath']];
         } else {
             $changes[$key]['targetPathID'] = null;
         }
     }
     $conn_w = $repository->establishConnection('w');
     $changes_sql = array();
     foreach ($changes as $change) {
         $values = array((int) $change['repositoryID'], (int) $change['pathID'], (int) $change['commitID'], $change['targetPathID'] ? (int) $change['targetPathID'] : 'null', $change['targetCommitID'] ? (int) $change['targetCommitID'] : 'null', (int) $change['changeType'], (int) $change['fileType'], (int) $change['isDirect'], (int) $change['commitSequence']);
         $changes_sql[] = '(' . implode(', ', $values) . ')';
     }
     queryfx($conn_w, 'DELETE FROM %T WHERE commitID = %d', PhabricatorRepository::TABLE_PATHCHANGE, $commit->getID());
     foreach (array_chunk($changes_sql, 256) as $sql_chunk) {
         queryfx($conn_w, 'INSERT INTO %T
       (repositoryID, pathID, commitID, targetPathID, targetCommitID,
         changeType, fileType, isDirect, commitSequence)
       VALUES %Q', PhabricatorRepository::TABLE_PATHCHANGE, implode(', ', $sql_chunk));
     }
     $this->finishParse();
 }
<?php

$table = new PhabricatorRepository();
$conn_w = $table->establishConnection('w');
$default_path = PhabricatorEnv::getEnvConfig('repository.default-local-path');
$default_path = rtrim($default_path, '/');
foreach (new LiskMigrationIterator($table) as $repository) {
    $local_path = $repository->getLocalPath();
    if (strlen($local_path)) {
        // Repository already has a modern, unique local path.
        continue;
    }
    $local_path = $repository->getDetail('local-path');
    if (!strlen($local_path)) {
        // Repository does not have a local path using the older format.
        continue;
    }
    $random = Filesystem::readRandomCharacters(8);
    // Try the configured path first, then a default path, then a path with some
    // random noise.
    $paths = array($local_path, $default_path . '/' . $repository->getID() . '/', $default_path . '/' . $repository->getID() . '-' . $random . '/');
    foreach ($paths as $path) {
        // Set, then get the path to normalize it.
        $repository->setLocalPath($path);
        $path = $repository->getLocalPath();
        try {
            queryfx($conn_w, 'UPDATE %T SET localPath = %s WHERE id = %d', $table->getTableName(), $path, $repository->getID());
            echo tsprintf("%s\n", pht('Assigned repository "%s" to local path "%s".', $repository->getDisplayName(), $path));
            break;
        } catch (AphrontDuplicateKeyQueryException $ex) {
            // Ignore, try the next one.
 private function buildRepositoryStatus(PhabricatorRepository $repository)
 {
     $viewer = $this->getRequest()->getUser();
     $is_cluster = $repository->getAlmanacServicePHID();
     $view = new PHUIStatusListView();
     $messages = id(new PhabricatorRepositoryStatusMessage())->loadAllWhere('repositoryID = %d', $repository->getID());
     $messages = mpull($messages, null, 'getStatusType');
     if ($repository->isTracked()) {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Repository Active')));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey')->setTarget(pht('Repository Inactive'))->setNote(pht('Activate this repository to begin or resume import.')));
         return $view;
     }
     $binaries = array();
     $svnlook_check = false;
     switch ($repository->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
             $binaries[] = 'git';
             break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
             $binaries[] = 'svn';
             break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
             $binaries[] = 'hg';
             break;
     }
     if ($repository->isHosted()) {
         if ($repository->getServeOverHTTP() != PhabricatorRepository::SERVE_OFF) {
             switch ($repository->getVersionControlSystem()) {
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                     $binaries[] = 'git-http-backend';
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                     $binaries[] = 'svnserve';
                     $binaries[] = 'svnadmin';
                     $binaries[] = 'svnlook';
                     $svnlook_check = true;
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                     $binaries[] = 'hg';
                     break;
             }
         }
         if ($repository->getServeOverSSH() != PhabricatorRepository::SERVE_OFF) {
             switch ($repository->getVersionControlSystem()) {
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
                     $binaries[] = 'git-receive-pack';
                     $binaries[] = 'git-upload-pack';
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
                     $binaries[] = 'svnserve';
                     $binaries[] = 'svnadmin';
                     $binaries[] = 'svnlook';
                     $svnlook_check = true;
                     break;
                 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
                     $binaries[] = 'hg';
                     break;
             }
         }
     }
     $binaries = array_unique($binaries);
     if (!$is_cluster) {
         // We're only checking for binaries if we aren't running with a cluster
         // configuration. In theory, we could check for binaries on the
         // repository host machine, but we'd need to make this more complicated
         // to do that.
         foreach ($binaries as $binary) {
             $where = Filesystem::resolveBinary($binary);
             if (!$where) {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))->setNote(pht("Unable to find this binary in the webserver's PATH. You may " . "need to configure %s.", $this->getEnvConfigLink())));
             } else {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Found Binary %s', phutil_tag('tt', array(), $binary)))->setNote(phutil_tag('tt', array(), $where)));
             }
         }
         // This gets checked generically above. However, for svn commit hooks, we
         // need this to be in environment.append-paths because subversion strips
         // PATH.
         if ($svnlook_check) {
             $where = Filesystem::resolveBinary('svnlook');
             if ($where) {
                 $path = substr($where, 0, strlen($where) - strlen('svnlook'));
                 $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths');
                 $in_path = false;
                 foreach ($dirs as $dir) {
                     if (Filesystem::isDescendant($path, $dir)) {
                         $in_path = true;
                         break;
                     }
                 }
                 if (!$in_path) {
                     $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Missing Binary %s', phutil_tag('tt', array(), $binary)))->setNote(pht('Unable to find this binary in `%s`. ' . 'You need to configure %s and include %s.', 'environment.append-paths', $this->getEnvConfigLink(), $path)));
                 }
             }
         }
     }
     $doc_href = PhabricatorEnv::getDocLink('Managing Daemons with phd');
     $daemon_instructions = pht('Use %s to start daemons. See %s.', phutil_tag('tt', array(), 'bin/phd start'), phutil_tag('a', array('href' => $doc_href), pht('Managing Daemons with phd')));
     $pull_daemon = id(new PhabricatorDaemonLogQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon'))->setLimit(1)->execute();
     if ($pull_daemon) {
         // TODO: In a cluster environment, we need a daemon on this repository's
         // host, specifically, and we aren't checking for that right now. This
         // is a reasonable proxy for things being more-or-less correctly set up,
         // though.
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Pull Daemon Running')));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Pull Daemon Not Running'))->setNote($daemon_instructions));
     }
     $task_daemon = id(new PhabricatorDaemonLogQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))->setLimit(1)->execute();
     if ($task_daemon) {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Task Daemon Running')));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Task Daemon Not Running'))->setNote($daemon_instructions));
     }
     if ($is_cluster) {
         // Just omit this status check for now in cluster environments. We
         // could make a service call and pull it from the repository host
         // eventually.
     } else {
         if ($repository->usesLocalWorkingCopy()) {
             $local_parent = dirname($repository->getLocalPath());
             if (Filesystem::pathExists($local_parent)) {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Storage Directory OK'))->setNote(phutil_tag('tt', array(), $local_parent)));
             } else {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('No Storage Directory'))->setNote(pht('Storage directory %s does not exist, or is not readable by ' . 'the webserver. Create this directory or make it readable.', phutil_tag('tt', array(), $local_parent))));
                 return $view;
             }
             $local_path = $repository->getLocalPath();
             $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT);
             if ($message) {
                 switch ($message->getStatusCode()) {
                     case PhabricatorRepositoryStatusMessage::CODE_ERROR:
                         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Initialization Error'))->setNote($message->getParameter('message')));
                         return $view;
                     case PhabricatorRepositoryStatusMessage::CODE_OKAY:
                         if (Filesystem::pathExists($local_path)) {
                             $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Working Copy OK'))->setNote(phutil_tag('tt', array(), $local_path)));
                         } else {
                             $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Working Copy Error'))->setNote(pht('Working copy %s has been deleted, or is not ' . 'readable by the webserver. Make this directory ' . 'readable. If it has been deleted, the daemons should ' . 'restore it automatically.', phutil_tag('tt', array(), $local_path))));
                             return $view;
                         }
                         break;
                     case PhabricatorRepositoryStatusMessage::CODE_WORKING:
                         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')->setTarget(pht('Initializing Working Copy'))->setNote(pht('Daemons are initializing the working copy.')));
                         return $view;
                     default:
                         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Unknown Init Status'))->setNote($message->getStatusCode()));
                         return $view;
                 }
             } else {
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')->setTarget(pht('No Working Copy Yet'))->setNote(pht('Waiting for daemons to build a working copy.')));
                 return $view;
             }
         }
     }
     $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH);
     if ($message) {
         switch ($message->getStatusCode()) {
             case PhabricatorRepositoryStatusMessage::CODE_ERROR:
                 $message = $message->getParameter('message');
                 $suggestion = null;
                 if (preg_match('/Permission denied \\(publickey\\)./', $message)) {
                     $suggestion = pht('Public Key Error: This error usually indicates that the ' . 'keypair you have configured does not have permission to ' . 'access the repository.');
                 }
                 $message = phutil_escape_html_newlines($message);
                 if ($suggestion !== null) {
                     $message = array(phutil_tag('strong', array(), $suggestion), phutil_tag('br'), phutil_tag('br'), phutil_tag('em', array(), pht('Raw Error')), phutil_tag('br'), $message);
                 }
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_WARNING, 'red')->setTarget(pht('Update Error'))->setNote($message));
                 return $view;
             case PhabricatorRepositoryStatusMessage::CODE_OKAY:
                 $ago = PhabricatorTime::getNow() - $message->getEpoch();
                 $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Updates OK'))->setNote(pht('Last updated %s (%s ago).', phabricator_datetime($message->getEpoch(), $viewer), phutil_format_relative_time_detailed($ago))));
                 break;
         }
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange')->setTarget(pht('Waiting For Update'))->setNote(pht('Waiting for daemons to read updates.')));
     }
     if ($repository->isImporting()) {
         $progress = queryfx_all($repository->establishConnection('r'), 'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d
       GROUP BY importStatus', id(new PhabricatorRepositoryCommit())->getTableName(), $repository->getID());
         $done = 0;
         $total = 0;
         foreach ($progress as $row) {
             $total += $row['N'] * 4;
             $status = $row['importStatus'];
             if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) {
                 $done += $row['N'];
             }
             if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) {
                 $done += $row['N'];
             }
             if ($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS) {
                 $done += $row['N'];
             }
             if ($status & PhabricatorRepositoryCommit::IMPORTED_HERALD) {
                 $done += $row['N'];
             }
         }
         if ($total) {
             $percentage = 100 * ($done / $total);
         } else {
             $percentage = 0;
         }
         // Cap this at "99.99%", because it's confusing to users when the actual
         // fraction is "99.996%" and it rounds up to "100.00%".
         if ($percentage > 99.98999999999999) {
             $percentage = 99.98999999999999;
         }
         $percentage = sprintf('%.2f%%', $percentage);
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green')->setTarget(pht('Importing'))->setNote(pht('%s Complete', $percentage)));
     } else {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')->setTarget(pht('Fully Imported')));
     }
     if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) {
         $view->addItem(id(new PHUIStatusItemView())->setIcon(PHUIStatusItemView::ICON_UP, 'indigo')->setTarget(pht('Prioritized'))->setNote(pht('This repository will be updated soon!')));
     }
     return $view;
 }
 protected function parseCommit(PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit)
 {
     $full_name = 'r' . $repository->getCallsign() . $commit->getCommitIdentifier();
     echo "Parsing {$full_name}...\n";
     if ($this->isBadCommit($full_name)) {
         echo "This commit is marked bad!\n";
         return;
     }
     // Check if the commit has parents. We're testing to see whether it is the
     // first commit in history (in which case we must use "git log") or some
     // other commit (in which case we can use "git diff"). We'd rather use
     // "git diff" because it has the right behavior for merge commits, but
     // it requires the commit to have a parent that we can diff against. The
     // first commit doesn't, so "commit^" is not a valid ref.
     list($parents) = $repository->execxLocalCommand('log -n1 --format=%s %s', '%P', $commit->getCommitIdentifier());
     $use_log = !strlen(trim($parents));
     if ($use_log) {
         // This is the first commit so we need to use "log". We know it's not a
         // merge commit because it couldn't be merging anything, so this is safe.
         // NOTE: "--pretty=format: " is to disable diff output, we only want the
         // part we get from "--raw".
         list($raw) = $repository->execxLocalCommand('log -n1 -M -C -B --find-copies-harder --raw -t ' . '--pretty=format: --abbrev=40 %s', $commit->getCommitIdentifier());
     } else {
         // Otherwise, we can use "diff", which will give us output for merges.
         // We diff against the first parent, as this is generally the expectation
         // and results in sensible behavior.
         list($raw) = $repository->execxLocalCommand('diff -n1 -M -C -B --find-copies-harder --raw -t ' . '--abbrev=40 %s^1 %s', $commit->getCommitIdentifier(), $commit->getCommitIdentifier());
     }
     $changes = array();
     $move_away = array();
     $copy_away = array();
     $lines = explode("\n", $raw);
     foreach ($lines as $line) {
         if (!strlen(trim($line))) {
             continue;
         }
         list($old_mode, $new_mode, $old_hash, $new_hash, $more_stuff) = preg_split('/ +/', $line, 5);
         // We may only have two pieces here.
         list($action, $src_path, $dst_path) = array_merge(explode("\t", $more_stuff), array(null));
         // Normalize the paths for consistency with the SVN workflow.
         $src_path = '/' . $src_path;
         if ($dst_path) {
             $dst_path = '/' . $dst_path;
         }
         $old_mode = intval($old_mode, 8);
         $new_mode = intval($new_mode, 8);
         switch ($new_mode & 0160000) {
             case 0160000:
                 $file_type = DifferentialChangeType::FILE_SUBMODULE;
                 break;
             case 0120000:
                 $file_type = DifferentialChangeType::FILE_SYMLINK;
                 break;
             case 040000:
                 $file_type = DifferentialChangeType::FILE_DIRECTORY;
                 break;
             default:
                 $file_type = DifferentialChangeType::FILE_NORMAL;
                 break;
         }
         // TODO: We can detect binary changes as git does, through a combination
         // of running 'git check-attr' for stuff like 'binary', 'merge' or 'diff',
         // and by falling back to inspecting the first 8,000 characters of the
         // buffer for null bytes (this is seriously git's algorithm, see
         // buffer_is_binary() in xdiff-interface.c).
         $change_type = null;
         $change_path = $src_path;
         $change_target = null;
         $is_direct = true;
         switch ($action[0]) {
             case 'A':
                 $change_type = DifferentialChangeType::TYPE_ADD;
                 break;
             case 'D':
                 $change_type = DifferentialChangeType::TYPE_DELETE;
                 break;
             case 'C':
                 $change_type = DifferentialChangeType::TYPE_COPY_HERE;
                 $change_path = $dst_path;
                 $change_target = $src_path;
                 $copy_away[$change_target][] = $change_path;
                 break;
             case 'R':
                 $change_type = DifferentialChangeType::TYPE_MOVE_HERE;
                 $change_path = $dst_path;
                 $change_target = $src_path;
                 $move_away[$change_target][] = $change_path;
                 break;
             case 'T':
                 // Type of the file changed, fall through and treat it as a
                 // modification. Not 100% sure this is the right thing to do but it
                 // seems reasonable.
             // Type of the file changed, fall through and treat it as a
             // modification. Not 100% sure this is the right thing to do but it
             // seems reasonable.
             case 'M':
                 if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
                     $change_type = DifferentialChangeType::TYPE_CHILD;
                     $is_direct = false;
                 } else {
                     $change_type = DifferentialChangeType::TYPE_CHANGE;
                 }
                 break;
                 // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible
                 // in theory but shouldn't appear here.
             // NOTE: "U" (unmerged) and "X" (unknown) statuses are also possible
             // in theory but shouldn't appear here.
             default:
                 throw new Exception("Failed to parse line '{$line}'.");
         }
         $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $file_type, 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => $change_target, 'targetCommitID' => $change_target ? $commit->getID() : null);
     }
     // Add a change to '/' since git doesn't mention it.
     $changes['/'] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => '/', 'changeType' => DifferentialChangeType::TYPE_CHILD, 'fileType' => DifferentialChangeType::FILE_DIRECTORY, 'isDirect' => false, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null);
     foreach ($copy_away as $change_path => $destinations) {
         if (isset($move_away[$change_path])) {
             $change_type = DifferentialChangeType::TYPE_MULTICOPY;
             $is_direct = true;
             unset($move_away[$change_path]);
         } else {
             $change_type = DifferentialChangeType::TYPE_COPY_AWAY;
             $is_direct = false;
         }
         $reference = $changes[reset($destinations)];
         $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => $change_type, 'fileType' => $reference['fileType'], 'isDirect' => $is_direct, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null);
     }
     foreach ($move_away as $change_path => $destinations) {
         $reference = $changes[reset($destinations)];
         $changes[$change_path] = array('repositoryID' => $repository->getID(), 'commitID' => $commit->getID(), 'path' => $change_path, 'changeType' => DifferentialChangeType::TYPE_MOVE_AWAY, 'fileType' => $reference['fileType'], 'isDirect' => true, 'commitSequence' => $commit->getEpoch(), 'targetPath' => null, 'targetCommitID' => null);
     }
     $paths = array();
     foreach ($changes as $change) {
         $paths[$change['path']] = true;
         if ($change['targetPath']) {
             $paths[$change['targetPath']] = true;
         }
     }
     $path_map = $this->lookupOrCreatePaths(array_keys($paths));
     foreach ($changes as $key => $change) {
         $changes[$key]['pathID'] = $path_map[$change['path']];
         if ($change['targetPath']) {
             $changes[$key]['targetPathID'] = $path_map[$change['targetPath']];
         } else {
             $changes[$key]['targetPathID'] = null;
         }
     }
     $conn_w = $repository->establishConnection('w');
     $changes_sql = array();
     foreach ($changes as $change) {
         $values = array((int) $change['repositoryID'], (int) $change['pathID'], (int) $change['commitID'], $change['targetPathID'] ? (int) $change['targetPathID'] : 'null', $change['targetCommitID'] ? (int) $change['targetCommitID'] : 'null', (int) $change['changeType'], (int) $change['fileType'], (int) $change['isDirect'], (int) $change['commitSequence']);
         $changes_sql[] = '(' . implode(', ', $values) . ')';
     }
     queryfx($conn_w, 'DELETE FROM %T WHERE commitID = %d', PhabricatorRepository::TABLE_PATHCHANGE, $commit->getID());
     foreach (array_chunk($changes_sql, 256) as $sql_chunk) {
         queryfx($conn_w, 'INSERT INTO %T
       (repositoryID, pathID, commitID, targetPathID, targetCommitID,
         changeType, fileType, isDirect, commitSequence)
       VALUES %Q', PhabricatorRepository::TABLE_PATHCHANGE, implode(', ', $sql_chunk));
     }
     $this->finishParse();
 }
 private function rebuildSummaryTable(PhabricatorRepository $repository)
 {
     $conn_w = $repository->establishConnection('w');
     $data = queryfx_one($conn_w, 'SELECT COUNT(*) N, MAX(id) id, MAX(epoch) epoch
     FROM %T WHERE repositoryID = %d AND (importStatus & %d) != %d', id(new PhabricatorRepositoryCommit())->getTableName(), $repository->getID(), PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
     queryfx($conn_w, 'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
     VALUES (%d, %d, %d, %d)
     ON DUPLICATE KEY UPDATE
       size = VALUES(size),
       lastCommitID = VALUES(lastCommitID),
       epoch = VALUES(epoch)', PhabricatorRepository::TABLE_SUMMARY, $repository->getID(), $data['N'], $data['id'], $data['epoch']);
 }