Git Operations Manual

51 min read
On this page

Start with the task at hand: read the state, then record, integrate, or recover changes. Sections stand on their own. Alternatives are identified explicitly; the whole page is not a script to execute in order.

Commands were checked with Git 2.53. Run Bash / Zsh commands from the target repository root. On older versions, check git --version and availability of restore, switch, and stash display options. Examples use remote origin, base branch main, and topic branch feature. Replace file.txt and path/to/file with real paths, and COMMIT / KNOWN_GOOD with revisions found in logs or tags. Your default branch may have another name. Output is illustrative; hashes and message language vary.

TaskSection
Understand changes and the next commitInspect changes
Start a branch and build a commitRecord changes
Pause work and resume laterStash work
Unstage, discard, or undo a commitUndo changes
Integrate upstream or transplant a fixIntegrate branches
Recover commits or locate a regressionHistory and recovery
Compare remote progress and pushRemote collaboration
Parallel work, dependencies, or release tagsWorktrees, submodules, and tags

Inspect changes: identify both sides

Distinguish three states: HEAD, the current commit; the index, the snapshot prepared for the next commit; and the working tree, the files you are editing. add copies the current content into the index. Later edits do not update that snapshot automatically.

Current branch and change summary

# Show staged and unstaged states alongside branch information
git status --short --branch

--short selects the compact format; --branch adds branch and upstream information. Example:

## feature...origin/feature [ahead 1, behind 2]
MM file.txt
 M README.md
A  new.txt
?? notes.txt
MarkerMeaning
MMFirst column: index differs from HEAD. Second: working tree also differs from index.
·MBlank first column: no staged change; the working file is modified.
A·A new file is staged with no further working-tree change.
??Untracked: not included in a commit automatically or shown by ordinary diff.

In the table, · represents a space so column positions remain visible; actual output uses spaces. ahead 1, behind 2 means one commit is unique to this branch and two to the locally recorded upstream. Status does not contact the server; fetch first for current remote progress. During conflicts, codes such as UU have special meanings; use conflict handling instead of the ordinary two-column interpretation. Status format

Choose a diff for the question

# Compare working tree with index: what has not been staged
git diff -- file.txt

Use this while editing or before add. -- separates revisions/options from paths. Omit file.txt to inspect unstaged changes in all tracked files.

# Compare index with HEAD: what this file contributes to the next commit
git diff --cached -- file.txt

--cached and --staged are synonyms. For the MM file above, this shows a different portion from the previous diff. Review it before committing. git diff --cached --stat gives a summary, not a substitute for reviewing the patch.

Example patch excerpt:

@@ -1 +1 @@
-timeout = 10
+timeout = 30

- lines belong to the starting side and + lines to the ending side; @@ identifies old and new line ranges. Whether this is a change prepared for commit depends on which diff you ran. Diff reference

Record changes: select content before committing

Create a branch for a new task

Use this to start from the current commit. Check your location with status first. Existing edits normally remain in the working tree; creating a branch does not commit them.

# Create a new feature branch at HEAD and switch to it
git switch -c feature

-c creates a branch. If it already exists, use git switch feature instead. If edits block switching, commit or stash them before retrying.

Stage a whole file or selected hunks

# Stage the current content of this file
git add -- file.txt

Use this when all changes in the file belong together. If the file was deleted, it stages the deletion. For a tracked file containing unrelated changes, choose interactive staging below instead of staging the whole file first:

# Select the hunks that belong in this commit
git add -p -- file.txt

At the prompt, y stages the hunk, n leaves it unstaged, s attempts a split, and q stops selection. If a hunk cannot be split, adjust the file or carefully edit the patch with e. Unstage a mistaken selection without losing the working file. Stage a new untracked file as a whole initially. Interactive staging

Review, commit, and verify

# Review the full staged patch before creating the commit
git diff --cached

Check the file set, patch, and project tests before proceeding. Ordinary git commit records the index, not later edits or untracked files.

# Commit the reviewed snapshot; replace the message with the purpose of your change
git commit -m "Fix timeout handling"
# Check the new commit and affected files
git show --stat --oneline HEAD

# Check which changes remain outside the commit
git status --short

These checks belong together after a commit. Remaining edits may be intentional after partial staging. To change only the message of the latest unshared commit, use git commit --amend --only -m "New message"; --only excludes other staged changes. Amend replaces the commit identity. Commit and amend

Stash work: save, inspect, restore, then remove

Use a stash when you need to interrupt work that is not ready for a commit. It is local to this repository, not a backup service. Handle edits inside submodules separately.

# Save tracked changes and untracked files with a recognizable description
git stash push -u -m "before branch switch"

-u includes untracked files, but not ignored files. After success, use git status --short to check that the intended changes have left the working tree before switching branches. Ignored files remain; the stash is not a copy of the entire directory.

# Find the intended entry by its description
git stash list
stash@{0}: On feature: before branch switch

The following example uses the identified stash@{0}. Creating or dropping other entries changes numbering; recheck before deleting.

# Review the stash patch, including saved untracked files
git stash show -p --include-untracked 'stash@{0}'

Switch to the intended destination branch and confirm that status shows no other uncommitted work before applying:

# Restore the content while retaining the stash entry for verification
git stash apply 'stash@{0}'

This does not restore the original staging split. To attempt that too, use git stash apply --index 'stash@{0}' instead. Check status, unstaged diff, staged diff, and relevant tests afterward. Resolve conflicts before doing anything else; do not apply again. Stash has no general --abort, which makes starting with a clean working tree useful.

# Remove this entry only after verifying the restored content and its current identity
git stash drop 'stash@{0}'

Confirm removal with git stash list. pop combines application with deletion on success; conflicts normally retain the entry. Separate apply/drop steps place verification before deletion. Stash behavior

Undo changes: choose which state to change

Inspect both diffs first to identify what must be kept. Each row below is a different task, not a step in a sequence.

GoalState changedSection
Undo add, keep filesIndexUnstage
Discard unstaged edits to a fileWorking treeRestore a file
Withdraw the latest unshared commitCurrent branch pointerReset a local commit
Undo a shared changeNew reversing commitRevert

The visualization always starts from HEAD B, index C, and working file D, with reset targeting an earlier A. Select an operation to see what changes. Choices are independent, not cumulative. A / B / C / D represent different contents of the example file.

Preparing the visual
Which copy does reset change?

From one fixed starting state, soft moves HEAD, mixed also resets the index, and hard also resets the worktree. restore --staged leaves HEAD in place.

Unstage without changing the working file

# Restore this index entry from HEAD while keeping the edited file
git restore --staged -- file.txt

--staged targets only the index and defaults to HEAD as its source. Afterward, git diff --cached -- file.txt should be empty; ordinary diff still shows edits to tracked files. A newly added file becomes untracked, visible in status but not ordinary diff. This assumes the repository has an initial commit. Restore source and destination

Discard unstaged edits or retrieve an older file

To discard only edits made after staging file.txt, inspect git diff -- file.txt and confirm that those changes are no longer needed:

# Overwrite the working file from the index, discarding its unstaged edits
git restore --worktree -- file.txt

Ordinary diff should now be empty, while the staged diff is unchanged. Without --source, this restores from the index, not necessarily the last commit. Reflog normally cannot recover never-committed edits.

Retrieving one file from an older commit is a separate task. First verify the revision and its content:

# Read the file at the chosen commit without overwriting the working tree
git show COMMIT:path/to/file
# After saving current edits, replace this working file with the chosen historical version
git restore --source=COMMIT --worktree -- path/to/file

--source chooses the source explicitly. This neither moves the branch nor stages the result. Review git diff -- path/to/file, then add/commit if intended. If the tracked path is absent from the source commit, restore removes it to match that source; check with show first.

Withdraw the latest local commit, keeping its changes

Use this for the latest unshared commit on the current branch, with a parent commit present. Inspect git show HEAD to confirm the target. Existing staged and unstaged edits are also retained and need review afterward. First anchor the current commit:

# Keep a reference to the current commit; this does not back up uncommitted edits
git branch backup-before-reset

Choose an unused backup branch name. Resolve any creation failure before proceeding with the soft reset:

# Move the current branch to its parent, preserving index and working files
git reset --soft HEAD~1

HEAD~1 is the first parent. Check the new position with git log -1 --oneline, then inspect both diffs before recommitting. To leave all changes unstaged, choose git reset --mixed HEAD~1 instead of soft. Running the same HEAD~1 again after soft would step back another commit.

The hard mode in the diagram also overwrites tracked working files and may remove untracked files obstructing target paths. It is for intentionally discarding the current state, not a final step after soft/mixed. If you reset the wrong commit, recover through reflog and anchor the original commit first. Reset modes

Reverse a shared commit

Start with a clean working tree and index, and review the ordinary commit with git show COMMIT. Revert adds a reversing commit while preserving shared history.

# Create a commit on the current branch reversing the target changes
git revert COMMIT

Inspect git show HEAD and run relevant tests after success; later code may depend on the reverted change. On conflict, edit files, mark them resolved with git add -- file.txt, then run git revert --continue. To abandon instead, use git revert --abort. Reverting a merge commit requires understanding which parent -m selects and the impact on later merges; do not use the ordinary-commit recipe blindly. Revert reference

Integrate branches: choose a strategy first

Begin with a clean working tree and index. origin/main is a local remote-tracking reference; fetch first to obtain the latest available base.

# Update remote-tracking refs without changing files on the current branch
git fetch origin
ChoiceResultUse when
mergeKeeps existing commit identities; may fast-forward or create a merge commitIntegrating another branch history
rebaseReplays commits onto a new base, usually with new identitiesUpdating an unshared topic branch
cherry-pickApplies selected commit changesTransplanting a fix with its dependencies understood

Preserve history: merge feature into main

Here main receives feature. Switch to the receiving branch first; the branch named in merge is the source.

# Switch to main, the branch receiving the changes
git switch main
# Fast-forward local main to fetched origin/main; stop on divergence
git merge --ff-only origin/main

If fast-forward is refused, inspect remote differences to understand the local-only commits before proceeding. After success, integrate the topic branch:

# Integrate feature into the current main branch
git merge feature

If main is an ancestor of feature, the default can fast-forward without adding a merge commit. Use git merge --no-ff feature instead when an explicit merge node is required. Inspect git log --graph --oneline -10, run tests, and follow the project workflow before pushing. Merge reference

Update an unshared topic branch with rebase

This is a different choice: feature receives a new base from main. Use it for unshared work; coordinate with other users before rewriting shared history.

# Switch to the topic branch whose base will change
git switch feature
# Replay the topic commits onto fetched origin/main
git rebase origin/main

After success, inspect git log --graph --oneline -10 and test the result. A conflict pauses at one commit; follow the next section. A clean replay alone does not prove semantic correctness. Rebase reference

When a conflict pauses integration

# Identify the paused operation and Git’s next-step guidance
git status

# List unresolved paths to work through
git diff --name-only --diff-filter=U

--diff-filter=U selects unmerged paths; --name-only lists their names. Read both changes and edit the intended result, removing markers such as <<<<<<<, =======, and >>>>>>>. Do not equate ours/theirs with “my/their work”; their context is especially easy to misread during rebase.

# Mark a manually resolved file as resolved; repeat with the actual paths
git add -- file.txt

If the resolution is deletion, use git rm -- file.txt instead of add. Repeat the unresolved-path query until it is empty, review git diff --cached, then choose one continuation matching the paused operation:

Paused operationContinue after resolvingAbandon instead
mergegit merge --continuegit merge --abort
rebasegit rebase --continuegit rebase --abort
cherry-pickgit cherry-pick --continuegit cherry-pick --abort

Rebase may pause again at later commits; repeat the checks and resolution. --skip omits a commit rather than preserving its change while ignoring an error. At completion, status should no longer report an operation in progress, and relevant tests should pass. Abort unwinds the integration; it is not a backup of pre-existing uncommitted work.

Transplant one fix

Run this on the branch that should receive the fix. Review the source commit and dependencies with git show COMMIT first. This recipe assumes an ordinary single-parent commit.

# Apply the selected fix here and record its source in the commit message
git cherry-pick -x COMMIT

-x makes the source traceable across maintenance branches. Inspect git show HEAD and test; after resolving conflicts, also check that source attribution remains in the message. Use the table above to continue or abort. Cherry-pick reference

History and recovery: trace an observation to a commit

Inspect branch relationships and individual changes

# Inspect recent history reachable from local refs and see branch positions
git log --graph --oneline --decorate --all -30

--graph draws connections, --oneline uses one line per commit, --decorate names refs, --all includes local refs such as remote-tracking refs, and -30 limits the count. It does not query the server. Expand a selected hash separately:

# Read the selected commit message and patch to confirm the change
git show COMMIT

Search code changes, not only commit messages

# Find commits changing the occurrence count of this string and show their patches
git log -p -S 'function_name' -- path/to/file

-S is useful for finding when a symbol was added or removed; moving it with no count change may not match. To match added or removed patch lines against a regular expression, use this instead:

# Search changed patch lines matching a timeout assignment
git log -p -G 'timeout[[:space:]]*=' -- path/to/file

-p displays patches and the path limits scope. Both searches default to history reachable from HEAD; supply another revision or --all when needed.

# Identify the last modifying commit for lines 40–60
git blame -L 40,60 -- path/to/file

Follow a hash with git show to understand the reason for the change. Blame attributes lines; formatting changes can become the latest modification, so attribution is not a diagnosis of responsibility. History search options

Recover after a mistaken reset or branch deletion

# Inspect local HEAD movements to find the earlier position
git reflog --date=iso

Example excerpt:

a1b2c3d HEAD@{2026-09-10 12:05:00 +0800}: reset: moving to HEAD~1
b2c3d4e HEAD@{2026-09-10 12:00:00 +0800}: commit: Fix timeout handling

Here the candidate is the earlier b2c3d4e. Use your actual hash as COMMIT below. Confirm the patch before anchoring it:

# Confirm that this candidate contains the lost changes
git show COMMIT
# Anchor the commit with a recovery branch without changing the working tree
git branch recovery COMMIT

recovery must be an unused branch name. Verify with git log -1 --oneline recovery, then use git switch recovery from a clean working tree, or inspect it in a separate worktree. Decide how to integrate it after inspecting the recovered state.

Reflog is local and subject to expiry and garbage collection. It does not record movements on other machines or guarantee recovery of every object. Do not assume a fixed retention window or use it as protection for uncommitted edits. Reflog reference

Find the introducing commit with bisect

Prepare a reproducible check, confirm the issue at HEAD, and identify an unaffected KNOWN_GOOD revision. Start clean; bisect checks out intermediate revisions, so keep build artifacts from contaminating the check.

# Start with HEAD as bad and the verified earlier revision as good
git bisect start HEAD KNOWN_GOOD

When Git selects a revision, run the same check and choose one mark based on the result:

ResultCommandNext
Issue absentgit bisect goodNarrow the range and select another commit
Issue presentgit bisect badNarrow the range and select another commit
Cannot build or judgegit bisect skipSkip it; the final result may remain ambiguous

Repeat until Git reports is the first bad commit; record and inspect the hash. The result assumes a stable check and a suitable good/bad boundary. Intermittent failures or repeated introduction and repair need additional validation. Finish or stop by restoring the original position:

# Leave bisect and return to the pre-bisect position
git bisect reset

Check git status --short --branch to confirm the original branch. For automation, git bisect run /absolute/path/check-regression.sh replaces manual marking: exit 0 means good, 1–127 except 125 means bad, and 125 means skip. Keep the script outside the checkout and verify its exit codes first; an unrelated build failure should not masquerade as this regression. Bisect reference

Remote collaboration: compare progress before pushing

Check the remote and commits unique to each side

# Confirm that origin fetch and push URLs point to the intended repository
git remote -v
# Fetch updates and prune tracking refs for deleted remote branches
git fetch --prune origin

With ordinary branch refspecs, --prune removes stale remote-tracking refs, not local working branches. Check the scope first if tag or mirror refspecs are configured. After fetching, compare the current branch with its intended peer, origin/feature:

# List commits unique to HEAD and to the remote-tracking branch
git log --oneline --left-right HEAD...origin/feature
< a1b2c3d Fix timeout handling
> b2c3d4e Update server defaults

The triple-dot range selects commits unique to either side: < belongs to HEAD, > to origin/feature. No output means neither side has unique commits; both markers mean divergence. If feature does not exist remotely yet, skip this comparison and use the first-push workflow.

Specify the push source and destination

The following sends current HEAD to remote feature. Confirm with git status --short --branch that you are on the intended local feature branch, and complete review and tests first.

# Preview the remote branch update without updating the ref
git push --dry-run origin HEAD:refs/heads/feature

The source is left of the colon and the remote destination is right of it; refs/heads/ explicitly names a branch. After confirming the target, perform the push:

# Push this branch and set origin/feature as its upstream
git push -u origin HEAD:refs/heads/feature

-u sets tracking; it does not force an update. After success, check the upstream with git branch -vv, and compare git ls-remote --heads origin refs/heads/feature with git rev-parse HEAD. The server may change again afterward. A dry run does not lock it or guarantee every server-side check will pass.

Distinguish divergence from intentional rewriting

non-fast-forward usually means the destination has history this push would not preserve. Fetch, inspect each side, then choose merge/rebase. For permissions, protected branches, or hook rejection, follow the server message; force is not a remedy for those causes.

Use the separate workflow below only for an agreed, intentional history rewrite. Plain --force-with-lease relies on remote-tracking refs that a background fetch can change. Pin the reviewed expectation in a variable:

# Fetch the branch state to review; stop if fetching fails
git fetch origin
# Record the expected remote commit in this shell; stop if it fails or is empty
expected_oid=$(git rev-parse refs/remotes/origin/feature)
# Review the history being replaced and account for both sides’ changes
git log --oneline --left-right HEAD..."$expected_oid"

This checkpoint requires review; a successful fetch is not permission to overwrite. Once confirmed, run in the same shell:

# Publish rewritten history only if remote feature still matches the reviewed expectation
git push \
  --force-with-lease="refs/heads/feature:${expected_oid:?Set expected_oid after reviewing remote history}" \
  origin HEAD:refs/heads/feature

${expected_oid:?...} refuses an unset or empty value. A failed lease means the expectation no longer holds: inspect the new remote work rather than merely refreshing the variable and retrying. Verify success as for an ordinary push. Push and explicit leases

Worktrees, submodules, and tags

Fix an issue in another directory without interrupting edits

A worktree is useful for a hotfix or bisect: it shares repository objects and most refs, while keeping its own working directory, HEAD, and index. Create a new hotfix branch and sibling directory ../hotfix from already-fetched origin/main. Choose unused names and a new path.

# Create a separate hotfix checkout while keeping current edits here
git worktree add -b hotfix ../hotfix origin/main

-b creates the branch, the path chooses the directory, and the last argument sets its starting commit. Git normally prevents checking out one branch in two worktrees. Verify the checkout with:

# Check worktree paths and their checked-out branches
git worktree list

# Inspect hotfix from the original directory
git -C ../hotfix status --short --branch

-C runs Git in the specified directory. Edit and test inside ../hotfix. After committing and integrating the fix, inspect remaining files with git -C ../hotfix status --short --ignored. Ignored files such as .env are also removed with the directory; move needed content elsewhere first. Then remove the checkout from the original directory:

# Remove the finished checkout and its registration, retaining the hotfix branch
git worktree remove ../hotfix

Confirm removal with git worktree list. A dirty-worktree refusal is a checkpoint: save needed content before proceeding. Removal retains the branch; once integrated and no longer needed, delete it separately with git branch -d hotfix. If Git refuses as unmerged, verify the integration method (for example squash) and destination content first. Worktree reference

Check out submodule versions recorded by the parent

Run this from the parent repository containing .gitmodules. Inspect recorded and checked-out states first:

# Inspect commits and initialization state for submodules recursively
git submodule status --recursive

A leading - means uninitialized, + means the checkout differs from the parent index entry, and U means conflict. A space means the commit matches, not that its working tree is clean. Before updating, check and save local work with git -C path/to/submodule status --short in each affected submodule.

# Initialize missing submodules and check out the commits recorded by the parent
git submodule update --init --recursive --checkout

--init initializes, --recursive handles nesting, and --checkout explicitly chooses checkout mode, normally leaving a detached HEAD. Recheck status: uninitialized or mismatched-commit prefixes should be gone. Resolve conflicts or blocking local edits before retrying.

Dependency upgrades are a different task. --remote selects a version from the submodule’s remote-tracking branch rather than reproducing the parent’s recorded version. Review that upgrade, test the parent project, and commit the new submodule reference in the parent. It is not an initialization shortcut. Submodule reference

Create and publish a tag for a verified commit

The example uses an unused v1.0.0 tag. Inspect the release commit with git show --stat HEAD and use git status --short to confirm that release content is committed. A tag points to a commit, not uncommitted working files.

# Create an annotated local tag at the explicitly selected HEAD commit
git tag -a v1.0.0 -m "Release v1.0.0" HEAD

-a creates a tag object with a message and tagger information. Verify it with git show v1.0.0, then preview publishing only this tag:

# Preview this tag’s destination without pushing other local tags
git push --dry-run origin refs/tags/v1.0.0:refs/tags/v1.0.0

Once the release target is confirmed:

# Publish the single verified tag to origin
git push origin refs/tags/v1.0.0:refs/tags/v1.0.0
# Read the local annotated tag object ID
git rev-parse refs/tags/v1.0.0

# Query the same tag object on the server and compare IDs
git ls-remote --tags origin refs/tags/v1.0.0

Compare tag object IDs here, not the commit IDs they point to. If a different tag with this name already exists remotely, inspect the release record before acting; do not casually overwrite a published release tag. Tag reference

Further reference

For the complete option set, run git help <subcommand>, for example git help restore. Use the official manual to confirm option semantics and the task sections here to connect inspection, decisions, execution, and verification.

Git reference · Pro Git