13 KiB
| id | title |
|---|---|
| mat-07-git | 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:
git --version
First time setup with git config
Set your identity (used in commits):
git config --global user.name "Name Surname"
git config --global user.email "user@provider.com"
Inspect settings:
git config --list
git config user.name
git config user.email
Getting help
Use Git’s built-in help:
git help <verb>
git <verb> --help
Getting a repository with git init
To start version control in an existing directory:
cd /path/to/project
git init
This creates a .git directory (the repository “skeleton”). To begin tracking existing files and make an initial commit:
git add <files...>
git commit -m "Initial project version"
Cloning a repository with git clone
To get a copy of an existing repository:
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
git status
Short status format
Use the short format:
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 fileAadded to staging areaMmodified and stagedMmodified but not stagedAMadded to staging area and then modified again
Staging with git add
Stage new files or stage changes of tracked files:
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:
git diff
To see what you staged that will go into the next commit:
git diff --staged
(--cached is a synonym of --staged.)
Committing with git commit
Create a commit (a snapshot of what you staged):
git commit -m "message"
Skipping the staging area with git commit -a
For tracked files, you can skip explicit staging and commit modifications directly:
git commit -a -m "message"
Removing files with git rm
Remove a file from your working directory and stage its removal:
git rm <file>
To keep the file in your working directory but remove it from Git tracking (stage an “untrack”):
git rm --cached <file>
Moving or renaming files with git mv
Rename/move a file:
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:
git log
A compact visualization is:
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):
git commit --amend
Unstaging files
If you staged a file but want to unstage it:
git reset HEAD <file>
Newer Git also provides git restore:
git restore --staged <file>
Discarding local modifications
To discard modifications in the working directory (dangerous; it overwrites your local changes):
git checkout -- <file>
Or with git restore:
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:
git branch testing
Switch to a branch:
git checkout testing
Create and switch in one step:
git checkout -b <newbranchname>
Newer Git provides git switch:
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:
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:
git merge <branchname>
Deleting branches after merge
After merging a topic branch, you often delete it:
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:
<<<<<<< HEAD
... your current branch ...
=======
... the other branch ...
>>>>>>> <branchname>
Resolution workflow:
- Open the file and resolve the conflict by editing.
- Stage the resolved file (
git add <file>). - Finish by committing (often
git commitafter 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:
git checkout <topic-branch>
git rebase <base-branch>
After resolving any conflicts during a rebase, you continue with:
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:
git remote
git remote -v
Add a remote:
git remote add <shortname> <url>
Fetching and pulling
Fetch downloads data from a remote without merging it into your current work:
git fetch <remote>
Pull fetches and then merges into your current branch (when your branch is set up to track a remote branch):
git pull
Pushing
Push commits to a remote branch:
git push <remote> <branch>
Tracking branches upstream
To set the upstream (tracking) relationship when pushing:
git push -u origin <branch>
Renaming and removing remotes
Rename a remote:
git remote rename <old> <new>
Remove a remote:
git remote remove <name>
Deleting remote branches
Delete a branch on the remote:
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:
git tag -a v1.4 -m "my version 1.4"
Create a lightweight tag:
git tag v1.4-lw
Listing and showing tags
List tags:
git tag
git tag -l "v1.*"
Show information for a tag:
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.
git checkout v2.0.0
Productivity
Git aliases
You can define aliases to shorten commands:
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:
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:
- Fork the project (create your copy on GitHub).
- Clone your fork locally.
- Add the original repository as an
upstreamremote. - Create a topic branch, make commits, and push to your fork.
- Open a pull request from your fork/topic branch into the upstream repository.
- 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.