Reflect on your 2024 year in code

Debugging Common Git Errors

Greg Foster
Greg Foster
Graphite software engineer
Try Graphite


Note

This guide explains this concept in vanilla Git. For Graphite documentation, see our CLI docs.


When working with Git, developers often encounter a variety of cryptic errors. These errors can stem from issues like incorrect branch states, conflicting changes, or simply a misunderstanding of how Git’s commit graph works. By understanding the meaning behind these errors and how Git’s internal structure operates, you can more effectively diagnose and fix them.

Below are some of the most frequent Git errors encountered by developers, along with examples, detailed explanations, and guidance on how tools like Graphite can help streamline your debugging process.

  • Merge conflicts: Occur when Git is unable to automatically resolve differences in code between two commits.

  • Detached HEAD: Happens when you check out a commit instead of a branch, leaving you in a state that is not attached to any branch.

  • Rebase issues: Can arise when replaying commits from one branch onto another and conflicts are found.

  • Permission denied: Often caused by incorrect file permissions or SSH key issues.

A merge conflict occurs when Git cannot automatically combine two sets of changes that affect the same lines in a file. When you run a command like git merge feature-branch on your main branch, Git tries to incorporate commits from the feature-branch into main. If it detects that both branches altered the same lines differently, you’ll see a conflict message such as:

Terminal
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.

Behind the scenes, Git attempts a three-way merge that looks at the merge base (the common ancestor) and the two sets of changes. A failure indicates that Git needs human assistance. To resolve this:

  1. Open the file indicated by Git. You’ll see conflict markers, such as <<<<<<< HEAD, =======, and >>>>>>> feature-branch.
  2. Manually edit the file to keep the desired changes. Remove the conflict markers.
  3. Once resolved, run:
Terminal
git add file.txt
git commit

A detached HEAD occurs when your Git HEAD (a reference pointer to the current branch, typically main) does not point to a branch tip but rather a specific commit. This state often happens if you check out a commit directly instead of a branch, for example:

Terminal
git checkout 123abc4

Here, 123abc4 is a commit hash. Under the hood, the HEAD pointer moves to that commit, leaving you without a branch reference. You might see a warning like:

"You are in 'detached HEAD' state. You can look around, make experimental changes and commit them..."

Working in this state means new commits won’t be associated with a named branch. To fix this, do one of the following:

  • If you want to keep the changes, create a new branch from the detached state:
Terminal
git checkout -b new-branch
  • If you just need to return to a known branch, run:
Terminal
git checkout main

Sometimes you commit changes to the wrong branch. For instance, you meant to commit on feature-branch but realized your HEAD was still on main. Internally, commits are just references that link back to parent commits. Git doesn’t enforce which branch a commit goes to; it’s all about where HEAD was pointing at the time.

To move commits to the correct branch, you can use git cherry-pick (a command that applies the changes of an existing commit onto another branch):

  1. Identify the commit hash you need to move:
Terminal
git log
  1. Switch to the correct branch:
Terminal
git checkout feature-branch
  1. Cherry-pick the commit:
Terminal
git cherry-pick <commit_hash>

After successfully applying the commit, return to the incorrect branch and use git reset --hard HEAD~1 to remove the accidental commit from it. The --hard flag modifies both the working directory and the staging area to match the specified commit, effectively removing the unwanted commit. Tools like Graphite give you a commit history dashboard, letting you quickly identify the misplaced commit and plan how to move it.

When pushing to a remote repository such as GitHub, developers might encounter authentication errors like:

Terminal
remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/user/repo.git/'

Git uses the credentials you’ve set up (often stored in your system’s credential manager or in a credentials file) to authenticate with the remote. Incorrect credentials, expired tokens, or revoked permissions can cause these errors.

To fix them:

  • Update your stored credentials using git config credential.helper or by editing your .gitconfig.
  • Refresh your OAuth tokens or personal access tokens if you’re using them.

If your remote branch has diverged from your local branch, pushing your local commits might fail with an error like:

Terminal
To https://github.com/user/repo.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/user/repo.git'

This error occurs because the remote has commits that your local branch doesn’t have. Git requires you to reconcile these differences before pushing. Internally, Git ensures the remote branch is a direct ancestor of what you’re pushing, maintaining a consistent commit graph across the remote and local histories.

To fix:

  • First, pull the latest changes:
Terminal
git pull --rebase origin main

Here, --rebase replays your local commits on top of the remote’s updated history, avoiding unnecessary merge commits.

  • Now push again:
Terminal
git push origin main

For additional support:

  • Consult the Git documentation for detailed explanations of commands and error messages.

  • Use community resources like Stack Overflow, where many common Git errors have been discussed and resolved.

  • Seek peer support for pair-programming or troubleshooting sessions.

Graphite's CLI simplifies debugging by streamlining complex Git operations and enhancing workflow clarity. One of its standout features is the ability to work with stacked pull requests. Debugging issues across multiple related branches can often become cumbersome, but with Graphite’s gt commands, you can easily pinpoint and amend errors in any part of your stack. For example, if a bug is found in a lower branch, you can use the gt modify command to make corrections while automatically restacking dependent branches, ensuring that changes are propagated without introducing inconsistencies.

Additionally, Graphite's gt sync command facilitates debugging by rebasing open branches with the latest changes from the main branch. This ensures that all branches are aligned with the most up-to-date codebase, reducing merge conflicts and potential debugging complexities. If merge conflicts arise, gt sync provides prompts and tools like gt restack to resolve conflicts efficiently. These features, combined with Graphite's focus on simplifying Git workflows, make debugging less error-prone and more manageable for teams.

Built for the world's fastest engineering teams, now available for everyone