559 lines
13 KiB
Markdown
559 lines
13 KiB
Markdown
---
|
||
id: mat-07-git
|
||
title: Git
|
||
---
|
||
|
||
# Introduction to source control
|
||
|
||
## Why version control
|
||
|
||
When you work on software, you inevitably create multiple versions of the same artifact (source code, docs, config). Source control (version control) is used to:
|
||
- track changes over time
|
||
- collaborate safely on the same codebase
|
||
- keep a detailed history and understand “who changed what, when, and why”
|
||
- revert to previous versions
|
||
- integrate with development tooling (IDEs, build automation, CI/CD)
|
||
|
||
## Types of version control systems
|
||
|
||
Version control systems are commonly grouped into:
|
||
- **Local**: versioning kept locally on one machine.
|
||
- **Centralized**: a central server stores the history; clients check out working copies.
|
||
- **Distributed**: every clone contains the full history; collaboration happens by exchanging commits.
|
||
|
||
## Git overview
|
||
|
||
Git is a distributed version control system (DVCS) used to track changes in source code during software development. It supports collaboration between multiple developers, branching and merging, and reverting to previous versions. Git works like a **stream of snapshots**, not deltas.
|
||
|
||
# Git data model and states
|
||
|
||
## Snapshots not deltas
|
||
|
||
Git stores data as a series of snapshots of the project over time. When you commit, Git records a snapshot of what you staged.
|
||
|
||
## Working directory staging area git directory
|
||
|
||
A Git project can be viewed as three “places”:
|
||
- **Working directory**: your checked-out files where you edit.
|
||
- **Staging area** (index): where you build the next commit by selecting specific changes.
|
||
- **Git directory** (`.git`): the database where Git stores committed snapshots and metadata.
|
||
|
||
## File states modified staged committed
|
||
|
||
Git describes file state using three key states:
|
||
- **Modified**: changed in the working directory but not committed.
|
||
- **Staged**: marked to go into the next commit snapshot.
|
||
- **Committed**: safely stored in the Git database.
|
||
|
||
## Integrity and sha1
|
||
|
||
Everything in Git is checksummed. Git uses a SHA-1 checksum to identify content; if file contents change, the checksum changes. This supports integrity: you can’t change content “silently” without changing the identifier.
|
||
|
||
# Getting started
|
||
|
||
## Installing Git and verifying
|
||
|
||
After installing Git, verify with:
|
||
|
||
```bash
|
||
git --version
|
||
```
|
||
|
||
## First time setup with git config
|
||
|
||
Set your identity (used in commits):
|
||
|
||
```bash
|
||
git config --global user.name "Name Surname"
|
||
git config --global user.email "user@provider.com"
|
||
```
|
||
|
||
Inspect settings:
|
||
|
||
```bash
|
||
git config --list
|
||
git config user.name
|
||
git config user.email
|
||
```
|
||
|
||
## Getting help
|
||
|
||
Use Git’s built-in help:
|
||
|
||
```bash
|
||
git help <verb>
|
||
git <verb> --help
|
||
```
|
||
|
||
## Getting a repository with git init
|
||
|
||
To start version control in an existing directory:
|
||
|
||
```bash
|
||
cd /path/to/project
|
||
git init
|
||
```
|
||
|
||
This creates a `.git` directory (the repository “skeleton”). To begin tracking existing files and make an initial commit:
|
||
|
||
```bash
|
||
git add <files...>
|
||
git commit -m "Initial project version"
|
||
```
|
||
|
||
## Cloning a repository with git clone
|
||
|
||
To get a copy of an existing repository:
|
||
|
||
```bash
|
||
git clone <url>
|
||
```
|
||
|
||
Unlike a “checkout” in some other systems, cloning pulls down a full copy of nearly all data, including project history.
|
||
|
||
# Recording changes
|
||
|
||
## Tracked and untracked files
|
||
|
||
Files are either:
|
||
- **Untracked**: not yet in Git’s database.
|
||
- **Tracked**: known to Git (from the last snapshot and any staged files). Tracked files can be unmodified, modified, or staged.
|
||
|
||
## Checking status with git status
|
||
|
||
Use `git status` to see:
|
||
- what branch you’re on
|
||
- which files are staged
|
||
- which files are modified but not staged
|
||
- which files are untracked
|
||
|
||
```bash
|
||
git status
|
||
```
|
||
|
||
## Short status format
|
||
|
||
Use the short format:
|
||
|
||
```bash
|
||
git status -s
|
||
```
|
||
|
||
It uses a two-column code. The left column refers to the staging area; the right column refers to the working directory. Examples:
|
||
- `??` untracked file
|
||
- `A ` added to staging area
|
||
- `M ` modified and staged
|
||
- ` M` modified but not staged
|
||
- `AM` added to staging area and then modified again
|
||
|
||
## Staging with git add
|
||
|
||
Stage new files or stage changes of tracked files:
|
||
|
||
```bash
|
||
git add <file>
|
||
git add .
|
||
```
|
||
|
||
Staging is how you choose what will go into the next commit snapshot.
|
||
|
||
## Viewing changes with git diff
|
||
|
||
To see what you changed but have **not staged** yet:
|
||
|
||
```bash
|
||
git diff
|
||
```
|
||
|
||
To see what you staged that will go into the next commit:
|
||
|
||
```bash
|
||
git diff --staged
|
||
```
|
||
|
||
(`--cached` is a synonym of `--staged`.)
|
||
|
||
## Committing with git commit
|
||
|
||
Create a commit (a snapshot of what you staged):
|
||
|
||
```bash
|
||
git commit -m "message"
|
||
```
|
||
|
||
## Skipping the staging area with git commit -a
|
||
|
||
For tracked files, you can skip explicit staging and commit modifications directly:
|
||
|
||
```bash
|
||
git commit -a -m "message"
|
||
```
|
||
|
||
## Removing files with git rm
|
||
|
||
Remove a file from your working directory **and** stage its removal:
|
||
|
||
```bash
|
||
git rm <file>
|
||
```
|
||
|
||
To keep the file in your working directory but remove it from Git tracking (stage an “untrack”):
|
||
|
||
```bash
|
||
git rm --cached <file>
|
||
```
|
||
|
||
## Moving or renaming files with git mv
|
||
|
||
Rename/move a file:
|
||
|
||
```bash
|
||
git mv oldname newname
|
||
```
|
||
|
||
## Ignoring files with gitignore
|
||
|
||
To ignore generated files, secrets, build outputs, etc., use a `.gitignore` file. Pattern rules include:
|
||
- Blank lines are ignored.
|
||
- Lines starting with `#` are comments.
|
||
- Standard glob patterns work (`*`, `?`, `[a-z]`).
|
||
- A leading `/` matches from the repository root.
|
||
- A trailing `/` matches directories.
|
||
- `!` negates a pattern.
|
||
- `**` can match nested directories.
|
||
|
||
A repository can have one `.gitignore` at the root, and it can also have additional `.gitignore` files in subdirectories; nested rules apply only under that directory.
|
||
|
||
## Viewing history with git log
|
||
|
||
View commit history:
|
||
|
||
```bash
|
||
git log
|
||
```
|
||
|
||
A compact visualization is:
|
||
|
||
```bash
|
||
git log --oneline --decorate --graph --all
|
||
```
|
||
|
||
# Undoing mistakes
|
||
|
||
## Amending the last commit
|
||
|
||
To change the last commit (for example, fix the message or add missing staged changes):
|
||
|
||
```bash
|
||
git commit --amend
|
||
```
|
||
|
||
## Unstaging files
|
||
|
||
If you staged a file but want to unstage it:
|
||
|
||
```bash
|
||
git reset HEAD <file>
|
||
```
|
||
|
||
Newer Git also provides `git restore`:
|
||
|
||
```bash
|
||
git restore --staged <file>
|
||
```
|
||
|
||
## Discarding local modifications
|
||
|
||
To discard modifications in the working directory (dangerous; it overwrites your local changes):
|
||
|
||
```bash
|
||
git checkout -- <file>
|
||
```
|
||
|
||
Or with `git restore`:
|
||
|
||
```bash
|
||
git restore <file>
|
||
```
|
||
|
||
## Restore versus reset and safety
|
||
|
||
`git restore` was introduced in Git 2.23.0 as an alternative to using `git reset`/`git checkout` for some common “undo” cases. Commands that discard changes are dangerous: once you overwrite local modifications, recovering them can be difficult.
|
||
|
||
# Branching
|
||
|
||
## What a branch is
|
||
|
||
Branching means you diverge from the main line of development and continue work without “messing” with the main line. Git branching is lightweight and switching branches is fast.
|
||
|
||
In Git, a **branch** is a lightweight movable pointer to a commit. The default branch name is often `master` (and on GitHub the default branch is commonly `main`).
|
||
|
||
## HEAD pointer and current branch
|
||
|
||
Git keeps a special pointer called **HEAD** to know what branch you’re currently on. When you commit, the current branch pointer moves forward.
|
||
|
||
## Creating and switching branches
|
||
|
||
Create a new branch:
|
||
|
||
```bash
|
||
git branch testing
|
||
```
|
||
|
||
Switch to a branch:
|
||
|
||
```bash
|
||
git checkout testing
|
||
```
|
||
|
||
Create and switch in one step:
|
||
|
||
```bash
|
||
git checkout -b <newbranchname>
|
||
```
|
||
|
||
Newer Git provides `git switch`:
|
||
|
||
```bash
|
||
git switch <branchname>
|
||
git switch -c <newbranchname>
|
||
```
|
||
|
||
When you check out another branch, your working directory files change to match the snapshot of that branch.
|
||
|
||
## Branches are cheap
|
||
|
||
A branch is represented by a small file that contains the 40-character SHA-1 checksum of the commit it points to. Creating and deleting branches is therefore cheap.
|
||
|
||
## Divergent history and viewing the graph
|
||
|
||
If you make commits on different branches, history diverges. You can visualize with:
|
||
|
||
```bash
|
||
git log --oneline --decorate --graph --all
|
||
```
|
||
|
||
# Merging
|
||
|
||
## Fast forward merges
|
||
|
||
If the branch you merge has not diverged (your current branch is directly behind it), Git can do a **fast-forward** merge: it just moves the branch pointer forward.
|
||
|
||
## Three way merges
|
||
|
||
If branches have diverged, Git performs a **three-way merge** using two branch tips and their common ancestor, creating a new merge commit (with 2+ parents).
|
||
|
||
Merge example:
|
||
|
||
```bash
|
||
git merge <branchname>
|
||
```
|
||
|
||
## Deleting branches after merge
|
||
|
||
After merging a topic branch, you often delete it:
|
||
|
||
```bash
|
||
git branch -d <branchname>
|
||
```
|
||
|
||
## Merge conflicts and resolution
|
||
|
||
If two branches change the same lines, Git can’t merge automatically. You will see conflicts (e.g., in `git status`), and the conflicted file contains conflict markers like:
|
||
|
||
```text
|
||
<<<<<<< HEAD
|
||
... your current branch ...
|
||
=======
|
||
... the other branch ...
|
||
>>>>>>> <branchname>
|
||
```
|
||
|
||
Resolution workflow:
|
||
1. Open the file and resolve the conflict by editing.
|
||
2. Stage the resolved file (`git add <file>`).
|
||
3. Finish by committing (often `git commit` after a merge conflict).
|
||
|
||
## Merge versus rebase
|
||
|
||
Merging and rebasing are two ways of integrating changes from one branch into another.
|
||
|
||
- **Merge** takes the endpoints of branches and merges them, creating a merge commit (when needed).
|
||
- **Rebase** replays commits from one branch onto another base, creating a “cleaner” linear history, but changing the history of the rebased commits.
|
||
|
||
## Basic rebasing workflow
|
||
|
||
A common rebase flow is to move a topic branch onto the updated base branch:
|
||
|
||
```bash
|
||
git checkout <topic-branch>
|
||
git rebase <base-branch>
|
||
```
|
||
|
||
After resolving any conflicts during a rebase, you continue with:
|
||
|
||
```bash
|
||
git rebase --continue
|
||
```
|
||
|
||
# Remotes
|
||
|
||
## Remote repositories and origin
|
||
|
||
A remote repository is a Git repository hosted elsewhere (for example on GitHub). When you clone, Git typically adds a remote named `origin`.
|
||
|
||
## Showing and adding remotes
|
||
|
||
Show remotes:
|
||
|
||
```bash
|
||
git remote
|
||
git remote -v
|
||
```
|
||
|
||
Add a remote:
|
||
|
||
```bash
|
||
git remote add <shortname> <url>
|
||
```
|
||
|
||
## Fetching and pulling
|
||
|
||
Fetch downloads data from a remote without merging it into your current work:
|
||
|
||
```bash
|
||
git fetch <remote>
|
||
```
|
||
|
||
Pull fetches and then merges into your current branch (when your branch is set up to track a remote branch):
|
||
|
||
```bash
|
||
git pull
|
||
```
|
||
|
||
## Pushing
|
||
|
||
Push commits to a remote branch:
|
||
|
||
```bash
|
||
git push <remote> <branch>
|
||
```
|
||
|
||
## Tracking branches upstream
|
||
|
||
To set the upstream (tracking) relationship when pushing:
|
||
|
||
```bash
|
||
git push -u origin <branch>
|
||
```
|
||
|
||
## Renaming and removing remotes
|
||
|
||
Rename a remote:
|
||
|
||
```bash
|
||
git remote rename <old> <new>
|
||
```
|
||
|
||
Remove a remote:
|
||
|
||
```bash
|
||
git remote remove <name>
|
||
```
|
||
|
||
## Deleting remote branches
|
||
|
||
Delete a branch on the remote:
|
||
|
||
```bash
|
||
git push origin --delete <branch>
|
||
```
|
||
|
||
# Tagging and releases
|
||
|
||
## Lightweight and annotated tags
|
||
|
||
Tags are typically used to mark releases.
|
||
- **Lightweight tag**: just a pointer to a commit.
|
||
- **Annotated tag**: stored as a full object with metadata and a message.
|
||
|
||
Create an annotated tag:
|
||
|
||
```bash
|
||
git tag -a v1.4 -m "my version 1.4"
|
||
```
|
||
|
||
Create a lightweight tag:
|
||
|
||
```bash
|
||
git tag v1.4-lw
|
||
```
|
||
|
||
## Listing and showing tags
|
||
|
||
List tags:
|
||
|
||
```bash
|
||
git tag
|
||
git tag -l "v1.*"
|
||
```
|
||
|
||
Show information for a tag:
|
||
|
||
```bash
|
||
git show v1.4
|
||
```
|
||
|
||
## Checking out tags and detached head
|
||
|
||
If you check out a tag, Git puts you in a **detached HEAD** state. You can inspect the code, but commits you make won’t belong to a named branch unless you create one.
|
||
|
||
```bash
|
||
git checkout v2.0.0
|
||
```
|
||
|
||
# Productivity
|
||
|
||
## Git aliases
|
||
|
||
You can define aliases to shorten commands:
|
||
|
||
```bash
|
||
git config --global alias.co checkout
|
||
git config --global alias.br branch
|
||
git config --global alias.ci commit
|
||
git config --global alias.st status
|
||
```
|
||
|
||
Aliases can also wrap longer commands, for example:
|
||
|
||
```bash
|
||
git config --global alias.unstage 'reset HEAD --'
|
||
git config --global alias.last 'log -1 HEAD'
|
||
```
|
||
|
||
## Credential helpers
|
||
|
||
To avoid typing credentials repeatedly, Git can use credential helpers (depending on your OS) to cache or store credentials for Git operations.
|
||
|
||
# Collaboration on GitHub
|
||
|
||
## Forking and pull requests
|
||
|
||
A common collaboration pattern on GitHub is:
|
||
1. Fork the project (create your copy on GitHub).
|
||
2. Clone your fork locally.
|
||
3. Add the original repository as an `upstream` remote.
|
||
4. Create a topic branch, make commits, and push to your fork.
|
||
5. Open a pull request from your fork/topic branch into the upstream repository.
|
||
6. Keep your fork updated by fetching from upstream and integrating changes.
|
||
|
||
## Suggested class assignment and homework workflow
|
||
|
||
The following sequence matches the lecture’s suggested lab/homework ideas:
|
||
- Create a new repository on GitHub and connect it to a local project.
|
||
- Make commits, inspect status/diff/log, and push to GitHub.
|
||
- Create a new branch, make changes, and merge back.
|
||
- Create a three-branch workflow (for example `develop`, `test`, `production`) and practice merging changes through the branches.
|
||
- Fork a colleague’s repository, make changes in a topic branch, and submit a pull request.
|