Git – Branching a subdirectory while keeping commits from other directories

gitsvn

I currently have a single pre-existing Git repo for a client/server solution that contains three subdirectories for 3 sub-projects:

/client  - An Angular2 SPA application (client-side TypeScript)
/dtos    - Language-agnostic web-service DTO (data contract) definitions written in T4
    /client - T4-generated TypeScript typings *.d.ts files
    /server - T4-generated C# DTO classes
/server  - The web-service

So far this is all in a single master branch, with appropriate temporary dev/ branches that are merged when their work is complete:

o--o--o--o - master
 \   /
  o-o      - dev/foo

We need to do some A/B (or rather, A/B/C/D) testing on radically different UX options in the Angular2 project – I would like to branch-off for each UX option, to generate different builds we can test, then after our UX research is done we merge that specific branch back into master for the remainder of the project:

o             - master
 \
  o--o--o---o - ux/red-background
   \
    o--o----o - ux/new-sidebar
        \
         o--o - ux/new-sidebar-and-banner

However I only want to branch the /Client directory off – so the /DTOs and /Server directories always contain the latest code.

In SVN and TFS this is easily doable because branches can be made from subdirectories, not just the entire repository (as they are with git), so if this were an SVN project then I would do something like this:

cd trunk  # this is the solution root with the client, dtos and server directories
svn copy ^/trunk/client ^/branches/red-background
svn copy ^/branches/red-background ^/branches/new-sidebar
# then after some commits to 'new-sidebar'...
svn copy ^/branches/new-sidebar ^/branches/new-sidebar-and-banner

Then to build the solution with each different branch of /client we modify the symlink:

cd trunk
# to 'checkout' the red-background version prior to building:
ln -s branches/red-background client
# or the new-sidebar version:
ln -s branches/new-sidebar client

The main advantage of this approach is that commits can continue to be made to the /dtos and /server directories without affecting client at all – regardless of whatever branch of client is being worked-in.

Whereas with git, a branch is a separate timeline entirely, so this scenario becomes impossible.

I understand that to get the result I want, I would still need to create different branches for each version to test or build, but changes to /dtos or /server would still need to happen in master (just like in trunk in SVN) but after every commit everyone working on the different client branches would need to rebase or merge from master, and that has the potential to get really ugly (especially if individual people rebase from multiple commits to /server choosing their own rebase points – they might end up with identical codebases but different commit hashes).

What's the solution?

(Another solution is to move client to its own git repo – then merging it back into the solution repo when it's all done – I'm not opposed to this but let's pretend that we've hit our maximum repo count in our organizational GitHub account)

Best Answer

The ideal way is to move client to its own repository and make it a submodule of the super project. But if you do not want to create a new repository you can try one of these solutions.

Multiple directories in one branch

If you are able to configure your project to use directory /client_A instead of /client then you can clone the directory /client to multiple directories

  • /client_A
  • /client_B
  • /client_C

and do separate development in each of these directories. Up to this point it is enough to use only master branch.
Later when you decide to "merge" directory /client_A to directory /client create a branch named e.g. branch_A. In this branch replace the content of directory /client with content of directory /client_A, delete directory /client_A and commit this state. Then merge branch_A to master and solve conflicts if any.
Do the same with directories /client_B and /client_C if you want to "merge" them with directory /client. You can choose different times for "merging" directories /client_A, /client_B and /client_C into directory /client.

Multiple branches without cloning directories

Choose a suitable start time and make as many branches from master as you want

  • branch_A
  • branch_B
  • branch_C

From this start time use strictly

  • master for changes in directories other than /client (/dtos and /server)
  • branch_A, branch_B, branch_C for changes in /client directory

Use branch_A, branch_B, branch_C for building different versions of your project. Merge master branch into these branches before building. Avoid merging these branches into master branch (the opposite direction).
Later you can merge e.g. branch_A into master. But after doing this you can't merge master into branch_B or branch_C. This means that you need to choose suitable end time and merge all branches branch_A, branch_B, branch_C into master at this time.

Related Topic