Subversion 存储库布局

发布于 2024-08-28 11:58:23 字数 513 浏览 7 评论 0原文

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

遵循这个建议,我拥有了一个具有以下布局的存储库:

/trunk
      /Project1
      /Project2
/branches
         /Project1
         /Project2
/tags
     /Project1
     /Project2

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

/Project1
         /trunk
         /branches
         /tags
/Project2
         /trunk
         /branches
         /tags       

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

Most subversion tools create a default repository layout with /trunk, /branches and /tags. The documentation also recommends not using separate repositories for each project, so that code can be more easily shared.

Following that advice has led to me having a repository with the following layout:

/trunk
      /Project1
      /Project2
/branches
         /Project1
         /Project2
/tags
     /Project1
     /Project2

and so on, you get the idea. Over time, I've found this structure a bit clumsy and it occurred to me that there's an alternative interpretation of the recommendations, such as:

/Project1
         /trunk
         /branches
         /tags
/Project2
         /trunk
         /branches
         /tags       

So, which layout do people use, and why? Or - is there another way to do things that I've completely missed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

段念尘 2024-09-04 11:58:24

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

  1. 制作原始存储库的备份副本。
  2. svn checkout 整个存储库。这花费了很长的时间和大量的磁盘空间。
  3. 在上一步的工作副本上运行下面的程序。
  4. 检查修改后的工作副本并清理所有遗留问题(例如,svn delete 过时的主干标签分支 文件夹)
  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");
        }
    }
}

I decided to bite the bullet and restructure my repository. I wrote a small program to assist (below). The steps I followed were:

  1. Make a backup copy of the original repository.
  2. svn checkout the entire repository. This took a long time and a lot of disk space.
  3. Run the program from below on the working copy from the previous step.
  4. Examine the modified working copy and tidy up any left over issues (eg. svn delete the obsolete trunk, tags and branches folders)
  5. svn commit back to the repository.

This whole process took time, but I decided to take this approach because modifying a working copy is a lot safer than hacking up a live repository and I had the options to simply throw away the working copy if it all went wrong, to fix any issue in the working copy and commit the entire restructure as a single revision.

Here's the C# code I used to do the moving. Requires SharpSvn library.

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");
        }
    }
}
起风了 2024-09-04 11:58:24

请参阅 存储库布局 来自 svnbook

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

<前><代码>/
树干/
分支机构/
标签/

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

这是此类布局的示例:

<前><代码>/
画/
树干/
分支机构/
标签/
计算/
树干/
分支机构/
标签/

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

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

Refer Repository Layout from the svnbook

There are some standard, recommended ways to organize the contents of
a repository. Most people create a trunk directory to hold the “main
line” of development, a branches directory to contain branch copies,
and a tags directory to contain tag copies.

/
   trunk/
   branches/
   tags/

If a repository contains multiple projects, admins typically index
their layout by project.

here's an example of such a layout:

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

Of course, you're free to ignore these common layouts. You can create
any sort of variation, whatever works best for you or your team.
Remember that whatever you choose, it's not a permanent commitment.
You can reorganize your repository at any time. Because branches and
tags are ordinary directories, the svn move command can move or rename
them however you wish. Switching from one layout to another is just a
matter of issuing a series of server-side moves; if you don't like the
way things are organized in the repository, just juggle the
directories around.

Remember, though, that while moving directories is easy to do, you
need to be considerate of other users as well. Your juggling can
disorient users with existing working copies. If a user has a working
copy of a particular repository directory and your svn move subcommand
removes the path from the latest revision, then when the user next
runs svn update, she is told that her working copy represents a path
that no longer exists. She is then forced to svn switch to the new
location.

土豪 2024-09-04 11:58:23

我发现 Subversion 存储库布局 博客文章很好地总结了这一点:

(...)有几种常见的布局
已被通过的
社区作为最佳实践和
因此人们可以将这些视为
建议。如果您的存储库是
向公众开放,如下
这些约定可能会让事情变得更容易
对于访问过其他网站的用户
Subversion 存储库查找什么
他们正在寻找。

有两种常用的布局:

<前><代码>主干
分支机构
标签

第一个布局是最好的选择
对于包含以下内容的存储库
单个项目或一组项目
与每一个都紧密相关
其他
。这种布局很有用,因为
分支或标记很简单
整个项目或一组项目
使用单个命令:

svn copy url://repos/trunk url://repos/tags/tagname -m "创建标记名"

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

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

<前><代码>项目A
树干
分支机构
标签
项目B
树干
分支机构
标签

在此布局中,每个项目都会收到
一个顶级文件夹,然后是
trunk/branches/tags 文件夹是
在其下方创建。这确实是
与第一个布局相同的布局,它是
只是这样,而不是把每个
项目在自己的存储库中,他们
全部都在一个存储库中。这
Apache 软件基金会使用这个
他们的存储库的布局
将他们的所有项目包含在一个中
单个存储库。

通过这种布局,每个项目都有自己的
拥有自己的分支和标签,这很容易
为其中的文件创建它们
使用一个命令进行项目,类似于
之前显示的:

svn copy url://repos/ProjectA/trunk url://repos/ProjectA/tags/tagname -m "创建标记名"

在这方面你不能轻易做到的事情
布局是创建一个分支或标签
包含来自 ProjectA 和
项目B.你仍然可以这样做,但它
需要多个命令,并且您
还必须决定是否要去

涉及的分支和标签
多个项目。如果你打算
需要经常这样做,你可能想要
考虑第一个布局。

因此,换句话来说:

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

整篇文章值得一读。

I find that the Subversion Repository Layout blog post summarizes this pretty well:

(...) there are several common layouts
that have been adopted by the
community as best practices and
therefore one could think of these as
recommendations. If your repository is
accessible to the public, following
these conventions might make it easier
for users that have accessed other
Subversion repositories to find what
they are looking for.

There are two commonly used layouts:

trunk
branches
tags

This first layout is the best option
for a repository that contains a
single project or a set of projects
that are tightly related to each
other
. This layout is useful because
it is simple to branch or tag the
entire project or a set of projects
with a single command:

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

This is probably the most commonly
used repository layout and is used by
many open source projects, like
Subversion itself and Subclipse. This
is the layout that most hosting sites
like Tigris.org, SourceForge.net and
Google Code follow as each project at
these sites is given its own
repository.

The next layout is the best option for
a repository that contains unrelated
or loosely related projects
.

ProjectA
   trunk
   branches
   tags
ProjectB
   trunk
   branches
   tags

In this layout, each project receives
a top-level folder and then the
trunk/branches/tags folders are
created beneath it. This is really the
same layout as the first layout, it is
just that instead of putting each
project in its own repository, they
are all in a single repository. The
Apache Software Foundation uses this
layout for their repository which
contains all of their projects in one
single repository.

With this layout, each project has its
own branches and tags and it is easy
to create them for the files in that
project using one command, similar to
the one previously shown:

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

What you cannot easily do in this
layout is create a branch or tag that
contains files from both ProjectA and
ProjectB. You can still do it, but it
requires multiple commands and you
also have to decide if you are going
to make a special folder for the
branches and tags that involve
multiple projects. If you are going to
need to do this a lot, you might want
to consider the first layout.

So, to paraphrase:

  • Use the first layout for a single or multiple related projects.
  • Use the second layout for non related projects.

The whole post is worth the read.

甜是你 2024-09-04 11:58:23

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

The second layout is the way to go. One good reason is to allow or deny a developer to work with one of the projects.

哭泣的笑容 2024-09-04 11:58:23

我更喜欢第二个。对于第二种,如果两个项目之间的人员权限不同,则更容易实现。

I prefer the second. With the second, if people's permissions are different between the two projects, it's easier to implement.

﹏雨一样淡蓝的深情 2024-09-04 11:58:23

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

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

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

备份/恢复操作一开始有点复杂,因为您必须循环遍历所有存储库才能进行热复制,但万一不幸,您只需恢复一个存储库 - 其他存储库不需要脱机或丢失任何数据。当项目终止时,可以简单地删除存储库,从而节省您未来备份的空间。

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

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

为未来版本的 svn 升级存储库模式需要更多的努力 - 您必须执行n次而不是一次...但是它可以编写脚本,并且您不需要立即协调每个人。

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

如果您走这条路,有一个建议 - 每个存储库都有一个不同的 .conf 文件,而不是一个巨大的文件,同样,它更容易管理,并且可以让您放心,某些时间戳将会过时 - 如果出现问题,您可以查找最近的变化更容易。

I greatly prefer the second, using maven or ant/ivy to ingest the artifacts from other projects if needed.

I also prefer to have a single project per repository, or a small number of related repositories.

It simplifies access control, which is easier at the repository level than the path level within the repository - particularly when authenticating against LDAP.

Backup/restore operations are a little more complicated initially as you have to loop through all the repositories to do a hot copy, but in the unlucky event you have to restore only one repo - the others needn't be taken offline or loose any data. As projects die, the repositories can simply be deleted thus saving you space on future backups.

Hook scripts become simpler when there is only one project (or a small number of related projects) per repository, you don't have to examine the affected path to conditionally take action in your hook.

As retracile noted, one monolithic repository is likely going to be a huge pain if you ever want to selectively export using svndumpfilter - the number of changed paths causing it to die is likely to be high.

Upgrading the repository schema for future versions of svn requires more effort - you have to do it n times rather than once... but it can be scripted and you needn't coordinate everyone at once.

If someone commits a password, and you have to obliterate it, you can do the dump/filter/reload quickly in one repo while not affecting other teams.

One piece of advice if you go this route - have a different .conf file per repo rather than one huge one, again it's easier to manage as well as providing comfort that some timestamps are going to be old - if something's amiss you can look for recent changes easier.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文