Skip to content

Git Recipes

My commit messages suck

Symptom

My colleagues and friends are notoriously complaining about my git commit messages. But not only that, they also say that my commits suck pretty often. My commits seem not to be atomic, include whitespace changes or include too much stuff... but, what is a good commit with an equally good commit message anyway?

Discussion and Solution

These topics have been discussed a lot and there thankfully is some common sense about these issues and good write-ups have been done, well worth reading:

git push rejected due to remote changes

Symptom

I have committed something (to the main branch) and forgot to pull first. Now I can't push anymore as git rejects the push with the following message:

git push rejected

Screenshot

Discussion and Solution

This usually means that somebody else already added a new commit and pushed to GitHub and now your commit conflicts with the other. What to do now? You would need to:

  • Remove your commit(s) and keep the changes
  • Run a git pull --ff-only to fetch the new commit from the other person
  • Re-apply your changes again on top of the latest commit

Solution

git pull --rebase

This command first fetches the latest commits and then rebases your commits on top of the latest commit, see git help pull. Now you may try to push your changes.

Damn, I committed a secret!

Symptom

You just committed a configuration file with a password in it? You entered a real password into a file instead of a dummy password like 123456 and committed it? You added disclosed something else by committing it to the repo? Fear not and read ahead!

Discussion and Solution

There's good cheat sheet from GitGuardian that features a flow chart on how to proceed and save the situation depending on the stage at which you realised that something wrong happened.

In addition, go and study the page "Removig sensitive data from a repository" in the GitHub Documenation.

Damn, I branched off wrong parent branch!

Symptom

You created a new feature branch and after some commits you realized that you branched off a feature branch instead of the master/main branch?

The situation now looks like this, right?

A---B---C---D  main
    \
      E---F---G  other-feature-branch
              \
                H---I---J new-feature-branch (HEAD)

Discussion and Solution

Well, you could create a new branch off main and then git cherry-pick all commits over, which would work, but implies additional work, i.e. if you already have setup a pull request and someone already reviewed the code as you would setup a new branch and therefore also a new PR and the review would have to be done again...

But we can use git rebase --onto command. It can do exactly what we need. Replace the old parent branch with new parent branch. In our case with main branch.
For the situation above, we would like to achieve this result:

A---B---C---D  main
            |\
            | E---F---G  other-feature-branch
            |
             \
              H'---I'---J' new-feature-branch (HEAD)

To replace parent branch with master, we need to be on new-feature-branch branch and do:

git rebase --onto main other-feature-branch

That’s it. Right now we have our current-feature-branch branch based on master branch, not like before based on feature-branch.

In the end, I would like to say two more things here. First, the general form - rebase some branch from one branch onto another - looks like this:

Solution

git checkout branch2move
git rebase --onto new-parent old-parent

The destination branch comes first follow by the old branch. The branch that is moved is the currently checked out!

Second, as you see one schema above, after using git rebase --onto we don’t have exactly the same commit like before. Code is the same, but the SHA number (you know the commit identifier, for example 2d4698b) for each commit is different. Everything will be fine, when you work alone on the branch where you want to do the trick. In case other people also work on this branch this command can provide problems as always when you rewrite history and change commit hashes.

More on git rebase --onto can be found in Git rebase --onto an overview

Extract a Subfolder to Its Own Repository

Symptom

Sometimes you come to a point where a certain bit of your code grows that much that you want it to manage in its own repository. Some common use cases might include:

  • A Submodule should now be its own library/plugin
  • Puppet code deserves its own module
  • An ansible playbook should be turned into its own role

You might say: "Well, create new repo, move the files over, check them in and do the first commit. Done. Where's the problem?" What if I tell you, that moving only the files is not enough. I'd want to also move the history of those files. I want all the old commits where these files were referenced in the new repo too!

Solution

It turns out that this is such a common and useful practice that the overlords of Git made it really easy. The magic command is git subtree split

  1. Prepare the old repo from where you want extract the subfolder, in this example the folder lib/myparser:

    cd path/to/bigrepo
    git subtree split -P lib/myparser -b myparser-only
    

    I now have a new branch myparser-only in the bigrepo, that exactly contains to contents and its histroy of this very folder. Yeah! Now let's create a now repo from that branch:

  2. Create the new repo and pull the prepared branch:

    mkdir path/to/myparser; cd path/to/myparser
    git init
    git pull path/to/bigrepo myparser-only
    
    The history has now been pull into the new repo.

  3. Time to attach the remote (already created at GH) and push:

    git remote add origin https://github.com/user/myparser
    git push -u origin main
    

  4. Cleanup inside the bigrepo and remove the extracted stuff:

    cd path/to/bigrepo
    git branch -D myparser-only
    git rm -rf lib/myparser
    git ci -m "Extraced myparser to its own repo"
    git push origin main
    
    You're done. Original source

Import a Repository (Into a Subfolder of) Another Repository

Symptom

You have a lot of repositories that you often deploy together or changes to one directory almost always require changes to another repo? Then it might be beneficial to import one repository into the other.

Discussion and Solution

Again, it turns out that also this use case is quite easy to achieve with a simple merge after adding the to be included repository as a remote and then fetching the branch to merge. Cleanup the branch before and then merge. The whole history is imported.

Solution

If you want to merge the main branch of project-a into HEAD of project-b:

cd path/to/project-b
git remote add project-a /path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/main # or whichever branch you want to merge
git remote remove project-a

In case you want to put project-a into a subdirectory, you can use git-filter-repo (filter-branch is discouraged). Run the following commands before the commands above:

cd path/to/project-a
# Optionally checkout a new branch and make changes if needed
# The use filter-repo as advised:
git filter-repo --to-subdirectory-filter project-a

Now is also a good moment to clenaup the history of the branch you want to import.


Last update: October 9, 2023