diff --git a/src/applications/config/controller/PhabricatorConfigVersionController.php b/src/applications/config/controller/PhabricatorConfigVersionController.php index 15877ddbd7..8f43192b3b 100644 --- a/src/applications/config/controller/PhabricatorConfigVersionController.php +++ b/src/applications/config/controller/PhabricatorConfigVersionController.php @@ -1,93 +1,220 @@ getViewer(); $title = pht('Version Information'); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb($title) ->setBorder(true); $versions = $this->renderModuleStatus($viewer); $nav = $this->buildSideNavView(); $nav->selectFilter('version/'); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setProfileHeader(true); $content = id(new PhabricatorConfigPageView()) ->setHeader($header) ->setContent($versions); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setNavigation($nav) ->appendChild($content) ->addClass('white-background'); } public function renderModuleStatus($viewer) { $versions = $this->loadVersions($viewer); $version_property_list = id(new PHUIPropertyListView()); - foreach ($versions as $name => $version) { - $version_property_list->addProperty($name, $version); + foreach ($versions as $name => $info) { + $version = $info['version']; + + if ($info['branchpoint']) { + $display = pht( + '%s (branched from %s on %s)', + $version, + $info['branchpoint'], + $info['upstream']); + } else { + $display = $version; + } + + $version_property_list->addProperty($name, $display); } $phabricator_root = dirname(phutil_get_library_root('phabricator')); $version_path = $phabricator_root.'/conf/local/VERSION'; if (Filesystem::pathExists($version_path)) { $version_from_file = Filesystem::readFile($version_path); $version_property_list->addProperty( pht('Local Version'), $version_from_file); } return $version_property_list; } private function loadVersions(PhabricatorUser $viewer) { $specs = array( 'phabricator', 'arcanist', 'phutil', ); $all_libraries = PhutilBootloader::getInstance()->getAllLibraries(); // This puts the core libraries at the top: $other_libraries = array_diff($all_libraries, $specs); $specs = array_merge($specs, $other_libraries); - $futures = array(); + $log_futures = array(); + $remote_futures = array(); + foreach ($specs as $lib) { $root = dirname(phutil_get_library_root($lib)); - $futures[$lib] = - id(new ExecFuture('git log --format=%s -n 1 --', '%H %ct')) + + $log_command = csprintf( + 'git log --format=%s -n 1 --', + '%H %ct'); + + $remote_command = csprintf( + 'git remote -v'); + + $log_futures[$lib] = id(new ExecFuture('%C', $log_command)) + ->setCWD($root); + + $remote_futures[$lib] = id(new ExecFuture('%C', $remote_command)) ->setCWD($root); } + $all_futures = array_merge($log_futures, $remote_futures); + + id(new FutureIterator($all_futures)) + ->resolveAll(); + + // A repository may have a bunch of remotes, but we're only going to look + // for remotes we host to try to figure out where this repository branched. + $upstream_pattern = '(github\.com/phacility/|secure\.phabricator\.com/)'; + + $upstream_futures = array(); + $lib_upstreams = array(); + foreach ($specs as $lib) { + $remote_future = $remote_futures[$lib]; + + list($err, $stdout) = $remote_future->resolve(); + if ($err) { + // If this fails for whatever reason, just move on. + continue; + } + + // These look like this, with a tab separating the first two fields: + // remote-name http://remote.uri/ (push) + + $upstreams = array(); + + $remotes = phutil_split_lines($stdout, false); + foreach ($remotes as $remote) { + $remote_pattern = '/^([^\t]+)\t([^ ]+) \(([^)]+)\)\z/'; + $matches = null; + if (!preg_match($remote_pattern, $remote, $matches)) { + continue; + } + + // Remote URIs are either "push" or "fetch": we only care about "fetch" + // URIs. + $type = $matches[3]; + if ($type != 'fetch') { + continue; + } + + $uri = $matches[2]; + $is_upstream = preg_match($upstream_pattern, $uri); + if (!$is_upstream) { + continue; + } + + $name = $matches[1]; + $upstreams[$name] = $name; + } + + // If we have several suitable upstreams, try to pick the one named + // "origin", if it exists. Otherwise, just pick the first one. + if (isset($upstreams['origin'])) { + $upstream = $upstreams['origin']; + } else if ($upstreams) { + $upstream = head($upstreams); + } else { + $upstream = null; + } + + if (!$upstream) { + continue; + } + + $lib_upstreams[$lib] = $upstream; + + $merge_base_command = csprintf( + 'git merge-base HEAD %s/master --', + $upstream); + + $root = dirname(phutil_get_library_root($lib)); + + $upstream_futures[$lib] = id(new ExecFuture('%C', $merge_base_command)) + ->setCWD($root); + } + + if ($upstream_futures) { + id(new FutureIterator($upstream_futures)) + ->resolveAll(); + } + $results = array(); - foreach ($futures as $key => $future) { + foreach ($log_futures as $lib => $future) { list($err, $stdout) = $future->resolve(); if (!$err) { list($hash, $epoch) = explode(' ', $stdout); $version = pht('%s (%s)', $hash, phabricator_date($epoch, $viewer)); } else { $version = pht('Unknown'); } - $results[$key] = $version; + + $result = array( + 'version' => $version, + 'upstream' => null, + 'branchpoint' => null, + ); + + $upstream_future = idx($upstream_futures, $lib); + if ($upstream_future) { + list($err, $stdout) = $upstream_future->resolve(); + if (!$err) { + $branchpoint = trim($stdout); + if (strlen($branchpoint)) { + // We only list a branchpoint if it differs from HEAD. + if ($branchpoint != $hash) { + $result['upstream'] = $lib_upstreams[$lib]; + $result['branchpoint'] = trim($stdout); + } + } + } + } + + $results[$lib] = $result; } return $results; } } diff --git a/src/docs/contributor/version.diviner b/src/docs/contributor/version.diviner index 81b1c845ef..a13c12d86f 100644 --- a/src/docs/contributor/version.diviner +++ b/src/docs/contributor/version.diviner @@ -1,57 +1,80 @@ @title Providing Version Information @group detail How to provide version information with reports made to the upstream. Overview ======== When you submit a bug report, we require that you include version information. Despite our insistence that users update before reporting issues, many reports we receive describe issues which have already been resolved. Including version information in your report allows us to quickly determine that you are out of date and that updating will fix your issue. That said, your report must also include reproduction steps, and you should be unable to generate valid reproduction steps for an issue which has already been resolved because valid reproduction steps must also reproduce against a clean, up-to-date install. See @{article:Providing Reproduction Steps} for details. Phabricator Version =================== To get Phabricator version information: - Go to the {nav Config} application. You can type "Config" into the global search box, or navigate to `https://your.install.com/config/`. You must be an administrator to access this application. - Click {nav Versions} in the left menu. - Copy and paste all of the information on the page into your report. Arcanist Version ================ To get Arcanist version information: - Run `arc version`. - Copy and paste all of the output into your report. Other Versions ============== In general, we use `git` commit hashes as version identifiers, so you can identify the version of something by running `git show` and copy/pasting the hash from the output. This may be useful if you're encountering an issue which prevents you from reaching the version reporting screen. +Running a Fork? +=============== + +If you've forked Phabricator and have local commits, please make sure you are +reporting upstream commit hashes, not local commit hashes. The UI will attempt +to figure out where you branched from, but it may not be able to in all cases. + +If you report local commit hashes instead of upstream commit hashes we can not +go look up the commit hashes to figure out which changes they correspond to, so +we can not use that information to determine out how old your install is or +which patches you are missing. + +In most cases, you can find the upstream commit you've branched from like this: + +``` +$ git merge-base HEAD origin/master +```` + +Note that if you report a bug and have local commits, we will almost always ask +you to reproduce the issue against a clean copy of Phabricator before we +continue. You can get help faster by doing this //before// reporting an issue. + + Next Steps ========== Continue by: - returning to @{article:Contributing Bug Reports}.