Branching project and solution files


In the comments of an earlier post, Pier asked:



“When you branch (share and branch) an entire solution you make a 1:1 copy of the files in it, .sln file too! inside .sln file there are absolute sourcesafe paths of the projects in it and .sln file on the branch still points to original projects on the trunk… I have to change them by hand.

Does Hatteras resolve this strange problem?”


Good news, Pier – the answer is “yes” – but I’m sure you wanted to know at least a little more than yes or no. 🙂


Solutions and project files under source control will use relative paths, so branching shouldn’t automatically break them. I’m sure we’ll have to chase down a few corner cases relating to this, because this is one of those things where there simply is no easy answer, but it should “Just Work” for those who aren’t going out of their way to create a weird Visual Studio / Hatteras environment.


I don’t know a ton about solution and project file formats, but I know there’s some ability to handle these sorts of complications in there. Relative paths and some hint paths related to source control are the answer. If anyone out there wants more specific details, let me know in the comments or via email – maybe I can work up a sample and walk you through how we deal with it, if there’s interest. It’s straying out of my area of direct experience but it’s an interesting problem.

Comments (4)

  1. What if the solution references a project that’s up a directory? That is, a solution in Source/Foo references a project in Source/Bar. If I branch Source, will the solution point to the branched project? What if I only branch Foo? Will the solution be able to find the project?

    This is an important question because of the necessity of using project references over DLL references in many cases. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/tdlg_rm.asp

    for more about this. Sharing one project among two solutions means that the solution reference projects that are not in a subdirectory of the directory containing the solution.

  2. Chris says:

    David,

    I’m going to work through an example before I answer definitively, but I’m pretty sure the answer is yes. I know project reference paths can be relative, but that probably doesn’t answer your question on its own.

    I also have projects with items added as links that are in peer or parent trees as well, and they have relative paths, which makes me think this will work.

    Stay tuned…Doing this in a way that’s at all approachable will take some work 🙂

  3. David:

    I’m in a similar situation to Chris in not working on this code directly, but from the last time it was explained to me, here’s the general idea:

    Project references and a lot of "things nearby" references are stored in the .sln/.*proj files as local-relative paths. As such, if Source/Foo refers to Source/Bar, he’ll do so as "../Bar" and because of that, when you "branch Source Target", Target/Foo will refer to Target/Bar because of the local-relative path resolving as such. This is definitely the real goal scenario we’re hoping people will be in.

    We provide hint paths as well which are basically server-relative paths. The idea is that the local-relative path may not exist because you decided to move stuff around on disk or whatever. Say:

    $/proj1/foo is on disk as c:srcfoo

    $/proj2/bar is on disk as c:srcbar

    c:srcfoo has a project reference to c:srcbar so we have a local-relative path of ..bar but a server-relative path of ../../proj2/bar

    Now you branch $/proj1/foo to $/proj3/foo and decide to put $/proj3/foo on disk at d:foo. Note that this is one of those "weird" scenarios – we didn’t branch at a common ancestor of foo and bar, so the project dependency isn’t wholly contained within our source and targets.

    We fail our local-relative check (d:bar doesn’t exist) so we fall back to server-relative – we’re $/proj3/foo so we go server-relative and get back to $/proj2/bar as our reference. Now that we have the correct server path (and that server path is indeed valid – yay!) then we follow the mapping back to our valid local path of d:foo

    Since we just branched foo and not bar, this is obviously the best we can do. Also, we eventually resolved to the "right" reference, but as the location is no longer on the same disk, we can’t store this as a "real" local-relative path any more, we have to store the reference as d:foo – because of this, future branching is going to be potentially weird.

    There’s lots of situations where both local-relative and server-relative can fail, and those are the corner cases where you’re going to have to fix the mappings yourself or with a UI. I’m not familiar with how the fixup works, so I’ll have to defer if you’re curious about that.

    The "right" approach (IMHO, there’s forthcoming docs/articles/books on this – the MSF rocks) is:

    – references to other things that "should branch with" the given project will be within the same dir.

    – to be specific, your case of Source/Foo referencing Source/Bar works nicely – both are within Source so as long as you do your branching at the Source (or higher) level, the local-relative and server-relative paths are both correct and everybody’s happy. As my situation showed, if branches are done at or below the "tree level" of your dependencies, things can get ugly. This doesn’t mean things break, but it means we’re less likely to just automagically do the right thing.

    – references to things that shouldn’t "branch with" a project should be outside the branch directory (kind of obviously).

    – meaning if you have a common corporate logo image or something similar that all branches reference the same file, he should be outside of the Source directory.

    Quick example scenario, starting with:

    $/

    KillerApp/

    trunk/

    proj1_source/

    proj2_source/

    images/

    docs/

    We’re getting close to a 1.0 release, so we branch our "trunk" over to a rel-1.0 branch so we can do bugfixes, stabilization, etc. without slowing down "real" development that’s heading to a 1.1 or 2.0 release.

    Then we look like:

    $/

    KillerApp/

    trunk/

    proj1_source/

    proj2_source/

    images/

    rel-1.0/

    proj1_source/

    proj2_source/

    images/

    docs/

    "trunk" is where your main devel is going on, rel-1.0 is a branch of trunk (you can merge in either direction, any changes from either side to the other) and docs.

    Obviously, the local-relative and server-relative paths didn’t need to change when we branched – given relative paths, the proj2 and proj1 references to each other work just as well in trunk and rel-1.0.

    Anyway, this is getting kind of long – hope it gets the gist across, just trying to get a quick response on here while waiting for a build to finish 🙂 If there’s anything specifically wrong/broken/confusing about what I’ve said, feel free to ask, hopefully I’ll have more time when I next post 🙂