Today I Learned

A Hashrocket project

109 posts about #git

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.

Viewing the Git Leaderboard

Everyone knows that writing code is a competition in which she with the most commits wins. So, obviously, you want to periodically check the leaderboard to see who the 10x developer on the team is.

How do you do that?

git shortlog

This will give you a list of all the contributors to the repository and the number of commits they have made. You can add the -s flag (for ‘summary’) to just see names and numbers.

Once you have acquired the evidence that you are indeed the mythical 10x developer, demand a raise from your employer and threaten to go work for Google if you don’t get it.

If you already work for Google, threaten to, I don’t know, get a job at SpaceX or something.

Skip all changes in this file

git add --patch gives you the opportunity to make a decision on every code change in all files individually, providing you a menu that looks like this:

Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?

Option d is

d - do not stage this hunk or any of the later hunks in the file

This comes in handy when there is a host of whitespace changes in a file that you don’t want to commit. Just hit d to skip all those whitespace changes and go to the next file.

Rebase Commits With An Arbitrary Command

Interactive rebasing is a powerful way to manage and tend to the history of a git repository. Rewording and squashing commits are fairly common actions. But what if you need to run some arbitrary command against a series of recent commits?

This is where the --exec flag comes in to play.

$ git rebase -i HEAD~3 --exec "git commit --amend --reset-authors -CHEAD"

This generates an interactive rebase file that you can review and save when ready.

pick ea4a215 Add Globally Install A Package With Yarn as a javascript til
exec git commit --amend --reset-author -CHEAD
pick a4f4143 Add Initialize A New JavaScript Project With Yarn as a javascript til
exec git commit --amend --reset-author -CHEAD
pick 2f00aeb Add Default And Named Exports From The Same Module as a javascript til
exec git commit --amend --reset-author -CHEAD

As you can see, the specified command is prepared for execution for each commit involved in the rebase.

h/t Patricia Arbona

List Merged Branches

Today I learned a new Git trick: to show branches already merged into a branch, try this command:

$ git branch --merged master
  add-basic-auth
  add-channels
  add-developer-username
  add-developers
  add-developers-show
  add-hashtag-to-slack-post

For an open source project like Today I Learned, this is an interesting way to get a sense of the project.

Allow an Empty Commit

Today my pair did something I’d never seen: create an empty commit. Out goal was to test a third-party integration, but we didn’t have a meaningful change to contribute at the time. In this situation, I think an empty commit is a good compromise, because it’s honest about our intent— to simply kick off a remote process.

Here was the command:

$ git commit --allow-empty

h/t Dorian Karter

Rename A Remote

If you just added a remote (git remote add ...) and messed up the name or just need to rename some existing remote, you can do so with the rename command.

First, let’s see the remotes we have:

$ git remote -v
origin  https://github.com/jbranchaud/til.git (fetch)
origin  https://github.com/jbranchaud/til.git (push)

To then rename origin to destination, for example, we can issue the following command:

$ git remote rename origin destination

See man git-remote for more details.

Create a github repository from the cmd line

In the effort to never leave the command line anything to do with github is always a frustration. The hub command line tool - a tool that augments git with github specific commands - helps out tremendously.

In this case I want to create a github repository to push my code to without opening a browser, going to github, creating a repo, copying the remote address for that repo and then setting up the remote locally.

It can all be done with one step.

hub create "exciting_open_source_tech_repo"

git init must be run first, so that there is a local repo to link to the github repo, but then your good. Start modifying, adding and commiting as you normally would.

Mass-Delete Git Tags

Building off this post:

I’m an advocate of Semantic Version tagging. It communicates to a team about every deploy and makes that rare rollback easier. So when does it not make sense to use a tag?

When you’re the only developer (nobody to communicate with except yourself), and also using a platform like Heroku that tags every release (your tags are redundant). This the case with my blog, so today I set out to delete all my Git tags.

First, delete them remotely (assuming a remote named origin):

$ git tag | xargs git push --delete origin

We also have to delete our local tags, or a tag push with create them again on the remote:

$ git tag | xargs git tag -d

$ git tag now returns nothing, and there are no remote tags.

Cherry Pick A Range Of Commits

Git’s cherry-pick command allows you to specify a range of commits to be cherry picked onto the current branch. This can be done with the A..B style syntax — where A is the older end of the range.

Consider a scenario with the following chain of commits: A - B - C - D.

$ git cherry-pick B..D

This will cherry pick commits C and D onto HEAD. This is because the lower-bound is exclusive. If you’d like to include B as well. Try the following:

$ git cherry-pick B^..D

See man git-cherry-pick for more details.

When Was This File Added to Version Control?

I learned a helpful Git command today. To determine when a file was added to version control, run the following:

$ git log --follow --diff-filter=A <filename>

This returns the Git history, following files through renames, filtering on when a file was added to version control. Here’s the output for the README of ‘Today I Learned’:

$ git log --follow --diff-filter=A README.md
commit 9a7984a48db19489bb4113378298ddaa97121c7a
Author: Jake Worth <dev+jwworth@hashrocket.com>
Date:   Sat Mar 28 12:52:19 2015 -0400

    Add a basic README

Useful for explaining mystery files in the repository 🔎.

Update The URL Of A Remote

I just changed the name of a Github repository. One of the implications of this is that the remote URL that my local git repository has on record is now out of date. I need to update it.

If I use git-remote with the -v flag. I can see what remotes I currently have.

$ git remote -v
origin  git@github.com:jbranchaud/pokemon.git (fetch)
origin  git@github.com:jbranchaud/pokemon.git (push)

Now, to update the URL for that remote, I can use git remote set-url specifying the name of the remote and the updated URL.

$ git remote set-url origin git@github.com:jbranchaud/pokemon_deluxe.git

If I check again, I can see it has been updated accordingly.

$ git remote -v
origin  git@github.com:jbranchaud/pokemon_deluxe.git (fetch)
origin  git@github.com:jbranchaud/pokemon_deluxe.git (push)

Accept Your Own Changes During Git Rebase

During a git rebase you may encounter conflicts in files between your current, HEAD, branch and the branch you’re rebasing. Ordinarily, you’ll want to go through each file and individually resolve each conflict to ensure that the proper changes are preserved.

Sometimes, however, you already know that you want to accept ALL the changes in a file on your local branch and discard the other branch’s edits. Instead of opening the file, finding the conflict regions, then making the appropriate changes, you can more succinctly prefer your changes with the following command:

git checkout --ours /path/to/file.js

Conversely, if you want to keep the other branch’s changes, run:

git checkout --theirs /path/to/file.ex

You can also do this for an entire directory:

git checkout --ours /path/to/dir/
git checkout --theirs . # Current working directory

When you [re]open the conflict files, you’ll see that your preferred branch’s changes have been written and the other branch’s have been discarded.

After you’ve finished, stage the the conflict files, and continue your rebase:

git add /path/to/conflict_file.rb
git rebase --continue # or --skip

Show commits in the order they were committed

When reviewing a series of commits its sometimes helpful to review them in the order they were committed.

By default, if you pass a range to git show, like git show HEAD..master (assuming you are on a different branch and master is ahead by a number of commits) the commits will be shown in the order of most recent to least recent.

To view the commits in least recent to most recent order use the --reverse flag:

git show HEAD..master --reverse

Submit a pull request from the command line

Github offers a git extension for augmenting the git command line tool with github specific functionality. This extension is called hub.

hub adds the git alias pull-request to git. This alias/command creates a pull request from the current branch to master by default:

git pull-request

Additionally, you can specify which branch would be the base with the first argument and which branch would be the head with the second argument, like so:

git pull-request devolop feature/add_colors

Grep For A Pattern On Another Branch

Git has a built-in grep command that works essentially the same as the standard grep command that unix users are used to. The benefit of git-grep is that it is tightly integrated with Git. You can search for occurrences of a pattern on another branch. For example, if you have a feature branch, my-feature, on which you’d like to search for occurrences of user.last_name, then your command would look like this:

$ git grep 'user\.last_name' my-feature

If there are matching results, they follow this format:

my-feature:app/views/users/show.html.erb:  <%= user.last_name %>
...

This formatting is handy because you can easily copy the branch and file directive for use with git-show.

See man git-grep for more details.

Viewing A File On Another Branch

Sometimes you want to view a file on another branch (without switching branches). That is, you want to view the version of that file as it exists on that branch. git show can help. If your branch is named my_feature and the file you want to see is app/models/users.rb, then your command should look like this:

$ git show my_feature:app/models/users.rb

You can even tab-complete the filename as you type it out.

See man git-show for more details.

source

Git Garbage Collection - optimize local repository

As you work with git a lot of garbage gets accumulated in your .git folder such as file revisions and unreachable objects.

On large repositories and long running projects this negatively affects both operating performance and disk space utilization.

To clean up the garbage git provides a command:

git gc

This will not impact the remote repository and will only optimize the local copy so it is safe to run on any git repo you might have. (In fact this operation is already run for you automatically after some git commands that leave behind too many loose objects)

If you want a deeper cleaning (which you should every few hunderd changesets or so), run:

git gc --aggressive

This will take longer but will provide better results.

To learn more:

git help gc

What Changed?

If you want to know what has changed at each commit in your Git history, then just ask git whatchanged.

$ git whatchanged

commit ddc929c03f5d629af6e725b690f1a4d2804bc2e5
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sun Feb 12 14:04:12 2017 -0600

    Add the source to the latest til

:100644 100644 f6e7638... 2b192e1... M  elixir/compute-md5-digest-of-a-string.md

commit 65ecb9f01876bb1a7c2530c0df888f45f5a11cbb
Author: jbranchaud <jbranchaud@gmail.com>
Date:   Sat Feb 11 18:34:25 2017 -0600

    Add Compute md5 Digest Of A String as an Elixir til

:100644 100644 5af3ca2... 7e4794f... M  README.md
:000000 100644 0000000... f6e7638... A  elixir/compute-md5-digest-of-a-string.md

...

This is an old command that is mostly equivalent to git-log. In fact, the man page for git-whatchanged says:

New users are encouraged to use git-log(1) instead.

The difference is that git-whatchanged shows you the changed files in their raw format which can be useful if you know what you are looking for.

See man git-whatchanged for more details.

Clean untracked files in Git

Given I am a developer
And I am working on a new branch in an existing project
And during one of my commits I introduced a few files/folders
And those files/folders are untracked (in .gitignore)
And those files/folders are automatically generated (e.g. node_modules/ webpack_bundle.js)
When I switch back to the main branch
Then I see those files
And I don’t want to…

If you find yourself in the above situation, you may want to clean your untracked files. Git provides a command for that: git clean.

This command comes with a way to see which files/folders are going to be deleted (DRY RUN):

git clean -n

You may notice that the above command does not show any untracked directories. To add directories to that list use the -d switch:

git clean -dn

Alternatively you may choose to only remove files/dirs that are in .gitignore with the -X option:

git clean -X -dn

If you are ready to take action use the -f switch and remove the -n switch:

git clean -fd

Filter Your Git Diffs

Sometimes reading a git diff can be a big task. Imagine working through a big file cleanup, removing and modifing hundreds of files, and one of those modifications had an undesireable side effect. How can we filter the noise to find the problem?

git diff has a --diff-filter flag for this purpose. The specific command I used today was:

$ git diff --diff-filter=M HEAD~5 > changes.txt

This showed only modified files over the previous five commits, excluding thousands of deleted references. By directing the output to a file and visually scanning, I quickly found the problem— a forced redirect to HTTPS on the development server.

See git diff --help for more info.

:Gbrowse!

One of my favorite developer-to-developer communication tools is :Gbrowse, provided by vim-fugitive. This command opens your current Vim buffer, or a line or range, in the hosting provider specified by your remote. It’s irreplaceable for quickly sharing code worthy of discussion.

Take this command to the next level with :Gbrowse! which puts the URL your browser would open into your paste buffer. From there, paste away into your project management tool or chat client, getting everybody on the same (web)page.