| */␊ |
| class IDF_Plugin_SyncMonotone␊ |
| {␊ |
| private $old_err_rep = 0;␊ |
| ␊ |
| public function __construct()␊ |
| {␊ |
| $this->old_err_rep = error_reporting(0);␊ |
| }␊ |
| ␊ |
| public function __destruct()␊ |
| {␊ |
| error_reporting($this->old_err_rep);␊ |
| }␊ |
| ␊ |
| private function _diagnoseProblem($msg)␊ |
| {␊ |
| $system_err = error_get_last();␊ |
| if (!empty($system_err)) {␊ |
| $msg .= ': '.$system_err['message'];␊ |
| }␊ |
| ␊ |
| error_reporting($this->old_err_rep);␊ |
| throw new IDF_Scm_Exception($msg);␊ |
| }␊ |
| ␊ |
| /**␊ |
| * Entry point of the plugin.␊ |
| */␊ |
|
| return;␊ |
| }␊ |
| ␊ |
| // This guard cleans up on any kind of error, and here is how it works:␊ |
| // As long as the guard is not committed, it keeps a reference to␊ |
| // the given project. When the guard is destroyed and the reference␊ |
| // is still present, it deletes the object. The deletion indirectly␊ |
| // also calls into this plugin again, as the project delete hook␊ |
| // will be called, that removes any changes we've made during the␊ |
| // process.␊ |
| $projectGuard = new IDF_Plugin_SyncMonotone_ModelGuard($project);␊ |
| ␊ |
| $projecttempl = Pluf::f('mtn_repositories', false);␊ |
| if ($projecttempl === false) {␊ |
| throw new IDF_Scm_Exception(␊ |
| __('"mtn_repositories" must be defined in your configuration file.')␊ |
| $this->_diagnoseProblem(␊ |
| __('"mtn_repositories" must be defined in your configuration file')␊ |
| );␊ |
| }␊ |
| ␊ |
| $usher_config = Pluf::f('mtn_usher_conf', false);␊ |
| if (!$usher_config || !is_writable($usher_config)) {␊ |
| throw new IDF_Scm_Exception(␊ |
| __('"mtn_usher_conf" does not exist or is not writable.')␊ |
| $this->_diagnoseProblem(␊ |
| __('"mtn_usher_conf" does not exist or is not writable')␊ |
| );␊ |
| }␊ |
| ␊ |
| $mtnpostpush = realpath(dirname(__FILE__) . '/../../../scripts/mtn-post-push');␊ |
| if (!file_exists($mtnpostpush)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not find mtn-post-push script "%s".'), $mtnpostpush␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not find mtn-post-push script "%s"'), $mtnpostpush␊ |
| ));␊ |
| }␊ |
| ␊ |
|
| }␊ |
| foreach ($confdir_contents as $content) {␊ |
| if (!file_exists($confdir.$content)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('The configuration file %s is missing.'), $content␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('The configuration file "%s" is missing'), $content␊ |
| ));␊ |
| }␊ |
| }␊ |
|
| $shortname = $project->shortname;␊ |
| $projectpath = sprintf($projecttempl, $shortname);␊ |
| if (file_exists($projectpath)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('The project path %s already exists.'), $projectpath␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('The project path "%s" already exists'), $projectpath␊ |
| ));␊ |
| }␊ |
| ␊ |
| if (!mkdir($projectpath)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('The project path %s could not be created.'), $projectpath␊ |
| if (!@mkdir($projectpath)) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('The project path "%s" could not be created'),␊ |
| $projectpath␊ |
| ));␊ |
| }␊ |
| ␊ |
|
| //␊ |
| $keydir = Pluf::f('tmp_folder').'/mtn-client-keys';␊ |
| if (!file_exists($keydir)) {␊ |
| if (!mkdir($keydir)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('The key directory %s could not be created.'), $keydir␊ |
| if (!@mkdir($keydir)) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('The key directory "%s" could not be created'),␊ |
| $keydir␊ |
| ));␊ |
| }␊ |
| }␊ |
|
| $parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo);␊ |
| }␊ |
| catch (Exception $e) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not parse key information: %s'), $e->getMessage()␊ |
| ));␊ |
| }␊ |
|
| foreach ($confdir_contents as $content) {␊ |
| $filepath = $projectpath.'/'.$content;␊ |
| if (substr($content, -1) == '/') {␊ |
| if (!mkdir($filepath)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not create configuration directory "%s"'), $filepath␊ |
| if (!@mkdir($filepath)) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not create configuration directory "%s"'),␊ |
| $filepath␊ |
| ));␊ |
| }␊ |
| continue;␊ |
| }␊ |
| ␊ |
| if (substr($content, -3) != '.in') {␊ |
| if (!symlink($confdir.$content, $filepath)) {␊ |
| if (!@symlink($confdir.$content, $filepath)) {␊ |
| IDF_Scm_Exception(sprintf(␊ |
| __('Could not create symlink "%s"'), $filepath␊ |
| __('Could not create symlink for configuration file "%s"'),␊ |
| $filepath␊ |
| ));␊ |
| }␊ |
| continue;␊ |
|
| ␊ |
| // remove the .in␊ |
| $filepath = substr($filepath, 0, -3);␊ |
| if (file_put_contents($filepath, $filecontents, LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not write configuration file "%s"'), $filepath␊ |
| if (@file_put_contents($filepath, $filecontents, LOCK_EX) === false) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write configuration file "%s"'),␊ |
| $filepath␊ |
| ));␊ |
| }␊ |
| }␊ |
|
| $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);␊ |
| }␊ |
| catch (Exception $e) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not parse usher configuration in "%s": %s'),␊ |
| $usher_config, $e->getMessage()␊ |
| ));␊ |
|
| foreach ($stanzas as $stanza_line) {␊ |
| if ($stanza_line['key'] == 'server' &&␊ |
| $stanza_line['values'][0] == $shortname) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('usher configuration already contains a server '.␊ |
| 'entry named "%s"'),␊ |
| $shortname␊ |
|
| ␊ |
| // FIXME: more sanity - what happens on failing writes? we do not␊ |
| // have a backup copy of usher.conf around...␊ |
| if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not write usher configuration file "%s"'), $usher_config␊ |
| if (@file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write usher configuration file "%s"'),␊ |
| $usher_config␊ |
| ));␊ |
| }␊ |
| ␊ |
|
| // step 6) reload usher to pick up the new configuration␊ |
| //␊ |
| IDF_Scm_Monotone_Usher::reload();␊ |
| ␊ |
| // commit the guard, so the newly created project is not deleted␊ |
| $projectGuard->commit();␊ |
| }␊ |
| ␊ |
| /**␊ |
|
| ␊ |
| $write_permissions = implode("\n", $key_ids);␊ |
| $rcfile = $projectpath.'/write-permissions';␊ |
| if (file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not write write-permissions file "%s"'), $rcfile␊ |
| if (@file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write write-permissions file "%s"'),␊ |
| $rcfile␊ |
| ));␊ |
| }␊ |
| ␊ |
|
| array('key' => 'allow', 'values' => array('*')),␊ |
| );␊ |
| }␊ |
| ␊ |
| $read_permissions = IDF_Scm_Monotone_BasicIO::compile(array($stanza));␊ |
| $rcfile = $projectpath.'/read-permissions';␊ |
| if (file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not write read-permissions file "%s"'), $rcfile␊ |
| if (@file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write read-permissions file "%s"'),␊ |
| $rcfile␊ |
| ));␊ |
| }␊ |
| ␊ |
|
| ␊ |
| $serverRestartRequired = false;␊ |
| if ($project->private && file_exists($projectfile) && is_link($projectfile)) {␊ |
| if (!unlink($projectfile)) {␊ |
| if (!@unlink($projectfile)) {␊ |
| IDF_Scm_Exception(sprintf(␊ |
| __('Could not remove symlink "%s"'), $projectfile␊ |
| ));␊ |
|
| $serverRestartRequired = true;␊ |
| } else␊ |
| if (!$project->private && !file_exists($projectfile)) {␊ |
| if (!symlink($templatefile, $projectfile)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| if (!@symlink($templatefile, $projectfile)) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not create symlink "%s"'), $projectfile␊ |
| ));␊ |
| }␊ |
|
| // seems to be ignored when the server should be started␊ |
| // again immediately afterwards␊ |
| IDF_Scm_Monotone_Usher::killServer($project->shortname);␊ |
| // give usher some time to cool down, otherwise it might hang␊ |
| // (see https://code.monotone.ca/p/contrib/issues/175/)␊ |
| sleep(2);␊ |
| IDF_Scm_Monotone_Usher::startServer($project->shortname);␊ |
| }␊ |
| }␊ |
|
| ␊ |
| $usher_config = Pluf::f('mtn_usher_conf', false);␊ |
| if (!$usher_config || !is_writable($usher_config)) {␊ |
| throw new IDF_Scm_Exception(␊ |
| __('"mtn_usher_conf" does not exist or is not writable.')␊ |
| $this->_diagnoseProblem(␊ |
| __('"mtn_usher_conf" does not exist or is not writable')␊ |
| );␊ |
| }␊ |
| ␊ |
|
| ␊ |
| $projecttempl = Pluf::f('mtn_repositories', false);␊ |
| if ($projecttempl === false) {␊ |
| throw new IDF_Scm_Exception(␊ |
| __('"mtn_repositories" must be defined in your configuration file.')␊ |
| $this->_diagnoseProblem(␊ |
| __('"mtn_repositories" must be defined in your configuration file')␊ |
| );␊ |
| }␊ |
| ␊ |
| $projectpath = sprintf($projecttempl, $shortname);␊ |
| if (file_exists($projectpath)) {␊ |
| if (!self::_delete_recursive($projectpath)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('One or more paths underknees %s could not be deleted.'), $projectpath␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('One or more paths underneath %s could not be deleted'), $projectpath␊ |
| ));␊ |
| }␊ |
| }␊ |
|
| if ($keyname && $keyhash &&␊ |
| file_exists($keydir .'/'. $keyname . '.' . $keyhash)) {␊ |
| if (!@unlink($keydir .'/'. $keyname . '.' . $keyhash)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not delete client private key %s'), $keyname␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not delete client private key "%s"'),␊ |
| $keyname␊ |
| ));␊ |
| }␊ |
| }␊ |
|
| $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);␊ |
| }␊ |
| catch (Exception $e) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not parse usher configuration in "%s": %s'),␊ |
| $usher_config, $e->getMessage()␊ |
| ));␊ |
|
| ␊ |
| // FIXME: more sanity - what happens on failing writes? we do not␊ |
| // have a backup copy of usher.conf around...␊ |
| if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not write usher configuration file "%s"'), $usher_config␊ |
| if (@file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write usher configuration file "%s"'),␊ |
| $usher_config␊ |
| ));␊ |
| }␊ |
| ␊ |
|
| return;␊ |
| }␊ |
| ␊ |
| $keyGuard = new IDF_Plugin_SyncMonotone_ModelGuard($key);␊ |
| ␊ |
| foreach (Pluf::factory('IDF_Project')->getList() as $project) {␊ |
| $conf = new IDF_Conf();␊ |
| $conf->setProject($project);␊ |
|
| $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);␊ |
| }␊ |
| catch (Exception $e) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not parse read-permissions for project "%s": %s'),␊ |
| $shortname, $e->getMessage()␊ |
| ));␊ |
|
| ␊ |
| $read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);␊ |
| ␊ |
| if (file_put_contents($projectpath.'/read-permissions',␊ |
| if (@file_put_contents($projectpath.'/read-permissions',␊ |
| $read_perms, LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not write read-permissions for project "%s"'), $shortname␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write read-permissions for project "%s"'),␊ |
| $shortname␊ |
| ));␊ |
| }␊ |
| }␊ |
|
| if (!in_array('*', $lines) && !in_array($mtn_key_id, $lines)) {␊ |
| $lines[] = $mtn_key_id;␊ |
| }␊ |
| if (file_put_contents($projectpath.'/write-permissions',␊ |
| if (@file_put_contents($projectpath.'/write-permissions',␊ |
| implode("\n", $lines) . "\n", LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write write-permissions file for project "%s"'),␊ |
| $shortname␊ |
| ));␊ |
|
| $stdio = $mtn->getStdio();␊ |
| $stdio->exec(array('put_public_key', $key->content));␊ |
| }␊ |
| ␊ |
| $key->commit();␊ |
| }␊ |
| ␊ |
| /**␊ |
|
| $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);␊ |
| }␊ |
| catch (Exception $e) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not parse read-permissions for project "%s": %s'),␊ |
| $shortname, $e->getMessage()␊ |
| ));␊ |
|
| ␊ |
| $read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);␊ |
| ␊ |
| if (file_put_contents($projectpath.'/read-permissions',␊ |
| if (@file_put_contents($projectpath.'/read-permissions',␊ |
| $read_perms, LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| __('Could not write read-permissions for project "%s"'), $shortname␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write read-permissions for project "%s"'),␊ |
| $shortname␊ |
| ));␊ |
| }␊ |
| }␊ |
|
| continue;␊ |
| }␊ |
| }␊ |
| if (file_put_contents($projectpath.'/write-permissions',␊ |
| implode("\n", $lines) . "\n", LOCK_EX) === false) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| if (@file_put_contents($projectpath.'/write-permissions',␊ |
| implode("\n", $lines) . "\n", LOCK_EX) === false) {␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('Could not write write-permissions file for project "%s"'),␊ |
| $shortname␊ |
| ));␊ |
|
| {␊ |
| $projecttempl = Pluf::f('mtn_repositories', false);␊ |
| if ($projecttempl === false) {␊ |
| throw new IDF_Scm_Exception(␊ |
| $this->_diagnoseProblem(␊ |
| __('"mtn_repositories" must be defined in your configuration file.')␊ |
| );␊ |
| }␊ |
| ␊ |
| $projectpath = sprintf($projecttempl, $project->shortname);␊ |
| if (!file_exists($projectpath)) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('The project path %s does not exists.'), $projectpath␊ |
| ));␊ |
| }␊ |
|
| $output = $return = null;␊ |
| exec($fullcmd, $output, $return);␊ |
| if ($return != 0) {␊ |
| throw new IDF_Scm_Exception(sprintf(␊ |
| $this->_diagnoseProblem(sprintf(␊ |
| __('The command "%s" could not be executed.'), $cmd␊ |
| ));␊ |
| }␊ |
|
| foreach ($scan as $subpath) {␊ |
| $status |= self::_delete_recursive($subpath);␊ |
| }␊ |
| $status |= rmdir($path);␊ |
| $status |= @rmdir($path);␊ |
| return $status;␊ |
| }␊ |
| }␊ |
| }␊ |
| ␊ |
| /**␊ |
| * A simple helper class that deletes the model instance if␊ |
| * it is not committed␊ |
| */␊ |
| class IDF_Plugin_SyncMonotone_ModelGuard␊ |
| {␊ |
| private $model;␊ |
| ␊ |
| public function __construct(Pluf_Model $m)␊ |
| {␊ |
| $this->model = $m;␊ |
| }␊ |
| ␊ |
| public function __destruct()␊ |
| {␊ |
| if ($this->model == null)␊ |
| return;␊ |
| $this->model->delete();␊ |
| }␊ |
| ␊ |
| public function commit()␊ |
| {␊ |
| $this->model = null;␊ |
| }␊ |
| }␊ |
| ␊ |