Skip to content

Instantly share code, notes, and snippets.

@skopp
Created May 14, 2013 18:33
Show Gist options
  • Select an option

  • Save skopp/5578308 to your computer and use it in GitHub Desktop.

Select an option

Save skopp/5578308 to your computer and use it in GitHub Desktop.
git bare vs non-bare repos
!!!
%html{:lang => "en"}
%head
%title Git bare vs. non-bare repositories
%meta{:charset => "utf-8"}/
%meta{:content => "git, mercurial, push, pull, revision control, repository, repositories, cvs, remote repository", :name => "keywords"}/
%meta{:content => "The Git revision control system has something called a bare and a non-bare repository. This article deals with the issue and also compares the Git design to the design of Mercurial and Bazaar.", :name => "description"}/
%link{:href => "/themes/bitflop/stylesheet.css", :media => "screen", :rel => "stylesheet", :type => "text/css"}/
%link{:href => "/rss.php", :rel => "alternate", :type => "application/rss+xml"}/
%body{:onload => "sh_highlightDocument();"}
.maindiv
.top
.menucontainer
.menu
.menuitems
%a.menuitem_home{:href => "http://www.bitflop.com/document/16"} Home
|
%a.menuitem{:href => "http://www.bitflop.com/category/articles"} Articles
|
%a.menuitem{:href => "http://www.bitflop.com/category/tutorials"} Tutorials
|
%a.menuitem{:href => "http://www.bitflop.com/document/30"} About me
|
%a.menuitem{:href => "http://www.bitflop.com/document/32"} Links
%div{:style => "width:728px;margin:20px auto 50px auto;"}
.page
%h1 Git bare vs. non-bare repositories
.posted_by Posted by: Kim N. Lesmer on 09.10.2010
%div
#abstract
The Git revision control system has something called a "bare" and a "non-bare" repository. This article deals with the issue and also compares the Git design to the design of Mercurial and Bazaar.
%br/
= succeed "NB!" do
%br/
The article isn't relevant to Git prior to version 1.7.0.
%p The other day I was working with Git and I got the following error after having tried to push some changes back to a remote repository that I had created.
%pre
:preserve
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error:
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error:
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
%p I didn't understand this error and being used to how Mercurial works, this didn't make any sense, so I did some diggin.
%p In the ideal world of distributed revision control there is no central repository. People just pull from whomever they want changes from and no pushes exist. That is actually how the Linux Kernel is being developed.
%p In the real world a central repository with push access is sometimes necessary and all the different distributed revision control systems allows this, but the way they deal with a push are very different.
%p In a distributed revision control system you work with a local repository that contains both the working tree and the revision history.
%p In Git you can create such a repository with the following command:
%pre
:preserve
$ mkdir my_repo
$ cd my_repo
$ git init
%p In Bazaar it is done the same way:
%pre
:preserve
$ mkdir my_repo
$ cd my_repo
$ bzr init
%p In Mercurial a repository is created in almost the same way the only difference is that the init command creates the directory if it doesn't exist:
%pre
:preserve
$ hg init my_repo
%p In all three example the working tree resides in the directory itself and the revision history and system files resides in a hidden sub-directory. People then normally pull changes from eachother.
%p
In Git the hidden sub-directory is called
= succeed "," do
%i .git
in Bazaar it is called
= succeed "," do
%i .bzr
and in Mercurial it is called
= succeed "." do
%i .hg
%p In Mercurial and Bazaar when you initialize a repository it can serve as a remote repository by default, and anyone with write access to the repository can push changes into it.
%p In Git that's not possible unless the repository is initialized as a "bare" repository.
%p
A "bare" repository in Git just contains the version control information and no working files (no tree) and it doesn't contain the special
%i .git
sub-directory. Instead, it contains all the contents of the
%i .git
sub-directory directly in the main directory itself.
%p A "non-bare" repository in Git is the same as the normal repository in Mercurial and Bazaar. It has a bunch of working files (the tree), and a hidden directory containing the version control information.
%p In Git (from version 1.7.0 and above) the repository has to be "bare" (no working files) in order to accept a push.
%p From a technical point of view you can (in theory) push and pull between repositories whether they are "bare" or not. Git has an index, which basically tells it what the head of the current branch looks like. If you push to a "non-bare" repository, Git will look at the working files, compare them to the index, and see that they differ - so it will think that the working files have changed.
%p "bare" repositories exist in Git as a way of having a central (mainly remote) repository that a number of people can push to. If you want to transfer changes from a "non-bare" repository to another, the correct way is to pull from the destination rather than push from the target.
%p In Git you should only use a "bare" repository to clone and pull from, and push to. It doesn't have a checked out tree, so it just does what the "server" notionally does in a centralized VCS - records commits, branches, etc when you push to it, and gives you the latest versions when you clone or pull from it.
%p In Mercurial any repository can serve as a remote repository as mentioned, but push changes only affects the version control and not the working tree unless someone physically access the remote repository (making it local from his or hers perspective) and updates the tree manually.
%p So in Git a push isn't possible unless the repository is "bare" (no working files) and in Mercurial a push is only affecting the version control (not any working files).
%p In Bazaar on the other hand any repository can serve as a remote repository, and any push changes also affects the working tree.
%p From a distributed technical point of view IMHO the Bazaar system is poorly designed and the way both Git and Mercurial addresses the issue is much safer.
%p In Git and Mercurial a repository with a working tree is expected to contain files that someone is working on. It isn't looked upon as a remote repository, but as a local distributed repository no matter where it resides physically. And this makes perfect sense from a distributed point of view.
%p In Mercurial if an empty repository is cloned and files are added and then pushed back into the remote repository then the remote repository will contain no working files (no tree) until and unless someone updates the repository manually.
%p If you want a Git repository to function like a remote backup, you have to create the repository as a "bare" repository. Meaning: Don't insist on having a working tree on your remote backup copy - only use it as a backup. It is not meant to contain any working files and it will not contain any.
%p A quick rule of thumb is to never push into a repository that has a work tree attached to it, until you REALLY know what you are doing no matter what distributed revision control system you are using.
%p As long as you don't need to physically work on the remote repository then having a working tree remotely doesn't make sense anyway and you don't need it. And if you do need to work on the remote repository then pull changes in rather than accept pushes.
%p Any repository that someone is working on is not something that should receive changes without their approval since such changes might create problems.
%p If you want to have Git working as a remote backup repository, you have to create the remote repository as a bare repository:
%pre
:preserve
$ mkdir my_remote_backup_repo
$ cd my_remote_backup_repo
$ git --bare init
%p Now you can clone that remote repository and push files back into it, but you wont have a working tree in the remote location.
%p Question: How do I turn an existing "non-bare" repository into a "bare" repository in Git (taken from the Git wiki)?
%p Answer: A safe method is to let Git handle all the internal settings for you by doing something like this:
%pre
:preserve
$ git clone --bare -l non_bare_repo new_bare_repo
%p Question: How do I do the opposite? (Turn an existing "bare" repository into a "non-bare" repository in Git)?
%p Answer: You just clone it and delete the original.
%p
See
%a{:href => "http://sitaramc.github.com/concepts/bare.html"} http://sitaramc.github.com/concepts/bare.html
for futher information on Git bare and non-bare repositories.
%p
Linus Thorvalds gives a great talk about
= succeed "." do
%a{:href => "http://www.youtube.com/watch?v=4XpnKHJAok8"} Git and distributed revision control on Google Talk
%p
%i If you have any comments or corrections feel free to email them to me.
%br/
%br/
%head
%title
all about "bare" repos -- what, why, and how to fix a non-bare push
:css
body { background: #fff; margin-left: 40px; font-size: 0.9em; font-family: sans-serif; max-width: 800px; }
h1 { background: #ffb; margin-left: -30px; border-top: 5px solid #ccc; }
h2 { background: #ffb; margin-left: -20px; border-top: 3px solid #ddd; }
h3 { background: #ffb; margin-left: -10px; }
h4 { background: #ffb; }
code { font-size: 1.1em; background: #ddf; }
pre { margin-left: 2em; background: #ddf; }
pre code { font-size: 1.1em; background: #ddf; }
%base{:href => "http://sitaramc.github.com/"}/
%p{:style => "text-align:center"}
%a{:href => "master-toc.html"} master TOC
|
%a{:href => "master-toc.html#concepts/bare"} chapter TOC
|
%a{:href => "license.html"} license
%p
%a{:name => "concepts_bare_all_about_bare_repos_what_why_and_how_to_fix_a_non_bare_push_"}
%h1 all about "bare" repos -- what, why, and how to fix a non-bare push
%p
%center
%strong
%em Update for git 1.7.0 and above
%blockquote
%p
As of git 1.7.0, the default value for
%code receive.denyCurrentBranch
has
changed from "warn" to "refuse". As a result, on 1.7.0 and above, by
default, you will not get into this sort of trouble. However, this
document is still useful as a detailed explanation of what precisely this
problem is. Just pretend, as you read along, that you're on a pre-1.7.0
system or that someone set
%code receive.denyCurrentBranch
to "warn", "ignore"
or 'false'.
%p
%a{:name => "concepts_bare_what_is_a_bare_repo__"}
%h2 what is a bare repo?
%p
A bare repository is a concept that is sort of unique to a Distributed VCS
like git (and, I presume, other such DVCSs like Hg/Bzr/etc also).
%p A normal git repository is a directory that contains
%ul
%li project directories and files (the "working tree" mentioned above)
%li
a single directory called
%code .git
containing all of git's
administrative and control files; we'll call it the
%strong magic
directory
because git can't do any magic without it :-)
%p
When you do a
%code git status
inside such a directory, git looks inside the
"magic" directory, compares your current working tree with the "current
branch" as recorded in the magic directory, and tells you what files have
changed, etc etc.
%p
A "bare" repo, as the git
%a{:href => "http://www.kernel.org/pub/software/scm/git/docs/gitglossary.html"} glossary
says, is a repository that does not contain a "working tree" at all. It
doesn't contain the special
%code .git
sub-directory either; instead, it
contains all the contents of the
%code .git
subdirectory right in the main
directory itself.
%p
%a{:name => "concepts_bare_yeah_yeah_but_why_do_I_need_a_bare_repo__"}
%h2
yeah yeah, but
%strong why
do I need a bare repo?
%p ok; demo time...
%p
Let's try creating a small repo, adding a file, committing, and checking
%code> git status
\:
%pre
%code
:preserve
mkdir a; cd a; git init
echo hi > a;git add a; git commit -m a
git status
%p This should respond
%pre
%code
:preserve
# On branch master
# nothing to commit (working directory clean)
%p
So far so good. Now someone clones our repository, adds a new file, commits,
and pushes his changes back to our repository:
%pre
%code
:preserve
cd ..;git clone a b
cd b; echo there >> b; git add b; git commit -m b
git push
%p
The
%code git push
above sends your new commits to the "origin" repository. More
specifically, it updates the "magic" directory on repo "a" with this new
commit.
%p
Now you go back to the main repo and check
%code git status
%pre
%code
:preserve
cd ../a
git status
%p which responds
%pre
%code
:preserve
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: b
%p
Whoa! What happened here? We
%strong added
a file called
%code b
in the cloned
repository and pushed to the "origin". But your origin now claims you
%strong deleted
that file...?
%p
To understand this, you need to realise that
= succeed ";" do
%strong
the "magic" directory is always
assumed to be correct
it is the "standard" against which your working tree
is compared to determine what changes you made in your working tree.
%p
So when you asked for a status, git first looked inside the magic directory.
The magic directory said you should have two files, "a" and "b", but your work
tree has only file "a". So
%code git status
concludes that you have deleted
the file "b"!
%p
In other words, when someone changes the "magic" directory
= succeed "," do
%strong
behind your
back
your locally checked out copy (your working tree) appears to have the
%strong opposite
changes made by you.
%p
All this confusion can (and
= succeed ")" do
%em should
be avoided by using a "bare"
repository to clone and pull from, and push to. It doesn't have a checked out
tree, so it just does what the "server" notionally does in a centralised VCS
\-- records commits, branches, etc when you push to it, and gives you the
latest versions when you clone or pull from it.
%p
%a{:name => "concepts_bare_how_do_I_fix_such_a_non_bare_push__"}
%h2 how do I fix such a non-bare push?
%p
If you did push into a non-bare repo, the first thing to do is make sure that
no one is working on that checked out work tree, and no local changes have
been made since the last checkout.
%p
Once you've made sure of that, you should log onto that machine, cd to that
repo, and do this:
%pre
%code
:preserve
git reset --hard HEAD
%p
If you had local changes on that work tree, or you are not sure if you had
any, then you have a bit of a challenge. There are ways to deal with it quite
nicely in git, but I haven't tried them yet. Ideas:
%ol
%li
%p
see if any of the HEADs in reflog match your current work tree exactly, in
which case you really do NOT have any local changes, and you can safely do
that reset. Use
%code git reflog show your-branch-name
to find the topmost
commit after the most recent push or set of pushes (in case there were
more than one) and compare your work tree against that.
%p Let's say your reflog output looks like this:
%pre
%code
:preserve
$ git reflog show master
fcbb5a7 master@{0}: push
e41b4ce master@{1}: push
6ef3360 master@{2}: commit: blah blah more blah
a48b324 master@{3}: commit: blah blah blah
48e4b98 master@{4}: commit (initial): first commit
%p
As you can see, there were two "behind your back" pushes, and it is very
likely that master@{2} is the "base" for your current work tree. So you
compare against that:
%pre
%code
:preserve
$ git diff --stat --exit-code master@{2}
%p
If there are no differences, you're probably OK to do that
= succeed "." do
%code
git reset
\--hard
If you're really paranoid, you'll check to make sure that
master@{2} is an ancestor of HEAD:
%pre
%code
:preserve
git rev-list HEAD..master@{2} # should be empty
%li
%p
anyway, if the diff above showed differences, you have a merge of some
sort coming up. Here's what you do:
%ul
%li
%p make a temporary branch off that same commit
%pre
%code
:preserve
git checkout -b temp master@{2}
%li
%p now you have two alternatives:
%ul
%li
%p
The safe way: commit those changes. If the work is not complete,
complete it -- you have all the time in the world now that you have
created a temp branch, and any number of pushes can now happen on
'master' without affecting you, since it isn't the checked out branch.
%li
%p
The quicker way: stash those changes using
= succeed "." do
%code git stash
I am 99% sure
stashing will work as well here, but would appreciate hearing from any
git.experts reading this (email me at [email protected])
%li
%p
now checkout master and either merge temp or
%code git stash pop
depending
on which option you chose above; it's just a normal branch now!
%p
%a{:name => "concepts_bare_how_do_I_prevent_the_problem_from_happening_again__"}
%h2 how do I prevent the problem from happening again?
%p The best option is upgrade to git 1.7.0 or later!
%p
If you cannot upgrade, and if you or others will continue to push into this
repo, you have to make it a bare repo.
%p
To make it a "real" bare repo, just delete all the files except
= succeed "," do
%code .git
then
%code> mv .git/* .; rmdir .git
\. Finally, edit the file called
%code config
and change
%code bare = false
to
= succeed "." do
%code bare = true
%p
If you're not comfortable playing with git at that level, there is another
alternative. You may have guessed by now that you can make a non-bare repo
look like a bare repo for push purposes by creating and checking out a branch
that your users will not push to. The entire repo does not have to be bare,
really...
%p
So just run
%code git checkout -b DUMMY_BRANCH_DONT_USE
(or some such name) and
keep this branch checked out forever in this repo. This is a cheap way to
make a non-bare repo into a bare repo for all practical purposes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment