At work we have a common library, call it libThing, that underlies several of the products we produce for one of our big customers. We bring it into the projects that use it with git submodules. And that's a bit of a problem.
Let me explain.
You see, the design of the submodules facility is clearly predicated on an understanding that the submodule is a separate thing, and is not edited in situ by a programmer working on the super-project. If the sub-project needs updating, it is assumed, you will send the maintainer a well defined change request, wait for it to happen, and point your super-project at the updated version and go about your business.
Which is what you would expect if the sub-project belongs to someone else.
And some of our changes look like that: "Folks, we have a note from the customer. There's a new file format for specifying widgets. Someone needs to update the WidgetLoader in libThing to handle it. Joe, you've worked in that module recently, can you get to it this week?". Fine. Joe updates libThing, pushes to the reference repository and the next release of each of our project can manage the new Widget files. Nice. And exactly as Linus envisioned it.
On the other hand a lot of times we find out that libThing needs updates because we're in the course of making changes to one of the project that use it. By working both side together we can work through the trade offs dynamically. Its more natural, and probably faster, to just work on them together. Even though submodules doesn't encourage it.
Buuuut ... if you're not careful, you'll make one or more commits to the sub-project in a detached head state.1
The rest of this post is a recipe getting safely back to a happy state after you make this mistake.
Advancing the branch your aren't on
If you had done this right, you'd have put the subproject on the branch before you started editing and the branch would have advanced as you made commits. We want to get the repository into the state it would have had.
- Determine the name of the branch you're suppose to be on by
looking in the
.gitmodules
file of the super-project. Remember that for later. - Give the current state of your work in the submodule a temporary
name with
git checkout -b life-preserver
2 - Get on the right branch with a
git checkout
to the branch you found in step 1. - Fast-forward with
git merge life-preserver
(and it should be a fast-forward merge; if not you should probably take stock at this point.) - Check that nothing is missing by examining the commit tree. I
like
gitk --all
. - Assuming all is well, dispose of the evidence with
git branch --delete life-preserver
. - Detach the head again (at least if you're done)
git checkout --detach HEAD
. - Pretend you're the kind of coder who never misses the little details.
1 If you don't work with git, then this post probably isn't much use to you, but short-short version:
- In git a "branch" is a name that refers to some remembered state of the project. A project can have a lot of branches, and you can start with one state and make different changes to the project and have both of the remembered as different branches. That is, they can split off like the, well, branches of a tree.* They can also join up which is called a "merge".
- You can tell git to remember a new state of the project. That's called a making a "commit" and each commit knows about the one(s) it came from, so the software can navigate back in time.
- A commit can be the target of zero, one, or more branches.
- You can be "on a branch" meaning that (a) you are working on the state of the project remembered by that branch name and (b) git has a record of which branch you're "on".
- When you're on a branch and you make a commit, git changes the association of branch name to the new commit. Remember that commits know what came before, so you can still go back, but the name now refers to the new state of the project.
- If you're not on a branch you are in a "detached head state" which means git knows which version of the project you start with but doesn't know a branch. Any commits you make in this state are nameless because there is no branch name to move forward.
* In computerese it's actually a directed acyclic graph (DAG), but that's neither here nor there.
2 Yes, I have a name I use for this. Not that it happens often or anything.