$v) { if ($k == 0) { continue; } switch (strtolower($v)) { case '-?': case '--help': case '-h': help(); case '--conf': $argk = 'confFile'; break; default: $confs[$argk] = $v; } } if (!empty($confs['confFile'])) { $confFile = $confs['confFile']; } elseif (!file_exists($confFile)) { $confFile = 'gitsync.ini'; if (!file_exists($confFile)) { help(); } } $confs = parse_ini_file($confFile, true); if (empty($confs['sync']['toPath']) || !file_exists($confs['sync']['toPath']) || !is_dir($confs['sync']['toPath'])) { gexit('sync.toPath not found'); } $tmpPath = sys_get_temp_dir(); if (!file_exists($tmpPath)) { gexit('system temp path not found'); } if (!is_writable($tmpPath)) { gexit('system temp path not writable'); } if (!empty($confs['git']['path'])) { if (!file_exists($confs['git']['path']) || !is_dir($confs['git']['path'])) { gexit('git.path not found'); } $dotGitPath = $confs['git']['path'] . '/.git'; if (!file_exists($dotGitPath) || !is_dir($dotGitPath)) { gexit('git.path: ".git" not found'); } $gitConfig = $dotGitPath . '/config'; if (!file_exists($gitConfig)) { gexit('git.path: ".git/config" not found'); } $gitConfs = parse_ini_file($gitConfig, true); if (empty($gitConfs['remote origin']['url'])) { gexit('git.path: git url not found in ".git/config"'); } $gitUrl = $gitConfs['remote origin']['url']; } elseif (!empty($confs['git']['url'])) { $gitUrl = $confs['git']['url']; } else { gexit('git url not found'); } if (!empty($confs['git']['user']) && !empty($confs['git']['password'])) { $e = parse_url($gitUrl); $gitUrl = $e['scheme'] . '://' . rawurlencode($confs['git']['user']) . ':' . rawurlencode($confs['git']['password']) . '@' . $e['host'] . $e['path']; } if (!function_exists('exec')) { gexit('exec() function not found'); } $branch = !empty($confs['git']['branch']) ? $confs['git']['branch'] : 'master'; $tmpPath = $tmpPath . '/gitsync/' . md5($gitUrl . '.' . $branch); if (!is_dir($tmpPath)) { @mkdir($tmpPath, 0777, true); if (!file_exists($tmpPath)) { gexit('system temp path not found'); } if (!is_writable($tmpPath)) { gexit('system temp path not writable'); } $output = $ret = null; $cmd = 'git clone -b ' . $branch . ' ' . $gitUrl . ' ' . $tmpPath; exec($cmd, $output, $ret) . "\n"; if ($ret) { gexit('git failed: ' . print_r($output, 1) . ' ' . $ret); } } else { chdir($tmpPath); $cmd = 'git fetch --all'; $output = $ret = null; exec($cmd, $output, $ret); echo implode("\n", $output) . "\n"; if ($ret) { gexit('git failed: ' . print_r($output, 1) . ' ' . $ret); } $output = $ret = null; $cmd = 'git reset --hard origin/' . $branch; exec($cmd, $output, $ret); echo implode("\n", $output) . "\n"; if ($ret) { gexit('git failed: ' . print_r($output, 1) . ' ' . $ret); } $output = $ret = null; $cmd = 'git pull'; exec($cmd, $output, $ret); echo implode("\n", $output) . "\n"; if ($ret) { gexit('git failed: ' . print_r($output, 1) . ' ' . $ret); } } $syncFromPath = $tmpPath . (!empty($confs['sync']['fromPath']) ? '/' . $confs['sync']['fromPath'] : ''); $syncToPath = $confs['sync']['toPath']; echo $syncFromPath . ' > ' . $syncToPath . "\n"; echo "...\n"; $_ENV['_md5data_root_'] = $syncFromPath; $_ENV['_md5data_'] = []; $_ENV['_skipdata_'] = $confs['sync']['skipFiles'] ?? []; fetchFiles($syncFromPath); copyFiles($syncToPath); if (!empty($confs['sync']['clearPaths'])) { $_ENV['_todata_root_'] = $confs['sync']['toPath']; foreach ($confs['sync']['clearPaths'] as $path) { if (substr($path, 0, 1) == ':') { $path = substr($path, 1); $recursion = false; } else { $recursion = true; } $path == '/' && $path = ''; clearFiles($confs['sync']['toPath'] . $path, $recursion); } } gexit('Done'); function help() { echo <<] 选项: -?/--help/-h 帮助 -conf 配置文件。默认文件名为 gitsync.ini,可放置在当前目录或者脚本所在目录 gitsync.ini 说明 -------------------- [git] path = "path" url = "https://gitUrl" user = "user" password = "password" branch = "branch1" [sync] fromPath = "path1" toPath = "path2" skipFiles[] = "/file1" clearPaths[] = "/path1" -------------------- git.path (可选)git 仓库的根目录,必须是包含 .git 文件夹的目录 git.url (可选)git 仓库的地址,如果填写了 git.path 可不填写此参数 git.user (可选)git 仓库的用户名,如果 git.path 或者 git.url 包含了账号信息可不填写此参数 git.password (可选)git 仓库的密码,填写了 git.user 则必须填写此参数 git.branch (可选)git 仓库的分支,默认为 master sync.fromPath (可选)如果从 git 仓库根目录开始同步则可不填写此参数 sync.toPath 同步的目标文件夹,绝对路径 sync.skipFiles (可选)同步时跳过的文件,"/" 开头 sync.clearPaths (可选)清理文件夹中的额外文件夹,"/" 开头,不递归查找以 ":" 开头,如:":/" 为仅清理根文件夹 Help; gexit(0); } function gexit($code = '') { echo $code; echo "\n\n"; exit; } function fetchFiles($path) { foreach (glob($path . '/*') as $file) { if (is_dir($file)) { fetchFiles($file); continue; } $_ENV['_md5data_'][str_replace($_ENV['_md5data_root_'], '', $file)] = md5_file($file); } } function copyFiles($path) { foreach ($_ENV['_md5data_'] as $k => $md5) { if (!empty($_ENV['_skipdata_']) && in_array($k, $_ENV['_skipdata_'])) { continue; } $file = $_ENV['_md5data_root_'] . $k; $toFile = $path . $k; if (!file_exists($toFile)) { @mkdir(dirname($toFile), 0777, true); $_dir = dirname($toFile); if (!is_dir($_dir) || !is_writable($_dir)) { gexit('copy file failed, dir not writable: ' . $toFile); } copy($file, $toFile); } else { $toMd5 = md5_file($toFile); if ($md5 != $toMd5) { copy($file, $toFile); } } $toMd5 = md5_file($toFile); if ($md5 != $toMd5) { unlink($toFile); copy($file, $toFile); $toMd5 = md5_file($toFile); } if ($md5 != $toMd5) { $toFileBackup = $toFile . '._bak_'; @unlink($toFileBackup); rename($toFile, $toFileBackup); if (!file_exists($toFileBackup)) { gexit('copy file failed, file not writable: ' . $toFileBackup); } copy($file, $toFile); if (file_exists($toFile)) { @unlink($toFileBackup); } $toMd5 = md5_file($toFile); } if ($md5 != $toMd5) { gexit('copy file failed, md5 not match: ' . $toFile); } } } function clearFiles($path, $recursion = true) { foreach (glob($path . '/*') as $file) { if (is_dir($file)) { $recursion && clearFiles($file); continue; } $key = str_replace($_ENV['_todata_root_'], '', $file); if (empty($_ENV['_md5data_'][$key])) { echo 'delete: ' . $file . "\n"; unlink($file); } } }