Skip to content

Git and GitHub

~15 min

In short

Git records the history of a folder as a series of snapshots called commits. GitHub stores copies of Git repositories online. This page takes you from an empty folder to a repository on GitHub, and shows how to undo mistakes.

Set up once

Check that Git is installed. Any recent version works.

$ git --version
git version 2.37.1 (Apple Git-137.1)

Tell Git who you are. Git stores this name and email in every commit, so use your own [1]:

$ git config --global user.name "Your Name"
$ git config --global user.email "you@example.com"

The examples in this page were run with a test identity. Your hashes and dates will differ.

Start a repository: git init and git status

git init turns a folder into a repository by creating a hidden .git folder inside it. git status shows what Git sees [2].

$ git init -b main
Initialized empty Git repository in /private/tmp/cs-lab/project/.git/
$ echo "# My Project" > README.md
$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    README.md

nothing added to commit but untracked files present (use "git add" to track)

An untracked file is one Git has not been asked to watch yet.

Stage and commit: git add, git commit, git log

Git saves in two steps. git add puts content into the staging area, and git commit saves that area as a snapshot with a message [2].

$ git add README.md
$ git commit -m "Add README"
[main (root-commit) 6a8e7e3] Add README
 1 file changed, 1 insertion(+)
 create mode 100644 README.md
$ printf 'print("hi")\n' > app.py && git add app.py && git commit -q -m "Add app"
$ git log --oneline
acfa4c4 Add app
6a8e7e3 Add README

git log --oneline prints one line per commit.

See what changed: git diff

git diff compares your working files with the staging area. git diff --staged compares the staging area with the last commit [2].

$ echo 'print("bye")' >> app.py
$ git diff
diff --git a/app.py b/app.py
index b80e322..e42aa4a 100644
--- a/app.py
+++ b/app.py
@@ -1 +1,2 @@
 print("hi")
+print("bye")
$ git add app.py && git diff --staged --stat
 app.py | 1 +
 1 file changed, 1 insertion(+)

Lines starting with + were added. Lines starting with - were removed.

Branches and merge: git switch, git merge

A branch is a movable label on a commit, so you can try work without disturbing main [3].

$ git switch -c topic
$ echo "topic line" > topic.txt && git add topic.txt && git commit -q -m "Topic work"
$ git switch main
$ echo "main line" > main.txt && git add main.txt && git commit -q -m "Main work"
$ git merge topic -m "Merge topic"
Merge made by the 'ort' strategy.
 topic.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 topic.txt
$ git log --oneline --graph -n 4
*   170b102 Merge topic
|\
| * 10ca431 Topic work
* | 3589624 Main work
|/
* e7dff50 Add gitignore

Both branches had new commits, so Git made a merge commit with two parents [3]. If main had no new commits, Git would only move its label forward. That case is a fast-forward [3]. Delete a finished branch with git branch -d topic.

Ignore files: .gitignore

A file named .gitignore lists files Git should not track: caches, virtual environments, and secrets. GitHub publishes templates for many languages [4].

$ printf '__pycache__/\n*.pyc\n.env\n' > .gitignore
$ git status --short --ignored
!! .env
!! __pycache__/

The !! marks ignored files. A trailing slash matches only folders [5]. If you commit a secret by mistake, change the secret, because the commit stays in the history.

Your first GitHub repository

  1. Sign in to GitHub. Click +, then New repository. Enter a name, choose public or private, and click Create repository [6]. Leave the README and .gitignore options off, because you already have a local repository.
  2. Copy the repository URL that GitHub shows.
  3. Connect and push:
$ git remote add origin https://github.com/YOUR-USERNAME/my-first-repo.git
$ git remote -v
origin  https://github.com/YOUR-USERNAME/my-first-repo.git (fetch)
origin  https://github.com/YOUR-USERNAME/my-first-repo.git (push)
$ git push -u origin main

git remote add gives a URL a short name, origin [7]. git push -u origin main uploads main and links it to origin/main. For Git operations, use a personal access token in place of your password [8]. GitHub's documentation also recommends the GitHub CLI or Git Credential Manager where possible [8].

Not run here

These commands need a GitHub account, so they were not run for this page. The same push to a local bare repository printed * [new branch] main -> main and set up tracking for origin/main.

Undo a mistake

Here are three common cases, each tested in the lab.

Undo the last commit but keep the changes. git reset --soft HEAD~1 moves the branch back one commit and leaves your files and staging area alone [9].

$ git commit -q -am "Oops commit"
$ git reset -q --soft HEAD~1
$ git status --short
M  README.md

Throw away an unwanted edit. git restore takes files back to the last commit [10]. This discards the edit, so use it only when you are sure.

$ git restore --staged README.md && git restore README.md
$ git status --short
$ echo "exit=$?"
exit=0

The empty git status output means the working tree is clean.

Recover a commit you deleted by mistake. git reset --hard discards the commits and changes after the target, and it can remove tracked files [9]. The commit is not gone yet, because Git keeps a log of where branch tips were. That log is the reflog, and it keeps entries for 90 days by default [11].

$ git reset -q --hard HEAD~1
$ ls draft.txt
ls: draft.txt: No such file or directory
$ git reflog -n 3
170b102 HEAD@{0}: reset: moving to HEAD~1
b83e087 HEAD@{1}: commit: Draft
170b102 HEAD@{2}: reset: moving to HEAD~1
$ git branch recovered b83e087
$ git checkout recovered -- draft.txt && cat draft.txt
important draft

Create a branch at the lost commit, copy the file back, commit it, and delete the temporary branch.

Try this

  1. Create a folder under /tmp, run git init, and make two commits.
  2. Change one file, run git diff, then git restore it.
  3. Push a small project to GitHub and write a README for it [12].

Sources

  • [1] Pro Git, "First-Time Git Setup". https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup
  • [2] Pro Git, "Recording Changes to the Repository". https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository
  • [3] Pro Git, "Basic Branching and Merging". https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
  • [4] GitHub, gitignore templates. https://github.com/github/gitignore
  • [5] git-scm, gitignore(5) documentation. https://git-scm.com/docs/gitignore
  • [6] GitHub Docs, "Creating a new repository". https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository
  • [7] Pro Git, "Working with Remotes". https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes
  • [8] GitHub Docs, "Managing your personal access tokens". https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
  • [9] git-scm, git-reset documentation. https://git-scm.com/docs/git-reset
  • [10] Pro Git, "Undoing Things". https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things
  • [11] git-scm, git-reflog documentation. https://git-scm.com/docs/git-reflog
  • [12] GitHub Docs, "About READMEs". https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-readmes