Permalink
Browse files

Require access to all the repository in case we do a forced push

A forced push can happen when you delete a tag or rewrite commits. We allow
this, but only if you have access to the root of the repository.
  • Loading branch information...
1 parent f3d1157 commit 373c2053d9ac29ab7d2750b25e939a5b4a0dfc17 @dsp committed Mar 2, 2012
Showing with 99 additions and 5 deletions.
  1. +7 −5 hooks/pre-receive
  2. +83 −0 lib/Git/PushInformation.php
  3. +9 −0 lib/Git/ReceiveHook.php
View
@@ -107,16 +107,18 @@ if ($hook->isKarmaIgnored()) {
accept("No karma check necessary. Thank you for your contribution.\n");
}
-$requested_paths = $hook->getReceivedPaths();
+$rep_name = $hook->getRepositoryName();
+$pi = new \Git\PushInformation($hook);
+$req_paths = ($pi->isForced()) ? [''] : $req_paths;
-if (empty($requested_paths)) {
+if (empty($req_paths)) {
deny("We cannot figure out what you comitted!");
}
-$prefix = sprintf('%s/', $hook->getRepositoryName());
+$prefix = sprintf('%s/', $repo_name);
$avail_lines = $hook->getKarmaFile();
-$requested_paths = array_map(function ($x) use ($prefix) { return $prefix . $x;}, $requested_paths);
-$unavail_paths = get_unavail_paths($user, $requested_paths, $avail_lines);
+$req_paths = array_map(function ($x) use ($prefix) { return $prefix . $x;}, $req_paths);
+$unavail_paths = get_unavail_paths($user, $req_paths, $avail_lines);
if (!empty($unavail_paths)) {
deny(sprintf(
@@ -0,0 +1,83 @@
+<?php
+namespace Git;
+
+class PushInformation
+{
+ const GIT_EXECUTABLE = 'git';
+
+ private $karmaFile;
+ private $repositoryBasePath;
+
+ private $hook = null;
+ private $repourl = null;
+
+ public function __construct(ReceiveHook $hook)
+ {
+ $this->repourl = \Git::getRepositoryPath();
+ }
+
+ /**
+ * Returns the common ancestor revision for two given revisions
+ *
+ * Returns false if no sha1 was returned. Throws an exception if calling
+ * git fails.
+ *
+ * @return boolean
+ */
+ protected function mergeBase($oldrev, $newrev)
+ {
+ $baserev = exec(sprintf('%s --git-dir=%s merge-base %s %s',
+ self::GIT_EXECUTABLE,
+ $this->repourl,
+ escapeshellarg($oldrev),
+ escapeshellarg($newrev)), $retval);
+
+ $baserev = trim($baserev);
+
+ if (0 !== $retval) {
+ throw new \Exception('Failed to call git');
+ }
+
+ if (40 != strlen($baserev)) {
+ return false;
+ }
+
+ return $baserev;
+ }
+
+ /**
+ * Returns true if merging $newrev would be fast forward
+ *
+ * @return boolean
+ */
+ public function isFastForward()
+ {
+ $result = $this->hook->mapInput(
+ function ($oldrev, $newrev) {
+ if ($oldrev == \Git::NULLREV) {
+ return true;
+ }
+ return $oldrev == $this->mergeBase($oldrev, $newrev);
+ });
+
+ return array_reduce($result, function($a, $b) { return $a && $b; }, true);
+ }
+
+ /**
+ * Returns true if updating the refs would fail if push is not forced.
+ *
+ * @return boolean
+ */
+ public function isForced()
+ {
+ $result = $this->hook->mapInput(
+ function($oldrev, $newrev) {
+ if ($oldrev == \Git::NULLREV) {
+ return false;
+ }
+ return $newrev == $this->mergeBase($oldrev, $newrev);
+ });
+
+ return array_reduce($result, function($a, $b) { return $a || $b; }, false);
+ }
+}
@@ -42,6 +42,15 @@ public function getRepositoryName()
return '';
}
+ public function mapInput(callable $fn) {
+ $result = [];
+ foreach($this->hookInput() as $input) {
+ $result[] = $fn($input['old'], $input['new']);
+ }
+
+ return $result;
+ }
+
/**
* Parses the input from git.
*

0 comments on commit 373c205

Please sign in to comment.