array( 'host' => 'localhost', 'user' => '', 'password' => '', 'name' => '' ), 'dump' => array( 'params' => '--allow-keywords --no-create-db --quote-names', 'file' => 'dump.sql' ), 'svn' => array( 'params' => '--no-auth-cache', 'fileCommitMessage' => 'logs/commit.message', //_relative_ from svnRoot path 'user' => '', 'password' => '', 'fileLog' => 'logs/smartyCommit.log' //_relative_ from svnRoot path ), 'misc' => array( 'dirTmp' => 'c:/windows/temp/', 'clearCommitMessage' => false, 'clearCommitMessageReport' => false ) ); private $_errChannel; public function __construct($clArguments){ $this->_errChannel = fopen('php://stderr', 'w'); $this->loadAndCheckOptions($clArguments); } public function __destruct(){ //free resources fclose($this->_errChannel); //remove all created files foreach (array('', FILENAME_FROMWC) as $namePart) @unlink($this->_options['misc']['dirTmp'] . $this->_options['uniq'] . $namePart . FILEEXT_DUMP); } public function run(){ $this->makeDbDump(); $this->replaceWCDump(); $this->commitSvn(); } protected function error($message, $type = SCE_FATAL){ fwrite($this->_errChannel, $type . ' ' . $message . "\n"); if ($type === SCE_FATAL) exit; } protected function makeDbDump(){ try{ $command = '%COMSPEC% /c "' . FILEPATH_DUMPER . ' ' . $this->_options['dump']['params'] . ' ' . $this->_options['db']['name'] . ' ' . '--user=' . $this->_options['db']['user'] . ' ' . '--password=' . $this->_options['db']['password'] . ' ' . '> ' . ($this->_options['misc']['dirTmp'] . $this->_options['uniq'] . FILEEXT_DUMP) . ' ' . '2>&3"'; exec($command, $output, $return); //dumper output error messages to stderr, so capture them and simply display if (!empty($return)) throw new Exception($output[0]); } catch (Exception $e){ $this->error('Dumping failed. ' . $e->getMessage()); } } protected function replaceWCDump(){ //all operations have undo possibility //backup dump in WC to temp directory try{ if (is_file($filePathWCDump = $this->_options['svnRoot'] . $this->_options['dump']['file'])){ if (!@rename($filePathWCDump, $this->_options['misc']['dirTmp'] . $this->_options['uniq'] . FILENAME_FROMWC . FILEEXT_DUMP)){ throw new Exception('backup of ' . $this->_options['dump']['file'] . ' to temp dir failed.'); } } else { $this->error($filePathWCDump . ' is not exist - nothing to backup.', SCE_NOTICE); } } catch (Exception $e){ //backup is not very necessary - you may use svn to revert changes made by code below $this->error('Can\'t backup WC dump: ' . $e->getMessage(), SCE_WARNING); } //move new dump to WC try{ if (!@rename($this->_options['misc']['dirTmp'] . $this->_options['uniq'] . FILEEXT_DUMP, $filePathWCDump)) throw new Exception('Replacing of ' . $filePathWCDump . ' by ' . $this->_options['misc']['dirTmp'] . $this->_options['uniq'] . FILEEXT_DUMP . ' failed.'); } catch (Exception $e){ $this->error('Can\'t put new dump to WC dir: ' . $e->getMessage()); } } protected function commitSvn(){ try{ $command = '%COMSPEC% /c "' . FILEPATH_SVN . ' commit ' . $this->_options['svn']['params'] . ' ' . (!empty($this->_options['svn']['user']) ? '--username ' . $this->_options['svn']['user'] . ' ' : '') . (!empty($this->_options['svn']['password']) ? '--password ' . $this->_options['svn']['password'] . ' ' : '') . '--file ' . $this->_options['svnRoot'] . $this->_options['svn']['fileCommitMessage'] . ' ' . $this->_options['svnRoot'] . ' ' . '> ' . $this->_options['svnRoot'] . $this->_options['svn']['fileLog'] . ' ' . '2>&3"'; exec($command, $output, $return); //convert all message strings to one if (!empty($return)){ foreach ($output as $index => $value) { $output[$index] = str_replace('svn: ', '', $value); if (preg_match('/\w$/', $value)) $output[$index] .= '.'; } throw new Exception(implode(' ', $output)); } //clear commitMessage file depending on the options if ($this->_options['misc']['clearCommitMessage']){ if (!@unlink($this->_options['svnRoot'] . $this->_options['svn']['fileCommitMessage']) || !$this->_createFile($this->_options['svnRoot'] . $this->_options['svn']['fileCommitMessage'])) $this->error('Can\'t clear commint message file', SCE_WARNING); elseif ($this->_options['misc']['clearCommitMessageReport']) $this->error('Commint message file cleared.', SCE_NOTICE); } } catch (Exception $e){ //rollback dump from backup $this->error('svn commit failed. ' . $e->getMessage(), SCE_ERROR); try{ if (is_file($filePathBackup = $this->_options['misc']['dirTmp'] . $this->_options['uniq'] . FILENAME_FROMWC . FILEEXT_DUMP)){ if (!@unlink($filePathDump = $this->_options['svnRoot'] . $this->_options['dump']['file'])) throw new Exception('can\'t delete modified ' . $filePathDump . ' for rollback.'); if (!@rename($filePathBackup, $filePathDump)) throw new Exception('can\'t move ' . $filePathBackup . ' to ' . $filePathDump); } else { $this->error('Backup of dump is not exist. Nothing to rollback.', SCE_NOTICE); } } catch (Exception $e){ $this->error('Rollback of backup of dump failed: ' . $e->getMessage()); } } } protected function loadAndCheckOptions($clArguments){ try{ //options file check... if (!isset($clArguments[1]) || !is_dir($this->_options['svnRoot'] = str_replace(array('\\', '//', '\/'), '/', $clArguments[1] . '/'))) throw new Exception('svnRoot dir is not exist (' . $this->_options['svnRoot'] . ').'); if (!is_file($this->_options['svnRoot'] . '/' . FILENAME_OPTIONS)) throw new Exception('options file does not found in ' . $this->_options['svnRoot'] . '.'); //...and include include_once $this->_options['svnRoot'] . '/' . FILENAME_OPTIONS; if (!isset($smartyCommit)) throw new Exception('$smartyCommit variable is not exist in options file.'); //set options by default $this->_setOptionsByDefault($smartyCommit); //as since user and password may by seted by default db name is important if (empty($this->_options['db']['name'])) throw new Exception('$smartyCommit[\'db\'][\'name\'] is empty.'); //svn commit log message file check if (empty($this->_options['svn']['fileCommitMessage'])) throw new Exception('$smartyCommit[\'svn\'][\'fileCommitMessages\'] is empty.'); elseif (!is_file($this->_options['svnRoot'] . $this->_options['svn']['fileCommitMessage']) && !$this->_createFile($this->_options['svnRoot'] . $this->_options['svn']['fileCommitMessage'])) throw new Exception('File for "svn commit" message is not exist and can\'t be created.'); //uniq filenames $this->_options['uniq'] = uniqid(); $this->_options['misc']['dirTmp'] .= '/'; //cleanup path from // and \/. Unnecessary, but clean in output. foreach ($this->_options as $name => $option) { if (is_string($option)) $this->_options[$name] = str_replace(array('\\', '//', '\/'), '/', $this->_options[$name]); } //db work check $this->checkDbConnection(); } catch (Exception $e){ $this->error('Getting options: ' . $e->getMessage()); } } private function _setOptionsByDefault($options){ foreach (array_keys($this->_options) as $section) { if (is_array($this->_options[$section])) foreach ($this->_options[$section] as $name => $defaultValue) if (!empty($options[$section][$name])) $this->_options[$section][$name] = $options[$section][$name]; } } private function _createFile($filePath){ if (!is_dir(dirname($filePath))) return false; $fname = @tempnam(dirname($filePath), ''); if ($fname !== false) return @rename($fname, $filePath); return false; } protected function checkDbConnection(){ //mysql, extend and redeclare method for another db try{ if (@mysql_connect($this->_options['db']['host'], $this->_options['db']['user'], $this->_options['db']['password'], true) === false) throw new Exception('could\'t connect to mysql://' . $this->_options['db']['user'] .':' . $this->_options['db']['password'] . '@' . $this->_options['db']['host'] . ' (' . mysql_error() . ').'); if (!mysql_select_db($this->_options['db']['name'])) throw new Exception('could\'t select db "' . $this->_options['db']['name'] . '" (' . mysql_error() . ').'); } catch (Exception $e){ $this->error('Db: ' . $e->getMessage()); } } } $sc = new smartyCommit($argv); $sc->run();