33

大多数颠覆工具使用 /trunk、/branches 和 /tags 创建默认存储库布局。该文档还建议不要为每个项目使用单独的存储库,以便更轻松地共享代码。

遵循该建议导致我拥有一个具有以下布局的存储库:

/树干
      /项目1
      /项目2
/分支机构
         /项目1
         /项目2
/标签
     /项目1
     /项目2

等等,你明白了。随着时间的推移,我发现这个结构有点笨拙,我突然想到对这些建议有另一种解释,例如:

/项目1
         /树干
         /分支机构
         /标签
/项目2
         /树干
         /分支机构
         /标签       

那么,人们使用哪种布局,为什么?或者 - 还有另一种方法可以做我完全错过的事情吗?

4

6 回答 6

33

我发现Subversion Repository Layout博客文章很好地总结了这一点:

(...) 社区采用了几种常见的布局作为最佳实践,因此可以将这些视为建议。如果您的存储库可供公众访问,那么遵循这些约定可能会使访问过其他 Subversion 存储库的用户更容易找到他们正在寻找的内容。

常用的布局有两种:

trunk
branches
tags

对于包含单个项目或一组彼此紧密相关的项目的存储库,第一种布局是最佳选择。这种布局很有用,因为使用单个命令可以很容易地对整个项目或一组项目进行分支或标记:

svn copy url://repos/trunk url://repos/tags/tagname -m "Create tagname"

这可能是最常用的存储库布局,并且被许多开源项目使用,例如 Subversion 本身和 Subclipse。这是大多数托管网站(如 Tigris.org、SourceForge.net 和 Google Code)所遵循的布局,因为这些网站上的每个项目都有自己的存储库。

下一个布局是包含不相关或松散相关项目的存储库的最佳选择

ProjectA
   trunk
   branches
   tags
ProjectB
   trunk
   branches
   tags

在此布局中,每个项目都会收到一个顶级文件夹,然后在其下创建 trunk/branches/tags 文件夹。这与第一个布局实际上是相同的布局,只是不是将每个项目放在自己的存储库中,而是将它们都放在一个存储库中。Apache Software Foundation 将这种布局用于他们的存储库,该存储库将所有项目都包含在一个存储库中。

使用这种布局,每个项目都有自己的分支和标签,并且很容易使用一个命令为该项目中的文件创建它们,类似于前面显示的命令:

svn copy url://repos/ProjectA/trunk url://repos/ProjectA/tags/tagname -m "Create tagname"

在此布局中您不能轻松地创建一个包含来自 ProjectA 和 ProjectB 的文件的分支或标签。您仍然可以这样做,但它需要多个命令,并且您还必须决定是否要为涉及多个项目的分支和标签创建一个特殊文件夹。如果您需要经常这样做,您可能需要考虑第一个布局。

所以,套用一句:

  • 将第一个布局用于单个或多个相关项目。
  • 对不相关的项目使用第二种布局。

整篇文章值得一读。

于 2010-04-09T22:12:52.960 回答
8

第二种布局是要走的路。一个很好的理由是允许或拒绝开发人员使用其中一个项目。

于 2010-04-09T22:06:43.533 回答
5

我更喜欢第二种。第二种,如果两个项目的人的权限不同,实现起来就更容易了。

于 2010-04-09T22:06:01.520 回答
3

我非常喜欢第二种,如果需要,使用 maven 或 ant/ivy 从其他项目中获取工件。

我也更喜欢每个存储库有一个项目,或者少量相关的存储库。

它简化了访问控制,这在存储库级别比在存储库中的路径级别更容易 - 特别是在针对 LDAP 进行身份验证时。

备份/恢复操作最初会稍微复杂一些,因为您必须遍历所有存储库才能进行热复制,但在不幸的情况下,您只需恢复一个存储库 - 其他存储库不需要脱机或丢失任何数据. 随着项目的终止,可以简单地删除存储库,从而为您节省未来备份的空间。

当每个存储库只有一个项目(或少量相关项目)时,挂钩脚本会变得更简单,您无需检查受影响的路径即可有条件地在挂钩中采取行动。

正如 retracile 所指出的,如果您想使用 svndumpfilter 有选择地导出,一个单一的存储库可能会是一个巨大的痛苦——导致它死亡的更改路径的数量可能很高。

为 svn 的未来版本升级存储库模式需要更多的努力——你必须做n次而不是一次......但它可以编写脚本,你不需要一次协调每个人。

如果有人提交了密码,而您必须删除它,您可以在一个 repo 中快速进行转储/过滤/重新加载,而不会影响其他团队。

如果你走这条路,有一条建议——每个 repo 有一个不同的 .conf 文件,而不是一个巨大的文件,同样它更容易管理,并且提供一些时间戳将会过时的安慰——如果有什么不对劲,你可以寻找最近的变化更容易。

于 2011-05-05T15:28:56.177 回答
1

我决定硬着头皮重组我的存储库。我写了一个小程序来辅助(下)。我遵循的步骤是:

  1. 制作原始存储库的备份副本。
  2. svn checkout 整个存储库。这花了很长时间和大量的磁盘空间。
  3. 在上一步的工作副本上从下面运行程序。
  4. 检查修改后的工作副本并整理任何遗留问题(例如svn delete过时的trunktagsbranches文件夹)
  5. svn commit回到存储库。

整个过程需要时间,但我决定采用这种方法,因为修改工作副本比破解实时存储库要安全得多,而且我可以选择在一切都出错时简单地丢弃工作副本,以解决任何问题在工作副本中并将整个重组提交为单个修订。

这是我用来移动的 C# 代码。需要 SharpSvn 库。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using SharpSvn;

/**
 * 
 * Program operation:
 * 1. Parse command line to determine path to working copy root
 * 2. Enumerate folders in the /trunk 
 * 3. Restructure each project folder in /trunk
 * 
 * 
 * Restructure a Project:
 * 1. Get the project name (folder name in /trunk/{Project})
 * 2. SVN Move /trunk/{Project} to /{Project}/trunk
 * 3. Reparent Project, branches
 * 4. Reparent Project, tags
 * 
 * Reparent(project, folder)
 * If /{folder}/{Project} exists
 *   SVN Move /{folder}/{Project} to /{Project}/{Folder}
 * else
 *   Create folder /{Project}/{Folder}
 *   SVN Add /{Project}/{Folder}
 * 
 **/

namespace TiGra.SvnRestructure
{
    /// <summary>
    /// Restructures a Subversion repository from
    ///     /trunk|branches|tags/Project
    /// to
    ///     /Project/trunk|branches|tags
    /// </summary>
    internal class Program
    {
        private static string WorkingCopy;
        private static string SvnUri;
        private static string Branches;
        private static string Tags;
        private static string Trunk;

        private static SvnClient svn;
        private static List<string> Projects;

        private static void Main(string[] args)
        {
            ProcessCommandLine(args);
            CreateSvnClient();
            EnumerateProjectsInTrunk();
            RestructureProjects();
            Console.ReadLine();
        }

        private static void RestructureProjects()
        {
            foreach (var project in Projects)
            {
                RestructureSingleProject(project);
            }
        }

        private static void RestructureSingleProject(string projectPath)
        {
            var projectName = Path.GetFileName(projectPath);
            var projectNewRoot = Path.Combine(WorkingCopy, projectName);
            bool hasBranches = Directory.Exists(Path.Combine(Branches, projectName));
            bool hasTags = Directory.Exists(Path.Combine(Tags, projectName));
            Reparent(Path.Combine(Trunk, projectName), Path.Combine(projectNewRoot, "trunk"));
            if (hasBranches)
                Reparent(Path.Combine(Branches, projectName), Path.Combine(projectNewRoot, "branches"));
            if (hasTags)
                Reparent(Path.Combine(Tags, projectName), Path.Combine(projectNewRoot, "tags"));
        }

        private static void Reparent(string oldPath, string newPath)
        {
            Console.WriteLine(string.Format("Moving {0} --> {1}", oldPath, newPath));
            svn.Move(oldPath, newPath, new SvnMoveArgs(){CreateParents = true});
        }

        private static void EnumerateProjectsInTrunk()
        {
            var list = EnumerateFolders("trunk");
            Projects = list;
        }

        /// <summary>
        /// Enumerates the folders in the specified subdirectory.
        /// </summary>
        /// <param name="trunk">The trunk.</param>
        private static List<string> EnumerateFolders(string root)
        {
            var fullPath = Path.Combine(WorkingCopy, root);
            var folders = Directory.GetDirectories(fullPath, "*.*", SearchOption.TopDirectoryOnly).ToList();
            folders.RemoveAll(s => s.EndsWith(".svn")); // Remove special metadata folders.
            return folders;
        }

        private static void CreateSvnClient()
        {
            svn = new SharpSvn.SvnClient();
        }

        /// <summary>
        /// Processes the command line. There should be exactly one argument,
        /// which is the path to the working copy.
        /// </summary>
        private static void ProcessCommandLine(string[] args)
        {
            if (args.Length != 1)
                throw new ArgumentException("There must be exactly one argument");
            var path = args[0];
            if (!Directory.Exists(path))
                throw new ArgumentException("The specified working copy root could not be found.");
            WorkingCopy = path;
            Branches = Path.Combine(WorkingCopy, "branches");
            Tags = Path.Combine(WorkingCopy, "tags");
            Trunk = Path.Combine(WorkingCopy, "trunk");
        }
    }
}
于 2011-08-21T21:05:47.373 回答
1

从 svnbook参考Repository Layout

有一些标准的、推荐的方法来组织存储库的内容。大多数人创建一个trunk目录来保存开发的“主线”,一个包含分支副本的分支目录,以及一个包含标签副本的标签目录。

/
   trunk/
   branches/
   tags/

如果存储库包含多个项目,管理员通常会按项目索引其布局。

这是这种布局的一个示例:

/
   paint/
      trunk/
      branches/
      tags/
   calc/
      trunk/
      branches/
      tags/

当然,您可以随意忽略这些常见的布局。您可以创建任何类型的变化,只要最适合您或您的团队。请记住,无论您选择什么,这都不是永久的承诺。您可以随时重新组织您的存储库。因为分支和标签是普通目录,所以 svn move 命令可以随意移动或重命名它们。从一种布局切换到另一种布局只是发出一系列服务器端动作的问题;如果您不喜欢存储库中事物的组织方式,只需调整目录即可。

但请记住,虽然移动目录很容易,但您也需要考虑其他用户。您的杂耍可能会使用户对现有的工作副本感到迷惑。如果用户有一个特定存储库目录的工作副本,并且您的 svn move 子命令从最新版本中删除了路径,那么当用户下次运行 svn update 时,她会被告知她的工作副本代表一个不再存在的路径。然后她被迫 svn 切换到新位置。

于 2017-11-18T11:18:36.413 回答