Today I Learned

A Hashrocket project

135 posts about #git

Find that change you know you made!

Sometimes you do awesome work…

Sometimes you even remember that awesome work when at a later point it is not there but everything in your being knows that you wrote it already.

Hopefully this will help:

git log --all --source -- path/to/my/file

This super useful command will show you changes on a particular file (that you totally know you changed) across branches; because sometimes our brains work so hard that we forget to get something merged and those important changes just sit there waiting to be useful again on another branch.

Set Git Tracking Branch on `push`

You hate this error, right?

$ git push
There is no tracking information for the current branch.

I especially hate git’s recommendation at this stage:

$ git branch --set-upstream-to=origin/<branch> my-branch

You can check for tracking information in your config file with:

$ git config -l | grep my-branch
# returns exit code 1 (nothing)

Yep, no tracking info. The first time you push you should use the -u flag.

# assuming you are on my-branch
$ git push -u origin HEAD

No do you have tracking info?

# returns the tracking information stored in config!
$ git config -l | grep my-branch
branch.my-branch.remote=origin
branch.my-branch.merge=refs/heads/my-branch
branch.my-branch.rebase=true

Did you forget to set up tracking on the first push? Don’t worry, this actually works anytime you push.

$ git push
There is no tracking information for the current branch.

$ git push -u origin HEAD
Branch 'my-branch' set up to track remote branch 'my-branch' from 'origin' by rebasing.

This is so more ergonomic than git’s recommendation.

Get Back To Those Merge Conflicts

You’ve probably experienced this:

Decision A
<<<<<<< HEAD
Decision H
Decision I
=======
Decision F
Decision G
>>>>>>> branch a
Decision E

And you wind up making some iffy decisions:

Decision A
Decision I
Decision G
Decision E

The tests don’t pass, you’re not confident in the choices you’ve made, but this is the third commit in a rebase and you don’t want to start over.

It’s easy to get back to a place where all your merge conflicts exist with:

git checkout --merge file_name
# or
git checkout -m file_name

Now you can re-evaluate your choices and make better decisions

Decision A
<<<<<<< HEAD
Decision H
Decision I
=======
Decision F
Decision G
>>>>>>> branch a
Decision E

H/T Brian Dunn

GitHub PR Team Reviews

Pull request openers on GitHub can request a review from a GitHub user, or a team that has at least read-access to the repository. In the screenshot below, deploying the ‘Ninjas’ to review this PR is effectively the same as requesting a review from each ninja on the team. If they have notifications for such things, they’ll be notified, and they’ll see a banner on the PR’s show page requesting their input on the change.

image

This is a handy way to request a lot of reviews at once.

About Pull Request Reviews

Switch branches in git with... `git switch`

It’s experimental. It’s intuitive. It’s in the newest version of git, version 2.23.0. It is:

git switch my-branch

And, with every git command there are many ways to use the command with many flags:

You might want to create a branch and switch to it:

git switch -c new-branch

You might want to switch to a new version of a local branch that tracks a remote branch:

git switch -t origin/remote-branch

You can throw away unstaged changes with switching by using -f.

git switch -f other-branch

I feel that if I were learning git from scratch today, this would be much easier to learn, there’s just so much going on with checkout.

Delete remote branches with confirmation

Branches on the git server can sometimes get out of control. Here’s a sane way to clean up those remote branches that offers a nice confirmation before deletion, so that you don’t delete something you don’t want to delete.

git branch -a | grep remotes | awk '{gsub(/remotes\/origin\//, ""); print;}' | xargs -I % -p git push origin :%

The -p flag of xargs provides the confirmation.

Worktrees in Git

Ever want to have multiple branches of the same repository checked out at the same time? One option is to clone the repository multiple times. But Git worktrees allow multiple working trees to be attached to the same repository. This can be simpler to use and save storage space.

From inside an existing repository run the following to create a new worktree:

git worktree add /path/to/new/checkout other-branch

other-branch is now checked out at /path/to/new/checkout. You can work in that directory just as you can the original.

To remove the worktree when you are done:

git worktree remove /path/to/new/checkout

GitHub Insert a Suggestion ☝

Today I learned that GitHub has allows you to insert a one-line suggestion while conducting a pull request review. Click on the button circled in red, and you’ll get a suggestion fenced code block, as shown.

image

Inside the code block is the current code; replace it with your proposed change. GitHub will present your suggestion in a nice diff format.

When the pull requester views your suggestion, they can accept the change with one click. Efficient!

Multiple-lines are not yet supported, according to this GitHub blog post it is a frequently requested feature.

h/t Jed and Raelyn

Git Log Relative Committer Date 📝

Git log format strings are endlessly interesting to me. There are so many different ways to display the data.

Today I learned a new one, %cr. This formats your commit from the committer date, relative to now. After pulling from the remote, use it like this to see how long ago the latest commmit was committed:

$ git log -1 --format=%cr
8 hours ago

I use this command when surveying a Github organization, or an old computer with lots of dusty projects laying around.

Push git branch to another machine

Whenever I’m working in the same git repo on multiple machines, and I need to work on a branch on both machines I usually push the branch to a shared remote for the sole purpose of pulling it down on the other machine. This third machine can be avoided by push directly between machines:

$ myproject(feature-branch): git remote add machine2 ssh://machine2:/Users/dillon/dev/myproject
$ myproject(feature-branch): git push machine2 feature-branch

Over on machine2:

$ myproject(master): git branch
* master
  feature-branch       <= 😯

Reduce Depth of an Existing Git Repo ⚓️

git pull --depth works on an existing repo to set the depth of references retrieved. To quote the --depth docs:

Limit fetching to the specified number of commits from the tip of each remote branch history.

So, what happens when you run this? Here’s me experimenting with the Elixir repo:

$ git clone https://github.com/elixir-lang/elixir.git
$ cd elixir
$ git log --oneline | wc -l
   15670 # 15670 entries!
$ git pull --depth=1
# ...pulling the shallow repo
$ git log --oneline | wc -l
   1 # 15670 to 1... woah!
$ git log
# ...output from just one commit

Is .git/ a lot smaller now? Not yet, because there are many dangling references. This Stack Overflow answer shows you how to cleanup that directory. After setting a depth of one and following its instructions, I reduced the Elixir repo’s .git/ size by 90%.

Check out man git-pull for more information.

Give a commit a new parent

Given I have these logical commits on two branches:

normalbranch: A - B - C - D

funkybranch: Z - Y - X
git co normalbranch
git rebase --onto X C

Then the logical commits of normalbranch will now be:

normalbranch: Z - Y - X - C - D

We’ve given commit C the new parent of X. C’s hash will change and D’s hash will change.

I use this when I build on top of a branch that I’ve already submitted for a PR. That branch will get merged into the new root branch, and then I’ll need the new parent on the root branch for the commits that I’m currently working on.

Git Interactive Rebase The First Commit

Everytime I want to do an interactive rebase I pass the number of commits back that I want using head~number:

> git rebase -i head~3

Recently I created a repo that had only 2 commits and I got an error when I tried to do a rebase the same way:

> git rebase -i head~2
fatal: Needed a single revision
invalid upstream 'head~2'

To avoid that error, you can use a --root option to rebase the first commit:

> git rebase -i --root

Pulling In Changes During An Interactive Rebase

My standard workflow when doing feature development is to checkout a feature branch and commit changes as I go. When the feature is finished, I clean up the commit history with an interactive rebase and then integrate those changes with master.

I initiate the interactive rebase like this (while on the feature branch):

$ git rebase -i master

This allows me to squash, fixup, and delete commits that I’ve made since checking out this branch from master.

It is important to note that an another thing will happen seemingly behind the scenes. Any commits on master since the feature branch was checked out will be applied to the feature branch before the effects of the interactive rebase are applied.

If you want to strictly do an interactive rebase of the commits on the feature branch ignoring what is on master, then reference the commit you checked out from — put another way, reference the commit before the first commit on this branch.

$ git rebase -i <sha-of-first-commit-on-this-branch>~

The tilde (~) will go back one commit from the specified commit sha.

See man git-rebase for more details.

Reset Hub Credentials

On a shared computer, multiple users logged into Hub can lead to confusing Github activity, such as a PR opened from the command line with your name on it that you didn’t open.

These credentials are stored in ~/.config/hub. To reset your Hub credentials, delete this file and run a Hub command, and you will get an opportunity to reautheniticate.

Show Only Commits That Touch Specific Lines

When you run git log, you are listing all commits in reverse-chronological order for the current branch. There are ways of filtering the commits that get output from git-log. As of Git 1.8.4, git-log output can be filtered by commits that touch a range of line numbers.

This is done with the -L flag.

For instance, if I want to see all commits that touched the 13th line of my README.md file, then I can do this:

$ git log -L13,13:README.md

I can alter the command to show commits that touched a range of lines like so:

$ git log -L19,45:README.md

I used the -L flag recently to find when a dependency was added to my package.json file even though the most recent changes to that line were version bumps.

source

Show Changes In The Compose Commit Message View

When you execute git commit, you’ll be dropped into your default editor and prompted to compose a commit message. By default you’ll only see the status and names of the files involved in the commit.

To also see the line-by-line-changes in this view, you’ll want to commit in verbose mode.

$ git commit --verbose

You can also set verbose mode as the default by updating your ~/.gitconfig file.

[commit]
    verbose = true

source

Find The Date That A File Was Added To The Repo

The git log command has a bunch of flags that you can use to filter commits and format their output.

We can get git log to only show the date for a commit in the short format with the following flags:

$ git log --pretty=format:"%ad" --date=short

We can also get git log to filter commits to just those that have files being added:

$ git log --diff-filter=A

Like many git commands, we can restrict the output to those that match a path or file.

$ git log -- README.md

If we put all of these together, then we have a one-line command for getting the date a specific file was added to the repository:

$ git log --pretty=format:"%ad" --date=short --diff-filter=A -- README.md
2015-02-06

See man git-log for more details.

Show better image diff on git

Today I learned how to improve my git diff with images. For context check this TIL first.

I gave a step further on my git config to allow a better diff of my images using imagemagick compare features.

git config --global diff.image.textconv 'imgcat'
git config --global diff.image.command 'imgdiff'

So textconv will be used by git show commands and it’s using iTerm2 imgcat.

And command will be used by git diff command and it uses my new shell script imgdiff added to my PATH:

#!/bin/bash

if [[ -f "$1" ]] && [[ -f "$2" ]]; then
  compare "$2" "$1" png:- | montage -geometry +4+4 "$2" - "$1" png:- | imgcat
else
  if [[ -f "$1" ]]; then
    echo "+ Image Added"
    imgcat "$1"
  else
    echo "- Image Removed"
    imgcat "$2"
  fi
fi

exit 0

With that I can have a diff like that:

git diff

So previous image to the left, new to the right and imagemagick comparison in the middle.

Show images on git diff

Today I learned how to show binary files, more specifically images, using git diff or git show CLI. For that I am using iTerm2 imgcat.

In order to get there I had to configure git to allow a custom diff command to specific file types.

So I used git attributes to do that:

echo "*.gif diff=image" >> ~/.gitattributes
echo "*.jpg diff=image" >> ~/.gitattributes
echo "*.png diff=image" >> ~/.gitattributes
git config --global core.attributesfile '~/.gitattributes'

Then I had to change the git diff text converter to use iterm2 imgcat:

git config --global diff.image.textconv 'imgcat'

Check this out:

git-diff-images

Notes:

  • imgcat does not work with new versions of tmux
  • git pagers like less or more won’t work either, so you can run git --no-pager diff or you can pipe with cat like:
git diff | cat

Check also Open images in vim

Delete remote git branch - the declarative way

Cleaning up after yourself is important, and not just in real life. Good Git Hygiene™ goes a long way.

One of the methods I like to clean up is deleting unused feature branches. I do that both locally and on the remote source control server (github/gitlab etc).

As is common with Git there are many ways to feed a cat. Some people use this:

git push origin :name-of-branch

I prefer the more declerative way, especially for potentially destructive operations such as deleting a remote branch:

git push origin --delete name-of-branch

Either way, keeping your remote branches trim makes for a happier development team!

Add an Empty Directory to Git

Have you ever seen a directory containing a single .gitkeep? Today I learned the history of that file.

Git won’t let us add an empty directory, but sometimes there’s a good reason to want to do that. For instance, I’m building a single-page app that requireds a src/data/ directory, even when there’s no data. Instead of each developer on the project making this directory by hand, I’d like to check it into version control.

There are two competing strategies to achieve this: adding a .gitkeep to the directory, or adding a .gitignore. I prefer .gitkeep because the name tells you what it does and it’s not conventionally used for another purpose.

$ touch src/data/.gitkeep

Stack Overflow

Git Push Force but with lease

If you need to run a git push --force to push a fixup or an amended commit you can try the --force-with-lease tag first for safety. It will protect you to overwrite a commit made by other dev.

alias gplease='git push --force-with-lease'

Check Git push documentation

--force-with-lease will protect all remote refs that are going to be updated by requiring their current value to be the same as the remote-tracking branch we have for them.

Open Bitbucket PR's from the Command Line

I’m a fan of Git command line tools. The more familiar I become with Git hosting platform’s website, the more I want to control it with a CLI.

When you push a branch to Bitbucket, part of the response is a pull request URL. That URL is a create page for a pull request against the default branch.

I’ve taken this idea a step further and created a script for my project that visits this page:

#!/bin/sh

BRANCH=`git rev-parse --abbrev-ref HEAD`
open "http://bitbucket.org/<organization>/<project>/pull-requests/new?source=${BRANCH}&t=1#diff"

This script reads my current branch name and visits the Bitbucket ‘create PR’ page in a diff view.

Work with a Gist locally

I like Gists, those fun-size Git repositories I fill with coding demos, scripts, and WIP markdown files. Today I learned you can edit these files locally and push them to a remote, just like any Git repo.

Grab your Gist URL:

https://gist.github.com/yourhandle/__5bac61ee3fe0083__

Alter it slightly, and clone:

$ git clone git@gist.github.com:5c61ee3fe0083.git
$ cd 5c61ee3fe0083/

Make changes, commit, and push away. Your commits will show up under the /revisions tab of your Gist.

Show What Is In A Git Stash

Usually when I want to inspect anything in git, I’ll use git show with a specific ref. This can even be done with stash refs.

$ git stash list
stash@{0}: WIP on ...
stash@{1}: Some commit on ...

$ git show stash@{0}
# ...

The git-stash command has a built-in way of showing stashes that will save you from having to type out the somewhat awkward stash@{n} ref.

$ git stash show 1

This will show you the stash@{1} ref. You can also omit a number which will show you the latest stash (stash@{0}).

See man git-stash for more details.

Git: Stash Everything

Running the git stash command will take all the changes to tracked files in the working directory and stash them away. You can tack on the --include-untracked flag to make sure that untracked files are also included in the stash.

You can take this a step further with the --all flag. This will stash everything included files that you’ve told git to ignore.

You probably don’t want to do this. I ran this command and realized after the command hung for about 10 seconds that I had just stashed the node_modules directory.

See man git-stash for more details.

Show Git Changes For Files That Match A Pattern

The git show command allows you to view the changes associated with a reference, such as a commit sha (e.g. git show 86748aacf14e).

Consider a commit that has changed a bunch of JS files as well as two CSS files. If we run git show abcd1234, we will see all of the changes to each file which can result in quite a bit of noise. What if we only want to view the changes to the CSS files?

We can instruct the command to only show changes to files that match a pattern by tacking that pattern on to the end.

$ git show abcd1234 *.css

Alternatively, we could scope the output of the command to the files that live in a certain directory.

$ git show abcd1235 src/css

Rename a file in git with different casing

On MacOS git doesn’t handle file name casing changes very well.

If I have a committed file Something.txt I can mv it and git doesn’t recognize the change:

> mv Something.txt something.txt
> git status
On branch master
nothing to commit, working tree clean

Git will recognize the change if you perform the move with git mv.

> git mv Something.txt something.txt
> git status

renamed: Something.txt -> something.txt

There is a configuration regarding this:

git config core.ignorecase false

This is set to false by default. Setting this to true may provide the behaviour you want.

On Linux, this is not an issue. The filesystem recognizes files with different casing as different files and git likewise.

Traversing Git Conflict Markers

Today I solved several nagging inefficiencies in my Vim setup. One was memorizing a mapping for traversing Git conflict markers.

If you’ve ever had a merge conflict and opened the unresolved file, you’ll see these markers:

<<<<<<<
console.log('keep this code?')
=======
console.log('...or this?')
>>>>>>>

Deciding what to keep can be a process, and Vim-Unimpaired makes it easier by providing mappings to jump between the markers— ]n for the next marker, [n for the previous marker. Use these to traverse the diff and learn about what might be gained or lost during resolution.

Dropping Commits With Git Rebase

I’ve been warned enough times about the potential dangers of git reset --hard ... that I always second guess myself as I type it out. Is it git reset --hard HEAD or git reset --hard HEAD~?

If the working directory and index are clean, then there is another way to remove commits. A way that gives me more confidence about what exactly is being removed.

Doing an interactive rebase gives you a number of options. One of those options is d (which stands for drop).

$ git rebase -i master

This pulls up an interactive rebase with all commits going back to what is on master — great for when working from a feature branch.

pick 71ed173 Add Create A Stream From An Array as a reasonml til
pick 80ac8d3 Add some clarity by distinguishing var names
d 4f06c32 Add Data Structures With Self-Referential Types as a reasonml til
d 01a0e75 Fix the name of this file

Adding d next to the commits you want to get rid of and saving will drop those commits. The great part is that there is zero ambiguity about which ones are being dropped.

h/t Jake Worth

git merge --squash

Today I learned a new Git command that’s really useful. git merge --squash takes all the changes from one branch and stages them on top of another branch, ready to be summarized.

Here’s a sample workflow:

$ git checkout -b feature-branch

# Make changes across multiple commits
$ echo 1 > 1.txt
$ git add 1.txt
$ git commit -m 'Add first textfile'
$ echo 2 > 2.txt
$ git add 2.txt
$ git commit -m 'Add second textfile'

# Stage all changes on master
$ git checkout master
$ git merge --squash feature-branch
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   1.txt
        new file:   2.txt

# Summarize
$ git commit -m 'Add files 1 and 2'

This is a fast way to boil down a lot of WIP commits from a feature branch into a single commit on master.

Configuring The Pager

When you run Git commands that produce a bunch of output, Git will use a pager to present the content starting at the beginning, rather than spitting it all out to the screen at once. This will almost always come in handy for commands like git-diff and git-log.

By default, Git uses less as its pager.

You can also choose to configure it to something else by updating your git config file or by running the following command:

$ git config --global core.pager 'more'

If you’d like to turn the pager off altogether, set it to a blank string.

$ git config --global core.pager ''

source

Git Checkout Lines of Code in Vim

Use case: you’ve made a bunch of changes to a file, and want to reset certain lines of the file back to the HEAD commit, while keeping other changes.

To solve this with git-fugitive, view the difference between your working copy and HEAD via :Gdiff . This will open two panes, HEAD (left) and your working copy (right).

Find the lines you want to reset in the working copy. Highlight those lines in Visual mode, and enter :diffget. Your working copy will restore the difference, for just the highlighted lines.

Show The Good And The Bad With Git Bisect

The git bisect command is a powerful tool for tracking down a past commit where something verifiable about the code changed — whether it be visual or functional. After using git bisect to traverse back and forth through your commit history, you may be wondering where things stand.

The git bisect log command will show you each commit that has been inspected and whether you’ve marked it as good or bad.

These records can be handy for double checking your work if you’re worried that you made a mistake along the way.

Show List Of Most Recently Committed Branches

The standard way to list your branches is with the git branch command. If you use branches extensively for feature work and bug fixes, you may find yourself overwhelmed by the list of branches trying to visually parse through them for the one that you had worked on recently.

With the git for-each-ref command, we can produce a better list of branches.

$ git for-each-ref --sort=-committerdate --count=10 --format='%(refname:short)' refs/heads/

The command itself will iterate over all of the repository’s refs and print them out as a list. The --sort=-committerdate option will ensure that list is sorted by refs mostly recently committed to. The --count=10 option limits the list output to 10 refs. The format flag cleans up the output a bit, only showing the shortname of the ref. Lastly, the refs/heads/ argument ensures that only local refs are included in the output, thus ignoring remote refs.

The result is a list of local branches ordered by recency which generally corresponds to relevance.

See man git-for-each-ref for more details.

source

Hi, Sierra

I recently upgraded my Mac to High Sierra and got this gross message when I tried to use Git:

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

My machine was expecting Git to be implemented as an Xcode command line tool. To fix the problem, I simply installed Xcode.

xcode-select --install

Include Some Stats In Your Git Log

A simple git log command is going to give you a concise set of information for each commit. Usually it is enough info. When it’s not, git log can provide additional information with the right flags. To include overall and per-file stats on the number of insertions and deletions, use the --stat flag.

$ git log --stat
commit 66e67741a1cd6857a4467d1453c9f17ef5849f20
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Mon Nov 13 21:24:41 2017 -0600

    Add Focus The URL Bar as an internet til

 README.md                     |  3 ++-
 internet/focus-the-url-bar.md | 10 ++++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

commit 9241e3919ef1e4f68b71a1491d368ae6361084aa
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sat Nov 11 11:41:40 2017 -0600

    Add Freeze An Object, Sorta as a javascript til

 README.md                            |  3 ++-
 javascript/freeze-an-object-sorta.md | 44 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 46 insertions(+), 1 deletion(-)

...

See man git-log for more details.

Show File Diffs When Viewing Git Log

Include the -p flag with the git log command to include the diffs along with the rest of the information for each commit. Here is an example of running this against my TIL repository.

$ git log -p
commit b9809a329acd8150b2474168f8faaf008f376e35
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Wed Oct 11 07:27:53 2017 -0500

    Add Inline Style Attributes Should Be Camel Cased as a react til

diff --git a/README.md b/README.md
index c982f8e..6ee7d32 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ smart people at [Hashrocket](http://hashrocket.com/).
 For a steady stream of TILs from a variety of rocketeers, checkout
 [til.hashrocket.com](https://til.hashrocket.com/).

-_574 TILs and counting..._
+_575 TILs and counting..._

 ---

See man git-log for more details.

List Commits That Change a File

Today I’m wrapping a PR with a large Git rebase. There are many commits on my branch that change the same file (.circleci/config.yml) and aren’t valuable on their own. In the end, I want them all squashed into a single commit. The challenge is that I changed other files in between; it’s kind of a mess. How can I squash the branch down to something manageable?

One technique is to view just changes to my file:

$ git log --follow --oneline .circleci/config.yml

c9f7108 Try to decode file before unzipping
87c8092 Quick push
327d419 Try this!

Using this list, I can execute a rebase that just squashes the commits that touch the file, leaving everything else intact.