Subversion存储库布局

Tim*_*ong 31 svn repository-design

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

根据该建议,我得到了一个具有以下布局的存储库:

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

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

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

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

Pas*_*ent 32

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

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

有两种常用的布局:

trunk
branches
tags
Run Code Online (Sandbox Code Playgroud)

对于包含单个项目或一组彼此紧密相关的项目的存储库,此第一个布局是最佳选项.这种布局很有用,因为使用单个命令分割或标记整个项目或一组项目很简单:

svn copy url://repos/trunk url://repos/tags/tagname -m "Create tagname"
Run Code Online (Sandbox Code Playgroud)

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

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

ProjectA
   trunk
   branches
   tags
ProjectB
   trunk
   branches
   tags
Run Code Online (Sandbox Code Playgroud)

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

使用此布局,每个项目都有自己的分支和标记,并且可以使用一个命令轻松地为该项目中的文件创建它们,类似于之前显示的命令:

svn copy url://repos/ProjectA/trunk url://repos/ProjectA/tags/tagname -m "Create tagname"
Run Code Online (Sandbox Code Playgroud)

在此布局中您不能轻易做的是创建包含ProjectA和ProjectB中的文件的分支或标记.您仍然可以这样做,但它需要多个命令,您还必须决定是否要为涉及多个项目的分支和标记创建一个特殊文件夹.如果您需要做很多事情,可能需要考虑第一个布局.

所以,换句话说:

  • 对单个或多个相关项目使用第一个布局.
  • 非相关项目使用第二种布局.

整篇文章值得一读.


Dav*_*art 8

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


小智 5

我更喜欢第二个.第二,如果两个项目的人员权限不同,则更容易实现.


Tim*_*ong 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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)