diff --git a/contrib/adc/able b/contrib/adc/able
index 1b73819..b327815 100755
--- a/contrib/adc/able
+++ b/contrib/adc/able
@@ -18,6 +18,7 @@ do
locs="$locs $HOME"
;;
* )
+ loc="$GL_REPO_BASE_ABS/$1.git"
[ -d $loc ] && locs="$locs $GL_REPO_BASE_ABS/$1.git"
[ -d $loc ] || echo "ignoring $1..."
;;
@@ -25,6 +26,8 @@ do
shift
done
+[[ -z "$locs" ]] && die "give me '@all' or some reponame"
+
case $op in
en|enable )
for l in $locs
@@ -35,9 +38,13 @@ case $op in
dis|disable )
# bashism
read msg <<<$(cat)
+ echo disabling following locations with message:
+ echo $msg
+ echo
for l in $locs
do
echo $msg > $l/.gitolite.down
+ echo $l
done
;;
* )
diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd
index 8ae1530..1f86bd6 100644
--- a/doc/3-faq-tips-etc.mkd
+++ b/doc/3-faq-tips-etc.mkd
@@ -28,6 +28,7 @@ In this document:
* "personal" branches
* custom hooks and custom git config
* bypassing gitolite
+ * disabling write access to take backups
* INconvenience features
* deleting a repo
* helping with gitweb
@@ -540,6 +541,33 @@ is NOT available if you bypass gitolite. Mucking with that repo in this
manner is strongly discouraged, as in "are you feeling lucky today?". Use
`gl-dont-panic` if you need to do some server-side surgery for that repo.
+
+
+##### disabling write access to take backups
+
+If you want to take normal, OS-level, backups of the system, you might want
+git to be quiescent during that time, so that the backup is clean. The best
+way to do this is to disable write-access to the server for the duration of
+the backup.
+
+Here's how:
+
+ cd $HOME # if running as "git" user, else "cd ~git" or whatever
+ echo writes disabled during backup window > .gitolite.down
+
+ # << RUN YOUR BACKUP COMMAND(s) HERE >>
+
+ rm .gitolite.down
+
+I leave it to you to
+
+ * make sure that if the backup script fails, the `.gitolite.down` file is
+ still removed (or not; maybe your policy is that if the backup failed, no
+ further writes are allowed. Whatever...)
+ * if you're extremely paranoid (even I wouldn't worry about this!) make sure
+ that no push is *in progress* by checking for any `git-receive-pack`
+ processes in a `ps` output.
+
#### INconvenience features
diff --git a/doc/admin-defined-commands.mkd b/doc/admin-defined-commands.mkd
index 1ee04ce..32fe267 100644
--- a/doc/admin-defined-commands.mkd
+++ b/doc/admin-defined-commands.mkd
@@ -189,26 +189,9 @@ You can also do this for one or more individual repos; in place of `@all`,
just use a space separated list of reponames (exactly as they would appear in
the config file). Wildcards are not supported; patches welcome ;-)
-**NOTE: This needs a specific secondary update hook**. Creating a secondary
-update hook is described in the sections on "custom hooks" and "hook chaining"
-in doc/2. You need code like this in `update.secondary` (don't forget to
-`chmod +x` the file):
+Note: please see [this][diswr] for more on this.
- #!/bin/bash
-
- for f in $HOME/.gitolite.down $PWD/.gitolite.down
- do
- if [ -f $f ]
- then
- echo >&2
- echo '*** ABORT ***' >&2
- echo >&2
- cat $f >&2
- exit 1
- fi
- done
-
- exit 0
+[diswr]: http://github.com/sitaramc/gitolite/blob/pu/doc/3-faq-tips-etc.mkd#_disabling_write_access_to_take_backups
diff --git a/src/gitolite.pm b/src/gitolite.pm
index 8b16641..29fb26c 100644
--- a/src/gitolite.pm
+++ b/src/gitolite.pm
@@ -787,6 +787,16 @@ sub can_read {
);
}
+# helper to manage "disabling" a repo or the whole site for "W" access
+sub check_repo_write_enabled {
+ my ($repo) = shift;
+ for my $d ("$ENV{HOME}/.gitolite.down", "$ENV{GL_REPO_BASE_ABS}/$repo.git/.gitolite.down") {
+ next unless -f $d;
+ die $ABRT . `cat $d` if -s $d;
+ die $ABRT . "writes are currently disabled\n";
+ }
+}
+
# ----------------------------------------------------------------------------
# setup the ~/.ssh/authorized_keys file
# ----------------------------------------------------------------------------
diff --git a/src/gl-auth-command b/src/gl-auth-command
index 0db6862..46cea7d 100755
--- a/src/gl-auth-command
+++ b/src/gl-auth-command
@@ -246,6 +246,9 @@ my $aa = ($verb =~ $R_COMMANDS ? 'R' : 'W');
die "$aa access for $repo DENIED to $user
(Or there may be no repository at the given path. Did you spell it correctly?)\n" unless $perm =~ /$aa/;
+# check if repo is write-enabled
+&check_repo_write_enabled($repo) if $aa eq 'W';
+
# ----------------------------------------------------------------------------
# over to git now
# ----------------------------------------------------------------------------
diff --git a/t/t64-write-able b/t/t64-write-able
new file mode 100644
index 0000000..528a4af
--- /dev/null
+++ b/t/t64-write-able
@@ -0,0 +1,154 @@
+# vim: syn=sh:
+for bc in 0 1
+do
+ cd $TESTDIR
+ $TESTDIR/rollback || die "rollback failed"
+ editrc GL_BIG_CONFIG $bc
+ editrc GL_WILDREPOS 1
+
+ rm -rf /tmp/glt-adc
+ mkdir /tmp/glt-adc || die "mkdir /tmp/glt-adc failed"
+ cp ../contrib/adc/* /tmp/glt-adc
+ echo "\$GL_ADC_PATH = '/tmp/glt-adc';" | addrc
+ runremote rm -f .gitolite.down
+
+ # ----------
+
+ name "INTERNAL"
+ echo "
+ @leads = u1 u2
+ @devs = u1 u2 u3 u4
+
+ repo foo
+ RW+ = u1
+
+ @gbar = bar/CREATOR/..*
+ repo @gbar
+ C = @leads
+ RW+ = @leads
+ RW = @devs
+ " | ugc
+ expect_push_ok "master -> master"
+
+ name "u1 push foo"
+ cd ~/td
+ rm -rf foo
+ runlocal git clone u1:foo
+ expect "warning: You appear to have cloned an empty repository."
+ cd foo
+ mdc; mdc
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+
+ name "u2 create and push bar/u2/r1"
+ cd ~/td
+ runlocal git clone u2:bar/u2/r1
+ expect "Initialized empty Git repository in /home/gitolite-test/repositories/bar/u2/r1.git/"
+ expect "warning: You appear to have cloned an empty repository."
+ cd r1
+ mdc; mdc
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+
+ name "disable entire site"
+ runremote ls -al .gitolite.down
+ expect "ls: cannot access .gitolite.down: No such file or directory"
+ (echo first line; echo second line) | runlocal ssh gitolite able dis
+ expect "give me '@all' or some reponame"
+ (echo first line; echo second line) | runlocal ssh gitolite able dis @all
+ expect "disabling following locations with message:"
+ expect "first line second line"
+ expect "/home/gitolite-test"
+ runremote ls -al .gitolite.down
+ expect "^.rw------- 1 gitolite-test gitolite-test .. ... .. ..:.. .gitolite.down"
+
+ name "u1 push foo fail"
+ cd ~/td/foo
+ mdc; mdc
+ runlocal git push origin master
+ expect ABORTING
+ expect "first line second line"
+ expect "fatal: The remote end hung up unexpectedly"
+
+ name "u2 create and push bar/u2/r1 fail"
+ cd ~/td/r1
+ mdc; mdc
+ runlocal git push origin master
+ expect ABORTING
+ expect "first line second line"
+ expect "fatal: The remote end hung up unexpectedly"
+
+ name "enable entire site"
+ runlocal ssh gitolite able en
+ expect "give me '@all' or some reponame"
+ runlocal ssh gitolite able en @all
+ expect "removed ./home/gitolite-test/.gitolite.down."
+ runremote ls -al .gitolite.down
+ expect "ls: cannot access .gitolite.down: No such file or directory"
+
+ name "u1 push foo"
+ cd ~/td/foo
+ mdc; mdc
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+
+ name "u2 create and push bar/u2/r1"
+ cd ~/td/r1
+ mdc; mdc
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+
+ name "disable foo"
+ runlocal ssh u1 able dis foo
+ expect "just .what. are you trying to pull, young man"
+ echo foo down|runlocal ssh gitolite able dis foo
+ expect "disabling following locations with message:"
+ expect "foo down"
+ expect "/home/gitolite-test/repositories/foo.git"
+ runremote ls -al /home/gitolite-test/repositories/foo.git/.gitolite.down
+ expect ".rw------- 1 gitolite-test gitolite-test . ... .. ..:.. /home/gitolite-test/repositories/foo.git/.gitolite.down"
+
+ name "u1 push foo fail"
+ cd ~/td/foo
+ mdc; mdc
+ runlocal git push origin master
+ expect ABORTING
+ expect "foo down"
+ expect "fatal: The remote end hung up unexpectedly"
+
+ name "u2 create and push bar/u2/r1"
+ cd ~/td/r1
+ mdc; mdc
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+
+ name "enable foo, disable bar/u2/r1"
+ runlocal ssh u1 able en foo
+ expect "just .what. are you trying to pull, young man"
+ runlocal ssh gitolite able en foo
+ expect "removed ./home/gitolite-test/repositories/foo.git/.gitolite.down."
+ echo bar/u2/r1 down | runlocal ssh u1 able dis foo
+ expect "just .what. are you trying to pull, young man"
+ echo bar/u2/r1 down | runlocal ssh gitolite able dis bar/u2/r1
+ expect "disabling following locations with message:"
+ expect "bar/u2/r1 down"
+ expect "/home/gitolite-test/repositories/bar/u2/r1.git"
+ runremote ls -al /home/gitolite-test/repositories/bar/u2/r1.git/.gitolite.down
+ expect ".rw------- 1 gitolite-test gitolite-test .. ... .. ..:.. /home/gitolite-test/repositories/bar/u2/r1.git/.gitolite.down"
+
+ name "u1 push foo"
+ cd ~/td/foo
+ mdc; mdc
+ runlocal git push origin master
+ expect_push_ok "master -> master"
+
+ name "u2 create and push bar/u2/r1i fail"
+ cd ~/td/r1
+ mdc; mdc
+ runlocal git push origin master
+ expect ABORTING
+ expect "bar/u2/r1 down"
+ expect "fatal: The remote end hung up unexpectedly"
+
+ name "INTERNAL"
+done