SVN使用过程中的几个疑问与解答
如何让两个已存在的、独立开发的项目(或目录),建立起主干与分支的关系,从而可以方便地进行合并
您之前的理解 “仓库B成为仓库A的分支”,在SVN的理念中,需要转化一下思路。SVN的合并机制是基于同一个仓库内的路径和版本历史。它通过一个叫做 svn:mergeinfo
的属性来跟踪哪些版本的改动已经从一个路径合并到了另一个路径。
要实现您想要的“轻松合并”,我们必须让项目A和项目B在同一个仓库中,并且建立起清晰的父子关系(即分支关系)。
下面是实现这个目标的标准且正确的步骤。这个过程比简单的移动要复杂一点,但这是为了建立正确的合并历史,让未来的工作变得简单。
目标
我们的目标是创建一个标准的项目结构,其中:
- 项目A成为新的主干 (
trunk
)。 - 项目B的内容,成为一个从
trunk
创建出来的分支 (branch
)。
操作步骤 (推荐使用 TortoiseSVN)
假设你当前的仓库结构是:
svn://server/repo/
├── project-A/ (你希望作为主干)
└── project-B/ (你希望作为分支)
第 1 阶段:建立标准结构并确立主干 (Trunk)
这一步是将项目A正式确立为项目的主线。
-
打开版本库浏览器:
在任意文件夹空白处右键 -> TortoiseSVN -> 版本库浏览器 (Repo-browser),然后输入你的仓库根URL (svn://server/repo/
)。 -
创建标准目录:
在浏览器右侧空白处右键 -> 创建文件夹 (Create folder…),分别创建trunk
和branches
这两个目录。 -
将项目A移动到
trunk
:
在版本库浏览器中,右键点击project-A
-> 重命名 (Rename…),将其重命名为trunk
。或者,你也可以右键拖动project-A
到根目录,选择“移动并重命名”,新名字叫trunk
。- 重要:这一步必须使用
svn move
或 TortoiseSVN 的重命名/移动功能,以保留project-A
的所有历史记录。
操作完成后,你的结构应该是:
svn://server/repo/ ├── trunk/ (原 project-A 的内容和历史) ├── branches/ (空的) └── project-B/ (保持原样)
- 重要:这一步必须使用
第 2 阶段:创建新分支并迁移项目B的改动
这是最关键的一步。我们不能直接把 project-B
移动到 branches
目录下,因为那样它和 trunk
之间没有“血缘关系”,SVN不知道如何正确合并。我们需要:
- 从
trunk
创建一个正式的分支。 - 将
project-B
相对于project-A
的独特改动,应用到这个新分支上。
操作如下:
-
创建新分支:
- 在版本库浏览器中,右键点击
trunk
目录 -> 分支/标记 (Branch/tag…)。 - 在弹出的对话框中,“至路径 (To path)” 填写新分支的路径,例如
/branches/project-B-branch
。 - 确保选中了 “HEAD revision in the repository”(最新版本)。
- 填写日志信息,如 “Create branch for project-B from trunk”。
- 点击“确定”。
现在,
branches/project-B-branch
是trunk
在此刻的一个完美副本,并且SVN已经记录了它们的父子关系。 - 在版本库浏览器中,右键点击
-
将
project-B
的改动合并到新分支:
这一步我们要用到一个强大的合并功能:“比较两棵树”。-
首先,在你的本地电脑上,找一个空文件夹,
checkout
你刚刚创建的新分支。右键 -> SVN Checkout…,URL填svn://server/repo/branches/project-B-branch
。 -
进入这个刚刚
checkout
出来的project-B-branch
工作副本文件夹。 -
在文件夹根目录空白处右键 -> TortoiseSVN -> 合并 (Merge)。
-
在合并向导中,选择 “合并两个不同的树 (Merge two different trees)”,点击“下一步”。
-
配置“From”和“To”:
- From (起始): 这里要填原始的
project-A
的URL。点击 “…” 按钮,在版本库浏览器中选择project-A
目录。 - To (结束): 这里要填原始的
project-B
的URL。点击 “…” 按钮,选择project-B
目录。
- From (起始): 这里要填原始的
-
点击“下一步”,然后点击 “合并 (Merge)”。
这个操作的原理是:TortoiseSVN会计算出
project-B
相对于project-A
所有的差异(即project-B
独有的修改),然后将这些差异应用到你当前的工作副本(也就是project-B-branch
)上。 -
-
解决冲突并提交:
- 合并可能会产生冲突,按照标准流程解决它们(右键 -> 编辑冲突 -> 解决冲突)。
- 彻底测试! 确保合并后的
project-B-branch
功能正常。 - 测试无误后,提交 (Commit) 你本地工作副本的所有改动。提交日志可以写:“Merge changes from legacy project-B into the new branch”。
第 3 阶段:清理
现在,trunk
是你的主线,branches/project-B-branch
是功能正确的、且与 trunk
有明确历史关联的分支。原来的 project-A
和 project-B
目录已经完成了它们的历史使命。
- 在版本库浏览器中,可以安全地删除顶层的
project-A
(如果重命名后还存在的话)和project-B
目录了。
最终成果与未来的合并
经过以上步骤,你的仓库结构变得清晰,并且SVN已经完全理解了 trunk
和 branches/project-B-branch
之间的关系。
现在,当你对 trunk
(即原来的A) 做了修改后,要合并到B分支上,操作就变得非常简单和标准了:
- 进入
project-B-branch
的本地工作副本。 - 右键 -> TortoiseSVN -> 合并 (Merge)。
- 选择 “合并一个修订范围 (Merge a range of revisions)”。
- “要合并的URL” 应该自动填充为
trunk
的URL。 - 点击 “显示日志 (Show log)”,选择你希望从
trunk
合并过来的版本,然后执行合并即可。
怎么更改对应的本地目录
场景一:移动本地工作副本的物理位置
你的需求是:你已经从 SVN 检出(checkout)了一个项目到本地的某个文件夹(例如 C:\project
),现在你希望把整个项目文件夹移动到另一个位置(例如 D:\work\project
),并保持其 SVN 版本控制功能正常。
这其实是最简单的操作。因为 SVN 的所有版本信息都存储在工作副本根目录下的一个名为 .svn
的隐藏文件夹里。只要你移动整个文件夹,.svn
文件夹也会被一起移动,版本控制信息就不会丢失。
方法 1:使用命令行或文件浏览器 (适用于所有用户)
- 确保没有未提交的修改:在移动前,最好先提交所有待处理的修改,或者确保工作副本是“干净”的。
- 直接移动文件夹:
- Windows:直接使用鼠标 剪切 (Cut)
C:\project
文件夹,然后到D:\work\
目录下 粘贴 (Paste)。 - Linux/macOS:使用
mv
命令:mv /path/to/old/location/project /path/to/new/location/
- Windows:直接使用鼠标 剪切 (Cut)
移动完成后,新的 D:\work\project
文件夹就是一个功能完全正常的 SVN 工作副本。你可以在新位置继续执行 svn update
, svn commit
等所有操作。
方法 2:使用 TortoiseSVN
TortoiseSVN 与 Windows 文件浏览器深度集成,所以操作和上面完全一样。
- 在 Windows 文件浏览器中,右键点击你的项目文件夹,选择 剪切 (Cut)。
- 导航到新的目标位置,右键点击空白处,选择 粘贴 (Paste)。
完成!TortoiseSVN 会自动识别新的位置,你可以在新文件夹上右键,看到所有的 TortoiseSVN 菜单都正常工作。
场景二:将本地工作副本切换到另一个分支或路径
你的需求是:你的本地文件夹 C:\project
当前对应的是 trunk
(主干) 的代码。现在你不想重新 checkout
一个新文件夹,而是希望让 当前这个文件夹 的内容直接变成 branch-A
分支的代码。
这个操作叫做 “切换” (Switch)。它的好处是,SVN 只会下载 trunk
和 branch-A
之间的差异部分,比重新 checkout
整个分支要快得多。
方法 1:使用命令行 (svn switch
)
-
打开命令行,
cd
进入你的工作副本目录 (C:\project
)。 -
执行
svn switch
命令,后面跟上你想要切换到的分支的 URL。# 假设你的版本库根URL是 ^/ # 从 trunk 切换到 branch-A svn switch ^/project/branches/branch-A# 或者使用完整的 URL svn switch svn://server/repo/project/branches/branch-A
-
SVN 会自动更新你的工作副本,将其内容变成
branch-A
的最新状态。
方法 2:使用 TortoiseSVN
-
在你的工作副本文件夹 (
C:\project
) 上,右键点击 -> TortoiseSVN -> 切换 (Switch…)。 -
在弹出的对话框中:
- 切换到 URL (To URL): 这里会显示当前的 URL。你需要把它改成目标分支的 URL。你可以手动输入,或者点击旁边的 “…” 按钮,在版本库浏览器中选择
branch-A
。 - 通常保持其他选项(如 “HEAD revision”)为默认即可。
- 切换到 URL (To URL): 这里会显示当前的 URL。你需要把它改成目标分支的 URL。你可以手动输入,或者点击旁边的 “…” 按钮,在版本库浏览器中选择
-
点击 “确定 (OK)”。TortoiseSVN 会开始更新文件,完成后,你的本地目录就对应
branch-A
了。
场景三:SVN 服务器的地址或协议变了
你的需求是:你的公司把 SVN 服务器从 svn://old-server/repo
迁移到了 http://new-server/svn/repo
。你的本地工作副本仍然有效,但它指向了旧的、无法访问的地址。你需要更新本地工作副本,让它指向新的服务器地址。
这个操作叫做 “重定位” (Relocate)。它只改变本地工作副本记录的服务器地址,而不改变文件内容。
方法 1:使用命令行 (svn relocate
)
-
打开命令行,
cd
进入你的工作副本目录。 -
执行
svn relocate
命令,后面跟上旧的 URL 前缀和新的 URL 前缀。svn relocate svn://old-server/repo http://new-server/svn/repo
方法 2:使用 TortoiseSVN
- 在你的工作副本文件夹上,右键点击 -> TortoiseSVN -> 重定位 (Relocate…)。
- 在弹出的对话框中,会显示当前的 URL。在 “至 URL (To URL)” 的输入框里,填入新的服务器地址。
- 点击 “确定 (OK)”。TortoiseSVN 会更新
.svn
目录中的服务器信息。
可以直接把本地一个文件夹上传到对应云端吗,还是说必须在云端创建一个然后检出
简单直接的回答是:可以!你可以直接把本地一个已有的文件夹上传到 SVN 服务器。这个操作在 SVN 中不叫 “上传”,而是有一个专门的术语,叫做 “导入” (Import)。
但是,这种“导入”方式和你提到的“先在云端创建然后检出”是两种不同的工作流程,适用于不同的场景。下面我为您详细解释这两种方式的区别和具体操作。
方式一:直接“导入”本地文件夹 (Import)
这种方式适用于:你本地已经有了一个完整的项目文件夹,现在想把它整个放到 SVN 服务器上开始进行版本管理。
可以把这个过程想象成:你已经收拾好了一箱子东西(你的项目文件夹),现在要把它整个寄存到仓库(SVN 服务器)里。
操作步骤 (使用 TortoiseSVN)
-
准备本地文件夹:假设你的项目在
D:\MyProject
。这个文件夹目前只是一个普通的文件夹,里面没有.svn
隐藏目录。 -
执行导入:
- 在
D:\MyProject
这个文件夹上,右键点击 -> TortoiseSVN -> 导入 (Import…)。
- 在
-
填写导入信息:
- URL of repository (版本库 URL):这里需要填写你希望把项目存放在服务器上的目标路径。这个路径通常是不存在的,导入操作会自动创建它。例如,你应该填写
svn://server/repo/MyProject/trunk
,而不是只填svn://server/repo/
。这会自动在服务器上创建MyProject
和trunk
目录,并将你的文件放进去。 - Import Message (导入信息):填写一段描述,这会成为这次导入的提交日志。通常写 “Initial import.” (初始导入)。
- URL of repository (版本库 URL):这里需要填写你希望把项目存放在服务器上的目标路径。这个路径通常是不存在的,导入操作会自动创建它。例如,你应该填写
-
点击“确定”,TortoiseSVN 会将你的整个文件夹内容上传到指定的服务器路径。
⚠️ 最关键的一步:导入之后
- 导入操作不会把你原来的
D:\MyProject
文件夹变成一个“工作副本”。它仍然是一个普通的文件夹,与 SVN 服务器没有建立任何联系。 - 你必须重新检出 (Checkout) 一份新的工作副本才能开始工作。
正确的后续操作是:
- 将你原来的
D:\MyProject
文件夹重命名或删除(例如改为D:\MyProject_old
)。 - 在
D:\
目录下,右键 -> SVN 检出 (SVN Checkout…)。 - 在 URL 处填写你刚才导入的地址
svn://server/repo/MyProject/trunk
,检出目录填写D:\MyProject
。 - 现在,新的
D:\MyProject
文件夹就是一个功能齐全的、与服务器关联的工作副本了。你可以开始修改、提交、更新了。
方式二:在云端创建结构,然后检出添加 (Create and Checkout)
这种方式是更标准、更推荐的做法,尤其适合全新的项目。
可以把这个过程想象成:你先在仓库(SVN 服务器)里规划好了区域(trunk
, branches
),然后租下一个空的工作台(检出空目录),再把你的东西(项目文件)一件件放上去并登记(添加并提交)。
操作步骤 (使用 TortoiseSVN)
-
在服务器上创建项目结构:
- 在任意位置右键 -> TortoiseSVN -> 版本库浏览器 (Repo-browser)。
- 输入你的仓库 URL,例如
svn://server/repo/
。 - 在浏览器中,右键 -> 创建文件夹 (Create folder…),依次创建你的项目根目录(如
MyNewProject
),以及它下面的trunk
,branches
,tags
子目录。这是 SVN 的标准布局。
-
检出空的
trunk
目录:- 在本地电脑上找一个空位置,右键 -> SVN 检出 (SVN Checkout…)。
- URL 填写你刚刚创建的
trunk
目录的地址:svn://server/repo/MyNewProject/trunk
。 - 指定一个本地的空文件夹作为检出目录,例如
D:\MyNewProject_work
。
-
添加文件并提交:
- 将你本地已有的项目文件(除了
.git
,.idea
等不需要版本控制的文件)复制到D:\MyNewProject_work
这个新文件夹里。 - 回到
D:\MyNewProject_work
文件夹,你会看到所有新文件都带有一个蓝色的问号图标。 - 在文件夹空白处右键 -> TortoiseSVN -> 添加 (Add…)。在弹出的窗口中,勾选所有你想加入版本控制的文件,然后确定。
- 添加后,文件图标会变成一个蓝色的加号。
- 最后,在文件夹空白处右键 -> SVN 提交 (SVN Commit…),填写日志信息(如 “Initial commit of project files.”),将这些文件首次提交到服务器。
- 将你本地已有的项目文件(除了
总结与对比
特性 | 方式一:导入 (Import) | 方式二:创建后检出 (Create & Checkout) |
---|---|---|
初始动作 | 在本地文件夹上右键 -> 导入 | 在服务器上用版本库浏览器创建目录结构 |
优点 | 操作快,一步到位把文件传上服务器 | 结构清晰,从一开始就遵循trunk/branches/tags 标准,是最佳实践 |
缺点 | 原始文件夹不会成为工作副本,必须重新检出,容易让新手混淆 | 步骤稍多(创建->检出->复制->添加->提交) |
适用场景 | 快速将一个已存在的、混乱的项目归档到SVN | 所有新项目的标准流程,强烈推荐 |
结论:
虽然你可以直接“导入”本地文件夹,但我们强烈推荐使用第二种方式。它能让你从一开始就建立起规范的项目结构,避免了“导入”后忘记重新检出而带来的困惑。
Merge与Switch区别
核心区别:操作的对象不同
-
Merge
(合并):操作的是 文件内容。它的目的是将一个路径下的改动(即差异 diff)应用到另一个路径下。它关心的是“代码发生了什么变化”。 -
Switch
(切换):操作的是 工作副本的指向。它的目的是让你本地的这个文件夹,从指向服务器的 A 地址,改为指向 B 地址。它关心的是“我的本地文件夹现在应该跟服务器的哪个目录同步”。
如果在这个场景下使用 Switch
会发生什么?
让我们回顾一下您的操作流程和当前状态:
- 您从
trunk
创建了一个新分支branches/project-B-branch
。 - 您
checkout
了这个新分支,所以您的本地工作副本project-B-branch
的内容和trunk
(也就是原来的project-A
) 是完全一样的。 - 您的目标是:把
project-B
独有的那些修改,应用到这个新分支上。
现在,如果您在这个本地工作副本上执行 switch
,并把 URL 指向原始的 project-B
目录:
svn switch svn://server/repo/project-B
结果将会是:
您本地 project-B-branch
文件夹里的所有内容,都会被完全替换成 project-B
目录的内容。
这会产生两个灾难性的后果:
- 丢失了与
trunk
的关联:您辛辛苦苦从trunk
创建的分支,其内容被完全覆盖了。所有来自trunk
(即project-A
) 的代码和历史基础都消失了。 - 破坏了分支关系:您的本地工作副本现在实际上就是
project-B
的一个检出。它和trunk
之间不再有 SVN 所能理解的“父子/分支”关系。未来的合并将变得不可能或极度困难。
简单来说,switch
会让您前功尽弃。
为什么必须使用 “合并两个不同的树”?
这个特殊的合并功能,正是为了解决您这样的场景而设计的。它的工作原理非常精妙:
-
它不是简单的文件覆盖。
-
它会进行一次“三方比较”:
- 基准 (Base):您指定的
From
URL,即project-A
。 - 目标 (Target):您指定的
To
URL,即project-B
。 - 接收方 (Receiver):您当前的本地工作副本 (
project-B-branch
)。
- 基准 (Base):您指定的
-
SVN 会在后台计算出:“
project-B
相对于project-A
到底多做了哪些修改?”。它会生成一个包含所有这些差异的“补丁”。 -
然后,SVN 将这个“补丁”应用到您的本地工作副本上。
最终效果是: 您的 project-B-branch
工作副本,保留了从 trunk
(即 project-A
) 继承下来的所有内容和历史,并在此基础上,只增加了 project-B
所特有的那部分修改。
这才是真正意义上的“创建分支并迁移修改”,它完美地保留了所有的历史脉络,为将来从 trunk
向这个分支合并更新铺平了道路。
总结对比
特性 | Merge (合并两个不同的树) | Switch (切换) |
---|---|---|
目的 | 将一个路径相对于另一个路径的内容差异,应用到当前工作副本。 | 更改当前工作副本所指向的服务器URL。 |
操作对象 | 文件内容 (The Diff) | 版本库指针 (The Pointer) |
对工作副本的影响 | 修改、增加、删除文件内容,以反映出差异。保留原有的历史基础。 | 完全替换工作副本的内容,使其与新URL的内容一模一样。 |
在此场景下的作用 | 正确:将B的独特改动“嫁接”到基于A创建的新分支上。 | 错误:将新分支完全变成B的副本,切断与A的联系。 |
结论:
请务必按照原计划使用 “合并两个不同的树” (Merge two different trees)。这个步骤虽然看起来复杂,但它是唯一能正确建立 project-A
和 project-B
之间逻辑分支关系,并保留所有必要历史信息的方法。这是一次性的设置,会让您后续的开发和合并工作变得简单可靠。
tortoisesvn怎么查看当前url对应的本地目录
似乎没法直接这样做,但是可以使用“检查修改”功能 (最推荐、最强大的方法)
这是查找所有工作副本的最佳方法,因为它会扫描并列出您计算机上所有被 TortoiseSVN 缓存的工作副本。
在任意位置(比如桌面空白处)右键,选择 TortoiseSVN -> 检查修改 (Check for modifications…)。
会弹出一个空的“检查修改”对话框。注意,此时列表是空的。
点击对话框左上角的 “检出工作副本 (Scan Working Copies)” 按钮。这个按钮的图标是一个文件夹和放大镜。
TortoiseSVN 会开始扫描您的硬盘,查找所有 SVN 工作副本。扫描完成后,它会把找到的所有工作副本都列出来。
查看结果:现在,您会看到一个列表,这个列表包含两列关键信息:
路径 (Path):这正是您要找的本地目录。
URL:这是该本地目录对应的服务器 URL。
您现在可以轻松地在 URL 列中找到您想要查询的那个 URL,然后查看它左边的 路径 (Path),就知道它在您本地电脑的哪个位置了。您甚至可以直接在列表中的路径上右键,选择“在浏览器中打开”来直接跳转到那个文件夹。