Svn 常用操作
服务端
下载
VisualSVN server 下载链接
文件定义
svn的存储地址中,文件夹表明是仓库,groups.conf
是组文件,htpasswd
是用户文件
htpasswd 文件
文件内容如下,如果有 #disabled#
,说明该用户现在被禁用。每行内容由3部分组成:是否禁用(不一定有),用户名,密码(加密,php可以用 password_hash($password, PASSWORD_BCRYPT)
来实现)
- 用户被删除时,需要将所有repo authz 文件中有关这个用户的权限行删除
admin:$2y$10$D3Ut76yRlWoIZc/XVs7ma.LGldlSUBeDG86mlaWghrh3nTt5ZXlOO
liubei:$2y$10$6D5G0VvB89sDi.qUG8MkSuDWx9IoP2Smhp/tTcGyMXs8G0NAOa3zq
guanyu:$2y$10$KV0ASnkyNpypniD2WKY9tur9b1euMImvE62kKALMPdZ3WnLO/LhAO
zhangfei:$2y$10$M5rSt2PulD9e11Q0luq50emBiyrTrjSPyVp4FWkQRnwuo.cM6CTsy
zhaoyun:$2y$10$HVKbgiY/eKvgRUcRSnKpkOP7VS4T61S7urv5x0//G1UM9JlLYpwOy
test1:$2y$10$wfzDTodTSVdy0nScu5BaOOLqfSJkTtocAG86e8EqQl/aIu.xw1k2W
test2:$2y$10$fZA.4PxdEq/CVo6cSnxBeunbq6E2wGiwmdS2iu6YiHEdZ1wQUP.Ka
#disabled#te:$2y$10$2BSqMmF5gaWWXxQx7hThmeAFRl8vIfEr9ypWy/FWH3GZwFWzQL4vy
一些可供操作的函数
// 获取用户列表
public function getUserList()
{
if(!is_readable($this->htpasswdFile) || !is_writable($this->htpasswdFile)) return array();
$lines = file($this->htpasswdFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach($lines as $index => $line)
{
$disabled = strpos($line, '#disabled#') === 0;
$line = str_replace('#disabled#', '', $line);
list($account, $password) = explode(':', $line, 2);
$userList[$account] = (object)array('id' => $index + 1, 'account' => $account, 'password' => $password, 'status' => $disabled ? 'disabled' : 'normal');
}
return $userList;
}
// 如果用户不存在,创建,否则编辑
public function saveUser($account, $password)
{
if(!is_readable($this->htpasswdFile) || !is_writable($this->htpasswdFile)) return false;
$userList = $this->getUserList();
$hashPassword = password_hash($password, PASSWORD_BCRYPT);
if(isset($userList[$account]))
{
$userList[$account]->password = $hashPassword;
}
else
{
$userList[$account] = (object)array('id' => count($userList) + 1, 'account' => $account, 'password' => $hashPassword, 'status' => 'normal');
}
$this->writePasswdFile($userList);
}
// 删除用户
public function deleteUser($account)
{
if(!is_readable($this->htpasswdFile) || !is_writable($this->htpasswdFile)) return false;
$userList = $this->getUserList();
if(isset($userList[$account])) unset($userList[$account]);
$this->writePasswdFile($userList);
}
// 全量写入user
public function writePasswdFile($userList)
{
$file = fopen($this->htpasswdFile, 'w');
foreach($userList as $user)
{
$info = $user->status == 'disabled' ? '#disabled#' : '';
$info .= "{$user->account}:{$user->password}";
fwrite($file, $info . PHP_EOL);
}
fclose($file);
}
groups.conf 文件
有可能有注释,文件内容以[groups]
开头,每一行是分组名称和分组内容的key value
对,以等号连接,分组内容以逗号分割,如果带有 @
,则说明这是一个分组
- 分组的成员不能有自己,否则会导致循环引用,如p2=@p2是非法的
- 分组被删除时,需要将所有repo authz 文件中有关这个分组的权限行删除
# This configuration file stores VisualSVN Server authorization settings. The
# file is autogenerated by VisualSVN Server and is not intended to be edited
# manually.
#
# DO NOT EDIT THIS FILE MANUALLY!
#
# Use VisualSVN Server Manager or VisualSVN Server PowerShell module to
# configure access permissions on your server.
[groups]
p2=admin,liubei,guanyu,zhangfei,@p2,@p_3,@dev
p_3=admin
dev=
lai=te
一些可供操作的函数
// 获取分组名称和成员键值对
public function getGroupMemberPairs()
{
if(!is_readable($this->groupsFile) || !is_writable($this->groupsFile)) return array();
$lines = file($this->groupsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$groupMemberPairs = array();
foreach($lines as $line)
{
$line = trim($line);
if(strpos($line, '#') === 0) continue;
if($line == '[groups]') continue;
list($name, $member) = array_map('trim', explode('=', $line, 2));
$groupMemberPairs[$name] = $member;
}
return $groupMemberPairs;
}
// 如果分组不存在,创建,否则编辑
public function saveGroup($groupName, $selectGroup = array(), $selectUser = array())
{
$groupMemberPairs = $this->getGroupMemberPairs();
if($selectGroup || $selectUser)
{
foreach($selectGroup as $index => $group) $selectGroup[$index] = '@' . $group;
$groupMemberPairs[$groupName] = implode(',', array_merge($selectUser, $selectGroup));
}
else
{
$groupMemberPairs[$groupName] = '';
}
$this->writeGroupFile($groupMemberPairs);
}
// 重命名分组名称
public function renameGroup($groupName, $afterName)
{
$groupMemberPairs = $this->getGroupMemberPairs();
if(isset($groupMemberPairs[$groupName]))
{
$groupMemberPairs[$afterName] = $groupMemberPairs[$groupName];
unset($groupMemberPairs[$groupName]);
}
else
{
$groupMemberPairs[$afterName] = '';
}
$this->writeGroupFile($groupMemberPairs);
}
// 删除分组
public function deleteGroup($groupName)
{
$groupMemberPairs = $this->getGroupMemberPairs();
unset($groupMemberPairs[$groupName]);
$this->svn->writeGroupFile($groupMemberPairs);
}
// 全量写入分组信息
public function writeGroupFile($groupMemberPairs)
{
$file = fopen($this->groupsFile, 'w');
fwrite($file, "[groups]" . PHP_EOL);
foreach($groupMemberPairs as $name => $member) fwrite($file, "{$name}={$member}" . PHP_EOL);
fclose($file);
}
VisualSVN-SvnAuthz.ini 版本库内的权限文件,在一个仓库目录的conf文件夹内
内容一般如下,/
是根目录的权限,其他是特定目录的权限,权限默认只有三种
- rw 读写
- r 只读
- ''(空字符串) 无权限
[/]
*=rw
admin=rw
@1=rw
[/a]
*=rw
admin=rw
@p2=rw
[/a/a1]
guanyu=rw
liubei=rw
存在继承机制,比如其他目录默认继承根目录,可以重定义继承的权限但无法删除,比如/a/a1
的权限有:
guanyu(rw)
liubei(rw)
everyone(inherit rw)
admin(inherit rw)
@p2(inherit rw)
@1(inherit rw)
一些可供操作的函数
// 解析authz 文件的权限
/* e.g
Array
(
[/] => Array
(
[*] => rw
[admin] => rw
[@1] => rw
)
[/a] => Array
(
[*] => rw
[admin] => rw
[@p2] => rw
)
[/a/a1] => Array
(
[guanyu] => rw
[liubei] => rw
)
)
*/
public function parseRepoAuthFile($authPath)
{
if(!is_readable($authPath)) return array();
$config = [];
$section = '';
$lines = file($authPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach($lines as $line)
{
$line = trim($line);
if(strpos($line, '#') === 0) continue;
// 解析 [section]
if(preg_match('/^\[(.+)\]$/', $line, $matches))
{
$section = $matches[1];
$config[$section] = [];
}
// 解析 key=value
elseif(strpos($line, '=') !== false)
{
list($key, $value) = array_map('trim', explode('=', $line, 2));
$config[$section][$key] = $value;
}
}
return $config;
}
// 获得一个路径下的权限,返回一个数组,第一个数组为自身的权限,第二个数组为继承的权限
/* e.g
如果传入path = /a/a1
$selfPrivs = array
(
[guanyu] => rw
[liubei] => rw
)
$inPrivs = array
(
[*] => rw
[admin] => rw
[@p2] => rw
[@1] => rw
)
*/
public function getRepoPathAuth($authPath, $path)
{
$permissions = $this->parseRepoAuthFile($authPath);
$currentPath = $path;
$selfPrivs = array(); //自己的权限
$inPrivs = array(); //继承的权限
$mergePriv = function($cpath, $allpriv, $inpriv)
{
if(isset($allpriv[$cpath]))
{
foreach($allpriv[$cpath] as $user => $perm)
{
// 如果已经有了权限,就说明离path近的目录已经单独设置了,此时用路程近的目录权限,不要再替换
if(isset($inpriv[$user])) continue;
$inpriv[$user] = $perm;
}
}
return $inpriv;
};
$selfPrivs = $mergePriv($currentPath, $permissions, $selfPrivs);
while($currentPath !== "/")
{
$currentPath = dirname($currentPath);
if($currentPath == DIRECTORY_SEPARATOR) $currentPath = '/';
$inPrivs = $mergePriv($currentPath, $permissions, $inPrivs);
}
// 如果已经有自定义的权限了,且还存在继承的权限,先用自定义的,不要继承的
foreach($inPrivs as $user => $perm)
{
if(isset($selfPrivs[$user])) unset($inPrivs[$user]);
}
return array($selfPrivs, $inPrivs);
}
// 写入全量的authz 配置
public function writeRepoAnthFile($authPath, $config)
{
if(!file_exists($authPath) && !touch($authPath)) return false;
if(!is_writable($authPath)) return false;
$file = fopen($authPath, 'w');
foreach($config as $dir => $privs)
{
if($dir != '/') rtrim($dir, '/');
fwrite($file, "[$dir]" . PHP_EOL);
foreach($privs as $user => $priv)
{
if($priv == 'inherit') continue; // 如果是继承,不写入具体内容,visual svn server会自动处理
if($priv == '' || $priv == 'no') $priv = '';// 如果是no access,设置为''空即可,由于zui picker不能设置空的键值(故设置为no),并且可能存在原先写入的文件中就有空的情况,所以这里坐转换
fwrite($file, "{$user}={$priv}" . PHP_EOL);
}
}
fclose($file);
}
客户端
TortoiseSVN 下载链接
服务端教程
安装好 VisualSVN server后,可以在搜索中进行搜索并启动
界面介绍
创建版本库
获取版本库url
进入Setting 界面
查看svn的仓库存储地址
查看svn的ip和port
ip
通过cmd
的ipconfig
来查看,port
通过查看svn的网络查看 这样就可以用url来操作svn:
svn list http://10.0.0.167:81/<relativePath>
客户端教程
下载安装完小乌龟后,右键菜单会出现 TortoiseSVN,如果没有或者没有想要的选项且系统是win11,使用右键的显示更多选项
使用 check 操作来同步 SVN 项目,url在上文提到guo
使用 svn update 来拉取最新代码,如果有提交,使用 svn commit 来提交
Linux下使用Shell远程操作SVN
安装客户端并尝试
sudo apt install subversion
# 看一下仓库目录试试水
svn list <remote url> --non-interactive --username=<user> --password <password>
报错SSL认证不通过解决方案
方案1
svn: E170013: Unable to connect to a repository at URL '<remote url>'
svn: E230001: Server SSL certificate verification failed: certificate issued for a different hostname, issuer is not trusted
在本地开发的时候,往往给出的远程地址也是本地起的,比如 linux
下开发,Windows
上装了 visual svn server
,那么此时在 linux
中"远程"调用 Windows
的 svn
,就会出现这种情况,此时先执行
svn list <remote url>
会得到如下结果,选择p,永久忽略就行
Error validating server certificate for '<remote url>':
- The certificate is not issued by a trusted authority. Use the
fingerprint to validate the certificate manually!
- The certificate hostname does not match.
Certificate information:
- Hostname: Liberty
- Valid: from Jan 17 05:38:59 2025 GMT until Jan 15 05:38:59 2035 GMT
- Issuer: Liberty
- Fingerprint: B1:31:5D:99:17:65:4A:0F:DD:61:A9:F4:1F:9C:AB:31:25:6E:E2:22
(R)eject, accept (t)emporarily or accept (p)ermanently?
但这个方案只能在使用命令行的时候解决,如果是 php exec
则无法避免
方案2
使用信任证书参数
svn list --trust-server-cert-failures=unknown-ca,cn-mismatch,expired,not-yet-valid,other <remote url>
可以解决 php exec
的报错
方案3
取消https,改用http
常用命令
- svn 命令后面可以直接跟远程
url
或者是本地checkout
出来的副本路径svn <command> <url>/<path>
- svn命令中
@
是保留字,代表版本,如果<url>/<path>
中有@
符号,那么需要在后面再跟一个@
,比如svn delete <url>/repo/module/a@.php@
后缀
## 不要交互提示
--non-interactive
## 设置用户和密码
--username=<username> --password <password>
## linux常用的输出错误
2>&1
## 添加评论,最好用双引号,单引号在windows上有问题
-m "<comment>"
## 举个离职
svn mkdir <url> -m "<comment>" --non-interactive --username=<username> --password <password> 2>&1
svn
svn list(svn ls) 获取列表
# 展示详细信息,包括版本号、作者、日期、大小
svn list -v <url>
# 递归列出所有子目录的内容
svn list -R <url>
# 以xml格式输出,可以避免字符集问题,正确输出中文
svn list --xml <url>
svn mkdir 创建文件夹
# 创建文件夹
svn mkdir <url> -m "<comment>"
svn move(svn mv) 移动、重命名
## 移动文件或目录,无法跨版本库迁移
svn move <beforeURL> <afterURL> -m "<comment>"
## 如果只有文件或者目录名字不一样,则是重命名功能,举例
svn move <url>/repo/module/action/contro.php <url>/repo/module/action/control.php -m "<comment>"
svn copy 常用于打标签
## 通过复制来实现打tag
svn copy <url> <tagUrl> -m "tag"
svn export 导出svn目录或文件
## 导出,--force参数确保如果文件或目录已经存在exportPath时,覆盖导出
svn export --force <url> <exportPath>
svnadmin
svnadmin hotcopy
## 热拷贝,复制当前版本库的所有信息,直接出来一个一样的拷贝,是对目录的操作
svnadmin hotcopy <svnPath> <backPath>
svnadmin dump
## 备份版本库,类似mysqldump,选择一个版本库路径然后生成备份文件
svnadmin dump <svnPath> > <filePath>
svnadmin load
## 加载一个dump文件,必须是新建的版本库的path,不能有提交
svnadmin load <svnPath> < <filePath>