14 KiB
Git
Git
Basic configuration
Just to make Emacs follow the convention in terms of indentation, I’m forcing it to use tabs.
# -*- indent-tabs-mode: t; -*-
Setting Up Personal Information and Preferences
Let’s set some of my personal information, namely my name, my email, and which GPG key I sign my commits with.
[user]
email = lucien@phundrak.com
name = Lucien Cartier-Tilet
signingkey = BD7789E705CB8DCA
In terms of core configuration, I simply set Emacs as my default Git editor. I also have my global gitignore file, described below in Global gitignore file.
[core]
editor = emacsclient -c -a emacs
excludesfile = ~/.config/git/global-ignore
Let’s not forget to tell Git to use the main
branch by default.
[init]
defaultBranch = main
This is entirely a matter of personal preferences, but I like to use Zen, a Firefox fork with really nice features.
[web]
browser = zen-browser
Better Diffing
Git’s diffing algorithm evolved and improved over time, but its default algorithm did not. Take this example, from this Gitbutler article:
Not really readable, I think you’ll agree. I mean, you can sort of see
what happens, but really, we just moved the h2
styling below the
.event
styling. Git seems to think otherwise. However, let’s turn on
the histogram
algorithm on:
Immediately, we have a much clearer picture of what happened! But I’ll
let you on another secret: you can make it even clearer by using the
colorMoved
option to color differently lines that were moved from
lines that were actually modified!
I’ll also add a configuration to make it easier to see what is being
compared using mnemonicPrefix
. As per man git-config
:
git diff
compares the (i)ndex and the (w)ork tree;
git diff HEAD
compares a (c)ommit and the (w)ork tree;
git diff --cached
compares a (c)ommit and the (i)ndex;
git diff HEAD:<file1> <file2>
compares an (o)bject and a (w)ork tree entity;
git diff --no-index <a> <b>
compares two non-git things <a> and <b>.
That means you can see I was comparing to objects not tracked by git in my screenshot above.
Finally, renames
set to copies
not only better detects file renames,
but also file copies.
[diff]
algorithm = histogram
colorMoved = plain
mnemonicPrefix = true
renames = copy
Better Fetching, Pulling, Rebasing, and Pushing
By default, when I pull new commits, I do not want to create a merge commit, but rather to rebase my commits on what’s new upstream.
[pull]
rebase = true
However, there is a problem with git’s default behaviour: it wont allow me to pull changes or perform a rebase as long as my worktree is dirty. I either have to commit or stash my changes before I can do something. And you know what? Git can auto stash your changes for you before performing a rebase. In fact, while we’re at it, let’s also automatically squash commits that need to be squashed, and update all refs.
[rebase]
autoSquash = true
autoStash = true
updateRefs = true
And oh, buggers, you have a merge conflict! I’m used to it, but since
git 2.3, there is a new feature that adds some context to git
conflicts: zdiff3
(which stands for zealous diff3). Not only will it
show you the conflicting incoming and HEAD changes, but it will also
show you what the code was before either modified the conflicting
area. Not a must-have feature, but a nice one to have. Compare this:
To this:
We have a new line beginning with |||||||
with the original line
below. Also, it’s nice to see Emacs supports this syntax out of the
box!
[merge]
conflictstyle = zdiff3
Finally, once we’re good to go, we may want to push our changes to the
remote repository. Sometimes, git is confused and isn’t sure where it
should push your branch. Let’s tell it to simply push your current
branch to the branch with the same name on the remote with
push.default
. If the upstream branch is not set yet, you can
automatically set it up with push.autoSetupRemote
. Finally, I don’t
want to push separately my tags, so let’s push them with any other
push.
[push]
default = simple
autoSetupRemote = true
followTags = true
Making Git Look Better
First, let’s activate colors in git by default when we are in a terminal.
[color]
ui = auto
Getting a raw list of things –branches, tags, …– is not nice. So, let’s make it a bit nicer and split these lists in columns.
[column]
ui = auto
Simply using the column.ui
option sets everything to use columns when
using a terminal. If you want more granularity, you can instead use
column.branch
, column.status
, etc… Look up man git-config
for more
info.
Better Sorting Branches and Tags
By default, branches are sorted alphabetically. This may be fine for most people, but I prefer something else: sorting them by how recently they were comitted to. This is actually quite easy to configure:
[branch]
sort = -committerdate
Sorting tags is not nice by default. For instance, git will show you version 11 before version 2, because 11 is technically before 2 alphabetically speaking. Let’s fix that.
[tag]
sort = version:refname
Did You Mean "Commit"?
Sometimes, I fat finger my git commands and white a subcommand that
does not exist, like git pul
or git comitt
. By default, git will
simply tell you that, no, that subcommand does not exist, but will be
kind enough to suggest a few commands that may be what you are looking
for. Let’s make git not only suggest these, but also ask you if you
want to run the one you most likely wanted to run.
[help]
autocorrect = prompt
Aliases
abbreviation | equivalent |
---|---|
a |
add --all |
aca |
!git add --all && git commit --amend |
acan |
!git add --all && git commit --amend --no-edit |
abbreviation | equivalent |
---|---|
b |
branch |
bd |
branch -d |
bdd |
branch -D |
abbreviation | equivalent |
---|---|
c |
commit -S |
ca |
commit -Sa |
can |
commit -Sa --no-edit |
cm |
commit -Sm |
cam |
commit -Sam |
abbreviation | equivalent |
---|---|
co |
checkout |
cob |
checkout -b |
cod |
checkout develop |
abbreviation | equivalent |
---|---|
cl |
clone |
cl1 |
clone --depth 1 |
abbreviation | equivalent |
---|---|
f |
fetch |
fp |
fetch --prune |
abbreviation | equivalent |
---|---|
ps |
push |
psf |
push --force-with-lease |
pso |
push origin |
psfo |
push --force-with-lease origin |
pushall |
!git remote \vert{} xargs -L1 git push |
psl |
!git remote \vert{} xargs -L1 git push |
pullall |
!git remote \vert{} xargs -L1 git pull |
pll |
!git remote \vert{} xargs -L1 git pull |
abbreviation | equivalent |
---|---|
pl |
pull |
pb |
pull --rebase |
abbreviation | equivalent |
---|---|
r |
rebase |
ra |
rebase --abort |
rc |
rebase --continue |
rd |
rebase develop |
ri |
rebase -i |
abbreviation | equivalent |
---|---|
rmf |
rm -f |
rmd |
rm -r |
rmdf |
rm -rf |
abbreviation | equivalent |
---|---|
sm |
submodule |
sms |
submodule status |
sma |
submodule add |
smu |
submodule update |
smui |
submodule update --init |
smuir |
submodule update --init --recursive |
abbreviation | equivalent |
---|---|
st |
stash |
stc |
stash clear |
stp |
stash pop |
stw |
stash show |
abbreviation | equivalent |
---|---|
u |
reset -- |
unstage |
reset -- |
abbreviation | equivalent |
---|---|
d |
diff -w |
l |
log --oneline --graph --decorate |
s |
status |
staged |
diff --cached |
a = add --all aca = !git add --all && git commit --amend acan = !git add --all && git commit --amend --no-edit
[alias]
<<abbrev-gen(abbrevs=git-add-abbrev)>>
<<abbrev-gen(abbrevs=git-branch-abbrev)>>
<<abbrev-gen(abbrevs=git-commit-abbrev)>>
<<abbrev-gen(abbrevs=git-checkout-abbrev)>>
<<abbrev-gen(abbrevs=git-clone-abbrev)>>
<<abbrev-gen(abbrevs=git-fetch-abbrev)>>
<<abbrev-gen(abbrevs=git-push-abbrev)>>
<<abbrev-gen(abbrevs=git-pull-abbrev)>>
<<abbrev-gen(abbrevs=git-rebase-abbrev)>>
<<abbrev-gen(abbrevs=git-rm-abbrev)>>
<<abbrev-gen(abbrevs=git-submodule-abbrev)>>
<<abbrev-gen(abbrevs=git-stash-abbrev)>>
<<abbrev-gen(abbrevs=git-unstage-abbrev)>>
<<abbrev-gen(abbrevs=git-single-abbrev)>>
Tools
Sendemail
[sendemail]
smtpserver = mail.phundrak.com
smtpuser = lucien@phundrak.com
smtpencryption = tls
smtpserverport = 587
[credentials "smtp://lucien@phundrak.com@mail.phundrak.com:587"]
helper = "secret-tool lookup password email_lucien-phundrak-com"
Magit
[magithub]
online = true
[magithub "status"]
includeStatusHeader = true
includePullRequestsSection = true
includeIssuesSection = true
GPG
[gpg]
program = gpg2
[commit]
gpgsign = true
Merge
[merge]
tool = ediff
[mergetool.ediff]
cmd = emacs --eval \" (progn (defun ediff-write-merge-buffer () (let ((file ediff-merge-store-file)) (set-buffer ediff-buffer-C) (write-region (point-min) (point-max) file) (message \\\"Merge buffer saved in: %s\\\" file) (set-buffer-modified-p nil) (sit-for 1))) (setq ediff-quit-hook 'kill-emacs ediff-quit-merge-hook 'ediff-write-merge-buffer) (ediff-merge-files-with-ancestor \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" nil \\\"$MERGED\\\"))\"
Git forges
[github]
user = phundrak
[url "https://phundrak@github.com"]
insteadOf = https://github.com
[url "https://phundrak@labs.phundrak.com"]
insteadOf = https://labs.phundrak.com
[url "https://github.com/RustSec/advisory-db"]
insteadOf = https://github.com/RustSec/advisory-db
LFS
[filter "lfs"]
required = true
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
process = git-lfs filter-process
Global gitignore file
This is my global gitignore file, specifying files that will always be ignored by Git, as described in Basic configuration.
You may see some lines beginning with ,*
. Just read it as a simple *
,
this is done in order to avoid org-mode being confused by a
line-initial *
usually marking headings.
First, let’s just ignore dotenv files and direnv’s directories.
.env
.direnv/
Now, let’s ignore files generated by Emacs.
,*~
\#*\#
,*.elc
auto-save-list
.\#*
,*_flymake.*
/auto/
.projectile
.dir-locals.el
# Org mode files
.org-id-locations
,*_archive
Finally, let’s ignore some files we generally do not want.
,*.out
,*.o
,*.so
# Archives
,*.7zz
,*.dmg
,*.gz
,*.iso
,*.jar
,*.rar
,*.tar
,*.zip
,*.log
,*.sqlite
dist/