user456814
user456814

Reputation:

Why does my remote Git repository have uncommitted changes after pushing to it?

I set up a new Git repository using the following commands:

mkdir plans-for-world-domination
cd plans-for-world-domination
git init
echo "MWA HA HA HA HA!" > plans.txt
git add .
git commit -m "Beginning my plans..."

Then I made a clone of this repository, made some changes, committed them, and then tried to push:

cd ..
git clone plans-for-world-domination clone
cd clone
echo "Step 1: set up super secret spy base in Cleveland, Ohio" >> plans.txt
git commit -am "Update plans"
git push origin master

When I cd back into the plans-for-world-domination repository though, there are changes staged in the staging-area/index that are the reverse of the changes that I just pushed:

$ cd ../plans-for-world-domination
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   plans.txt

$ git diff --staged
diff --git a/plans.txt b/plans.txt
index febb495..ce01362 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1,2 +1 @@
 MWA HA HA HA HA!
-Step 1: set up super secret spy base in Cleveland, Ohio

Why does my first repo have these unstaged changes that are the reverse of what I just pushed, and how can I fix this?

Upvotes: 3

Views: 5310

Answers (1)

user456814
user456814

Reputation:

Pushes don't update the working-copy and staging-area in non-bare repositories

The staging area in the first repository appears to contain the reverse of the changes that were just pushed because it's a non-bare repository, meaning that it contains a working copy, which is also frequently referred to as a working (directory) tree in the Git documentation. Bare repositories, on the other hand, don't have a working copy directory.

Since the repository is non-bare, when you push to it, the push only updates branch references, and the symbolic HEAD reference, because git push doesn't operate on the working-copy and staging area that are present in non-bare repos.

As a consequence of this, the working-copy and staging-area of the non-bare repo are still left on the same state of the repository that was present before the push that updated HEAD. In other words, the actual state of the working-copy and staging-area do not match the state of the commit pointed to by HEAD. This is why those differences between the two states show up when git status and git diff are run:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   plans.txt

$ git diff --staged
diff --git a/plans.txt b/plans.txt
index febb495..ce01362 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1,2 +1 @@
 MWA HA HA HA HA!
-Step 1: set up super secret spy base in Cleveland, Ohio

(Sub-optimal) solution: hard reset

Since the working-copy and staging-area are out of sync with HEAD, a solution to get them to match again is to simply use

git reset --hard HEAD
git reset --hard

to reset the working-coy and staging-area to the commit pointed to by HEAD.

However, this is not the ideal solution...

Ideal solution: push to bare repositories instead

You're not really supposed to push to non-bare repositories, because of this exact issue of their working-copies and staging-areas de-syncing with the repository references. Instead, unless you have an unusual reason to push to a non-bare repository, you really ought to push to bare repositories instead, which don't have a working copy.

To create a bare repository, simply use the --bare flag:

# Initialize a bare repo
mkdir bare
cd bare
git init --bare

# Push changes to the bare repo
cd ..
mkdir project
cd project
# Make some changes and commit
git remote add origin ../bare
git push origin master

# Or create a bare clone from another bare or non-bare repo
git clone --bare <repo-path-or-uri>

Pushing to non-bare repositories is denied by default since Git 1.6.2

Note that since Git version 1.6.2, pushing to non-bare repositories has been denied by default:

With the next major release, git push into a branch that is currently checked out will be refused by default. You can choose what should happen upon such a push by setting the configuration variable receive.denyCurrentBranch in the receiving repository.

In fact, when you try to push to a non-bare repo with current versions of Git, you're push should be denied with the following error message (slightly modified for brevity):

$ git push origin master
Total 0 (delta 0), reused 0 (delta 0)
error: refusing to update checked out branch: refs/heads/master
error: By default, updating the current branch in a non-bare repository
error: is denied, because it will make the index and work tree inconsistent
error: with what you pushed, and will require 'git reset --hard' to match
error: the work tree to HEAD.
error:
error: You can set 'receive.denyCurrentBranch' configuration variable to
error: 'ignore' or 'warn' in the remote repository to allow pushing into
error: its current branch; however, this is not recommended unless you
error: arranged to update its work tree to match what you pushed in some
error: other way.
error:
error: To squelch this message and still keep the default behaviour, set
error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To non-bare
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to 'non-bare'

As the error message above explains, you can disable the safety checks that prevent you from pushing into a non-bare repo by disabling the receive.denyCurrentBranch config setting in the remote non-bare repo:

git config receive.denyCurrentBranch warn   # Warn when pushing to non-bare repo
git config receive.denyCurrentBranch ignore # Don't even bother warning

Upvotes: 8

Related Questions