Find git branches that contain a commit sha
If you are trying to find which of your git repo's branches include a specific commit sha, use this command.
git branch --contains <commit-sha>
Shout out to Andrew for showing me this.
If you are trying to find which of your git repo's branches include a specific commit sha, use this command.
git branch --contains <commit-sha>
Shout out to Andrew for showing me this.
If you want to rename a local git branch you can use the command
git branch -m <your-new-branch-name>
Git will create a reflog of the rename too.
If the need arises to change the name of a git remote, in the past, I've normally done one of these two things:
Turns out there is a git command for this and I completely missed it!
git remote rename OLD_NAME NEW_NAME
As you can probably imagine, this handles the name change as well as the reference change in the config.
After cloning a repo, git will set your branch to whatever the value of HEAD is in the source repo:
$ ssh git@example.com
$ cat my-repo.git/HEAD
ref: refs/heads/master
If there is a different branch you want to clone, use the --branch
flag:
$ git clone --branch my-feature git@example.com/my-repo.git
$ git branch
* my-feature
I always use git show
to see the changes for the last commit or git show sha1
to see the changes from a specific commit.
I rarely use git merge commits and recently I was working on a project that had a merge commit. When I did git show sha1
on that merge commit nothing showed up. Then I discovered that you can pass the -m
option and it shows all the diff on that merge commit.
git show -m sha1
Pretty neat!
In your global git config you can set core.excludesfile
to point to a file where you define things you'd rather not ever go into a repo. This is especially useful for things that are part of your local workflow that don't need to be shared across projects, but which are best kept in the project dir for other reasons.
Ever wanted to rename a local git branch? The git branch -m
command is your friend
Want to rename a branch that's not currently checked out?
git branch -m <original_name> <new_name>
Or to rename your current local branch, you can exclude the original name in the git branch args:
git branch -m <new_name>
To review the git history of a specific file, you can use the git log
command and pass the relative path for the file in question:
git log app/models/user.rb
You can also pass the -p
flag to auto expand the diffs for the file:
git log -p app/models/user.rb
As of git version 2.23, a new command was introduced for switching branches -- git switch
Want to switch to an existing branch?
git switch <branch_name>
Want to switch to a new branch?
git switch -c <new_branch_name>
Full documentation: https://git-scm.com/docs/git-switch
h/t Dillon
Given this folder structure:
app
├── bin
│ └── parse-ansi-codes.rs
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│ ├── cursor.rs
│ ├── lib.rs
│ └── style.rs
├── target
│ └── debug
└── test
I can add the entire app
directory to git, while ignoring the bin folder:
$ git add . ':!bin'
Today I tried out Github to Resolve Git Conflicts of an open PR. I noticed that Github has this gray button Resolve conflicts
:
Then it drives me to a managing conflicts page with a simple editor where I could manually choose and edit the conflicts:
After my editing is done I can Mark as resolved
:
And finally I can Commit merge
:
Wait, what? oh no, I know that merging is the easiest way to solve conflicts as you solve all conflicts once, no matter how many commits your branch has, but to be honest I did not like that. I put some small effort to keep my git history clean and I really avoid merge commits as they are not necessary in general. I tend to keep a single history line on my git repos in the main branch, most of the times at least.
I guess that it is what it is, every time we use a tool to "simplify" the work we are giving up a bit of control on how the things are executed, right?
So here's the resolve of Github's solving git conflicts flow:
There's an open source project called github1s that makes code browsing hosted on Github really easy. It's pretty much a VSCode in the browser.
In order to open that get the github repo url that you want to browse and change the host from github.com
to github1s.com
, simple as that.
Check out how's the Tilex code on this tool.
I use git log -p
a lot.
git log
shows all the commits and messages in descending order and the -p
flag includes the code that changed.
It helps me understanding why some changes happened in the codebase, to search when things got included and more.
Recently I wanted to actually see the history from the beginning and guess what, there's a flag for that:
git log -p --reverse
I'm working on a long-running Git branch with a long name, and I'd like to have an alias for that long branch name. The best solution I've found so far is to use a Git symbolic ref:
$ git symbolic-ref refs/heads/epic refs/heads/long-epic-branch-name
Once in place, this alias can be used to reference the longer branch name:
$ git checkout epic
Switched to branch 'epic'
$ git branch --show-current
long-epic-branch-name
Resources:
git-symbolic-refs
Answer: Creating aliases for Git branch names
Git allows you to stash untracked files with the --include-untracked
or -u
option:
git stash -u
Git also has an --all
or -a
option for stash that will stash both untracked and ignored files:
git stash -a
Here's a challenging real-world scenario: you're doing a big merge or rebase on a JavaScript project, and you keep getting conflict after conflict in your package-lock.json
. These conflicts are tough to resolve, because your package-lock.json
is not easy to read, and is, say, 30,000 lines long. What do you do?
When you hit to conflict, on the the conflicting Git SHA, run the following command. Your lockfile will be regenerated, conflict resolved:
$ npm install --package-lock-only
💥
From the resolving lockfile conflicts docs:
git config
is only as helpful as the options you pass.
In the simplest instance I only get a value:
> git config user.email
dev@example.com
If I pass the --get-regexp
flag I get the key and the value for all the instances of that key:
> git config --get-regexp user.email
user.email computer@example.com
user.email dev@example.com
If I pass the --show-scope
flag (added in 2.26) I get the scope:
> git config --show-scope --get-regexp user.email
global user.email computer@example.com
local user.email dev@example.com
If I pass the --show-origin
then I also get the file where the key was configured:
> git config --show-scope --show-origin --get-regexp user.email
global file:/home/dev/.gitconfig user.email computer@example.com
local file:.git/config user.email dev@example.com
The git blog is an incredible way to learn about the new functionality in each git release.
Something I've learned pairing with Chris Erin is to checkout code you don't want in patches. Here's the command:
$ git checkout --patch
Like the git add --patch
command many developers know, this puts you an interactive workflow where you can decide how to handle each chunk, or patch, of your changes. It works opposite of git add --patch
, in that y
means check out the patch (abandon the changes), and n
means keep the changes.
This is a great flow to go through before trying to commit code; use it to get rid of debugger statements, pseudocode, unwanted autoformatter changes, etc.
There are lot of tutorials about Git. One that is built right into Git is the 'everyday help'. Print this help with:
$ git help everyday
The output is a series of must-know commands for a variety of roles in a technical organization: individual developer (standalone), individual developer (participant), integrators, and admins.
Pick a lane and go! 🏁
Git has one feature that I find irksome; you have to be in the directory with the Git repository to run commands against it.
...Or do you?
Git provides the -C
flag for this scenario. Follow it with a directory name, and Git will execute your command as if it was run from <path>
.
$ git -C ~/tilex status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: lib/tilex_web/templates/layout/app.html.eex
no changes added to commit (use "git add" and/or "git commit -a")
This week, we discovered an interesting feature: when the target branch for PR is deleted, GitHub closes (permanently, from what I can tell) the PR. To observe this in action:
Delete your branches carefully; doing so could unintentionally close one or more PRs.
Today I learned that the git-diff
command accepts a <path>
argument. I used this knowledge to construct a command that runs only the RSpec test files on my branch that differ from master.
(not-master)$ git diff master --name-only spec | xargs rspec
This is handy on a big test suite; when you can't run every test, make sure you run the ones that you have changed.
See man git-diff
for more info.
gh
is a new github cli in the alpha phase right now hosted at https://github.com/cli/cli. It's auth right now is through the browser which doesn't work great on a remote machine.
When you try to do something with gh
that should connect to github you'll get a bunch of errors and this:
$ gh pr create
Please open the following URL manually:
https://github.com/login/oauth/authorize?client_id=XXXXXXXXXXXXXXXXXX&redirect_uri=http%3A%2F%2Flocalhost%3A34869%2Fcallback&scope=repo&state=XXXXXXXXXXXXXXXXXXXX
This is running a server in the background that is waiting for a request, don't kill the process until the authentication is complete.
You can copy that url to your browser, authenticate with github and then you are redirected to a localhost url where the server is hosted
Copy the localhost url that you were redirected to, open a new window and then curl the localhost url.
$ curl http://localhost:34869/callback&scope=repo&state=XXXXXXXXXXXXXXXXXXXX
Now you should be authenticated and the gh
process should continue.
Today I became familiar with the Git configuration interactive.singleKey
. In the Hashrocket dotfiles this is enabled by default, and I'm not sure how common it is out in the wild. To quote the docs:
In interactive commands, allow the user to provide one-letter input with a single key (i.e., without hitting enter).
This allows git add --patch
to be very powerful by enabling a quick vote up or down on a patch with a single keystroke.
Make sure you have the Perl module Term::ReadKey
installed; it is a dependency.
If you rename a file, git won't show history of the previous name:
$ git log --pretty=oneline things/text.txt
8567d... Move file into things directory
However, the --follow
flag will allow you to see the history of commits beyond the rename:
$ git log --follow --pretty=oneline things/text.txt
8567d... Move file into things directory
1458a... Fix something
0aac5... Add something
Docs:
--follow
Continue listing the history of a file beyond renames (works only for a single file).
You can get a list of all the branches in git using the very programmatically named for-each-ref
command. This command by itself isn't very useful and needs quite a bit of tailoring for it to produce the information you want.
Just by itself it returns all refs
git for-each-ref
# returns all the references
This shows all refs (branches, tags, stashes, remote branches). To just get branches pass an argument that corresponds to the .git
directory that contain branch refs.
git for-each-ref ref/heads
To reduce the number of refs it produces use --count
git for-each-ref --count=5
# returns just 5 refs
This outputs a line that looks like this:
c1973f1ae3e707668b500b0f6171db0a7c464877 commit refs/heads/feature/some-feature
Which is a bit noisy. To reduce the noise we can use --format
.
git for-each-ref --format="$(refname)"
# line is just: refs/heads/feature/some-feature
Which can be shortened with :short
git for-each-ref --format="$(refname:short)"
#just: feature/some-feature
To get most recent branches we'll need to sort. The -
in front of the field name reverses the sort.
git for-each-ref --sort=-committerdate
All together:
git for-each-ref \
--format="%(refname:short)" \
--count=5 \
--sort=-committerdate \
refs/heads/
After deleting branches on github, or any other tracked remote, you can remove references to deleted branches without having to fetch changes by simply using the remote prune sub-commands:
$ git remote prune origin
Another remote:
$ git remote add bitbucket git@bitbucket.org...
$ git remote prune bitbucket
If I delete a bunch of files and just want to stage the deleted ones, I can use xargs
to add them:
$ git ls-files --deleted | xargs git add
Example output:
$ git status
deleted: lib/to/deleted_1.txt
deleted: lib/to/deleted_2.txt
modified lib/that/changed_1.txt
deleted: lib/to/deleted_3.txt
deleted: lib/to/deleted_4.txt
modified lib/that/changed_2.txt
deleted: lib/to/deleted_5.txt
modified lib/that/changed_3.txt
$ git ls-files --deleted | xargs git add
Changes to be committed:
deleted: lib/to/deleted_1.txt
deleted: lib/to/deleted_2.txt
deleted: lib/to/deleted_3.txt
deleted: lib/to/deleted_4.txt
deleted: lib/to/deleted_5.txt
Changes not staged for commit:
modified lib/that/changed_1.txt
modified lib/that/changed_2.txt
modified lib/that/changed_3.txt
I have some git aliases in my dotfiles and sometimes I use an alias for too long that I actually forget what it does under the hood.
I can open my ~/.gitconfig
file in nvim (which I have an alias for) and search for the line that introduced the alias, but when pairing sometimes it's easier and faster to just use git help
to get the definition of an alias:
git help doff
'doff' is aliased to 'reset HEAD^'
Some of my repos have a lot of branches, and a command I discovered this week sorts them by the most recent committer date:
$ git branch --sort=-committerdate
* new-branch
not-so-new-branch
super-old-branch
The -
reverses the order, putting the most-recently updated branch at the top, rather than the bottom, of my output. That's more intuitive to me, but it certainly works in reverse.
Github released few days ago an easy code navigation for projects using:
And it looks like this:
Go for more information about Github Navigation Code
If you type git config --global diff.noprefix true
into your terminal, it will set the value like so in your ~/.gitconfig
file:
[diff]
noprefix = true
The upshot is that git diff
s will no longer prefix files with a/
and b/
, such that you can copy a useful path when you double-click to select it in the terminal.
--- a/file/that/changed.awesome
+++ b/file/that/changed.awesome
becomes
--- file/that/changed.awesome
+++ file/that/changed.awesome
Credit to @brandur for the tip. Here's the source tweet
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.
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.
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
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.
This is a handy way to request a lot of reviews at once.
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
.
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.
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
Today I learned how to list my git aliases with:
git config --get-regexp alias
# alias.st status
On this case git st
is the same as git status
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.
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 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.
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 <= 😯
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.
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.
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
git reflog
can show timestamps of when things happened with the --date
flag
git reflog --date=iso
b3d1bab HEAD@{2019-03-20 16:09:19 -0400}: reset: moving to HEAD
b3d1bab HEAD@{2019-03-20 16:08:16 -0400}: reset: moving to HEAD
b3d1bab HEAD@{2019-03-20 16:08:04 -0400}: reset: moving to HEAD^
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.
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.
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.