Today I Learned

25 posts about #git

Force push safely using --force-with-lease

We periodically force push feature branches when we rebase them.

To do this safely, I first check manually to see if my pair has pushed any commits I don't yet have in my local branch.

But there's an easier and safer way to do this. The --force-with-lease option will fail if there are upstream changes not yet merged.

It will work smoothly for standard rebasing situations, but protect against any inadvertent destructive force pushes.

Thanks to Jason K for sharing this tip with me!

An Improvement to the Behaviour of the Git Pager

First, capture your current git pager settings (in case you want to go back):

git config --list | grep core.pager

Second, configure your new and improved git pager settings:

git config --global core.pager "less $LESS --tabs=2 -RFX"
--tabs=2: Is just right for displaying Ruby diffs

-R: Repaint the screen, discarding any buffered input
-F: Causes less to exit if a file is less than one screens worth of data
-X: Leave file contents on screen when less exits.

If you want to get even fancier consider diff-so-fancy.

Cherry-picking a range of commits in git

Problem

I would like to cherry-pick a range of commits (e.g. an entire branch). For example, I want to cherry-pick the commits f00b4r to f00ba5, where foob4r is the oldest commit on the branch.

Solution

git cherry-pick f00b4r~..f00ba5.

Ignore whitespace-only changes in Gitlab MRs

Problem

You are reviewing a Gitlab Merge Request (MR). It involes changes where a Ruby file is moved into or out of particular modules. This means that one or more module ... end constructs are either added or removed around the entire class, resulting in a tabbing-width change on almost every line of the file. This makes reviewing the file in the MR quite difficult, as the reviewer will need to sift through the code diff and separate out actual logic changes from whitespace-only changes.

Solution

By appending ?w=1 to the end of the URL of the MR, Gitlab will ignore all lines that only feature a whitespace change in the code diff. This means that the only changes visible will be ones with a non-whitespace change, thus allowing the reviewer to focus in on actual logic changes and not waste time reading whitespace changes.

Time to poke Gitlab EE support on this issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/1393

Global gitignore

To enable a global .gitignore for a specific user you can use the git config core.excludefiles as in:

git config --global core.excludesfile '~/.gitignore'

This will make the .gitignore in your home folder to be used in every git project in adition to local .gitignore files.

For more info read the git documentation on gitignore

Convenient Git Command to Discard Local Changes

In order to discard all local commits on a branch, that is, to make the local branch identical to the upstream of the branch, run:

git reset --hard @{u}

where @{u} is the short form of @{upstream} and denotes the upstream branch.

Why Git Uses (:<BRANCH>) to Delete Remote Branch

It would appear that the colon in git push origin :<branch-to-delete> is used exclusively to delete branches. But such is not the case.

The format for the refspec is*:

<source>:<destination>

This tells Git to push the source branch to the destination branch in remote. So if the source is blank, we get a leading colon. This has the effect of deleting the destination branch. Its like saying push null pointer to destination.

*You can learn more about the refspec in its entirety in this Stack Overflow

Add executable flags in git file

There is support in the git add command to make a file tracked in your git repository executable. For example, let's say you added a foo.sh script to your repo but forgot to add the executable bit to its file permissions. You can now do this:

git add --chmod=+x foo.sh

One gotcha of this approach is that this will only change the permissions tracked by git, but not the actual permissions of the file on YOUR filesystem. You will still need to run chmod +x foo.sh to modify your local permissions. However, your teammates should be able to pick up the permission changes from a git pull.

Courtesy of http://stackoverflow.com/a/38285435/814576

Add extra line to git commit message from CLI

You can add extra lines to your commit messages by adding an extra -m flag to the git commit flag. This is useful if you have extra information that you want captured in your commit, but you don't want it in your commit message header. For example:

git commit -am "Updates the README with copyright information" -m "This conforms to requirements from Legal."

Will produce the following commit message:

Updates the README with copyright information

This conforms to requirements from Legal.

Now your commit message is split up into a header and a body. You can also add another -m flag for a footer.

[GIT] Change capitalization of file

HOW DO I CHANGE THE NAME OF A FILE UNDER GIT CONTROL WHERE NOTHING CHANGES EXCEPT THE CAPITALIZATION OF THE LETTERS?

For example, let's say that you had a file called myfile.js and you wanted to rename it to MyFile.js. This change would not be recognized in git status and therefore cannot be committed because none of the characters actually changed. It appears that git treats files in a ignores-case way when scanning for changes. Whereas, if I added or removed any of the characters in that name, ie MyFile1.js, then git would recognize the rename.

SOLUTION

As per this Stackoverflow post, you can still commit this filename capitalization change using a git mv command. So in this example, we would want to execute this:

git mv myfile.js MyFile.js

Learning lessons the hard way: git clean

I had a huge list of superfluous changes in git that I wanted to clean up. Most of them were additions so doing a git checkout wasn't going to work.

I followed some instructions online and ran: git clean -r -d -x

The trojan horse of this command is the -x flag which will delete all files in your .gitignore!

This led to a half day of setting up my dev environment again to recover all the lost environmental configurations deleted by this command.

Stay tuned for tomorrow's lesson: Adventures with rm -rf /

Get Overwritten/Discarded Commits using reflog

git reflog

shows commits including the overwritten/discarded ones. This is useful in case of accidents such as unwanted git commit --amend and git reset.

Source

https://www.atlassian.com/git/tutorials/rewriting-history/git-reflog

Extra

Use git reflog --date=iso to include dates.

Diffing a topic branch against the base branch

I often want to check all the changes made in my branch, relative to the base branch. Let's say the base branch is master.

Most of the time, I use plain ol' git diff.

git diff master
git diff master --name-only  # only list the changed files

However, git diff master doesn't always work. For example, if I pulled the latest changes in master, and haven't rebased my branch, then git diff master will show all kinds of changes I didn't make.

So what is a dev to do?

git merge-base to the rescue!

Here's the description from git help merge-base:

Find as good common ancestors as possible for a merge

The following gives me a good commit to compare against:

> git merge-base master HEAD
0783e77e5ca810e36fc7080753ac62526c09d0e4

Therefore, I can check all my changes using:

git diff `git merge-base master HEAD`

zsh function:

gdb() {
  git diff `git merge-base master HEAD` $@
}

Git Cherry-Pick A Merge Commit

PROBLEM

I picked up work on a topic branch that has a WIP commit. I reset the branch, fix a few lines, then committed. I noticed now that I'm 1 Ahead and 1 Behind on my branch. I should do a force push....but I forgot to! Instead, I pulled and thus created a merge commit. Ruh roh!

                 WIP
             /        \
A --- B --- C --- D --- M --- E --- F

Not only will this merge commit (empty, by the way) make our overall git history look ugly, it is also going to make rebasing off master very difficult, since we will have to resolve a conflict for all future commits (E and F). I want to keep my topic branch flat.

SOLUTION

You can escape this mess via a series of git cherry picks. The key part of this is how we applied the cherry-pick on merge commit F. We need to specify which parent to base the commit off of.

git checkout -b new_branch
git cherry-pick A
git cherry-pick B
git cherry-pick C
git cherry-pick D
git cherry-pick M -m 1
git cherry-pick E
git cherry-pick F

Quickly find the git commit that broke a test

Git bisect is a very cool tool that automate a binary search for you to find the first bad commit. Here is an example on how to find a commit that broke a test:

  1. git bisect start
  2. git bisect good <good_sha> # <good_sha> is any commit where the test is passing
  3. git bisect bad <bad_sha> # <bad_sha> is any commit where the test is failing
  4. git bisect run zeus rspec <broken test> # remove zeus if you don't use it

Git bisect will perform a binary search and run the test on every step. It uses rspec exit status to know if the commit is good or bad (0 exit status means good, otherwise it's bad). When it's done it will print the first bad commit:

a5cf29ac1dd64e5ce05336f28aa0ffc17e57fc10 is the first bad commit
commit a5cf29ac1dd64e5ce05336f28aa0ffc17e57fc10
Author: Arturo Pie <example@example.com>
Date:   Fri Apr 1 08:55:31 2016 -0400

    This is the commit message

:040000 040000 b2399ed1361548a743d95aa6aa95e42096f5ffd3 b500421bbfb9bb3dfebee1e45ff2197a7f32a43e M  app
bisect run success

When you are done debugging, run git bisect reset to end the bisect.

Happy Hacking!

Where Does That git Setting Come From?

Yesterday git config learnt a new --show-origin option to indicate where configuration values come from.

$ git config --show-origin user.email

Note: available in the 2.8.0 release.

Stashing untracked files in Git

Scenario

I have created new files in the process of spiking an implementation, and want to stash them to prevent Untracked files from appearing in git status.

Solution

Use git stash save -u.

How to view changes from only a particular branch

git log master.., assuming your branch was based off of master. If based off of something else, use that branch name (ie, git log production..)

Courtesy of http://stackoverflow.com/a/4649377/814576

Find all commits by a particular author

Do you want to know quickly about all the commits that a single author has pushed? Easy, just run a git log command with the --author` flag, like this:

git log --author="evanb@nulogy.com"

Stashing only some of your files

If you want to keep some files and stash the others, use git add to add the files you want to keep, and type

git stash --keep-index

Add a message to git stash

Problem:

You want to stash a number of patches and add a message to describe what each includes.

Context:

Using git stash without arguments stacks your code changes on the top of a the stash stack, and adds a default message: WIP on branchname …​. If you stash multiple patches, you may find it difficult to recall what each stash includes:

$ git stash list

stash@{0}: WIP on 1234_add_new_feature: 1a2b3c4 #1234 - updates new feature template
stash@{1}: WIP on 1234_add_new_feature: 1a2b3c4 #1234 - updates new feature template

Solution:

You can add a message to the the stash by using the save argument:

$ git stash save "refactors how the UI elements are rendered"
$ git stash list

stash@{0}: On 1234_add_new_feature: refactors how the UI elements are rendered
stash@{1}: WIP on 1234_add_new_feature: 1a2b3c4 #1234 - updates new feature template
stash@{2}: WIP on 1234_add_new_feature: 1a2b3c4 #1234 - updates new feature template


This workflow is especially useful when you're stashing commits while spiking.

Using git stash for spiking

Often, I do a small spike to see if a certain code change makes sense. When it's time to actually make code changes, I want to start from scratch to do proper TDD. However, sometimes I want to reference the code I spiked out to guide me.

One option is to stash away the spike, and reference it using:

git stash show -v

This will show you the contents of the most recently stashed code.

If it's not the most recently stashed change, you can specify the stashed change you want to see:

git stash list
git stash show -v @{2}

Selectively stashing your changes

git stash -p will allow you to interactively stash changes at a patch level (like other git commands that support the -p option)

Removing untracked files/directories in git

To remove untracked files/directories, use git-clean.

Running git-clean prints out what it would remove, without actually removing them.

-f flag removes the files and -d flag removes empty directories

So to remove untracked files and directories:

git clean -df

To also remove ignored files:

git clean -dfx

Search through git commit messages

Problem:

How do you find the commits associated with a specific story?

Context:

At Nulogy, we use a story tracking system that assigns a number to each story. When we're developing, we tag each commit message with the story number:

$ git commit -m "#1234 - refactors spec"

Solution:

We can use git log --grep, which greps the commit messages in the repo:

$ git log --grep="#1234"