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?
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.
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.
I prefer the second. With the second, if people's permissions are different between the two projects, it's easier to implement.
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.
I decided to bite the bullet and restructure my repository. I wrote a small program to assist (below). The steps I followed were:
- Make a backup copy of the original repository.
svn checkout
the entire repository. This took a long time and a lot of disk space.
- Run the program from below on the working copy from the previous step.
- Examine the modified working copy and tidy up any left over issues (eg.
svn delete
the obsolete trunk, tags and branches folders)
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");
}
}
}
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.