Skip to content

The Basic Git Workflow

Git development follows a small cycle that you repeat throughout the life of a project:

  1. Edit your files.
  2. Review what changed.
  3. Select the changes you want to include.
  4. Create a commit.
  5. Continue working and repeat.

Remember the checkpoint analogy: a commit is a saved point in the project’s history. However, Git does not automatically include every modified file in the next commit. You first choose what should be included using the staging area.

Before learning the commands, it is important to understand the three main areas in Git.

The working directory contains the project files currently available on your computer.

When you create, edit, rename, or delete a file, the change first exists in the working directory.

At this point, the file has changed on disk, but the change has not been included in a Git commit.

The staging area is where you prepare the exact changes that should be included in the next commit.

Use git add to move selected changes into the staging area.

The staging area allows you to create focused commits instead of including every unfinished change.

The repository contains the committed history of the project.

When you run git commit, Git creates a new snapshot from the changes currently in the staging area and stores it in the repository’s history.

Working Directory

Edit project files

Uncommitted changes

Staging Area

git add

Selected changes

Repository

git commit

Recorded snapshot

Start by creating or editing files normally in your code editor.

For example, create a file named auth.js and add some code to it.

The change currently exists only in the working directory.

Use the following command:

Terminal window
git status

git status shows:

  • Modified files.
  • New untracked files.
  • Deleted files.
  • Changes in the staging area.
  • Changes that have not been staged.

It is a good habit to run git status before and after git add.

To add one file to the staging area:

Terminal window
git add auth.js

To stage multiple specific files:

Terminal window
git add auth.js login.js

To stage all changes in the current directory and its children:

Terminal window
git add .

After staging the required changes, create a commit:

Terminal window
git commit -m "Add user login"

The text after -m is the commit message. It should briefly explain the purpose of the change.

The commit records a snapshot of the staged changes in the local repository.

It does not automatically upload anything to GitHub.

After creating a commit, continue developing the next feature or fix.

Then repeat the same cycle:

EditStageCommitContinueCommit again

For example:

Terminal window
# Make the login feature
git status
git add auth.js login.js
git commit -m "Add user login"
# Continue working on another feature
git status
git add theme.css
git commit -m "Add dark theme"

Small, meaningful commits are easier to understand, review, restore, and share than one enormous commit containing unrelated work.

A commit is a recorded snapshot of the staged state of your project at a particular moment.

You can think of it as a labeled checkpoint in the repository’s history.

A commit is not simply another manually copied project folder, and it is not only a copy of one changed line.

Conceptually, Git records the project state represented by that commit. Internally, Git stores data efficiently and reuses unchanged content instead of needlessly duplicating everything.

A commit connects several important pieces of information.

The commit points to the recorded state of the project files at that moment.

Only changes added to the staging area are included in the commit.

A short explanation of why the change was created:

Add user login

A useful message helps your future self and your teammates understand the history.

The commit records information including:

  • Author name.
  • Author email.
  • Date and time.

The author name and email come from the Git configuration discussed in the previous lesson.

Most commits point to a previous commit known as the parent.

These parent links connect commits together and form the history of the project.

The first commit in a repository normally has no parent. A merge commit may have more than one parent.

Each commit has a unique hash, sometimes called a commit ID.

Example of a shortened commit hash:

a1b2c3d

Git uses this identifier to refer to a specific commit.

Every Git commit has an identifier called a Commit ID or commit hash.

Git uses this identifier to refer to one exact commit in the repository’s history.

A full SHA-1 Commit ID contains 40 hexadecimal characters:

a3f8c21b9e04d7c834cfe698014771847ba01f12

A hexadecimal identifier uses the characters:

0 1 2 3 4 5 6 7 8 9 a b c d e f

Git commands commonly display a shorter version:

a3f8c21

The short value is only a prefix of the complete Commit ID. It is not a different ID.

A common simplification is to say that Git calculates the Commit ID from the changed files. This is not completely accurate.

Git creates the ID by hashing the complete serialized commit object.

Conceptually, a commit object contains information similar to:

tree 92b8...
parent 731a...
author Omar Ahmed <omar@example.com> 1788825600 +0300
committer Omar Ahmed <omar@example.com> 1788825600 +0300
Add user authentication

The important fields are:

  • Tree: identifies the recorded state of the project.
  • Parent: identifies the previous commit.
  • Author: the person who originally created the change.
  • Committer: the person who created this commit object.
  • Timestamp: records when the authoring and committing occurred.
  • Message: explains the purpose of the commit.

Git serializes this information in a precise format and calculates a cryptographic hash from it. With the traditional SHA-1 object format, the calculation can be represented conceptually as:

SHA-1("commit " + content-length + "\0" + commit-content)

The result becomes the Commit ID.

Changing any information used to create the commit object produces a different Commit ID.

For example, the ID changes if you change:

  • The recorded project content.
  • The parent commit.
  • The author or committer information.
  • The timestamp.
  • The commit message.

This is why editing an old commit or rebasing a branch creates new Commit IDs for the rewritten commits.

Even when the visible files appear identical, commits can have different IDs if their metadata or history is different.

Most existing Git repositories use SHA-1, which produces 40-character object IDs.

Git also supports repositories using SHA-256, which produces 64-character object IDs.

Therefore, documentation and commands should not assume that every Git object ID must always contain exactly 40 characters. The repository’s configured object format determines the full length.

For this course, examples may use SHA-1 because it is the traditional and most familiar format.

Git uses several object types to store repository data.

The three most important types for understanding commits are:

Blob

Stores the content of one file. Its object ID is calculated from that content, so identical files can share a blob.

Tree

Represents a directory. Adds names, file modes, and structure, and references blobs and other trees.

Commit

Points to one root tree, plus its parent, author, committer, timestamps, and message.

A blob stores the content of a file.

A blob does not normally store:

  • The file name.
  • The complete file path.
  • The commit message.
  • Who created the file.

It stores the file content, and its object ID is calculated from that content.

If two files contain exactly the same content, Git can reuse the same blob object for both.

A tree represents a directory.

It stores entries containing information such as:

  • File or directory names.
  • File modes.
  • References to blob objects.
  • References to other tree objects.

The tree gives names and structure to the content stored in blobs.

A commit object points to the root tree representing the project at that moment.

It also records:

  • Its parent commit or parents.
  • Author information.
  • Committer information.
  • Timestamps.
  • The commit message.

The object it produces links the project tree together with its metadata:

Commit
├── Root Tree
│ ├── auth.js → Blob A
│ ├── app.js → Blob B
│ └── src/ → Another Tree
├── Parent Commit
├── Author and Committer
├── Timestamps
└── Message

Does Git Store a Complete Copy Every Time?

Section titled “Does Git Store a Complete Copy Every Time?”

Git presents each commit as a snapshot of the project, but that does not mean it unnecessarily duplicates every file for every commit.

Suppose a project contains three files:

auth.js
app.js
style.css

The first commit points to blobs representing all three files.

If the next commit changes only auth.js:

  • Git creates a new blob for the new content of auth.js.
  • Git can reuse the existing blobs for app.js and style.css.
  • A new tree represents the new project state.
  • The new commit points to that tree and to the previous commit.

Conceptually:

Commit A
├── auth.js → Blob 1
├── app.js → Blob 2
└── style.css → Blob 3
Commit B
├── auth.js → Blob 4 (new content)
├── app.js → Blob 2 (reused)
└── style.css → Blob 3 (reused)

This allows Git to represent complete project snapshots while avoiding unnecessary duplication of unchanged content.

Conceptually, Git stores snapshots, not a sequence of patches describing only the changed lines.

When you ask Git to show what changed between two commits, Git compares their trees and file contents:

Terminal window
git diff <commit-a> <commit-b>

Git calculates and displays the differences between the two snapshots.

Most commits contain the ID of one parent commit.

Commit ACommit BCommit C

Each arrow points from a commit to its parent. Commit A is the first commit and has no parent.

These parent links form the project’s history.

  • The first commit normally has no parent.
  • A normal commit usually has one parent.
  • A merge commit usually has more than one parent.

Because the parent ID is part of the commit object, changing an earlier commit can also change the IDs of every rewritten descendant commit.

A full SHA-1 ID looks like:

a3f8c21b9e04d7c834cfe698014771847ba01f12

A shortened version may look like:

a3f8c21

Git allows a shortened ID when the prefix is long enough to identify one object unambiguously inside the repository.

For example:

Terminal window
git show a3f8c21

No. Seven characters are a traditional and commonly seen abbreviation, but they are not guaranteed to remain unique in every repository.

In a small repository, the first seven characters will usually identify one object.

As the repository grows and contains more objects, two IDs may begin with the same seven characters:

a3f8c21b9e0...
a3f8c2174ac...

In that situation, a3f8c21 is ambiguous, so more characters are required:

a3f8c21b
a3f8c217

Git can calculate an abbreviation long enough to distinguish the object from other objects in the repository.

Display the full Commit IDs:

Terminal window
git log

Display an abbreviated history:

Terminal window
git log --oneline

Ask Git for an unambiguous short ID:

Terminal window
git rev-parse --short HEAD

Display the complete ID of the current commit:

Terminal window
git rev-parse HEAD

You can request a minimum abbreviation length:

Terminal window
git rev-parse --short=10 HEAD

Git may use more characters than the requested minimum if additional characters are required to avoid ambiguity.

A Commit ID is not a sequential version number such as:

v1
v2
v3

The ID is generated from the commit object and changes when the object changes.

Human-readable release names such as v1.0.0 are normally created separately using Git tags.

Commit IDs allow you to select one exact point in the project history.

Many Git commands accept a Commit ID:

Terminal window
git show a3f8c21
git checkout a3f8c21
git revert a3f8c21
git reset a3f8c21

These commands perform different operations and will be explained later. The Commit ID simply tells Git which commit you are referring to.

A Commit ID is a cryptographic hash of the complete commit object, not merely a hash of the changed lines.

The commit points to a tree representing the project snapshot, and the tree points to blobs containing file content. Unchanged blobs can be reused by later snapshots.

The commonly displayed seven-character ID is only an abbreviation. It is convenient and often unique in small repositories, but larger repositories may require a longer prefix.

To display the project history:

Terminal window
git log

For a shorter one-line view:

Terminal window
git log --oneline

Example:

7fd31a2 Add dark theme
35ac981 Add user login
9e142bd Initial commit

Each line contains a shortened commit hash followed by its message.

Creating a commit does not:

  • Upload your work to GitHub.
  • Include unstaged changes.
  • Save unrelated files automatically.
  • Sign you in to a remote service.
  • Replace normal file saving in your editor.

A commit is stored locally until you deliberately send it to a remote repository using a command such as git push.

Terminal window
git status
git add auth.js
git status
git commit -m "Add authentication logic"
git log --oneline

The sequence means:

  1. Inspect the current changes.
  2. Stage auth.js.
  3. Confirm what is staged.
  4. Create a commit with a meaningful message.
  5. Review the recorded history.

The basic Git workflow is:

Modify files → Review changes → Stage selected changes → Commit → Repeat

The working directory contains your current edits, the staging area prepares the next snapshot, and the repository stores committed snapshots in the project history.

A commit is a labeled checkpoint containing the staged project state, a message, author information, time, a unique identifier, and a connection to its parent commit.

A commit stores a project snapshot, while its message explains why that snapshot was created.

We can create a commit with:

Terminal window
git commit -m "message"

The text after -m becomes the commit message.

For example:

Terminal window
git commit -m "Fix login validation"

A clear commit message helps us understand the project history when using commands such as git log.

It also helps teammates understand the purpose of a change without inspecting every modified file.

Many development teams use this simple format:

type: short description

For example:

feat: add login with phone OTP

The message has two parts:

  • feat describes the type of change.
  • add login with phone OTP briefly describes what changed.

The colon separates the change type from its description.

feat

Use it for
Adding a new feature or capability
Example
git commit -m “feat: add login with phone OTP”

fix

Use it for
Correcting a bug or incorrect behavior
Example
git commit -m “fix: reject empty password on login”

refactor

Use it for
Improving code structure without changing behavior
Example
git commit -m “refactor: extract AuthService”

Smaller, supporting types are used the same way:

  • docs Documentation changes — git commit -m “docs: update installation guide”
  • test Adding or modifying tests — git commit -m “test: add authentication tests”
  • chore Maintenance work that is not a feature or a fix — git commit -m “chore: update project dependencies”

Use feat when adding a new feature or capability.

Terminal window
git commit -m "feat: add login with phone OTP"

This tells the reader that the commit introduces new functionality.

Use fix when correcting a bug or incorrect behavior.

Terminal window
git commit -m "fix: reject empty password on login"

This tells the reader that the commit fixes an existing problem.

Use refactor when improving the internal code structure without intentionally changing the system’s behavior.

Terminal window
git commit -m "refactor: extract AuthService"

The code is reorganized or made cleaner, but the feature should continue behaving in the same way.

Use docs when the change affects documentation.

Terminal window
git commit -m "docs: update installation guide"

Use test when adding or modifying tests.

Terminal window
git commit -m "test: add authentication tests"

Use chore for maintenance work that is not a new feature or bug fix.

Terminal window
git commit -m "chore: update project dependencies"

Suppose we are building authentication with phone OTP.

Instead of placing every change inside one large, unclear commit, we create meaningful checkpoints:

Terminal window
git commit -m "feat: add login with phone OTP"

This commit adds the new login feature.

Later, we discover and fix a bug:

Terminal window
git commit -m "fix: reject empty password on login"

This commit fixes incorrect validation behavior.

Afterward, we reorganize the code without changing its external behavior:

Terminal window
git commit -m "refactor: extract AuthService"

Now the history tells a clear story:

feat: add login with phone OTP
fix: reject empty password on login
refactor: extract AuthService

Each commit represents one understandable checkpoint and its message explains the kind of change it contains.

Avoid unclear messages such as:

update
changes
fix stuff
work
final version

These messages do not explain what happened.

Prefer specific messages:

feat: add login with phone OTP
fix: prevent login with empty password
refactor: extract authentication logic into AuthService

Weak

  • update
  • changes
  • fix stuff
  • work
  • final version

Useful

  • feat: add login with phone OTP
  • fix: prevent login with empty password
  • refactor: extract authentication logic into AuthService

A useful commit message should be:

  • Short.
  • Specific.
  • Focused on one clear change.
  • Easy to understand later.
  • Written consistently with the team’s convention.

No.

Git accepts any non-empty commit message, for example:

Terminal window
git commit -m "Add phone login"

The following is also valid:

Terminal window
git commit -m "feat: add login with phone OTP"

The second format follows a convention commonly known as Conventional Commits.

Git itself does not require or automatically enforce this format. A team chooses to follow it to make the history easier to read and keep messages consistent.

The important distinction is:

Git requires a commit message, but the type: description structure is a convention chosen by developers or teams.

First, stage the intended change:

Terminal window
git add auth.js

Then create a commit with a meaningful message:

Terminal window
git commit -m "feat: add login with phone OTP"

The message describes the staged snapshot being recorded. The Staging Area determines the contents of the commit; the message only describes the saved change.

type: short description

Examples:

feat: add a new feature
fix: correct a bug
refactor: improve code structure without changing behavior
docs: update documentation
test: add or modify tests
chore: perform maintenance work

Remember:

The Staging Area determines what enters the commit. The commit message explains why that checkpoint exists.