How many projects have you ever worked on that involved a 'configuration source' that you wanted to maintain as a separate GIT repository from the rest of the code? A typical example would be a project laid out something like this:

-+- config
 |  |
 |  +- config.json
 |  |
 |  +- config.json.sample
 |
 +- controllers
 |
 +- models
 |
 +- views
 .
 .
 .

You'd love it if that config/ folder up there was managed as a separate GIT repository. More specifically, you'd love it if only the config/config.json file was maintained in its own repo while everything else (the controllers, models, and other folders) were managed in a repo of its own.

Give yourself a little time to figure out how you could manage a folder with overlapping GIT repositories with regular GIT. It is possible but it's hard. If you know of an easy way please feel free to reach out to me and explain how. For me, it seemed like finding a solution using pure GIT would require more energy than I was willing to spend.

So, in lieu of using plain old GIT to manage overlapping repositories I use: Multigit.

Multigit

Multigit is an excellent way to manage the scenario I describe in the introduction. You can find Multigit here. You can check out the documentation for the basic usage there too.

Installing Multigit

The easiest thing to do is to put Multigit somewhere in your PATH. You'll call Multigit through ./mgit or mgit if you're in Windows.

Quick Start

Here's the way I usually start off a project that I know will involve multiple GIT repos:

$ mkdir my_project
$ cd my_project

$ mgit clone https://github.com/bob/project_code
$ mgit clone https://github.com/bob/project_config

I clone the base project code: the controllers, models, views - even the sample configuration files - from one repo and I pull-in the project's working config from another repo.

My project's working configs might be in a private repository in Gitlab while my project's general code is staged in a public repository in Github.

This all allows me to nicely separate code from configuration.

Day-by-Day

The day-by-day usage of multigit is not much different from using regular old git except we usually have to include the repo name in our commands. Here's an example:

Let's say I've been updating the config.json.sample file. You'll recall that the config.json.sample file is a part of the code repo. The idea is that the sample config file will be shipped with the code.

In order to add, commit and push my changes to the project_code repo I simply:

mgit project_code add config/config.json.sample
mgit project_code commit -m "my commit"
mgit project_code push

Essentially the only burden is including the repo name in our normal git-style commands.

Let's say I updated my running, working config config.json:

mgit project_config add config/config.json
mgit project_config commit -m "changed setting x"
mgit project_config push

Pretty clean and easy!

Things That Might Seem Troubling

You might imagine that a large project would eventually become more difficult to manage. You might wonder:

  • "How do I know what changes are outstanding? What's the status of the project?"
  • "How do I know what repo "file_x" belongs to?"
  • "For that matter: How do I know what repos belong to this multi-layered project?"
  • "Can I ignore files?"
  • "I'm lazy. I can't be typing mgit project command all day long!"

All of these issues have been addressed. Let's go through some more scenarios:

What's My Status?

You step into a project and you need to know what the status is. Try:

mgit st[atus]

This will show you the current status of any changed files in the folder - no matter what repo they belong to.

What Repos are Here?

If you want to know all the cloned repos under this folder just do:

mgit ls

In our opening example you'd see:

project_code
project_config

You can do the opposite too. Let's say you want to know what repo "file_x" belongs to. Just do:

mgit which <filename>

Using our opening example if you wanted to find out which repo the config/config.json.sample file belonged to you would do:

mgit which config/config.json.sample

…and it would render:

project_code

Now you know what repo the config.json.sample file belongs to - even if you didn't set up this project.

So, What's Really Going on Here?

Multigit is simply a clever wrapper for git. Even the .git/ directory under your project is wrapped in the .mgit/ directory.

Under .mgit/ you'll find sub-directories for each project repo you've added. And under those sub-directories you'll finally see the familiar .git/ folder.

Multigit still respects the .gitignore file, though. So, you still have control over what files should be ignored… which one small adjustment:

By default, Multigit adds a '*' at the end of the .gitignore file. This effectively ignores everything in your folder! The idea behind this is to prevent us from accidentally ADD'ing all your folder's files to multiple repos. This would lead to frustration, confusion and rage since multiple repos would be tracking the same files.

Because of this default safety-net we have to force file additions to any repo. Let's say, for example, that I want to add the file controllers/default.controller.js to my project_code repo. Instead of simply typing mgit project_code add controllers/default.controller.js, I should do this:

mgit project_code add -f controllers/default.controller.js

Speaking of Adding Files - How Do I Know What Files are Not Currently Tracked?

All of the above is great when you at least know:

  • the repo(s) you're using or
  • the file(s) that are tracked

But what happens when you create a new files? How can you reliably know what files are un-tracked but ought to be tracked?

Try this:

mgit ls-untracked

This works great… until you start using NPM or add some large library to your project that you don't want to track and you don't want to see!

For example, if you're building a Node application and you've got packages you're managing through NPM and a package.json file, you'll see something like this when you run mgit ls-untracked:

mgit ls-untracked

config/my_untracked_config.json
db/my_untracked.db
node_modules/abbrev/abbrev.js
node_modules/abbrev/LICENSE
node_modules/abbrev/package.json
node_modules/abbrev/README.md
node_modules/accepts/HISTORY.md
.
.
.
(a few more pages of node modules
.
.
.
(a few more...)
.
.
.
node_modules/yargs/README.md
package-lock.json
public/files/gcpftn262ij31
public/files/tvo6xtgfmox91
public/thumbs/gcpftn262ij31
public/thumbs/tvo6xtgfmox91
views/my_untracked_view_file

Ugly!

Here's the way I get around this issue:

mgit ls-untracked | grep -v node_

Now, I get a more manageable, relevant list of un-tracked files. Now, I can make a better decision about what to do with them.

Conclusion

Multigit has become a standard tool in my programming kit. Learn more at: Multigit at Github