build a SVN env

本文介绍如何快速搭建Subversion版本控制系统服务器,包括安装配置过程、启动服务及客户端使用方法。
快速搭建Subversion


作者:开发诱惑 来自:优快云blog

服务器端Subversion
Subversion是一个自由/开源版本控制系统,和VSS相比,它的特点有:

·采用复制-修改-合并模型

允许多人同时编辑一个文件,在提交的时候,有服务器进行合并,发生冲突的时候需要手工解决。

·目录结构纳入版本控制

支持目录结构的修改和文件改名等操作,并且这些操作都将进行版本管理。

·原子提交

一系列的改动,要么全部提交到版本库,要么一个也不提交,这样可以让用户构建一个所要提交修改的逻辑块,防止部分修改提交到版本库。

·可选的网络层

Subversion可以作为一个扩展模块与Apache结合,这给了Subversion在稳定性和交互性方面很大的好处,可以直接使用服务器的特性—认证、授权和传输压缩等等。也有一个轻型的,单独运行的Subversion服务,这个服务使用自己的协议可以轻松的用SSH封装。

·有效率的分支和标签

分支与标签的代价不与工程的大小成比例,Subversion建立分支与标签时只是拷贝整个工程,使用了一种类似于硬链接的机制,因而这类操作通常只会花费很少并且相对固定的时间。

·多种存储方式

Subversion可以采用数据库进行代码的存储,也可以使用文件存储。

·更有效的处理二进制文件

只记录变化的部分,使得Subversion处理二进制文件更加有效。

·……

资源

Subversion服务器端:http://subversion.tigris.org/files/documents/15/31465/svn-1.3.1-setup.exe

安装

执行安装包,不需要特别的修改,假设安装在“C:\Program Files\Subversion”

配置

1. 确认环境变量

安装会自动在系统环境变量的Path变量中加入“C:\Program Files\Subversion\bin”,如不存在请手工编辑。

2. 建立一个源代码仓库

在windows控制台中运行“svnadmin create d:\data\subversion”,这样就会在指定的目录中建立代码仓库。

3. 设置全局访问权限

进入代码仓库的conf文件夹(d:\data\subversion\conf),编辑其中的svnserve.conf文件

[general]

### These options control access to the repository for unauthenticated

### and authenticated users. Valid values are "write", "read",

### and "none". The sample settings below are the defaults.

# anon-access = read

# auth-access = write

### The password-db option controls the location of the password

### database file. Unless you specify a path starting with a /,

### the file's location is relative to the conf directory.

### Uncomment the line below to use the default password file.

# password-db = passwd




其中#起始的行表示注释,因此在修改anon-access和auth-access属性后要删除行首的#。一般来说设置为anon-access = none,auth-access = none,即未验证的用户不能进行读写,通过验证的用户可以读写。password-db = passwd把行首的#去掉,表示密码文件为passwd。

4. 添加用户

进入代码仓库的conf文件夹(d:\data\subversion\conf),编辑其中的passwd文件

### This file is an example password file for svnserve.

### Its format is similar to that of svnserve.conf. As shown in the

### example below it contains one section labelled [users].

### The name and password for each user follow, one account per line.


[users]

# harry = harryssecret

# sally = sallyssecret



[users]节下定义了用户,等号前是用户名,等号后面是密码,注意删除行首的#。

5. 启动服务

在windows控制台中运行“svnserve -d -r d:\data\subversion”,Subversion服务开始监听3690端口,客户端通过这个端口和服务器进行交互,进行源代码的管理。如果需要自定义端口号,运行“svnserve -d --listen-port 自定义端口号 -r d:\data\subversion”启动服务即可。

注意

1. 服务启动后,不要关闭控制台窗口,一旦关闭窗口,服务就停止运行了。可以将其包装为windows服务,参见服务器端实用工具——SVN Service Wrapper for Windows。

2. Subversion使用Socket通讯,请在防火墙中将使用的端口号设置成例外。

参考资源

Subversion官方网站:http://subversion.tigris.org/

Subversion中文文档:http://svnbook.red-bean.com/

服务器端实用工具——SVN Service Wrapper for Windows
对于在windows平台下运行的Subversion,可以将控制台程序包装成服务,这样就可以在服务器启动的时候自动启动服务,方便服务器的管理。

资源

SVN Service Wrapper for Windows:http://clanlib.org/~mbn/svnservice/SVNService.zip

安装

解开压缩包,将其中的SVNService.exe文件,复制到Subversion的bin目录,如“C:\Program Files\Subversion\bin”

·安装服务:

在控制台中运行“SVNService -install [svnserve命令的参数]”,如“SVNService -install -d -r d:\data\subversion”

·更改参数:

在控制台中运行“SVNService -setup [svnserve命令的参数]”,如“SVNService -setup -d --listen-port 6681 -r d:\data\subversion”

·卸载服务:

在控制台中运行“SVNService –remove”

参考资源

官方网站:http://clanlib.org/~mbn/svnservice/

客户端——TortoiseSVN
TortoiseSVN是Subversion的一个客户端,它最大的特点是不需要任何IDE,而与windows资源管理器集成。

资源

客户端:http://nchc.dl.sourceforge.net/sourceforge/tortoisesvn/TortoiseSVN-1.3.3.6219-svn-1.3.1.msi

语言包:http://jaist.dl.sourceforge.net/sourceforge/tortoisesvn/LanguagePack_1.3.3_zh_CN.exe

安装

执行客户端安装包,注意选择自定义安装,有个针对.net web项目的选项,默认安装在“C:\Program Files\TortoiseSVN”,然后进行语言包的安装。

配置

1. 改变语言

虽然安装了语言包,但TortoiseSVN也不会自动切换界面语言,需要在windows资源管理器的菜单栏中,选择“文件->TortoiseSVN->Settings”命令,在弹出的窗口中选择“简体中文”。

常用命令

1. 文件库浏览器

在URL框中输入SVN服务器的地址,如svn://oa.menhoo.com:6681,按照提示输入用户名和密码,即可进入版本库的浏览界面,右上方有个版本“Head按钮”,Head就是最新版本的意思,在这里可以进行一些打开、保存、删除等常用操作。注意,这里进行的操作将直接影响服务器版本库中的内容,而不是本地的副本。

2. 导出

将版本库中选中的目录和文件按照指定的版本号的内容进行导出,默认情况下将导出最新版本,这个命令类似于VSS中的获取最新版本。注意导出和取出的区别,可以将这个命令简单的理解为下载。

3. 在此创建版本库

创建一个新的版本库,只有服务器才需要这个操作。

4. 导入

将当前路径下的所有文件导入到版本库中。类似VSS中创建了一个新项目,然后添加文件。与VSS不同的是,当前路径下的文件可以被删除掉,因为它们不再被Subversion所使用,可以将导入命令简单的理解为上传。

5. 取出

在当前文件夹中建立版本库中项目的副本,类似VSS中的设置工作目录和获取最新文件。当文件取出完成后,在当前目录里有了一个版本库的个人拷贝,可以编辑你的工作备份中的文件,并且提交到版本库。

参考资源

TortoiseSVN官方网站:http://tortoisesvn.tigris.org/
pipeline { agent any environment { // 许可证配置 RLM_LICENSE = 'C:\\HighTec\\licenses\\license.lic' // Eclipse 无界面构建相关配置 ECLIPSE_EXE = 'C:\\HighTec\\ide\\htc-ide-v2.9.0\\eclipsec.exe' WORKSPACE_DATA = 'E:\\hightec-volant-test' PROJECT_PATH = '.\\M_MOT_VOLANT_STATE' // 构建输出目录 BUILD_OUTPUT_DIR = "${WORKSPACE}\\build-output" // 新增:两个打包文件夹路径(基于workspace) PACKAGE_FILES = "${WORKSPACE}\\package_files" // 存放.hex/.elf/.map和DBC文件 PACKAGE_PROJECT = "${WORKSPACE}\\package_project" // 存放工程文件和其他dev文件 // Eclipse参数配置 ECLIPSE_ARGS = '-nosplash -consolelog ' // GitLab配置 GITLAB_URL = 'http://git.santroll.com' GITLAB_PROJECT = 'volant/volant_tc375.git' GIT_CREDENTIALS_ID = '71e64a5e-1089-4cc9-8869-b1fb547f233f' // SVN配置 SVN_WORKSPACE = 'F:\\Document\\jenkins-test' SVN_REPO_URL = 'https://172.22.4.106/svn/Document/jenkins-test' SVN_CREDENTIALS_ID = 'SVN' SVN_LOG_FILE = "${WORKSPACE}\\svn_commit.log" // Git用户配置(临时) GIT_USER_NAME = 'Jenkins CI' GIT_USER_EMAIL = 'jenkins@example.com' } triggers { gitlab( triggerOnPush: false, triggerOnMergeRequest: true, branchFilterType: 'All', secretToken: "${env.GITLAB_WEBHOOK_TOKEN}" ) } parameters { string(name: 'SOURCE_BRANCH', defaultValue: 'Jenkins-test', description: '源分支名称(手动触发时使用)') string(name: 'TARGET_BRANCH', defaultValue: 'dev', description: '目标分支名称(手动触发时使用)') } stages { stage('环境准备') { steps { bat ''' @echo off chcp 65001 >nul echo 清理工作空间... if exist "%WORKSPACE%" rmdir /S /Q "%WORKSPACE%" if exist "%WORKSPACE_DATA%" rmdir /S /Q "%WORKSPACE_DATA%" echo 创建输出目录... mkdir "%BUILD_OUTPUT_DIR%" mkdir "%PACKAGE_FILES%" rem 创建第一个打包文件夹 mkdir "%PACKAGE_PROJECT%" rem 创建第二个打包文件夹 echo 验证Eclipse和许可证... if not exist "%ECLIPSE_EXE%" ( echo 错误: Eclipse可执行文件不存在 - %ECLIPSE_EXE% exit /b 1 ) if not exist "%RLM_LICENSE%" ( echo 错误: 许可证文件不存在 - %RLM_LICENSE% exit /b 1 ) echo 准备SVN工作目录... if not exist "%SVN_WORKSPACE%" ( mkdir "%SVN_WORKSPACE%" ) else ( echo 清理SVN工作目录旧文件... del /q "%SVN_WORKSPACE%\\*.*" ) ''' } } stage('检测分支信息') { steps { script { def sourceBranch = '' def targetBranch = params.TARGET_BRANCH echo "===== 触发方式调试信息 =====" echo "GITLAB_MERGE_REQUEST_IID: ${env.GITLAB_MERGE_REQUEST_IID}" echo "BUILD_CAUSE: ${env.BUILD_CAUSE}" echo "手动参数SOURCE_BRANCH: ${params.SOURCE_BRANCH}" echo "==========================" if (env.GITLAB_MERGE_REQUEST_IID) { echo "检测到GitLab MR触发: #${env.GITLAB_MERGE_REQUEST_IID}" sourceBranch = bat( script: "curl --header \"PRIVATE-TOKEN: ${GITLAB_API_TOKEN}\" " + "\"${GITLAB_URL}/api/v4/projects/${env.GITLAB_PROJECT_ID}/merge_requests/${env.GITLAB_MERGE_REQUEST_IID}\" | " + "jq -r '.source_branch'", returnStdout: true ).trim() targetBranch = bat( script: "curl --header \"PRIVATE-TOKEN: ${GITLAB_API_TOKEN}\" " + "\"${GITLAB_URL}/api/v4/projects/${env.GITLAB_PROJECT_ID}/merge_requests/${env.GITLAB_MERGE_REQUEST_IID}\" | " + "jq -r '.target_branch'", returnStdout: true ).trim() } else { echo "未检测到MR触发,使用手动参数分支" sourceBranch = params.SOURCE_BRANCH } if (!sourceBranch || sourceBranch == "null") { error "源分支为空,请检查配置或参数" } env.SOURCE_BRANCH = sourceBranch env.TARGET_BRANCH = targetBranch echo "确认合并信息:${sourceBranch} -> ${targetBranch}" } } } stage('代码拉取与合并') { steps { script { bat "git config --global user.name \"${env.GIT_USER_NAME}\"" bat "git config --global user.email \"${env.GIT_USER_EMAIL}\"" checkout scm: [ $class: 'GitSCM', branches: [[name: "*/${env.TARGET_BRANCH}"]], userRemoteConfigs: [[ url: "${GITLAB_URL}/${GITLAB_PROJECT}", credentialsId: "${GIT_CREDENTIALS_ID}" ]] ] bat "git checkout ${env.TARGET_BRANCH}" bat "git pull origin ${env.TARGET_BRANCH}" def targetSHAOutput = bat(script: 'git rev-parse HEAD', returnStdout: true).trim() def targetSHA = (targetSHAOutput =~ /[0-9a-fA-F]{40}/)[0] ?: "" if (!targetSHA) { error "无法提取目标分支${env.TARGET_BRANCH}的SHA值,输出为:${targetSHAOutput}" } env.TARGET_SHA = targetSHA echo "目标分支${env.TARGET_BRANCH}最新SHA: ${targetSHA}" def sourceExists = bat( script: "git ls-remote --heads origin ${env.SOURCE_BRANCH} | find /c /v \"\"", returnStdout: true ).trim() if (sourceExists == "0") { error "源分支 ${env.SOURCE_BRANCH} 不存在" } bat "git checkout -b merge-test-branch" bat "git pull origin ${env.SOURCE_BRANCH}" def sourceSHAOutput = bat(script: 'git rev-parse HEAD', returnStdout: true).trim() def sourceSHA = (sourceSHAOutput =~ /[0-9a-fA-F]{40}/)[0] ?: "" env.SOURCE_SHA = sourceSHA echo "源分支${env.SOURCE_BRANCH}最新SHA: ${sourceSHA}" try { bat "git merge ${env.TARGET_SHA} -m \"模拟合并 ${env.SOURCE_BRANCH} 到 ${env.TARGET_BRANCH}\"" def mergeSHA = bat(script: 'git rev-parse HEAD', returnStdout: true).trim() mergeSHA = (mergeSHA =~ /[0-9a-fA-F]{40}/)[0] ?: "" env.MERGE_SHA = mergeSHA echo "合并成功,合并后SHA: ${mergeSHA}" } catch (Exception e) { echo "合并冲突或失败:${e.message}" bat "git diff --name-only --diff-filter=U" def isMerging = bat(script: 'if exist .git/MERGE_HEAD (echo 1) else (echo 0)', returnStdout: true).trim() if (isMerging == "1") { bat "git merge --abort" } error "合并失败,终止构建" } } } } stage('编译合并后代码') { steps { bat ''' @echo off echo 开始Eclipse无头构建... "%ECLIPSE_EXE%" %ECLIPSE_ARGS% ^ -application org.eclipse.cdt.managedbuilder.core.headlessbuild ^ -data "%WORKSPACE_DATA%" ^ -import "%PROJECT_PATH%" ^ -build all set ECLIPSE_EXIT_CODE=%ERRORLEVEL% if %ECLIPSE_EXIT_CODE% neq 0 ( echo [ERROR] Eclipse编译失败 exit /b 1 ) else ( echo [INFO] Eclipse编译成功 ) ''' bat ''' if exist "%PROJECT_PATH%\\iROM" ( robocopy "%PROJECT_PATH%\\iROM" "%BUILD_OUTPUT_DIR%" /E /NFL /NDL ) else ( echo [ERROR] 未找到编译输出目录iROM exit /b 1 ) exit /b 0 ''' } } stage('文件筛选与打包') { steps { bat ''' @echo off setlocal enabledelayedexpansion :: 定义需要排除的关键目录 set "EXCLUDE_DIRS=%PROJECT_PATH% .git %BUILD_OUTPUT_DIR% %PACKAGE_FILES% %PACKAGE_PROJECT%" echo 1. 打包编译结果文件(.hex/.elf/.map)和DBC文件... robocopy "%BUILD_OUTPUT_DIR%" "%PACKAGE_FILES%" *.hex *.elf *.map /S /NFL /NDL if !ERRORLEVEL! geq 8 ( echo [ERROR] 复制编译结果文件失败 exit /b 1 ) robocopy "%WORKSPACE%" "%PACKAGE_FILES%" *.dbc /S /NFL /NDL /XD !EXCLUDE_DIRS! if !ERRORLEVEL! geq 8 ( echo [ERROR] 复制DBC文件失败 exit /b 1 ) echo 2. 打包合并后的工程文件和dev分支其他文件... robocopy "%PROJECT_PATH%" "%PACKAGE_PROJECT%\\工程文件" /E /NFL /NDL if !ERRORLEVEL! geq 8 ( echo [ERROR] 复制工程文件失败 exit /b 1 ) robocopy "%WORKSPACE%" "%PACKAGE_PROJECT%\\dev其他文件" /S /NFL /NDL /XD !EXCLUDE_DIRS! if !ERRORLEVEL! geq 8 ( echo [ERROR] 复制dev其他文件失败 exit /b 1 ) echo 两个打包文件夹已生成(无嵌套循环): echo - 文件包:%PACKAGE_FILES% echo - 工程包:%PACKAGE_PROJECT% ''' } } // 修复并完善:提取Git合并信息阶段(关键修改点) stage('提取Git合并信息') { steps { script { def mergeSHA = env.MERGE_SHA // 检查MERGE_SHA有效性 if (!mergeSHA || mergeSHA.length() != 40) { error "无效的MERGE_SHA: ${mergeSHA},无法提取Git信息" } // 关键修改:使用双引号包裹Git格式参数,兼容Windows批处理 def commitAuthor = bat( script: "git log -1 --format=\"%%an\" ${mergeSHA}", // %%an在Groovy中会解析为%an returnStdout: true ).trim() def commitEmail = bat( script: "git log -1 --format=\"%%ae\" ${mergeSHA}", returnStdout: true ).trim() def commitDate = bat( script: "git log -1 --format=\"%%ad\" --date=iso ${mergeSHA}", returnStdout: true ).trim() def commitMessage = bat( script: "git log -1 --format=\"%%s\" ${mergeSHA}", returnStdout: true ).trim() // 保存到环境变量(处理空值) env.GIT_COMMIT_AUTHOR = commitAuthor ?: "未知作者" env.GIT_COMMIT_EMAIL = commitEmail ?: "unknown@example.com" env.GIT_COMMIT_DATE = commitDate ?: new Date().format('yyyy-MM-dd HH:mm:ss') env.GIT_COMMIT_MESSAGE = commitMessage ?: "无提交信息" echo "提取的Git合并信息:" echo "合并SHA: ${mergeSHA}" echo "作者: ${env.GIT_COMMIT_AUTHOR} <${env.GIT_COMMIT_EMAIL}>" echo "提交日期: ${env.GIT_COMMIT_DATE}" echo "提交信息: ${env.GIT_COMMIT_MESSAGE}" } } } stage('上传到SVN') { steps { script { // 复制两个打包文件夹到SVN工作目录 bat ''' @echo off echo 复制文件包到SVN工作目录... robocopy "%PACKAGE_FILES%" "%SVN_WORKSPACE%\\文件包" /E /NFL /NDL /XO if %ERRORLEVEL% leq 7 exit /b 0 else exit /b %ERRORLEVEL% echo 复制工程包到SVN工作目录... robocopy "%PACKAGE_PROJECT%" "%SVN_WORKSPACE%\\工程包" /E /NFL /NDL /XO if %ERRORLEVEL% leq 7 exit /b 0 else exit /b %ERRORLEVEL% ''' // SVN提交(使用Git信息生成日志) withCredentials([usernamePassword(credentialsId: env.SVN_CREDENTIALS_ID, passwordVariable: 'SVN_PWD', usernameVariable: 'SVN_USER')]) { bat """ @echo off cd /d "%SVN_WORKSPACE%" echo 执行SVN更新... svn update --username %SVN_USER% --password %SVN_PWD% ^ --trust-server-cert-failures=unknown-ca,cn-mismatch ^ --non-interactive if %ERRORLEVEL% neq 0 ( echo SVN更新成功 ) else ( echo [ERROR] SVN更新失败 exit /b 1 ) echo 生成SVN提交日志(基于Git合并信息)... echo "Git合并提交信息:" > "%SVN_LOG_FILE%" echo "合并SHA: ${env.MERGE_SHA}" >> "%SVN_LOG_FILE%" echo "作者: ${env.GIT_COMMIT_AUTHOR} <${env.GIT_COMMIT_EMAIL}>" >> "%SVN_LOG_FILE%" echo "提交日期: ${env.GIT_COMMIT_DATE}" >> "%SVN_LOG_FILE%" echo "提交信息: ${env.GIT_COMMIT_MESSAGE}" >> "%SVN_LOG_FILE%" echo "源分支: ${env.SOURCE_BRANCH}" >> "%SVN_LOG_FILE%" echo "目标分支: ${env.TARGET_BRANCH}" >> "%SVN_LOG_FILE%" echo 提交到SVN... svn add --force * --depth infinity svn commit --username %SVN_USER% --password %SVN_PWD% -F "%SVN_LOG_FILE%" ^ --encoding utf-8 ^ --trust-server-cert-failures=unknown-ca,cn-mismatch ^ --non-interactive if %ERRORLEVEL% equ 0 ( echo SVN提交成功 ) else ( echo [ERROR] SVN提交失败 exit /b 1 ) """ } } } } } post { always { echo '构建流程结束' } success { script { if (env.GITLAB_MERGE_REQUEST_IID) { echo "[INFO] MR #${env.GITLAB_MERGE_REQUEST_IID} 构建成功,两个打包文件夹已上传SVN" } else { echo "[INFO] 手动构建成功: ${env.SOURCE_BRANCH} -> ${env.TARGET_BRANCH},两个打包文件夹已上传SVN" } } } failure { script { if (env.GITLAB_MERGE_REQUEST_IID) { echo "[ERROR] MR #${env.GITLAB_MERGE_REQUEST_IID} 构建失败" } else { echo "[ERROR] 手动构建失败: ${env.SOURCE_BRANCH} -> ${env.TARGET_BRANCH}" } } } } }
08-01
SVNSubversion)中设置忽略规则,可以通过 `svn:ignore` 属性来实现。这个属性可以设置在某个目录上,用来告诉 SVN 忽略该目录下的某些文件或子目录(不会提交到版本库中)。 --- ### ✅ 方法一:使用 `svn propset` 命令设置忽略规则(推荐) #### 1. 忽略单个文件或多个文件 ```bash # 进入要设置忽略的目录 cd /path/to/your/directory # 设置 svn:ignore 属性,忽略 build/ 目录和 *.o 文件 svn propset svn:ignore "build *.o temp.log" . ``` > 注意:上面的 `"build\n*.o\ntemp.log"` 是多行字符串,每行一个忽略模式。 #### 2. 提交属性更改 ```bash svn commit -m "Add ignore rules for build/, *.o and temp.log" ``` --- ### ✅ 方法二:从文件读取忽略规则(适合大量规则) 创建一个文本文件,比如 `.svnignore`: ```txt # .svnignore 内容 build/ *.log *.tmp node_modules/ .DS_Store ``` 然后使用命令导入: ```bash svn propset svn:ignore -F .svnignore . svn commit -m "Set batch ignore rules from .svnignore" ``` --- ### ✅ 方法三:追加新的忽略规则(保留原有规则) SVN 不支持直接追加,但可以用脚本方式先获取旧规则,再合并: ```bash # 获取当前忽略规则,追加新项,写回 (svn propget svn:ignore .; echo "new_folder/") | svn propset svn:ignore -F - . ``` --- ### ✅ 图形化工具设置(如 TortoiseSVN) 1. 右键点击目标文件夹 → **TortoiseSVN** → **Properties** 2. 点击 **New...** → 选择 `svn:ignore` 3. 添加要忽略的文件名或模式,每行一个 4. 确定并提交 --- ### ⚠️ 注意事项 - `svn:ignore` **只作用于未受控文件**(unversioned files)。如果文件已经被 SVN 跟踪(已提交过),需要先从版本库中删除: ```bash svn delete --keep-local filename ``` 然后再设置忽略规则。 - `svn:ignore` 是**目录级属性**,只能设置在目录上,不能设置在整个项目根路径自动生效所有子目录。 - 如果想在多个目录忽略相同内容(如 `node_modules`),需要在每个目录单独设置,或使用脚本批量处理。 --- ### ✅ 示例:忽略常见开发中的文件(Python 项目) ```bash # 在项目根目录执行 svn propset svn:ignore " __pycache__ *.pyc *.pyo *.pyd .Python env/ venv/ dist/ build/ *.egg-info .coverage htmlcov " . ``` --- ### 🔍 查看当前忽略规则 ```bash svn propget svn:ignore . ``` --- ### 解释 - `svn propset`:用于设置 Subversion 的属性。 - `svn:ignore`:是 SVN 内建的一个属性,用于指定哪些未受控文件应被 `svn status` 和 `svn add` 忽略。 - 设置后,这些文件将不再出现在 `svn status` 的 `?` 列表中(除非你强制添加)。 --- ### ❌ 常见误区 - ❌ 在父目录设置 `svn:ignore` 无法影响子目录 —— 每个目录需单独设置。 - ❌ 忽略已提交的文件无效 —— 必须先 `svn delete` 并保留本地文件。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值