Skip to content

Instantly share code, notes, and snippets.

@sndsgd
Created April 21, 2016 00:21
Show Gist options
  • Save sndsgd/c59ef1d3943a5c86747e783a1fb6a502 to your computer and use it in GitHub Desktop.
Save sndsgd/c59ef1d3943a5c86747e783a1fb6a502 to your computer and use it in GitHub Desktop.
A directory hashing script
#!/usr/bin/env php
<?php
list($dir, $isDump) = DirHasher::getArgs($argv);
$hasher = new DirHasher($dir);
if ($isDump) {
echo $hasher->getHashes()."\n";
} else {
echo $hasher->getHash()."\n";
}
class DirHasher
{
/**
* Attempt to parse arguments from an array
* @param array<string> $args
* @return array
* [0] => The directory
* [1] => Whether to dump all the hashes
*/
public static function getArgs(array $args)
{
$isDump = false;
foreach (['--dump', '-dump', '-d'] as $test) {
if (in_array($test, $args)) {
$isDump = true;
$key = array_search($test, $args);
unset($args[$key]);
break;
}
}
$arglen = count($args);
if ($arglen > 2) {
fwrite(STDERR, "bad input\n");
exit(1);
} elseif ($arglen === 1) {
$dir = getcwd();
} else {
$dir = array_pop($args);
}
return [$dir, $isDump];
}
/**
* Absolute path to the directory to hash
*
* @var string
*/
protected $dir;
/**
* A map of relative path => file hash
*
* @var array<string,string>
*/
protected $hashes = [];
/**
* @param string $dir Absolute path to the directory to hash
*/
public function __construct($dir)
{
if (!file_exists($dir)) {
fwrite(STDERR, "'$dir' does not exist\n");
exit(1);
} elseif (!is_dir($dir)) {
fwrite(STDERR, "'$dir' is not a directory\n");
exit(1);
} elseif (!is_readable($dir)) {
fwrite(STDERR, "'$dir' is not readable\n");
exit(1);
}
$this->dir = $dir;
}
/**
* Get a single hash for all files in the directory
*
* @return string
*/
public function getHash()
{
if (empty($this->hashes)) {
$this->generateHashes();
}
return sha1(json_encode($this->hashes));
}
/**
* Get all hashes encoded as pretty printed JSON
*
* @return string
*/
public function getHashes()
{
if (empty($this->hashes)) {
$this->generateHashes();
}
return json_encode($this->hashes, 448);
}
private function getIterator()
{
$options = \RecursiveDirectoryIterator::SKIP_DOTS;
$iterator = new \RecursiveDirectoryIterator($this->dir, $options);
$options = \RecursiveIteratorIterator::SELF_FIRST;
return new \RecursiveIteratorIterator($iterator, $options);
}
private function generateHashes()
{
$dirLength = strlen($this->dir);
foreach ($this->getIterator() as $file) {
if (!$file->isFile()) {
continue;
}
$realpath = $file->getRealPath();
$path = $file->getPath() . DIRECTORY_SEPARATOR . $file->getFilename();
// skip aliases
if ($realpath !== $path) {
continue;
}
// map hashes by a lowercase version of the path
// this should prevent issues caused by case sensitive filesystems
$path = substr($realpath, $dirLength);
$lowerPath = strtolower($path);
if (isset($this->hashes[$lowerPath])) {
$error = "duplicate filename found:\n $path\n $lowerPath\n";
fwrite(STDERR, $error);
exit(1);
}
$this->hashes[$lowerPath] = sha1_file($realpath);
}
ksort($this->hashes);
return count($this->hashes);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment