.gitignore
Overwrite remote branch with local branch
git push --force
overwrites a remote branch with your local branch.
git push --force-with-lease
is a safer option that will not overwrite any work on the remote branch if more commits were added to the remote branch (by another team-member or coworker or what have you). It ensures you do not overwrite someone elses work by force pushing.
You can create an alias to make it easier to use --force-with-lease
by default:
git config --global alias.pushfl "push --force-with-lease"
Or you can use a wrapper script to condition you to relearn your muscle memory:
Add to your ~/.bash_profile
, ~/.bashrc
or ~/.zshrc
.
git() {
if [[ $@ == 'push -f'* || $@ == 'push --force'* ]]; then
echo "Hey stupid, use --force-with-lease instead of --force"
exit 1
else
command git "$@"
fi
}
Overwrite local branch with remote branch
Replaces your current branch with its remote tracking branch:
git reset --hard @{u}
Undo a commit (git reset)
git reset
does know five “modes”: soft, mixed, hard, merge and keep. I will start with the first three, since these are the modes you’ll usually encounter. After that you’ll find a nice little a bonus, so stay tuned.
soft
When using git reset --soft HEAD~1
you will remove the last commit from the current branch, but the file changes will stay in your working tree. Also the changes will stay on your index, so following with a git commit
will create a commit with the exact same changes as the commit you “removed” before.
mixed
This is the default mode and quite similar to soft. When “removing” a commit with git reset HEAD~1
you will still keep the changes in your working tree but not on the index; so if you want to “redo” the commit, you will have to add the changes (git add
) before committing.
hard
When using git reset --hard HEAD~1
you will lose all uncommitted changes in addition to the changes introduced in the last commit. The changes won’t stay in your working tree so doing a git status
command will tell you that you don’t have any changes in your repository.
Tread carefully with this one. If you accidentally remove uncommitted changes which were never tracked by git
(speak: committed or at least added to the index), you have no way of getting them back using git
.
keep (Bonus)
git reset --keep HEAD~1
is an interesting and useful one. It only resets the files which are different between the current HEAD
and the given commit. It aborts the reset if any of these files has uncommitted changes. It basically acts as a safer version of hard
.
This mode is particularly useful when you have a bunch of changes and want to switch to a different branch without losing these changes - for example when you started to work on the wrong branch.
Note
When doing git reset
to remove a commit the commit isn’t really lost, there just is no reference pointing to it or any of it’s children. You can still recover a commit which was “deleted” with git reset
by finding it’s SHA-1 key, for example with a command such as git reflog
.
Reuse commit message after reset
After a git reset
, this one-liner can do it:
git commit --reuse-message=ORIG_HEAD
# or
git commit --reuse-message=HEAD@{1}
# or even shorter:
git commit -C HEAD@{1}
To edit on reuse:
git commit --reedit-message=HEAD@{1}
To change the author:
git commit --reset-author
Cleanup
How can I undo every change made to my directory after the last commit, including deleting added files, resetting modified files, and adding back deleted files?
-
You can undo changes to tracked files with:
git reset HEAD --hard
-
You can remove untracked files with:
git clean -f
-
You can remove untracked files and directories with:
git clean -fd
but you can’t undo changes to untracked files.
-
You can remove ignored and untracked files and directories
git clean -fdx
but you can’t undo change to ignored files.
You can also set clean.requireForce
to false
:
git config --global --add clean.requireForce false
to avoid using -f
(--force
) when you use git clean
.
List staged files
git --no-pager diff --name-only --cached
Remove a directory tree from staging area
If you unintentionally added a directory that should have been ignored, run:
git rm --cached -r directory-name
to recursively remove the tree rooted at directory-name from the index.
List repo root dir
"$(git rev-parse --show-toplevel)"
Filter log
git log --author="username" --pretty=format:"%h - %an, %ar : %s"
%an
- author name
%ae
- author email
%cn
- committer name
You don’t need to use the whole name:
git log --author=John
git log --author="John Doe"
Add --all
if you intend to search all branches and not just the current commit’s ancestors in your repo.
You can also easily match on multiple authors as regex is the underlying mechanism for this filter:
git log --author="\(John\)\|\(Bob\)"
In order to exclude commits by a particular author or set of authors using regular expressions as noted in this question, you can use a negative lookahead in combination with the --perl-regexp
switch:
git log --author='^(?!John|Bob).*$' --perl-regexp
Show all files modified by a user:
git log --no-merges --author="John" --name-only --pretty=format:"" | sort -u
Show all commits to a file:
The --follow
works for a particular file and accounts for renames.
git log --follow -- <filename|dirname>
Difference to other solutions given
Note that other solutions include git log path
(without the --follow
). That approach is handy if you want to track e.g. changes in a directory, but stumbles when files were renamed (thus use --follow filename
).
Edit author of specific commits
Interactive rebase off of a point earlier in the history than the commit you need to modify (git rebase -i <earliercommit>
). In the list of commits being rebased, change the text from pick
to edit
next to the hash of the one you want to modify. Then when git prompts you to change the commit, use this:
git commit --amend --author="Author Name <email@address.com>" --no-edit
For example, if your commit history is A-B-C-D-E-F
with F
as HEAD
, and you want to change the author of C
and D
, then you would…
- Specify
git rebase -i B
(here is an example of what you will see after executing thegit rebase -i B
command)- if you need to edit
A
, usegit rebase -i --root
- if you need to edit
- Change the lines for both
C
andD
frompick
toedit
- Exit the editor (for vim, this would be pressing Esc and then typing
:wq
). - Once the rebase started, it would first pause at
C
- You would
git commit --amend --author="Author Name <email@address.com>"
- Then
git rebase --continue
- It would pause again at
D
- Then you would
git commit --amend --author="Author Name <email@address.com>"
again git rebase --continue
- The rebase would complete.
- Use
git push -f
to update your origin with the updated commits.
Hooks
Hooks can either be configured locally on a per repository basis ($GIT_DIR/hooks/*
) or globally for all git repositories (git config core.hooksPath/*
). To use this hook:
- Copy it to
.git/hooks/post-merge
- Ensure it’s executable with
chmod +x .git/hooks/post-merge
A list of available hooks can be found here: https://git-scm.com/docs/githooks
post-merge
#!/usr/bin/env python
import sys
import subprocess
diff_requirements = 'git diff ORIG_HEAD HEAD --exit-code -- requirements.txt'
exit_code = subprocess.call(diff_requirements.split())
if exit_code == 1:
print 'The requirements file has changed! Remember to install new dependencies.'
else:
print 'No new dependencies.'
List git aliases
$ git config --get-regexp alias
# alias.st status
In this case git st
is the same as git status
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 mypackage.json
file even though the most recent changes to that line were version bumps.
Currently, as of Git 2.25 (Q1 2020), the line-log functionality (git log -L
) only supports displaying patch output (-p | --patch
, its default behavior) and suppressing it (-s | --no-patch
).
In short this means:
- that
-L
implies-p
, - that patch output can be suppressed using
-s
, - and that all other diff formats are not allowed.
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/5c61ee3fe0083
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 shortlog of committers and their commits
You can use git shortlog
to get a quick summary of committers and their commits on a project.
$ git shortlog
Andrew Vogel (10):
Initial commit
Add local zshrc, vimrc, and tmux.conf
Add README
Remove unneeded stuffs
Fix Markdown links
Add installation info and image to README
Update local zsh config
Add local vim plugins
Add lightline to vimrc
Update README
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.
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(-)
...
Automatically track remote branch with git
As of git 2.37.0, this is now possible with git configuration.
Run to update your configuration:
git config --global --add --bool push.autoSetupRemote true
Then git push
will automatically setup the remote branch.
Clean up dangling branches
This deletes local branches that are no longer present in the remote:
git fetch -p && for branch in `LC_ALL=C git branch -vv | grep ': gone]' | awk '{print $1}'`; do git branch -D $branch; done
Sources: