Git Operations Reference
A practical Git operations quick reference for real-world scenarios, helping you quickly find useful but hard-to-remember commands, and clarifying their usage conditions and execution impacts.
- First published
- Last updated
On this page
Basic Notations and Terminology
Notations in Command Formats
| Notation | Meaning |
|---|---|
<name> | Placeholder to be replaced with the actual value; angle brackets themselves are not entered |
[<name>] | Optional placeholder; square brackets themselves are not entered |
<name>... | One or more values of the same kind can be provided; ellipsis itself is not entered |
A | B | Choose one from A and B; the vertical bar itself is not entered |
-ish
The English suffix -ish means “somewhat like” or “having the characteristics of”. Git documentation uses it to name a category of expressions that can be resolved to a specific object; it’s not a command parameter to be entered verbatim.
commit-ish means a name or expression that can ultimately be resolved to a commit, such as:
main # Branchv1.0.0 # Tag pointing to a commita1b2c3d # commit IDHEAD # Current commitHEAD~2 # The second-generation ancestor of the current commitorigin/main # Remote-tracking branchTherefore, [<commit-ish>] means you can fill in a branch, tag, commit ID, or HEAD expression here, or it can be omitted.
tree-ish means a name or expression that can ultimately be resolved to a tree object. Branches, commits, and tags pointing to commits can all be further resolved to the project file tree of that commit, so main, v1.0.0, HEAD, tree object IDs, and HEAD^{tree} can all serve as tree-ish.
HEAD
HEAD is a special reference: under normal conditions, it points to the current branch; in a detached HEAD state, it points directly to a commit.
When the current branch is main, modifying files from a clean state, staging modifications, and creating commits will not change the HEAD → main reference relationship:
If you directly switch to a certain commit ID, HEAD will no longer point to a branch, but directly to that commit. This is called detached HEAD, meaning HEAD is detached from branches:
HEAD → <commit-id>You can still modify, stage, and commit under a detached HEAD state, but creating a commit will not move any local branch forward. To retain these commits, you should execute git switch -c <new-branch> to create a branch before leaving.
When executing an ordinary git commit, the new commit records the commit where the current branch was previously located; this commit immediately preceding the new commit is called its parent commit. The root commit at the beginning of history has no parent; an ordinary commit usually has one parent; and a commit resulting from merging multiple histories can have multiple parents. Both ^ and ~ leverage this relationship to navigate backward through commits.
Both start from a given commit, but their lookup rules differ:
<commit-ish>^<n>: Selects then-th parent of<commit-ish>, wherenstarts from1; when<n>is omitted, it selects the first parent.<commit-ish>~<n>: Starting from<commit-ish>, consecutively selects the first parentntimes; when<n>is omitted, it only selects once.
Therefore, ^2 means “the second parent commit,” while ~2 means “trace back two generations along the first parent chain”; <commit-ish>^, <commit-ish>^1, <commit-ish>~, and <commit-ish>~1 all represent the same commit.
For example, feature branches off from C1 on main. After executing git merge feature on main, a merge commit M is produced:
Each directed line in the figure points from a parent commit to the subsequent commit it produced.
First and second are not inferred based on positions or branch names in the figure, but rather on the order of multiple parent records within the merge commit object. When git merge feature is executed on main:
- Before the merge,
HEADpoints tomain, andmainpoints toC2; Git writesC2into the firstparentrecord. featurepoints toF2; during the merge, Git usesMERGE_HEADto record this incoming commit, and writesF2into the secondparentrecord.
Therefore, the relevant content of the merge commit M is equivalent to:
parent <commit ID of C2>parent <commit ID of F2>The two directed lines in the figure pointing from C2 and F2 to M correspond to these two records. The HEAD^, HEAD^2, and HEAD~2 in the figure all assume that you remain on main after the merge is complete, at which point HEAD points to the merge commit M. Applying the aforementioned rules to HEAD at this time:
HEAD^: Takes the first parent ofM, yieldingC2;HEAD^2: The number2means taking the second parent ofM, yieldingF2, not tracing back two generations;HEAD~2: Takes the first parent consecutively twice, going throughM → C2 → C1, yieldingC1.
These notations start searching from the commit HEAD points to at the time they are used, and do not fixedly represent a certain commit in the figure. If you subsequently switch to feature, HEAD will point to F2, and at that point HEAD^ would represent F2’s first parent F1; the parent commit relationships of the merge commit M have not changed, it’s just no longer accessed using HEAD as the starting point.
If you instead executed git merge main on feature, HEAD before the merge would be F2, so the first record would be F2, and the second record would be the incoming C2.
Merge commits are not limited to just two parents. When merging multiple branches into main simultaneously, for example:
git merge feature-a feature-bIf this command successfully generates a multi-branch merge commit, its commit object can contain three parent records:
parent <commit ID where main was before merge>parent <commit ID of feature-a>parent <commit ID of feature-b>This type of merge is called an Octopus merge. In this case, HEAD^3 reads the third parent record; it differs from HEAD~3, which would trace back three generations consecutively along the first parent record. Commits can record any number of parents, and the specific order can be viewed directly in the commit object:
git cat-file -p HEADWorking Tree Status
The working tree (also often called the working directory) is the actual project files available for editing in the directory corresponding to the repository. Modifying files directly changes the working tree; newly created files that have not yet been tracked by Git will show up here as untracked files.
The index (also known as the staging area) stores the file snapshot information that will be used for the next commit; it is not a directory for people to edit directly. git add writes the current content of the specified files into the index, and git commit then creates a new commit based on the index.
The HEAD introduced earlier provides the file snapshot of the current commit. Based on the official Git git-status documentation, common statuses can be categorized into the following groups:
| Common Status | Working Tree | Index | git status Official Category |
|---|---|---|---|
clean | Matches index for tracked files; no untracked files (except ignored ones) | Matches HEAD | working tree clean |
| Staged modifications | May match index, or might be modified again after staging | Saves modifications to be included in next commit, differs from HEAD | Changes to be committed |
| Unstaged modifications | Tracked files contain modifications not yet written to index | Hasn’t saved latest working tree content, may also save earlier staged content | Changes not staged for commit |
| Untracked files | New paths not yet tracked by Git exist | No record of the path | Untracked files |
| Unmerged paths | Conflict resolution not yet complete | May hold multiple pending versions of the same path | Unmerged paths |
Except for clean, other statuses can appear simultaneously. For example, if a file is modified again after being staged, it will have both staged modifications and unstaged modifications simultaneously.
switch: Switch Branches
git switch <target-branch> attempts to make the current worktree use the target branch. The process for handling existing local states during the switch is as follows:
restore: Unstage
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
-S, --staged | Sets the restoration target to the index |
# Purpose: Unstage modifications for specified paths# Result: Restores the corresponding content in the index to HEAD's version, keeping the working directory files unchangedgit restore --staged <pathspec>...# Example 1: Unstage README.md, but keep the file modificationsgit restore --staged README.mdrebase: Replay Commits on a New Base
rebase transplants a commit sequence onto a new base. When executing git rebase <upstream>, Git finds the commits in the current branch that are not in <upstream>, recreating these commits sequentially after <upstream>, and finally points the current branch to the rebuilt sequence; <upstream> itself does not move.
C' and D' are newly created commits based on the modifications of C and D. Because their parent commits have changed, they have new commit IDs. Rebasing is suitable for tidying up local history that has not yet been shared; if other branches or users are already working based on these commits, you should coordinate before rewriting them.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
--onto <new-base> | Changes the starting point of the rebuilt commits to <new-base> |
-i, --interactive | Edit commit order and handling methods before rebuilding |
--autostash | Temporarily saves local modifications before rebasing, then reapplies them afterward |
-r, --rebase-merges | Attempts to recreate original merge structure on the new base |
--continue | Continues the rebase after resolving conflicts or finishing edits |
--skip | Skips the commit currently being applied |
--abort | Aborts the rebase and restores the branch, index, and tracked working directory content to before the start |
# Purpose: Reapply commits unique to the current branch onto a new base# Conditions: The current branch is the one to be rewritten; index and tracked files have no uncommitted modifications; related commits have not been shared or rewrite has been coordinated# Result: Rebuilds current branch's commits after <upstream>, and points current branch to the new commit sequence; <upstream> remains unchangedgit rebase <upstream># Example 1: Current branch is feature/example, reapply its unique commits after maingit rebase main
# Purpose: Separately specify new base, commit selection boundary, and branch to rewrite# Conditions: Index and tracked files have no uncommitted modifications; <branch> is not used by other worktrees; related commits have not been shared or rewrite coordinated# Result: Selects commits unique to <branch> relative to <upstream>, reapplies them onto <new-base>, then points <branch> to the new sequencegit rebase --onto <new-base> <upstream> <branch># Example 1: Reapply commits from topic/example unique relative to integration onto maingit rebase --onto main integration topic/example
# Purpose: Adjust commit order, modify commit messages, squash or drop commits# Conditions: Index and tracked files have no uncommitted modifications; pending commits not yet shared or rewrite coordinated# Result: Opens a to-do list of commits arranged from oldest to newest, and recreates commits after <upstream> according to the saved contentgit rebase -i <upstream># Example 1: Interactively tidy up the last 5 commits of the current branchgit rebase -i HEAD~5
# Purpose: Perform rebase when local modifications exist# Conditions: No unresolved merge conflicts exist; ordinary untracked files must not block Git writes; related commits not shared or rewrite coordinated# Result: Creates a temporary stash before rebasing, reapplies it after rebasing finishes; reapplying may still produce conflictsgit rebase --autostash <upstream># Example 1: Temporarily save local modifications, rebase current branch to main, then reapply these modificationsgit rebase --autostash main
# Purpose: Recreate original merge structure during rebase# Conditions: Index and tracked files have no uncommitted modifications; related commits not shared or rewrite coordinated# Result: Reapplies ordinary commits on new base and attempts to recreate merge commits; previous manual conflict resolutions may need to be handled againgit rebase --rebase-merges <upstream># Example 1: Rebase current branch to main, and recreate its merge structuregit rebase --rebase-merges main
# Purpose: Continue rebase after resolving conflicts or finishing interactive edits# Conditions: Rebase is paused; conflicts are resolved and staged with git add, or current editing is complete# Result: Completes the rebuilding of the current commit and continues processing remaining commitsgit rebase --continue
# Purpose: Skip currently un-applicable commit and continue rebase# Conditions: Rebase is paused, and it is confirmed the new history doesn't need the current commit's modifications# Result: The current commit will not enter the rebuilt history, rebase continues processing subsequent commitsgit rebase --skip
# Purpose: Cancel a paused rebase# Conditions: Rebase has started but not yet finished# Result: Abandons the unfinished rebase, and restores to the state before the rebase startedgit rebase --abortstash: Temporarily Save Modifications
stash records modifications in the current working directory and index into a local stash, returning the corresponding files to the HEAD state. stash@{0} denotes the newest record, and subsequent records are stash@{1}, stash@{2}, etc. By default, stashes are only saved in the current repository and won’t be sent to remote repositories by a regular push.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
-m <message>, --message <message> | Use <message> as the stash description |
-u, --include-untracked | Save untracked files as well, but exclude ignored files |
-p, --patch | When used with push, interactively choose modifications to save |
-p, --patch | When used with show, show the full patch |
--stat | Show diff statistics |
-- | End option parsing |
# Purpose: View the list of stashes in the current repositorygit stash list
# Purpose: Temporarily save modifications to tracked files# Conditions: Index must not contain unresolved merge conflicts# Result: Creates a stash, saving staged and unstaged modifications, and returns corresponding files to HEAD stategit stash push -m <message># Example 1: Save current modifications to tracked files, and label the record as wip: examplegit stash push -m "wip: example"
# Purpose: Save modifications to tracked files and untracked files simultaneously# Conditions: Index must not contain unresolved merge conflicts# Result: Creates a stash, saving staged, unstaged modifications, and untracked files; then returns tracked files to HEAD, and removes saved untracked files from working tree; ignored files remain unchangedgit stash push -u -m <message># Example 1: Save current modifications and untracked files, but not ignored filesgit stash push -u -m "wip: example"
# Purpose: Only save modifications in specified paths# Conditions: Index must not contain unresolved merge conflicts# Result: Only writes modifications matching paths to stash and reverts them, other paths remain unchangedgit stash push -m <message> -- <pathspec>...# Example 1: Only save modifications in docs/ and README.mdgit stash push -m "wip: docs" -- docs/ README.md
# Purpose: View diff statistics for a specific stash# Result: Shows diff stats of the stash relative to its base commit when created, changes no statesgit stash show --stat <stash># Example 1: View diff stats of the newest stashgit stash show --stat stash@{0}
# Purpose: View full patch of a specific stash# Result: Shows the full diff of the stash relative to its base commit when created, changes no statesgit stash show -p <stash># Example 1: View full patch of the newest stashgit stash show -p stash@{0}
# Purpose: Apply a specific stash, while keeping the record# Conditions: Working directory must match index# Result: Applies recorded modifications to current state; may produce conflicts, but won't delete the stashgit stash apply <stash># Example 1: Apply newest stash, keeping the recordgit stash apply stash@{0}
# Purpose: Apply a specific stash, and delete the record upon success# Conditions: Working directory must match index# Result: Applies recorded modifications to current state; deletes stash on success, keeps it on conflictgit stash pop <stash># Example 1: Apply newest stash, and delete the record upon successgit stash pop stash@{0}
# Purpose: Create a branch from the base commit when the stash was created and apply modifications# Conditions: <new-branch> must not exist, current local modifications must not prevent switching to the stash's base commit# Result: Creates and switches to new branch, applies stash; deletes stash record upon successgit stash branch <new-branch> <stash># Example 1: Create recovery/example branch and apply newest stashgit stash branch recovery/example stash@{0}worktree: Manage Multiple Working Trees
worktree allows the same repository to have multiple working directories simultaneously, using different branches or commits in each. The state of each worktree is mutually independent, but they share repository data, so there’s no need to repeatedly clone the repository to work on multiple branches at once. The main worktree is the default repository directory created when cloning or initializing; git worktree add can create more worktrees outside of it.
Main worktree/├── Working directory files└── .git/ ($GIT_COMMON_DIR) ├── objects/ (shared by all worktrees) ├── refs/heads/ (shared by all worktrees) ├── refs/tags/ (shared by all worktrees) ├── config (shared by default) ├── HEAD → main (independent to main worktree) ├── index (independent to main worktree) └── worktrees/ (subdir name usually takes terminal dir name of worktree, appending numbers on collision; not a commit ID) ├── <id-a>/ (corresponds to worktree A) │ ├── HEAD → branch-a │ └── index └── <id-b>/ (corresponds to worktree B) ├── HEAD → branch-b └── index
worktree A/├── Working directory files└── .git (regular file) └── Content: gitdir: Main worktree/.git/worktrees/<id-a>
worktree B/├── Working directory files└── .git (regular file) └── Content: gitdir: Main worktree/.git/worktrees/<id-b>- The same local branch can by default only be used in one worktree. Each worktree has its own
HEAD, but local branch references are shared by the repository. If you force theHEADof two worktrees to simultaneously point to the same local branch, creating a commit in one will move the branch forward; the other worktree’sHEADimmediately points to the new commit, but its index and working directory retain the previous contents. At this point Git will show staged differences not resulting from actual edits, and continuing to commit may produce a commit that undoes the previous modification.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
-b <new-branch> | Create <new-branch>, and have new worktree use this branch |
-d, --detach | Have new worktree’s HEAD point directly to a commit, not using a local branch |
-n, --dry-run | Only show records that prune would clean up |
# Purpose: View all worktrees associated with the repositorygit worktree list
# Purpose: Create a new branch from a specified starting point, and create a worktree for it# Conditions: Working directory where command is executed can be clean or unclean# Result: Creates a new branch starting from <commit-ish>, and establishes a worktree for it at <path>git worktree add -b <new-branch> <path> [<commit-ish>]# Example 1: Create hotfix/example branch starting from origin/main, establish worktree in adjacent project-hotfix/git worktree add -b hotfix/example ../project-hotfix origin/main# Example 2: Create feature/example branch starting from current HEAD, establish worktree in adjacent project-feature/git worktree add -b feature/example ../project-feature# Example 3: Create hotfix/v1.2.1 branch starting from v1.2.0, establish worktree in adjacent project-hotfix-v1.2.1/git worktree add -b hotfix/v1.2.1 ../project-hotfix-v1.2.1 v1.2.0
# Purpose: Create a worktree for an existing local branch# Conditions: Working directory can be clean or unclean; <branch> is not used by other worktrees# Result: Establishes a worktree using <branch> at <path>; uncommitted modifications and staged content from original working directory are not copied overgit worktree add <path> <branch># Example 1: Establish a worktree using branch release/1.x in adjacent project-release/git worktree add ../project-release release/1.x
# Purpose: Create a temporary worktree not bound to a branch for a specific version, used for inspecting old versions, testing, or executing bisect# Conditions: Working directory where command is executed can be clean or unclean# Result: Establishes worktree at <path>, its HEAD points directly to specified commit in detached HEAD state; original working directory state remains unchangedgit worktree add --detach <path> <commit-ish># Example 1: Directly use abbreviated commit ID to establish temp worktree pointing to 9f3a2c1 in adjacent project-inspect-commit/git worktree add --detach ../project-inspect-commit 9f3a2c1# Example 2: Establish temp worktree pointing to commit referenced by tag v1.2.0 in adjacent project-inspect-tag/git worktree add --detach ../project-inspect-tag v1.2.0
# Purpose: Remove a linked worktree no longer needed, but keep its branch and commits# Conditions: Working directory can be clean or unclean; target must be clean, unlocked, and have no submodules# Result: Deletes <worktree> working directory and its management records, does not delete corresponding branch and commitsgit worktree remove <worktree># Example 1: Remove adjacent project-hotfix/ worktreegit worktree remove ../project-hotfix
# Purpose: Preview invalid worktree management records that can be cleaned up# Conditions: Working directory can be clean or unclean; needs to be executed within any worktree of the repository# Result: Lists management records where working directory no longer exists and meets cleanup conditions, without actually deletinggit worktree prune --dry-run
# Purpose: Clean up invalid worktree management records# Conditions: Working directory can be clean or unclean; needs to be executed within any worktree of the repository# Result: Deletes management records where working directory no longer exists and meets cleanup conditions, doesn't delete existing worktrees, branches, or commitsgit worktree prune
# Purpose: Repair invalidated associations after manually moving main worktree or linked worktree# Conditions: Working directories of execution and target can be clean or unclean; new path must be provided if moving linked worktree# Result: Re-establishes bidirectional association between repository management directory and corresponding worktree, doesn't change files, branches, or commitsgit worktree repair [<path>...]# Example 1: Repair a linked worktree already manually moved to ../project-hotfix/git worktree repair ../project-hotfixcherry-pick: Apply Specific Commits
cherry-pick reads the modifications introduced by an existing commit, applies them to the current branch, and by default creates a new commit with the corresponding content but a different commit ID. It is suitable for moving isolated fixes into other branches without merging the entire history leading up to the original commit.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
-n, --no-commit | Do not automatically create a commit |
# Purpose: Introduce an existing commit into the current branch# Conditions: Current working tree must be clean# Result: Applies modifications introduced by target commit, and creates a new commit on current branchgit cherry-pick <commit-ish># Example 1: Introduce commit 9f3a2c1 into current branchgit cherry-pick 9f3a2c1
# Purpose: Apply an existing commit's modifications, but don't immediately create a commit# Conditions: Index must not contain unresolved merge conflicts; existing local modifications must not be overwritten by target modifications# Result: Writes target modifications to working directory and index, HEAD and current branch remain unchangedgit cherry-pick --no-commit <commit-ish># Example 1: Apply modifications of commit 9f3a2c1, check or adjust before manually committinggit cherry-pick --no-commit 9f3a2c1revert: Revert Specific Commits with a New Commit
revert creates a new commit that undoes the target commit, without deleting or rewriting the original commit, making it suitable for reverting history that has already been shared.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
-m <parent-number>, --mainline <parent-number> | Specify the parent commit numbered <parent-number> as the mainline |
--no-patch | When used with git show, do not display file patches |
--pretty=raw | When used with git show, display commit metadata in raw format |
# Purpose: Revert modifications introduced by an ordinary commit# Conditions: Current working tree must be clean# Result: Creates a new commit that undoes the target commit, original commit and existing history remain unchangedgit revert <commit-ish># Example 1: Revert commit 9f3a2c1 with a new commitgit revert 9f3a2c1
# Purpose: Revert a merge commit; due to multiple parents, -m must specify which mainline to keep# Conditions: Current working tree must be clean; target is a merge commit; <parent-number> corresponds to one of its parents# Result: Reverses the changes of the merge relative to that parent commit; does not revert that parent, nor delete the original merge commitgit revert -m <parent-number> <merge-commit># Example 1: Check parent order first, then keep the mainline represented by the first parent and revert the mergegit show --no-patch --pretty=raw 6d8f2a1git revert -m 1 6d8f2a1# Note: Revert only reverses content, original merge relationship still exists; merging again won't automatically restore the reverted contentreset: Reset HEAD and File State
reset moves the current branch or detached HEAD to a specified commit, and decides whether to simultaneously reset the index and working directory based on the mode. It is suitable for tidying up local states that haven’t been shared; if related commits have already been used by others, moving the branch will rewrite the history they rely on.
The meanings of three common command-line parameters are as follows:
| Parameter | Current Branch or HEAD | Index | Working Directory |
|---|---|---|---|
--soft | Moved to target commit | Remains unchanged | Remains unchanged |
--mixed | Moved to target commit | Reset to target commit content | Remains unchanged |
--hard | Moved to target commit | Reset to target commit content | Reset to target commit content |
# Purpose: Move current branch, while keeping contents in index and working directory# Conditions: Index must not contain unresolved merge conflicts# Result: Current branch or detached HEAD moved to target commit; index and working directory remain unchangedgit reset --soft <commit-ish># Example 1: Retract current commit, while keeping its modifications in a staged stategit reset --soft HEAD^
# Purpose: Move current branch and cancel staged state after target commit# Conditions: Current working tree can be clean or unclean# Result: Current branch or detached HEAD moved to target commit, index reset to target content, working directory remains unchangedgit reset --mixed <commit-ish># Example 1: Retract current commit and cancel staged state of its modifications, but keep file modificationsgit reset --mixed HEAD^
# Purpose: Make current branch, index, and tracked files all return to specified commit# Conditions: Current working tree can be clean or unclean# Result: Current branch or detached HEAD moved to target commit; index and tracked files overwritten by target content, blocking untracked paths might also be deletedgit reset --hard <commit-ish># Example 1: Retreat current branch to previous commit, and discard uncommitted modifications in index and working directorygit reset --hard HEAD^reset --hard will discard uncommitted modifications to tracked files, and should not be used for ordinary cleanup.
reflog: View Reference Log
reflog (reference log) records where branches and other references in the local repository previously pointed. It differs from commit history: commit history saves parent-child relationships between commits, while reflog saves the movement of references due to local operations. git reflog by default shows the reference log of HEAD, which also records branch switches; viewing a specific branch’s reflog only reflects the movements of that branch reference itself.
Reflogs only exist locally and are not synchronized between repositories via fetch, pull, or push. As long as the corresponding record hasn’t expired and the target object still exists, commits no longer pointed to by the current branch or tags can be accessed via reflog.
Reflog positions are expressed using <ref>@{<specifier>}:
| Notation | Meaning |
|---|---|
<ref>@{0} | The value of <ref> after the most recent recorded move, usually its current value |
<ref>@{<n>} | The position <ref> was in <n> moves ago; numbering starts from 0 |
<ref>@{<date>} | The position <ref> was in at a specified time; based on reference update time, not commit creation time |
HEAD@{2} | The position HEAD was in two moves ago |
main@{yesterday} | The position local main was in yesterday |
main@{one.week.ago} | The position local main was in a week ago |
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
-n <count> | Show at most <count> records |
--date=<format> | Show record times according to <format> |
# Purpose: View update records for HEAD or specified reference# Result: Shows reflog position, commit ID, and operation description in reverse chronological order, changes no referencesgit reflog show [-n <count>] [--date=<format>] [<ref>]# Example 1: Show recent 10 update records of HEAD in local timegit reflog show -n 10 --date=local HEAD# Example 2: Show recent 5 update records of maingit reflog show -n 5 main
# Purpose: List references in current repository that have a reflog# Result: Outputs reference names, changes no reflog or referencesgit reflog list
# Purpose: Check if specified reference has a reflog# Result: Returns exit status 0 if exists, non-zero otherwise; does not output reflog contentgit reflog exists <ref># Example 1: Check if local main branch has a refloggit reflog exists refs/heads/main
# Purpose: Inspect an old position recorded in reflog# Conditions: Specified record has not expired, and the Git object it points to still exists# Result: Shows the commit corresponding to that position and its patch, does not move any referencesgit show <ref>@{<specifier>}# Example 1: Inspect the commit HEAD pointed to two moves agogit show HEAD@{2}
# Purpose: Retain a commit found in reflog as a new branch# Conditions: Specified record and its commit still exist; <new-branch> must not exist# Result: Creates a new branch pointing to that commit, does not switch branches, does not move existing referencesgit branch <new-branch> <ref>@{<specifier>}# Example 1: Retain the commit HEAD pointed to two moves ago as rescue/examplegit branch rescue/example HEAD@{2}Reflog is not a permanent backup. By default, records reachable from current references are controlled by gc.reflogExpire, kept for 90 days by default; records unreachable from current references are controlled by gc.reflogExpireUnreachable, kept for 30 days by default. Actual durations can be modified via configuration; after records expire, if the corresponding commits are no longer protected by other references or reflogs, they might later be deleted by garbage collection. Daily viewing and recovery do not require directly executing reflog expire, delete, or drop.
bundle: Bundle Repository History
bundle writes references and their reachable Git objects into a single file, which can transmit repository data offline or via alternative media without network access. Self-contained bundles contain complete history and can be directly cloned; incremental bundles only contain new commits after a designated base, and the receiving repository must already possess the prerequisite commits.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
--all | Pack all references |
# Purpose: Create a self-contained bundle# Conditions: Execution working directory can be clean or unclean; can pack all references or specified branch# Result: Writes self-contained repository data into <file>git bundle create <file> <git-rev-list-args># Example 1: Pack all references in current repository into project.bundlegit bundle create project.bundle --all# Example 2: Only pack history of main branch into main.bundlegit bundle create main.bundle main
# Purpose: Create an incremental bundle# Conditions: Current repository must possess the specified new commits and base commits# Result: Writes only the history from <base> to <new-commit> into <file>git bundle create <file> <base>..<new-commit># Example 1: Create an incremental bundle from v1.0.0 up to maingit bundle create update.bundle v1.0.0..main
# Purpose: Verify if a bundle is valid and complete# Conditions: Execute within a Git repository; when verifying incremental bundles, should execute in receiving repository# Result: Checks file integrity, and lists prerequisite commits missing in current repository; imports no objectsgit bundle verify <file># Example 1: Verify project.bundlegit bundle verify project.bundle
# Purpose: Create new repository from a self-contained bundle# Conditions: <file> must be a bundle with no missing prerequisite commits, <directory> must not be an existing non-empty directory# Result: Creates new repository in <directory> containing bundle historygit clone <file> <directory># Example 1: Create project/ repository from project.bundlegit clone project.bundle project
# Purpose: View references provided by a bundle# Result: Shows readable references and their commit IDs within, imports no objectsgit ls-remote <file># Example 1: View references provided by project.bundlegit ls-remote project.bundle
# Purpose: Fetch specified reference from bundle# Conditions: Execute in receiving repository; repository must possess bundle's prerequisite commits# Result: Imports required objects, and updates local reference according to refspecgit fetch <file> <source-ref>:<destination-ref># Example 1: Fetch main branch of project.bundle as bundle/main remote-tracking referencegit fetch project.bundle refs/heads/main:refs/remotes/bundle/mainarchive: Archive Versioned Files
archive generates a source code package from a specified commit, tag, or tree object. By default it only reads tracked files in that version, and does not include .git, uncommitted modifications, or untracked files.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
--format=<format> | Set archive format to <format> |
--prefix=<prefix>/ | Prepend <prefix>/ to paths inside the archive |
-o <output>, --output=<output> | Write archive to <output> |
# Purpose: Export specified version as a source package# Conditions: Execution working directory can be clean or unclean# Result: Writes files in specified version to <output>, and prepends <prefix>/ to paths inside the packagegit archive --format=<format> --prefix=<prefix>/ -o <output> <tree-ish># Example 1: Export v1.2.0 tag as project-1.2.0.tar.gzgit archive --format=tar.gz --prefix=project-1.2.0/ -o project-1.2.0.tar.gz v1.2.0.gitignore indirectly affects archive contents: files ignored and not added to the repository are not in the commit, so naturally git archive won’t export them. But .gitignore cannot exclude files that already exist in the archived version; to exclude such tracked paths from release packages, set export-ignore in the .gitattributes of that version:
.github/** export-ignoretests/** export-ignorebisect: Binary Search to Locate Commits Introducing Changes
bisect is used to find the location where state changed between a commit known to be good and a commit known to be bad. Git selects a candidate commit each time; after marking it as good, bad, or skip, Git narrows the range and selects the next candidate commit, until the boundary is located.
The entire process must use the same judgment standard, and the target state should only change from good to bad once within the selected range. Commits that cannot be reliably judged should be marked as skip; if such commits are adjacent to the boundary, Git might only be able to provide multiple candidate commits.
# Purpose: Specify bad and good boundaries and start bisection# Conditions: Both boundaries have been verified; working directory can safely switch commits# Result: Records HEAD before starting, and switches to first candidate commitgit bisect start <bad> <good># Example 1: Start bisect between current commit and v1.0.0git bisect start HEAD v1.0.0
# Purpose: Mark current candidate commit as good/normal# Conditions: bisect in progress, and current commit confirmed good# Result: Updates good boundary, and switches to next candidate or reports resultgit bisect good
# Purpose: Mark current candidate commit as bad/abnormal# Conditions: bisect in progress, and current commit confirmed bad# Result: Updates bad boundary, and switches to next candidate or reports resultgit bisect bad
# Purpose: Skip candidate commit that cannot currently be reliably judged# Conditions: bisect in progress, and current commit cannot be classified as good or bad# Result: Selects other candidate commit; may not determine unique result if skips are near boundarygit bisect skip
# Purpose: End bisection# Conditions: bisect in progress# Result: Clears bisection state, and restores branch or commit position before bisection startedgit bisect resetWhen state can be stably judged using a command or script, git bisect run can automatically test each candidate commit:
| Test Command Exit Status | Judgment |
|---|---|
0 | good |
1–127, excluding 125 | bad |
125 | skip |
| Other values | Abort bisect |
# Purpose: Automatically test candidate commits with specified command# Conditions: bisect in progress; command can return stable exit status as agreed# Result: Repeatedly tests and marks candidate commits, until boundary located or bisect abortedgit bisect run <command> [<argument>...]# Example 1: Automatically test candidates using script outside repositorygit bisect start HEAD v1.0.0git bisect run ../test-regression.shgit bisect resetThe test command must be executable across candidate commits. It should only return bad when the target state occurs; build failures or environmental issues unrelated to the target should return 125.
# Purpose: Output operation records of current bisection process# Conditions: bisect in progress# Result: Outputs records for inspection, saving, or replay usage, changes no bisect stategit bisect log# Example 1: Save records to a file outside repositorygit bisect log > ../bisect.log
# Purpose: Restore bisect progress from existing records# Conditions: <log-file> is valid record generated by git bisect log; working directory can safely switch commits# Result: Replays start, good, bad, and skip operations from recordsgit bisect replay <log-file># Example 1: Restore bisect progress from saved recordsgit bisect resetgit bisect replay ../bisect.logmaintenance: Maintain Repository Data
maintenance is used to tidy up accumulating repository data such as commits, objects, pack files, and references, to shorten the time required for history traversal, object fetching, and reference reading. It is not aimed at modifying project files in the working directory, and the working directory can be clean or unclean when executed.
When maintaining large or frequently updated repositories long-term, you usually don’t need to choose maintenance tasks individually. The most direct entry point is git maintenance start: it adds the current repository to the user-level maintenance list, and establishes a background scheduler shared by all registered repositories. If no maintenance strategy was configured before, Git adopts the incremental strategy, whose actual scheduling is as follows:
| Frequency | Automatically Executed Tasks | Actual Effect |
|---|---|---|
| Hourly | commit-graph, prefetch | Updates commit graph; prefetches remote objects to refs/prefetch/, but doesn’t move ordinary remote-tracking branches |
| Daily | loose-objects, incremental-repack | Batches and packs loose objects, gradually merges smaller pack files |
| Weekly | pack-refs | Tidies loose references, speeds up traversal of large numbers of references |
This strategy does not schedule comprehensive gc. prefetch will access the remote repository, but will not update ordinary remote-tracking branches or tags; when executing a routine fetch later, the objects needing transmission will usually be fewer.
Command-line parameters used in this section:
| Parameter | Meaning |
|---|---|
--task=<task> | Only run specified task; can be reused, and executes in given order |
--auto | Only run when repository state reaches corresponding task’s trigger threshold |
--scheduler=<scheduler> | Specify auto, crontab, systemd-timer, launchctl, or schtasks for start |
--global | When used with git config, read current user’s global configuration |
--get-all <name> | Output all values for configuration item <name> |
--unset <name> | Delete configuration item <name> |
# Purpose: Start scheduled maintenance for current repository# Conditions: Execution working directory can be clean or unclean; available scheduler exists in OS# Result: Registers current repository, sets to incremental if no strategy configured, turns off auto-maintenance triggered by regular Git commands, and creates or updates user-level background schedulegit maintenance start [--scheduler=<scheduler>]# Example 1: Let Git automatically choose scheduler based on OSgit maintenance start --scheduler=auto
# Purpose: Add another repository to maintenance list without altering existing scheduler# Conditions: Execute in repository to be added; working directory can be clean or unclean# Result: Registers current repository and completes same repository configuration as start, but doesn't create or start schedulergit maintenance register# Example 1: Scheduler already started by other repositories, add adjacent project-b to same maintenance listcd ../project-bgit maintenance register
# Purpose: View registered repositories# Result: Outputs all maintenance.repo values in current user's global configgit config --global --get-all maintenance.repo
# Purpose: Stop scheduled maintenance only for current repository# Conditions: Current repository already registered; working directory can be clean or unclean# Result: Removes current repository from maintenance list, other repositories and scheduler remain unchanged; maintenance.auto=false is still retainedgit maintenance unregister# Optional: Restore default behavior of regular Git commands triggering auto-maintenance as neededgit config --unset maintenance.auto
# Purpose: Pause scheduled maintenance for all registered repositories# Conditions: Working directory can be clean or unclean# Result: Stops and removes shared user-level scheduler, but retains maintenance list; executing start later can continue processing these repositoriesgit maintenance stop
# Purpose: Manually run specified tasks for explicit issues# Conditions: Execution working directory can be clean or unclean; each <task> must be a supported task name# Result: Only executes specified tasks in order of appearance, doesn't run other tasksgit maintenance run --task=<task> [--task=<task>...]# Example 1: After importing massive commits, immediately update commit-graphgit maintenance run --task=commit-graph# Example 2: Repository accumulated many loose objects and small pack files, execute incremental repackgit maintenance run --task=loose-objects --task=incremental-repack# Example 3: When a full tidy is needed, run gc alone; don't put loose-objects in the same maintenance rungit maintenance run --task=gc
# Purpose: Only run task when repository reaches corresponding maintenance threshold# Conditions: Execution working directory can be clean or unclean# Result: Executes maintenance when threshold reached, otherwise doesn't alter repository datagit maintenance run --autoA comprehensive gc might take a long time, and might also clean up data that has passed its retention period and is unreachable. After enabling start, there is usually no need to manually run the above tasks anymore; explicitly specifying --task is mainly used for immediately addressing confirmed repository data issues.
Local and Remote Histories Diverge After Rebasing a Pushed Branch onto the Latest main
This section covers a specific scenario: the commits on feature/example have already been pushed, and the local branch is then rebased onto an updated origin/main. The rebase rewrites only the local history, so the local branch diverges from the old history on the remote. At this point, a regular push is rejected, and pull does not directly solve the problem either.
How This Happens
- Create
feature/examplefrommainand make two commits,CandD. - Push the branch. At this point, both
feature/exampleandorigin/feature/examplepoint toD. mainthen advances by 29 commits.- On
feature/example, rungit fetch originandgit rebase origin/main. Git recreatesC′andD′after the new base. The local branch now points toD′, while the remote still points to the oldD.
Commit Graph After the Rebase
In the graph, M* represents the 29 new commits on main in a collapsed form. The rebase has rewritten only the local history: the remote ref remains on C and D, while the local branch passes through M* and points to the recreated C′ and D′:
The commit graph also explains the ahead and behind counts. Check the short-format status:
git status --short --branchThe output will be:
## feature/example...origin/feature/example [ahead 31, behind 2]ahead and behind count commits by reachability; they do not compare file contents:
ahead 31: the 29 commits represented byM*, plus the localC′andD′.behind 2: the old remote-only commitsCandD.
More generally, suppose main gains N commits after the branches diverge and all K commits already on the feature branch are rebased. If no other divergence exists, the status will show ahead N + K and behind K. Here, behind 2 confirms that the commit graph has diverged, but the number alone does not mean that someone added new work to the remote.
What Happens If You Run git push Now
git push origin feature/exampleA regular push is rejected. The output usually includes:
! [rejected] feature/example -> feature/example (non-fast-forward)error: failed to push some refs to '<remote-url>'A regular push permits only a fast-forward update: the remote branch tip before the push must be an ancestor of the commit being pushed. Here, the remote tip is D, while the local tip D′ is on a different line of history. D is not an ancestor of D′. This check still fails even if D and D′ ultimately introduce the same file changes.
What Happens If You Run git pull Now
git pullpull first fetches the remote state, then tries to integrate origin/feature/example into the current branch. Because the two sides have diverged, what happens next depends on the pull strategy:
| pull strategy | What happens |
|---|---|
| Neither merge nor rebase is configured | Git stops and reports Need to specify how to reconcile divergent branches |
pull.ff=only or git pull --ff-only | Git stops and reports Not possible to fast-forward |
| merge | Git tries to merge D and D′; this may cause conflicts, and a successful merge leaves a merge commit connecting both the old and new histories |
| rebase | Git uses the old origin/feature/example as the base and replays the local-only commits again; this may cause conflicts or produce an unexpected history |
When pull.rebase=true is configured, git pull takes the last path. If conflicts occur, the output usually includes CONFLICT and could not apply, and the repository is left in an unfinished rebase.
All of these behaviors try to integrate the old remote history. What this scenario actually requires is replacing C and D with C′ and D′. Therefore, even if pull can complete, it is not the correct way to handle this situation.
If an accidental pull is currently stopped at a conflict, abort the operation that is actually in progress:
# If pull is using rebasegit rebase --abort
# If pull is using mergegit merge --abortIf pull failed before integration began, there is nothing to abort. If it has already completed, first use reflog to recover the branch position from before the pull, then continue with the steps below.
Resolution
The correct goal is to move the remote feature/example from the old commit D to the rebased D′. This requires that the branch may be rewritten and that the remote contains no new work from anyone else.
First update the remote-tracking ref and inspect the remote-only commits:
git fetch origin
# View the complete graph after the histories divergedgit log --graph --oneline --decorate --boundary \ feature/example...origin/feature/example
# View only commits that exist on the remote but not locallygit log --oneline \ feature/example..origin/feature/example
# Check whether those remote commits have patch-equivalent local versionsgit cherry -v \ feature/example origin/feature/exampleIn this example, the only remote-only commits should be the pre-rebase C and D. git cherry will usually mark them with -, indicating that patch-equivalent C′ and D′ exist locally. A - is only supporting evidence; use the commit IDs, messages, and collaboration context to verify that these really are the old commits you intend to replace.
Once verified, update the remote with a protected force push:
# Purpose: Replace the old remote history with the rebased local history# Conditions: You have fetched and confirmed that the remote contains only the pre-rebase commits; the branch may be rewritten# Result: If the protective checks pass, updates origin/feature/example to the local feature/examplegit push \ --force-with-lease \ --force-if-includes \ origin feature/exampleThis push command contains two layers of protection: --force-with-lease checks whether the remote branch is still at the expected position, while --force-if-includes checks whether that remote position was genuinely part of the local branch’s history. Before examining these checks, distinguish the three refs in this example:
| Location | Points to | Meaning |
|---|---|---|
Server-side feature/example | D | The remote branch that the push will actually change |
Local origin/feature/example | D | The remote position recorded by the most recent fetch |
Local feature/example | D′ | The new rebased history ready to be pushed |
Think of --force-with-lease as saying, “I agree to overwrite only the remote version I expect.”
No expected value is specified here, so Git uses the value of the local origin/feature/example, D, as the expectation. The server compares the values atomically when updating the ref:
- The server still points to
D: the remote branch has not moved away from the state recorded locally, so the check passes and Git may proceed with trying to update it toD′. - The server already points to
Xpushed by someone else:Xdiffers from the locally recordedD, so the check fails and the remote remains unchanged.
This check does not compare file contents or determine whether C and D really are obsolete copies. You must establish those facts during the preceding history inspection.
--force-with-lease still has a gap: after someone pushes X, an editor might automatically run fetch and update the local origin/feature/example to X as well. The server and the local record now match again, so the lease alone would pass even though you may never have seen or handled X.
--force-if-includes addresses this gap. It requires the reflog of the local feature/example to show that the current remote-tracking tip was previously contained in that branch’s history, rather than merely recorded by fetch:
- In this example,
Dwas the tip offeature/examplebefore the rebase, and the branch reflog retains that position, so the check passes. - If
Xwas only fetched in the background intoorigin/feature/example, the local feature branch never containedX, so the check fails. - If you deliberately integrated
Xinto the local branch, the ancestry relationship and reflog can establish that fact, allowing the check to pass.
Together, the checks produce the following results:
| Situation | --force-with-lease | --force-if-includes | Result |
|---|---|---|---|
The remote is still at the old commit D | passes | passes | update allowed |
Someone pushed X, and you have not fetched it | fails | no need to continue | update rejected |
A background fetch retrieved X, but you did not integrate it | passes | fails | update rejected |
You deliberately integrated X locally | passes | passes | update allowed |
“Update allowed” means only that these two protective checks have passed; it does not mean that Git has determined your intended outcome for you. If either check fails, fetch and inspect again. Do not switch to an unconditional --force.
After a successful push, verify the result:
git fetch origingit status --short --branchThe expected output no longer contains ahead or behind:
## feature/example...origin/feature/examplePinning an Explicit Expected Value for the Lease
In the previous command, the lease expectation comes from origin/feature/example, and a later fetch may update that ref. To pin the push condition to “the server-side branch must still point to the D that I just inspected,” explicitly provide the commit ID of D:
# Print and manually verify the remote tip; in this example, it should be the old pre-rebase commit Dgit rev-parse origin/feature/example
# Substitute the full commit ID from the previous command for <expected-remote-tip>git push \ --force-with-lease=refs/heads/feature/example:<expected-remote-tip> \ origin feature/example:feature/exampleThis form directly compares the server-side refs/heads/feature/example with the specified commit ID and no longer depends on a remote-tracking ref that might change. The update is accepted only if the two values match; otherwise, the remote remains unchanged.
When --force-with-lease=<ref>:<expect> is used, --force-if-includes has no effect and should not be combined with it. If the server-side check fails, fetch and inspect the remote history again instead of switching to --force.
When the Remote Contains New Commits from Other Sources
If git log feature/example..origin/feature/example shows unfamiliar commits other than C and D, or if git cherry produces an unexplained +, stop the force push. You can no longer assume that the remote contains only the old history you intend to replace.
What to do next depends on which history you need to preserve:
| Goal | Approach |
|---|---|
| Preserve the complete existing remote history | Merge origin/feature/example, resolve any conflicts, then push normally |
| Keep the rebased history linear | Coordinate with the commit authors, cherry-pick only the confirmed new commits onto D′, then use a protected force push |
| The branch is protected or may not be rewritten | Push the rebased history as a new branch and handle it through a pull request |
To preserve the complete remote history:
# Conditions: The working tree has no uncommitted changes that would interfere with the mergegit merge origin/feature/examplegit push origin feature/exampleThe merge commit contains both the local and remote tips, so a regular push can fast-forward the remote. The tradeoff is that both the pre-rebase and post-rebase feature branch histories are retained.
If you need only particular new commits from the remote, select them explicitly:
git cherry-pick <remote-new-commit>...After resolving conflicts and running tests, repeat the remote-history inspection from this section, then use the protected force push. Do not mechanically run git rebase origin/feature/example: that command chooses the old remote feature history as the new base and may also replay the batch of main commits as local-only commits, defeating the original goal of basing the feature branch on the latest main.
If the branch may not be rewritten, keep the current rebased result and push it as a new branch:
git switch -c feature/example-rebasedgit push --set-upstream origin feature/example-rebasedReducing This Kind of Divergence
-
Before creating a feature branch, update the remote-tracking refs and start from the latest
origin/main:Terminal window git fetch origingit switch -c feature/example origin/main -
Keep feature branches short-lived to reduce the need to rewrite already-pushed history just to catch up with
main. -
On a personal branch that may be rewritten, treat “rebase onto the latest
main” and “force-push with a lease” as a single operation. Do not run Pull or Sync between them. -
For a shared branch whose history should not be rewritten, merge the latest
mainto preserve the existing commit IDs:Terminal window git fetch origingit merge origin/maingit push origin feature/example -
If the hosting platform prohibits force pushes, follow its branch-protection rules. When you need a new linear history, push it to a new branch and handle it through a pull request.