Git GOOD
Mastering the "the information manager from hell"
Being able to jump efficiently between different states of a project, collaborate with other devolopers seemlessly and generally understanding the complexity of version control is one of the most empowering skills a programmer can attain.
What is git?
Git
is a version control software a tools that allows you to time travel. With git you can freeze certain states of a project in time, your freezer bags are called commits
a commit is a snap shot of all the data contained in a directory (repository
).
Base Commands
First you have to initialize
git in the directory you want to track
1# Change to the directory you want to track with Git
2cd your-project
3
4# Initialize a new Git repository in the current directory
5git init
6
Now make changes and save / commit
them
1# Change content in the repository
2touch README.md
3
4# Check the status / discover the differenece between current change and last commit
5git status
6> On branch main
7>
8> No commits yet
9>
10> Untracked files:
11> (use "git add <file>..." to include in what will be committed)
12> README.md
13>
14> nothing added to commit but untracked files present (use "git add" to track)
15
16# Now add changes to the next commit
17git add README.md
18
19# Create / name commit
20git commit -m "docs: add README"
21
Commits should be granular and capture logical steps, towards a feature of a fix (commit often or learn it the hard way),
for files and directories that contain sensitive information (such as .env
) or are large and easily reproducible (like
node_modules
or __pycache__
), add them to a .gitignore
file to prevent them from being tracked by Git.
1# Create a .gitignore file
2echo "node_modules/" >> .gitignore
3git add .gitignore
4git commit -m "chore: add .gitignore"
5
Finally transfer repository state from your local computer to a remote machine / cloud.
1# Add / link the remote repository (github in this example)
2git remote add origin https://github.com/yourusername/your-repo.git
3
4# Push your commit to the remote repository's main branch
5git push -u origin main
6
From now on, you can run git push
after you have committed to sync your local repository, or run git pull
to retrieve
the latest changes from the remote repository.
When you want to retrieve a repository from github use git clone
.
1# Clone an existing repository from GitHub
2git clone https://github.com/username/repo.git
3
When renaming or moving files or directories use git mv
it moves the file and you can still track the differences within as
opposed to deleting one and adding another file.
1# Move the entire folder 'old_folder' to 'new_folder'
2git mv old_folder new_folder
3
4# Move and rename file.txt to docs/guide.md
5git mv file.txt docs/guide.md
6
Collaboration
Git is probably best known for enabling multiple devolopers to work on one project simultaneously, this is facilitated by
branches
as one might be able to guess by the name branches allow devolopers to branch of from the tree root branch
(often called main or master) to tinker through the stages of creating a feature or fix without interfereing, as others can do
the same from the clean main branch (which should always be in a working state).
1# List all branches (shows which branch you're on)
2git branch
3
4# Create a new branch
5git branch my-new-feature
6
7# Switch to your new branch
8git checkout my-new-feature
9
10# Alternatively, create and switch in one step
11git switch -c feature/my-new-feature
12
13# Make changes, then stage and commit as usual
14echo "Some new feature" > feature.txt
15git add feature.txt
16git commit -m "feat: add new feature"
17
18# Push your branch to the remote repository
19git push -u origin my-new-feature
20
Once your work on the branch is complete and tested, you’ll want to merge
it back into the main.
1# Switch back to the main branch
2git checkout main
3
4# Pull the latest changes from the remote (optional but recommended)
5git pull
6
7# Merge your branch into main
8git merge my-new-feature
9
10# Push the updated main branch to the remote repository
11git push
12
In practice you probably want to merge changes from main first into your branch (ensuring it's up to date with the latest changes) before merging it into main this allows for testing against the latest state, smoother merge into main and a cleaner main history.
1# Make sure you're on your feature branch
2git checkout my-feature
3
4# Update your branch with the latest changes from main
5git fetch origin
6git merge origin/main
7
8# Resolve any conflicts, test your code
9
10# Switch back to main and merge your branch
11git checkout main
12git merge my-feature
13git push
14
Alternatively, you can use git rebase origin/main
instead of git merge origin/main
for a linear history and this is
where the configuration start and this is where the configuration starts.
Configuration
My workflow when it comes to using "the information manager from hell" (as it's creator Linus Torvals called it, in it's first commit) is largely based on minimizing typing (chars & commands).
Git
can be configured through the ~/.gitconfig
file, I believe the configurability is a largely overlooked part in optimizing your git experience, lets break down my gitconfig.
User Identity & Security
1[user]
2 # name & email asscoiated with your commits
3 name = Mats Julius Funke
4 email = 125814808+matsjfunke@users.noreply.github.com
5 signingkey = ~/.ssh/github_signing # Path to SSH private key used for signing commits
6[commit]
7 gpgsign = true # Automatically sign all commits with your SSH key
8[gpg]
9 format = ssh # Use SSH keys instead of GPG keys for signing commits
10
Core Git Behavior
1[core]
2 editor = nvim # Default text editor for commit messages, interactive rebase, fixing merge conflicts etc.
3 pager = delta # fancy diff view (more later)
4[init]
5 defaultBranch = main # Use 'main' instead of 'master' for new repositories (personal preference)
6[rebase]
7 autoSquash = true # Automatically squash commits marked with 'fixup!' or 'squash!'
8 autoStash = true # Automatically stash changes before rebase and pop after
9 updateRefs = true # Update branch pointers that point to rebased commits
10
Remote Operations
My personal preference is to rebase on every pull as i've hinted earlier.
1[pull]
2 rebase = true # Always rebase instead of merge when pulling (cleaner history)
3[fetch]
4 prune = true # delete local branch if remote was deleted
5[push]
6 autoSetupRemote = true # Automatically create remote branch on first push of local
7
Git Large File Storage configuration
1[filter "lfs"]
2 required = true # LFS is required for this repo
3 clean = git-lfs clean -- %f # Clean filter for storing files
4 smudge = git-lfs smudge -- %f # Smudge filter for retrieving files
5 process = git-lfs filter-process # Process filter for efficiency
6
UI / UX
These settings make the outputs look prettier.
1[color]
2 ui = auto # Enable colored output in terminal (auto = only when outputting to terminal)
3[column]
4 ui = auto # Display output in columns
5[delta]
6 line-numbers = true # Show line numbers in delta diff viewer
7[help]
8 autocorrect = prompt # Hint for typos
9[advice]
10 skippedCherryPicks = false # Don't show advice about skipped cherry-picks during rebase
11[tag]
12 sort = version:refname # Sort tags by version number (v1.0, v1.1, v2.0, etc.)
13[branch]
14 sort = -committerdate # Sort branches by most recent commit (newest first)
15
The git diff
command is great for visualizing the granular changes within a file the syntax highlighting requires installing delta via brew install delta
:
List branches ordered and in columns:
Aliases
[alias] allows for setting up convenient shortcuts.
1# Stash operations
2staash = stash --all # Stash everything including untracked files
3sm = stash push -u -m # Stash with message
4
5# Navigation & switching
6sw = switch # Switch branches
7swc = switch -c # Create and switch to new branch
8
9# Status & information
10st = status -s # Consice status output
11lg = log --oneline --graph --decorate --all # Pretty log with graph and all branches
12wtl = worktree list # List all worktrees
13
14# Remote operations
15p = pull # Simple pull shorthand
16ub = !git fetch origin && git reset --hard origin/main # Update branch to match origin/main
17
18# Commit operations
19uncommit = reset --soft HEAD~1 # Undo last commit but keep changes staged
20lfg = "!f() { git add . && git commit -m \"$1\" && git push; }; f" # LETS F*CKING GO: Add, commit, push in one command
21
22# GitHub integration
23pr = "!f() { \n gh pr create --title \"$1\" --body \"\" --fill;\n}; f" # Create PR with GitHub CLI
24
The git log
is really useful the visualize the history and different branches of a repo:
Consice status:
Worktrees
Usually when i'm in the process working on something and I need to switch to e.g. fix something i commit with type wip
like git lfg "wip: xyz is in progress
when I don't think my state is commit worthy i stash it git sm "some changes"
but for larger project I prefer to have multiple directories of the same repository as its more convenient to
switch in between and you don't need to break your state for a quick fix, this is where git worktrees come in.
Git worktrees are branches in separate directories each directory is linked to the same underlying repository but maintains its own working state, so you can easily jump between features, bug fixes, or experiments without disrupting your main workflow (my go to hotfix choice).
These shell functions are needed since gitconfig doesn't support cd, add them to your .zshrc/.bashrc if you want to use them.
1# git worktree add
2wta() {
3 if [ -z "$1" ]; then
4 echo "Usage: wta <branch-name>"
5 return 1
6 fi
7
8 git worktree add -b "$1" "../$1" main && cd "../$1"
9 echo "Successfully created worktree & branch '../$1' and switched to it"
10}
11
12# git worktree delete
13wtd() {
14 # If no argument provided, use current directory name
15 if [ -z "$1" ]; then
16 branch_name=$(basename $(pwd))
17 echo "No branch specified, using current directory: $branch_name"
18 else
19 branch_name="$1"
20 fi
21
22 # Get the main repo name by finding the main worktree
23 main_repo_path=$(git worktree list | head -n 1 | awk '{print $1}')
24 repo_name=$(basename "$main_repo_path")
25 echo "Main repo: $repo_name"
26
27 # Check if we're currently in the worktree we want to delete
28 current_dir=$(pwd)
29
30 if [[ "$(basename "$current_dir")" == "$branch_name" ]]; then
31 echo "You're currently in the worktree directory. Switching to main repo..."
32 # Change directory BEFORE removing the worktree
33 cd "../$repo_name"
34
35 # Remove the worktree using the full path
36 git worktree remove "../$branch_name" --force
37
38 # Delete the branch
39 git branch -D "$branch_name"
40
41 echo "Deleted worktree and branch: $branch_name"
42 else
43 # We're not in the target directory, safe to delete
44 git worktree remove "../$branch_name" --force
45 git branch -D "$branch_name"
46 echo "Deleted worktree and branch: $branch_name"
47 fi
48}
49
My worktree ergonomics are:
1# add a worktree
2wta hotfix-deployment
3
4# add, commit, push (which auto create remote branch
5git lfg "fix: everything"
6
7# open github pr
8git pr "fix: You are welcome"
9
10# delte worktree (directory and branch)
11git wtd
12
13# list worktrees (verify desired state)
14git wtl
15