Files
web/content/notes/ready/git.md

361 lines
13 KiB
Markdown

---
title: Version Control [GIT]
description:
draft: false
tags:
- programming
- tools
author: TrudeEH
showToc: true
---
Git is a version control system first developed by Linus Torvalds. It facilitates collaboration on large projects, keeps track of changes, and allows mistakes to be rolled back into a previous state.
## Configure Git
Git uses a hierarchy of configuration files:
- **System**: (`/etc/gitconfig`) Configuration for all users in a system.
- **Global**: (`~/.gitconfig`) Configure Git for all project of the current user.
- **Local**: (`.git/config`) Configure Git for the current project.
- **Worktree**: (`.git/config.worktree`) Configure part of a project.
```Shell
# Check if user name and email are set.
git config --get user.name
git config --get user.email
# If not, set those values.
git config --add --global user.name "username"
git config --add --global user.email "email@example.com"
git config --add --global init.defaultBranch main # GitHub's default
git config --unset example.key # Remove a configuration value
git config --unset-all example.key # Remove all instances of a configuration key
git config --remove-section section # Remove an entire section
# Rebase on pull by default to keep a linear history
git config --global pull.rebase true
```
## Create a Repository
Git stores all project information in the `.git` directory. This includes branches, commits, and metadata.
```Shell
mkdir project && cd project
git init
```
## Status
A file can be in one of several states in a Git repository.
- `untracked`: Not being tracked by Git
- `staged`: Marked to be included in the next commit
- `committed`: Saved to the repository's history
```Shell
git status # Shows he state of a repository.
```
## Staging
Untracked files need to be indexed before being committed.
```Shell
git add filename.ext # Add a file
git add . # Add every file in the current directory, recursively.
```
## Commit
A commit is a snapshot of the entire repository at a given point in time. Each commit has a message that describes the changes made in that commit.
To commit all staged files:
```Shell
git commit -m "message" # Commit all staged files
git commit --amend -m "message" # Replace the last commit's message
```
## Git Log
The `git log` command shows the history of commits in a repository. It provides information on who made a commit, when it was made, and what files were changed.
Each commit also has a unique identifier (commit hash).
For example, this is a valid commit hash: `46a4b5904d4ad737447052fed90c754ce8c616b6`.
Since these identifiers are very long, they are often shortened to their first `7` characters (`46a4b59` in this example).
```Shell
git log # Show the log in an interactive pager
git --no-pager log -n 10 # Show the last 10 lines from the log, without using the pager
git log -1 # Fetch the header of the first commit
git log --decorate=full # Shows the full pointer to a commit
git log --decorate=no # Don't show branch names
git log --oneline # Show each commit in a single line. ("compact" mode)
git reflog # History of actions. Commits, clone, pull, etc.
```
## Git Diff
Show differences:
```Shell
git diff # Differences between the working tree and the last commit
git diff HEAD~1 # Same, but includes the last commit and uncommitted changes
git diff COMMIT_HASH_1 COMMIT_HASH_2 # Differences between two commits
```
## Git Tags
A tag is a name linked to a commit that doesn't move between commits. Useful to mark versions.
```Shell
git tag # List the current tag
git tag -a "tag name" -m "tag message" # Add a new tag
git tag -a v3.10.2 -m "Fixed a lil bug" # Example marking a release
```
### Semantic Versioning (Semver)
Naming convention for versioning software.
```Plain
v3.12.5
| | |
| | Patch (safe bug fixes)
| Minor (safe features)
Major (breaking changes)
```
## Branch
A Git branch allows you to keep track of different changes separately.
```Plain
D - E other_branch
/
A - B - C main
```
```Shell
git branch # Check which branches are available, and which one is selected
git branch -m oldname newname # Rename a branch
git branch my_new_branch # Create a new branch
git switch -c my_new_branch # Create a new branch and switch to it immediately
git switch branch_name # Switch to an existing branch
git checkout branch_name # Deprecated alternative to git switch
git branch -d branch_name # Delete a branch
```
### Merge
After modifying a new branch, all commits performed on it can be merged into the main branch.
```Plain
A - B - C - F main
\ /
D - E other_branch
```
```Shell
git log --oneline --graph --all # Show an ASCII representation of the commit history
git log --oneline --decorate --graph --parents # Also display any parent branches
git merge branch_name # Merge a branch into the current branch
```
### Rebase
After a branch is created, it might fall behind its origin. A rebase includes the new changes from the origin into the current branch, without leaving a merge message behind. While merging can often accomplish the same task, a rebase keeps history linear and makes it easier to read. It's recommended to use it on the main branch, for example, when updating smaller ones.
Before a rebase, the commit history might have the following structure:
```Plain
A - B - C main
\
D - E feature_branch
```
After a rebase, the target branch is updated against its origin:
```Plain
A - B - C main
\
D - E feature_branch
```
```Shell
git rebase origin_name # Rebase against the origin
```
> You should *never* rebase a public branch (like `main`) onto anything else, to not break commit history.
### Conflicts
If a modified is pushed to another branch where that same file has been modified as well, a conflict arises. These can be fixed manually by editing text, or using Git commands.
```Shell
git checkout --theirs path/to/file # Discard the current branch's changes and accept the target's changes
git checkout --ours path/to/file # Discard the target changes and replace with the ones in the current branch
git reset --soft HEAD~1 # Undo an accidental conflict resolution
```
### Squash
Combine various commits into one:
1. Start an interactive rebase with the command `git rebase -i HEAD~n`, where `n` is the number of commits you want to squash.
2. Git will open your default editor with a list of commits. Change the word `pick` to `squash` for all but the first commit.
3. Save and close the editor.
> If the squashed commits already existed in the remote repository, it might be needed to push using: `git push origin main --force`.
### Stash
`git stash` saves the state of the current working directory and the index (staging area), then returns the repository to `HEAD`. This allows you to work on a different issue, and then resume your previous task.
```Shell
git stash
git stash -m "message" # It's also possible to stash with a message
git stash list # List all stashes
git stash pop # Apply the most recent stash to the working directory
git stash apply # Same as before, but doesn't delete the stash after applying
git stash drop # Discard a stash without applying changes
git stash apply stash@{2} # Apply the third most recent stash
```
### Worktrees
The directory where the code tracked with Git lives. (And where the `.git` directory is located).
Use instead of a `stash` if the current worktree is too busy, or for long-lived changes. This acts like cloning the repo again and working there, except it doesn't take up space on the host machine.
Any change done on a linked worktree is reflected on the main one instantly. Think of it as a different view of the main worktree.
```Shell
git worktree list # Lists worktrees
git worktree add <path> [<branch>] # Create a worktree linked to the main one
git worktree remove WORKTREE_NAME # Remove a worktree
git worktree prune # Remove an empty worktree if its directories were removed
```
### Bisect
Find a specific commit with binary tree search.
For example, if trying to find a bug in 100 commits, `git bisect` allows it to be found with only `7` attempts.
1. `git bisect start`
2. Use: `git bisect good <commitish>` to select a commit where the bug wasn't introduced yet.
3. Select a commit where the bug exists using: `git bisect bad <commitish>`.
4. Git will checkout a commit between the good and bad commits for you to test to see if the bug is present.
5. Execute `git bisect good` or `git bisect bad`.
6. Loop back to step 4 (until `git bisect` completes)
7. Exit the bisect mode with `git bisect reset`
```Shell
git bisect start
git bisect good <commitish> # Select a commit where the bug wasn't introduced yet.
git bisect bad <commitish> # Select a commit where the bug exists
```
#### Automated Bisect
If you have a script that can tell if the current source code is good or bad, you can bisect by issuing the command:
```Shell
git bisect run <script> <arguments>
```
The script should exit with code `0` if the current source code is good/old, and exit with a code between `1` and `127` (inclusive), except `125`, if the current source code is bad/new.
### Cherry-Pick
Apply only the selected commit to the working directory.
```Shell
git cherry-pick <commit-hash>
```
## Undo Changes
```Shell
git reset --soft COMMITHASH # Undo the last commit, but keep its changes staged (does not delete files)
git reset --hard COMMITHASH # Undo the last commit and discard all changes (deletes files).
git reset --hard a1b2c3d # Rollback to an earlier commit, deleting all changes up to that point.
git revert COMMITHASH # Create a new commit that does the opposite of the one provided. (Reset, but keeps history)
```
### Recover a Deleted Commit
`HEAD` always keeps track of every change, including rollbacks, so it can be used to recover lost files.
```Shell
git merge HEAD@{1}
```
## Git Remote
'Remotes' are eternal repositories with a similar Git history to our local one. GitHub, for example, is a remote repository. It is not part of Git, but is often used as the "source of truth" for convenience.
If a repository is considered to be the project's "true" source, it should be named `origin`.
```Shell
git remote add <name> <uri> # Add a remote repository (local folder or external url)
git fetch # Download a copy of the origin's metadata (.git/objects)
git log remote/branch # See the log of a remote branch after fetching data
git merge remote/branch # Merge between local and remote repos
git push origin main # Push (send) local changes to the selected remote
git push origin <localbranch>:<remotebranch> # Push a local branch to a remote, with a different name
git push origin :<remotebranch> # Push an empty branch to delete the remote branch
git pull [<remote>/<branch>] # Update local repo with remote changes (downloads files).
```
### Pull Requests
Propose changes to a repository, before they are actually applies. A pull request is typically accepted by a maintainer or other team members.
### GitHub
GitHub serves several purposes:
- As a backup of all your code on the cloud in case something happens to your computer
- As a central place to share your code and collaborate on it with others
- As a public portfolio for your coding projects
```Shell
# Install GitHub CLI either through a package manager, or using the command:
curl -sS https://webi.sh/gh | sh
# Login through the browser
gh auth login
# Add a remote from GitHub
git remote add origin https://github.com/your-username/repo-name.git
# List remote repos
git ls-remote
```
#### Forks
On GitHub (and similar platforms), a repository can be forked, or, copied, to serve as the base for a future pull request.
The steps to submit a PR are usually as follows:
1. Fork their repo into your account
2. Clone your fork to your local machine
3. Create a new branch (let's call it `your_feature`)
4. Make changes
5. Commit and push changes to your fork's remote `your_feature` branch
6. Create a pull request to `original_owner/repo` `main` from `your_username/repo` `your_feature`
## `.gitignore`
Prevents the specified files from being tracked by git.
```Shell
folder_name # Ignores all directories with that name, even subdirectories of different directories
file.txt # Ignores a specific file in the current directory
folder/file.txt # Ignores a file inside a subdirectory
*.txt # Ignore all text files
/main.py # Ignore a file only in the current directory, not subdirectories
!important.txt # Track a file that would previously be ignored
```
If a file was already staged or committed, it won't be ignored, however. Use the following command to remove it from cache and ignore it on the next commit:
```Shell
git rm --cached file
```
> It's common to have `.gitignore` files in subdirectories. These only affect the directories they are inside of.
**Which files should be ignored?**
1. Ignore things that can be *generated* (e.g. compiled code, minified files, etc.)
2. Ignore dependencies (e.g. `node_modules`, `venv`, `packages`, etc.)
3. Ignore things that are personal or specific to how you like to work (e.g. editor settings)
4. Ignore things that are sensitive or dangerous (e.g. `.env` files, passwords, API keys, etc.)