365Git

  1. Search
  2. About
  3. Subscribe
  4. Archive
  5. Random

365Git

Regular small snippets and workflows for Git

My original plan of a post everyday turned out to be unrealistic, but I'll carry on posting as regularly as I can.

Find me @abizern on twitter if you have anything you'd like me to cover.

  • Git merge —squash

    There is a flag to the git merge command: --squash which at first looks as if it does the same thing as a rebase, but this isn’t exactly so. This post attempts to explain the difference.

    Consider the following repository structure:

    merge_squash1

    There is a master branch and two feature branches feat1 and feat2. The feat2 branch will introduce a merge conflict, which is why it is highlighted in red.

    Say you want to bring the two features into the master branch as a single change. There are, as you would expect with Git, multiple ways of doing this.

    Merging

    One way to do that is by merging the two branches into the master. From the master branch you can just git merge feat1 feat2 (yes, you can merge multiple branches in one step) You will get an error because of the merge conflict, which you should know how to fix by now, and this will give you this structure:

    merge_squash2

    Rebasing

    That’s fine if you want to be able to show that the merges have happened, but sometimes, that isn’t the case and you want to be able to show a clean history and a single change. To do that, you can use the rebase command. First by rebasing feat1 onto feat2 git checkout feat1; git rebase feat2

    merge_squash3

    And then you can rebase this branch onto the master with git rebase --interactive, where you can squash the three individual commits to a single commit, and resolve the merge conflict introduced by feat2:

    merge_squash4

    And now, switch to the master branch and rebase it onto the feat1 branch (or merge it, the result will be the same as it will resolve as a fast-forward merge). This will give you a single commit with a clean history and all the merged changes from feat1 and feat2:

    merge_squash5

    git merge —squash

    Now to the topic of this post; using the --squash flag with git merge. What this does is similar to an interactive rebase where you squash all of the commits, except it doesn’t check in the change, it just updates the index (and by extension, the working directory). So, starting from the original repository structure we can get the same result by first git merge --squash feat1 feat2 and fixing the merge conflict. This won’t affect the the checked in state of the master branch, but your index will have all the changes:

    merge_squash6

    What’s the advantage of this? Well, you can get a clean history by just commiting the changes:

    merge_squash7

    But, even more interestingly, you can now back out the changes from the index (which leaves them in the working directory) and add back selective changes using git add --patch and create an entirely different set of commits.

    Which should you use?

    For most cases, merging would be the choice of most people. It’s simple enough, and if there aren’t many micro commits the history will be coherent. In this example it only takes three steps (1. merge 2. fix conflicts 3. commit).

    If you are the sort that makes many micro commits (like me) and you then rebase them into larger chunks before merging the changes to another branch, then the rebasing method is the one you are already using, even if you squash all the changes into a single commit. I prefer this method, because you have greater flexibility with what changes you make. I also find it easier to handle merge conflicts this way. But it does take longer (1. rebase feat1 onto feat2 2. Interactive rebase 3. Amend the commit messages 4. fix conflicts 5. commit 6. rebase master onto feat1).

    But, if you’re aware of what conflicts might occur and how to resolve them, or if you want to use interactive patching, then using git merge --squash is a good choice; and it might be faster (1. merge 2. fix conflicts 3a. commit or 3b/3c … patch add the changes)

    Tagged: git merge rebase squash conflict clean history

    Posted on April 5, 2011 with 14 notes ()

  • Fixing merge conflicts

    Branching is great. Being able to work on code that you don’t have to check in, that doesn’t risk polluting the main branch until it’s finished is great; but you have to be able to bring the changes back in at some stage. One way is to use git merge. Here’s a nice contrived example of a simple repository containing two files. Greetings.txt and Partings.txt. There are three branches; master, es and fr. The currently checked out branch is es so the HEAD reference points to that. Can you spot the deliberate mistakes in the contents of the files?

    Before the merge

    I want to merge the changes from the fr branch into the es branch and since I am on the es branch already, I use the command git merge fr, and that’s when the problems start. It looks like the merge didn’t work and has left me with conflicts.

    Merge conflict

    Conflicts are an unavoidable fact of working with merges in any version control system. If there has been a change to the same line in both branches then git does not know which one to apply. Although it’s very good at interleaving changes to different lines in files, there is nothing magical about git: it isn’t going to risk corrupting my files in cases where there is a conflict. So, that means that they need to be fixed by hand. Before we look at ways of fixing these commits, let’s take a look at the state of our repository now.

    Examining the conflicts

    Run git status to see the state of your working directory.

    git status

    Here you can see that the two files are in the working directory, but not in the index. These files have been modified by git with familiar conflict markers.

    cat files

    Notice that it has marked the changes to the HEAD and fr branches in each case. What this doesn’t show you are the hidden references to the state of the files at each branch. This is what your repository looks like now:

    With Conflicts

    Quick and dirty resolution

    For small files like this the fixes are quite simple:

    1. Search for the conflict markers in each file.
    2. Edit the file to the state that you want, and remember to remove the conflict markers.
    3. Add the changed files to the index with git add.
    4. Once all the conflicted files are fixed, commit them with git commit.
    5. Edit the commit message to show that I’ve merged the files and fixed the conflicts.

    Not so dirty resolution

    In this case, I can see that files in one or the other branch should take priority. That is, changes in those files are the ones to be applied in conflicts. So here; I am on the es branch and I can see that I want my version of the Greetings.txt file to be in the merge, and the fr branch version of Partings.txt is the correct one. What I can do is to use the references to check out the version of the file that I want. So what I do here is:

    1. Get the es version of Greetings.txt with git checkout --ours Greetings.txt. Note: I am using --ours because it is the one from the branch I am currently on.
    2. Get the fr version of Partings.txt with git checkout --theirs Partings.txt. Note: I am using --theirs because it is the one from the branch I am merging with.
    3. Do a quick sanity check of the files; and I can see that the correct versions exist, and without the conflict markers.
      Resolving es conflict
    4. Add the files to the index with git add .
    5. Commit the changes with git commit
    6. Edit the commit message to show that I’ve merged the files and fixed the conflicts.

    Resolved Merge

    After resolving the conflicts and checking in the changes this is what the repository looks like.

    post merge

    You can see that there is a new commit with the correct versions of the files. Also, since this was a merge commit, this new commit has two parents which are the commits that it merged.

    Summary

    This is a contrived example but merge conflicts are a fact of life. Git doesn’t replace communication between team members and cannot read minds when deciding which changes to apply if there are conflicts. It’s also generally safe to merge branches because you will be asked to resolve conflicts rather than have changes applied under you that might mess up the project.

    There are other ways of joining branches and dealing with the conflicts. If you can’t remember them all, the quick and dirty method is the one that you can always use.

    Of course, if things become too much of mess, and there are too many conflicts to resolve all at once, you can always about a merge (before resolving and committing, of course) with git reset --merge which will restore the repository to the state it was before you started the merge. You can then use other methods to get your project into shape before trying to merge changes.

    Tagged: conflict day9 git merge checkout

    Posted on April 1, 2010 with 32 notes ()

Field Notes Theme. Designed by Manasto Jones. Powered by Tumblr.