Go back to previous git branch shortcut
git checkout -
takes you right back to the previously checked out branch.
git checkout -
takes you right back to the previously checked out branch.
Say you had a committed file, foo.txt
and you wanted to rename that to Foo.txt
.
Well, on Mac, changing the file name will not show up when you run git status
.
So, what is the solution here?
git mv foo.txt Foo.txt
will rename the file, and let git keep track of it.
git stash clear
does exactly what it sounds like!
Today I Learned that HEAD~
is equivalent to HEAD~1
, HEAD~~
(2 ~
) is equivalent to HEAD~2
, and so on.
This is great - you wouldn't believe the number of times I mistype git show HEAD~1
.
Today I learned you can print the git log for an individual function with the -L
flag - -L :<funcname>:<file>
.
So if I have a function named friendlyFunction
in app/fileName.ts
, I can see it's history with:
% git log -L :friendlyFunction:app/fileName.ts
commit 740ab0c1eeb63e0a6bef06f89397e76407325f58 (HEAD -> main)
Author: Tony Yunker
Date: Mon Nov 6 17:22:52 2023 -0600
last commit
diff --git a/app/fileName.ts b/app/fileName.ts
--- a/app/fileName.ts
+++ b/app/fileName.ts
@@ -1,4 +1,4 @@
export const friendlyFunction = () => {
- console.log("hi there 👋");
+ console.log("bye now 👋");
return 2 + 2;
};
commit 314e725e56871e17c64b811de8e8cdb65badb08e
Author: Tony Yunker
Date: Mon Nov 6 17:22:31 2023 -0600
second commit
diff --git a/app/fileName.ts b/app/fileName.ts
--- a/app/fileName.ts
+++ b/app/fileName.ts
@@ -1,4 +1,4 @@
export const friendlyFunction = () => {
console.log("hi there 👋");
- return 1 + 1;
+ return 2 + 2;
};
commit 61998a2a9dfb64b0ee3aa5c23f783cca517ab15b
Author: Tony Yunker
Date: Mon Nov 6 17:22:08 2023 -0600
first commit
diff --git a/app/fileName.ts b/app/fileName.ts
--- /dev/null
+++ b/app/fileName.ts
@@ -0,0 +1,4 @@
+export const friendlyFunction = () => {
+ console.log("hi there 👋");
+ return 1 + 1;
+};
When creating a GitHub repo with the CLI tool, you have the opportunity to push up your commits immediately as well using the --push
or (-p
)
❯ gh repo create OWNER/REPO --push --source .
✓ Created repository OWNER/REPO on GitHub
✓ Added remote git@github.com:OWNER/REPO.git
Using the push flag requires the --source
flag to tell it where the repo is locally which is why you see that in the example above.
If you have not partaken, creating a GitHub repo with the CLI tool is pretty cool; however, one thing that always got me was that when you create a new repo it does not create a remote so I'd always have to go back and make an origin
.
There is an additional CLI flag that you can pass to do this for you.
gh repo create OWNER/REPO -r origin -s .
So, just for context, the -r
(or --remote
) flag is for naming the remote. This must also be used with the -s
(or --source
) flag, which tells the CLI where the repo lives on your local system. In the above example, I referenced the directory I was in using a .
.
There's a good chance you're using GitHub if you're a developer. A long while ago, GitHub wrapped git
commands with a tool they released called hub
. This was nice, then, as it helped bridge the gap between Git and GitHub. A few years ago, they released the GitHub CLI, a dedicated command line tool for GitHub... no more wrapping Git commands.
There are many things you can do with this tool, but the one I use the most is setting up a new repository.
With this simple command, you can generate the repo on GitHub.
> gh repo create OWNER/REPO
✓ Created repository OWNER/REPO on GitHub
git log
is great to see a detailed diff, but what if you only want to see the files that were changed?
There's git whatchanged
, but it's essentially deprecated, and git docs recommend using git log
instead:
% git whatchanged
commit 9925b8ec8fc8024544a36c71e210a23192a63bf4 (HEAD -> main, origin/main, origin/HEAD)
Author: Tony Yunker <tony.yunker@gmail.com>
Date: Fri Oct 20 15:07:09 2023 -0500
ruby - abbreviated assignment operators
:100644 100644 7d41153 7449733 M README.md
:000000 100644 0000000 5a432d1 A ruby/rubys_abbreviated_assignment_operators.md
So then we can use git log --name-only
to see the names of the files that were changed.
% git log --name-only
commit 9925b8ec8fc8024544a36c71e210a23192a63bf4 (HEAD -> main, origin/main, origin/HEAD)
Author: Tony Yunker <tony.yunker@gmail.com>
Date: Fri Oct 20 15:07:09 2023 -0500
ruby - abbreviated assignment operators
README.md
ruby/rubys_abbreviated_assignment_operators.md
But that won't tell us if they were updates or adds or whatnot. For that, we can use git log --name-status
% git log --name-status
commit 9925b8ec8fc8024544a36c71e210a23192a63bf4 (HEAD -> main, origin/main, origin/HEAD)
Author: Tony Yunker <tony.yunker@gmail.com>
Date: Fri Oct 20 15:07:09 2023 -0500
ruby - abbreviated assignment operators
M README.md
A ruby/rubys_abbreviated_assignment_operators.md
These flags can be used on git show
too!
View a list of your commits, less the merges with -
git log --no-merges
https://git-scm.com/docs/git-log#Documentation/git-log.txt---no-merges
In Git you can check out previous branches using @{-N}
, where N is how many "branches ago" you want to go back.
For example, say you were in main
then checked out branch-1
then checked out branch-2
, and finally you checked out branch-3
.
If you wanted to checkout the branch you were on "2 branches ago" you would just checkout @{-2}
> git checkout @{-2}
Switched to branch 'branch-1'
If you are using this to just go back to your previous branch @{-1}
, there is a nice shortcut:
> git checkout -
Switched to branch 'branch-2'
H/T Matt Polito
A Github repository's wiki can be an extremely useful tool with amazing information. However not everyone is best at organizing information and I've had mixed results with Github search.
I knew that each repo wiki was versioned and figured it was git but it turns out you can also get access to that.
Just add .wiki
to the end of your repo when cloning.
gh repo clone hashrocket/decent_exposure.wiki
Now you have all of the wiki files locally and can search however you feel.
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.