diff --git a/src/applications/config/controller/PhabricatorConfigVersionController.php b/src/applications/config/controller/PhabricatorConfigVersionController.php --- a/src/applications/config/controller/PhabricatorConfigVersionController.php +++ b/src/applications/config/controller/PhabricatorConfigVersionController.php @@ -39,8 +39,20 @@ $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')); @@ -67,16 +79,109 @@ $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); @@ -84,7 +189,29 @@ } 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 --- a/src/docs/contributor/version.diviner +++ b/src/docs/contributor/version.diviner @@ -49,6 +49,29 @@ 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 ==========