lock binary files... (manually tested)
Remember that true locking is not possible in a DVCS; see doc/locking.mkd for details and limitations of what is offered here.
This commit is contained in:
parent
d623388c9f
commit
06d3398fb0
153
doc/locking.mkd
Normal file
153
doc/locking.mkd
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
# locking binary files
|
||||||
|
|
||||||
|
Locking is useful to make sure that binary files (office docs, images, ...)
|
||||||
|
don't get into a merge state. (<font color="gray">If you think it's not a big
|
||||||
|
deal, you have never manually merged independent changes to an ODT or
|
||||||
|
something!</font>)
|
||||||
|
|
||||||
|
When git is used in a truly distributed fashion, locking is impossible.
|
||||||
|
However, in most corporate setups, there is a single central server acting as
|
||||||
|
the canonical source of truth and collaboration point for all developers. In
|
||||||
|
this situation it should be possible to at least prevent commits from being
|
||||||
|
pushed that contains changes to files locked by someone else.
|
||||||
|
|
||||||
|
The two "lock" programs (one a command that a user uses, and one a VREF that
|
||||||
|
the admin adds to a repo's access rules) together achieve this.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
[[TOC]]
|
||||||
|
|
||||||
|
## problem description
|
||||||
|
|
||||||
|
Our users are alice, bob, and carol. Our repo is foo. It has some "odt"
|
||||||
|
files in the "doc/" directory. We want to make sure these odt files never get
|
||||||
|
into a "merge" situation.
|
||||||
|
|
||||||
|
## admin/setup
|
||||||
|
|
||||||
|
First, someone with shell access to the server must add 'lock' to the
|
||||||
|
"COMMANDS" list in the rc file.
|
||||||
|
|
||||||
|
Next, the gitolite.conf file should have something like this:
|
||||||
|
|
||||||
|
repo foo
|
||||||
|
<...other rules...>
|
||||||
|
- VREF/lock = @all
|
||||||
|
|
||||||
|
However, see below for the difference between "RW" and "RW+" from the point of
|
||||||
|
view of this feature and adjust permissions accordingly.
|
||||||
|
|
||||||
|
## user view
|
||||||
|
|
||||||
|
Here's a summary:
|
||||||
|
|
||||||
|
* Any user with "W" permissions to any branch in the repo can "lock" any
|
||||||
|
file. Once locked, no other user can push changes to that file, *in any
|
||||||
|
branch*, until it is unlocked.
|
||||||
|
* Any user with "+" permissions to any branch in the repo can "break" a lock
|
||||||
|
held by someone else if needed.
|
||||||
|
|
||||||
|
For best results, everyone on the team should:
|
||||||
|
|
||||||
|
* Run 'git pull' or eqvt, then lock the binary file(s) before editing them.
|
||||||
|
* Finish the editing task as quickly as possible, then commit, push, and
|
||||||
|
unlock the file(s) so others are not needlessly blocked.
|
||||||
|
* Understand that breaking a lock require additional, (out of band)
|
||||||
|
communication. It is upto the team's policies what that entails.
|
||||||
|
|
||||||
|
## detailed example
|
||||||
|
|
||||||
|
Alice declares her intent to work on "d1.odt":
|
||||||
|
|
||||||
|
$ git pull
|
||||||
|
$ ssh git@host lock -l foo doc/d1.odt
|
||||||
|
|
||||||
|
Similarly Bob starts on "d2.odt"
|
||||||
|
|
||||||
|
$ git pull
|
||||||
|
$ ssh git@host lock -l foo doc/d2.odt
|
||||||
|
|
||||||
|
Carol makes some changes to d2.odt (**without attempting to lock the file or
|
||||||
|
checking to see if it is already locked**) and pushes:
|
||||||
|
|
||||||
|
$ ooffice doc/d2.odt
|
||||||
|
$ git add doc/d2.odt
|
||||||
|
$ git commit -m 'added footnotes to d2 in klingon'
|
||||||
|
$ git push
|
||||||
|
<...normal push progress output...>
|
||||||
|
remote: FATAL: W VREF/lock testing carol DENIED by VREF/lock
|
||||||
|
remote: 'doc/d2.odt' locked by 'bob'
|
||||||
|
remote: error: hook declined to update refs/heads/master
|
||||||
|
To u2:testing
|
||||||
|
! [remote rejected] master -> master (hook declined)
|
||||||
|
error: failed to push some refs to 'carol:foo'
|
||||||
|
|
||||||
|
Carol backs out her changes, but saves them away for a "manual merge" later.
|
||||||
|
|
||||||
|
git reset HEAD^
|
||||||
|
git stash save 'klingon changes to d2.odt saved for possible manual merge later'
|
||||||
|
|
||||||
|
Note that this still represents wasted work in some sense, because Carol would
|
||||||
|
have to somehow re-apply the same changes to the new version of d2.odt after
|
||||||
|
pulling it down. **This is because she did not lock the file before making
|
||||||
|
changes on her local repo. Educating users in doing this is important if this
|
||||||
|
scheme is to help you.**
|
||||||
|
|
||||||
|
She now decides to work on "d1.odt". However, she has learned her lesson and
|
||||||
|
decides to follow the protocol described above:
|
||||||
|
|
||||||
|
$ git pull
|
||||||
|
$ ssh git@host lock -l foo doc/d1.odt
|
||||||
|
FATAL: 'doc/d1.odt' locked by 'alice' since Sun May 27 17:59:59 2012
|
||||||
|
|
||||||
|
Oh damn; can't work on that either.
|
||||||
|
|
||||||
|
Carol now decides to see what else there may be. Instead of checking each
|
||||||
|
file to see if she can lock it, she starts with a list of what is already
|
||||||
|
locked:
|
||||||
|
|
||||||
|
$ ssh git@host lock -ls foo
|
||||||
|
|
||||||
|
# locks held:
|
||||||
|
|
||||||
|
alice doc/d1.odt (Sun May 27 17:59:59 2012)
|
||||||
|
bob doc/d2.odt (Sun May 27 18:00:06 2012)
|
||||||
|
|
||||||
|
# locks broken:
|
||||||
|
|
||||||
|
Aha, looks like only d1 and d2 are locked. She picks d3.odt to work on. This
|
||||||
|
time, she starts by locking it:
|
||||||
|
|
||||||
|
$ ssh git@host lock -l foo doc/d3.odt
|
||||||
|
$ ooffice doc/d3.odt
|
||||||
|
<...etc...>
|
||||||
|
|
||||||
|
Meanwhile, in a parallel universe where d3.odt doesn't exist, and Alice has
|
||||||
|
gone on vacation while keeping d1.odt locked, Carol breaks the lock. Carol
|
||||||
|
can do this because she has RW+ permissions for the repository itself.
|
||||||
|
|
||||||
|
However, protocol in this team requires that she get email approval from the
|
||||||
|
team lead before doing this and that Alice be in CC in those emails, so she
|
||||||
|
does that first, and *then* she breaks the lock:
|
||||||
|
|
||||||
|
$ git pull
|
||||||
|
$ ssh git@host lock --break foo doc/d1.odt
|
||||||
|
|
||||||
|
She then locks d1.odt for herself:
|
||||||
|
|
||||||
|
$ ssh git@host lock -l foo doc/d1.odt
|
||||||
|
|
||||||
|
When Alice comes back, she can tell who broke her lock and when:
|
||||||
|
|
||||||
|
$ ssh git@host lock -ls foo
|
||||||
|
|
||||||
|
# locks held:
|
||||||
|
|
||||||
|
carol doc/d1.odt (Sun May 27 18:17:29 2012)
|
||||||
|
bob doc/d2.odt (Sun May 27 18:00:06 2012)
|
||||||
|
|
||||||
|
# locks broken:
|
||||||
|
|
||||||
|
carol doc/d1.odt (Sun May 27 18:17:03 2012) (locked by alice at Sun May 27 17:59:59 2012)
|
||||||
|
|
36
src/VREF/lock
Executable file
36
src/VREF/lock
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib $ENV{GL_LIBDIR};
|
||||||
|
use Gitolite::Common;
|
||||||
|
|
||||||
|
# gitolite VREF to lock and unlock (binary) files. Requires companion command
|
||||||
|
# 'lock' to be enabled; see doc/locking.mkd for details.
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# see gitolite docs for what the first 7 arguments mean
|
||||||
|
|
||||||
|
die "not meant to be run manually" unless $ARGV[6];
|
||||||
|
|
||||||
|
my $ff = "$ENV{GL_REPO_BASE}/$ENV{GL_REPO}.git/gl-locks";
|
||||||
|
exit 0 unless -f $ff;
|
||||||
|
|
||||||
|
our %locks;
|
||||||
|
my $t = slurp($ff);
|
||||||
|
eval $t;
|
||||||
|
_die "do '$ff' failed with '$@', contact your administrator" if $@;
|
||||||
|
|
||||||
|
my ( $oldtree, $newtree, $refex ) = @ARGV[ 3, 4, 6 ];
|
||||||
|
|
||||||
|
for my $file (`git diff --name-only $oldtree $newtree` ) {
|
||||||
|
chomp($file);
|
||||||
|
|
||||||
|
if ($locks{$file} and $locks{$file}{USER} ne $ENV{GL_USER}) {
|
||||||
|
print "$refex '$file' locked by '$locks{$file}{USER}'";
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
124
src/commands/lock
Executable file
124
src/commands/lock
Executable file
|
@ -0,0 +1,124 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Getopt::Long;
|
||||||
|
|
||||||
|
use lib $ENV{GL_LIBDIR};
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
# gitolite command to lock and unlock (binary) files and deal with locks.
|
||||||
|
|
||||||
|
=for usage
|
||||||
|
Usage: ssh git@host lock -l <repo> <file> # lock a file
|
||||||
|
ssh git@host lock -u <repo> <file> # unlock a file
|
||||||
|
ssh git@host lock --break <repo> <file> # break someone else's lock
|
||||||
|
ssh git@host lock -ls <repo> # list locked files for repo
|
||||||
|
|
||||||
|
See doc/locking.mkd for other details.
|
||||||
|
=cut
|
||||||
|
|
||||||
|
usage() if not @ARGV or $ARGV[0] eq '-h';
|
||||||
|
$ENV{GL_USER} or _die "GL_USER not set";
|
||||||
|
|
||||||
|
my $op = '';
|
||||||
|
$op = 'lock' if $ARGV[0] eq '-l';
|
||||||
|
$op = 'unlock' if $ARGV[0] eq '-u';
|
||||||
|
$op = 'break' if $ARGV[0] eq '--break';
|
||||||
|
$op = 'list' if $ARGV[0] eq '-ls';
|
||||||
|
usage() if not $op;
|
||||||
|
shift;
|
||||||
|
|
||||||
|
my $repo = shift;
|
||||||
|
_die "You are not authorised" if access( $repo, $ENV{GL_USER}, 'W', 'any' ) =~ /DENIED/;
|
||||||
|
_die "You are not authorised" if $op eq 'break' and access( $repo, $ENV{GL_USER}, '+', 'any' ) =~ /DENIED/;
|
||||||
|
|
||||||
|
my $file = shift || '';
|
||||||
|
usage() if $op ne 'list' and not $file;
|
||||||
|
|
||||||
|
_chdir( $ENV{GL_REPO_BASE} );
|
||||||
|
_chdir("$repo.git");
|
||||||
|
|
||||||
|
my $ff = "gl-locks";
|
||||||
|
|
||||||
|
if ( $op eq 'lock' ) {
|
||||||
|
f_lock( $repo, $file );
|
||||||
|
} elsif ( $op eq 'unlock' ) {
|
||||||
|
f_unlock( $repo, $file );
|
||||||
|
} elsif ( $op eq 'break' ) {
|
||||||
|
f_break( $repo, $file );
|
||||||
|
} elsif ( $op eq 'list' ) {
|
||||||
|
f_list($repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# everything below assumes we have already chdir'd to "$repo.git". Also, $ff
|
||||||
|
# is used as a global.
|
||||||
|
|
||||||
|
sub f_lock {
|
||||||
|
my ( $repo, $file ) = @_;
|
||||||
|
|
||||||
|
my %locks = get_locks();
|
||||||
|
_die "'$file' locked by '$locks{$file}{USER}' since " . localtime( $locks{$file}{TIME} ) if $locks{$file}{USER};
|
||||||
|
$locks{$file}{USER} = $ENV{GL_USER};
|
||||||
|
$locks{$file}{TIME} = time;
|
||||||
|
put_locks(%locks);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_unlock {
|
||||||
|
my ( $repo, $file ) = @_;
|
||||||
|
|
||||||
|
my %locks = get_locks();
|
||||||
|
_die "'$file' not locked by '$ENV{GL_USER}'" if ( $locks{$file} || '' ) ne $ENV{GL_USER};
|
||||||
|
delete $locks{$file};
|
||||||
|
put_locks(%locks);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_break {
|
||||||
|
my ( $repo, $file ) = @_;
|
||||||
|
|
||||||
|
my %locks = get_locks();
|
||||||
|
_die "'$file' was not locked" unless $locks{$file};
|
||||||
|
push @{ $locks{BREAKS} }, time . " $ENV{GL_USER} $locks{$file}{USER} $locks{$file}{TIME} $file";
|
||||||
|
delete $locks{$file};
|
||||||
|
put_locks(%locks);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_list {
|
||||||
|
my $repo = shift;
|
||||||
|
|
||||||
|
my %locks = get_locks();
|
||||||
|
print "\n# locks held:\n\n";
|
||||||
|
map { print "$locks{$_}{USER}\t$_\t(" . scalar(localtime($locks{$_}{TIME})) . ")\n" } grep { $_ ne 'BREAKS' } sort keys %locks;
|
||||||
|
print "\n# locks broken:\n\n";
|
||||||
|
for my $b ( @{ $locks{BREAKS} } ) {
|
||||||
|
my ( $when, $who, $whose, $how_old, $what ) = split ' ', $b;
|
||||||
|
print "$who\t$what\t(" . scalar( localtime($when) ) . ")\t(locked by $whose at " . scalar( localtime($how_old) ) . ")\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_locks {
|
||||||
|
if ( -f $ff ) {
|
||||||
|
our %locks;
|
||||||
|
|
||||||
|
my $t = slurp($ff);
|
||||||
|
eval $t;
|
||||||
|
_die "do '$ff' failed with '$@', contact your administrator" if $@;
|
||||||
|
|
||||||
|
return %locks;
|
||||||
|
}
|
||||||
|
return ();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub put_locks {
|
||||||
|
my %locks = @_;
|
||||||
|
|
||||||
|
use Data::Dumper;
|
||||||
|
$Data::Dumper::Indent = 1;
|
||||||
|
$Data::Dumper::Sortkeys = 1;
|
||||||
|
|
||||||
|
my $dumped_data = Data::Dumper->Dump( [ \%locks ], [qw(*locks)] );
|
||||||
|
_print( $ff, $dumped_data );
|
||||||
|
}
|
Loading…
Reference in a new issue