Merge branch 'je/hooks'
* je/hooks: Added example hook script to save/restore permissions/ownership. Add post-merge hook, related documentation, and tests.
This commit is contained in:
commit
91d4b2ee81
@ -87,6 +87,19 @@ parameter, and is invoked after a commit is made.
|
||||
This hook is meant primarily for notification, and cannot affect
|
||||
the outcome of `git-commit`.
|
||||
|
||||
post-merge
|
||||
-----------
|
||||
|
||||
This hook is invoked by `git-merge`, which happens when a `git pull`
|
||||
is done on a local repository. The hook takes a single parameter, a status
|
||||
flag specifying whether or not the merge being done was a squash merge.
|
||||
This hook cannot affect the outcome of `git-merge`.
|
||||
|
||||
This hook can be used in conjunction with a corresponding pre-commit hook to
|
||||
save and restore any form of metadata associated with the working tree
|
||||
(eg: permissions/ownership, ACLS, etc). See contrib/hooks/setgitperms.perl
|
||||
for an example of how to do this.
|
||||
|
||||
[[pre-receive]]
|
||||
pre-receive
|
||||
-----------
|
||||
|
213
contrib/hooks/setgitperms.perl
Normal file
213
contrib/hooks/setgitperms.perl
Normal file
@ -0,0 +1,213 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Copyright (c) 2006 Josh England
|
||||
#
|
||||
# This script can be used to save/restore full permissions and ownership data
|
||||
# within a git working tree.
|
||||
#
|
||||
# To save permissions/ownership data, place this script in your .git/hooks
|
||||
# directory and enable a `pre-commit` hook with the following lines:
|
||||
# #!/bin/sh
|
||||
# . git-sh-setup
|
||||
# $GIT_DIR/hooks/setgitperms.perl -r
|
||||
#
|
||||
# To restore permissions/ownership data, place this script in your .git/hooks
|
||||
# directory and enable a `post-merge` hook with the following lines:
|
||||
# #!/bin/sh
|
||||
# . git-sh-setup
|
||||
# $GIT_DIR/hooks/setgitperms.perl -w
|
||||
#
|
||||
use strict;
|
||||
use Getopt::Long;
|
||||
use File::Find;
|
||||
use File::Basename;
|
||||
|
||||
my $usage =
|
||||
"Usage: setgitperms.perl [OPTION]... <--read|--write>
|
||||
This program uses a file `.gitmeta` to store/restore permissions and uid/gid
|
||||
info for all files/dirs tracked by git in the repository.
|
||||
|
||||
---------------------------------Read Mode-------------------------------------
|
||||
-r, --read Reads perms/etc from working dir into a .gitmeta file
|
||||
-s, --stdout Output to stdout instead of .gitmeta
|
||||
-d, --diff Show unified diff of perms file (XOR with --stdout)
|
||||
|
||||
---------------------------------Write Mode------------------------------------
|
||||
-w, --write Modify perms/etc in working dir to match the .gitmeta file
|
||||
-v, --verbose Be verbose
|
||||
|
||||
\n";
|
||||
|
||||
my ($stdout, $showdiff, $verbose, $read_mode, $write_mode);
|
||||
|
||||
if ((@ARGV < 0) || !GetOptions(
|
||||
"stdout", \$stdout,
|
||||
"diff", \$showdiff,
|
||||
"read", \$read_mode,
|
||||
"write", \$write_mode,
|
||||
"verbose", \$verbose,
|
||||
)) { die $usage; }
|
||||
die $usage unless ($read_mode xor $write_mode);
|
||||
|
||||
my $topdir = `git-rev-parse --show-cdup` or die "\n"; chomp $topdir;
|
||||
my $gitdir = $topdir . '.git';
|
||||
my $gitmeta = $topdir . '.gitmeta';
|
||||
|
||||
if ($write_mode) {
|
||||
# Update the working dir permissions/ownership based on data from .gitmeta
|
||||
open (IN, "<$gitmeta") or die "Could not open $gitmeta for reading: $!\n";
|
||||
while (defined ($_ = <IN>)) {
|
||||
chomp;
|
||||
if (/^(.*) mode=(\S+)\s+uid=(\d+)\s+gid=(\d+)/) {
|
||||
# Compare recorded perms to actual perms in the working dir
|
||||
my ($path, $mode, $uid, $gid) = ($1, $2, $3, $4);
|
||||
my $fullpath = $topdir . $path;
|
||||
my (undef,undef,$wmode,undef,$wuid,$wgid) = lstat($fullpath);
|
||||
$wmode = sprintf "%04o", $wmode & 07777;
|
||||
if ($mode ne $wmode) {
|
||||
$verbose && print "Updating permissions on $path: old=$wmode, new=$mode\n";
|
||||
chmod oct($mode), $fullpath;
|
||||
}
|
||||
if ($uid != $wuid || $gid != $wgid) {
|
||||
if ($verbose) {
|
||||
# Print out user/group names instead of uid/gid
|
||||
my $pwname = getpwuid($uid);
|
||||
my $grpname = getgrgid($gid);
|
||||
my $wpwname = getpwuid($wuid);
|
||||
my $wgrpname = getgrgid($wgid);
|
||||
$pwname = $uid if !defined $pwname;
|
||||
$grpname = $gid if !defined $grpname;
|
||||
$wpwname = $wuid if !defined $wpwname;
|
||||
$wgrpname = $wgid if !defined $wgrpname;
|
||||
|
||||
print "Updating uid/gid on $path: old=$wpwname/$wgrpname, new=$pwname/$grpname\n";
|
||||
}
|
||||
chown $uid, $gid, $fullpath;
|
||||
}
|
||||
}
|
||||
else {
|
||||
warn "Invalid input format in $gitmeta:\n\t$_\n";
|
||||
}
|
||||
}
|
||||
close IN;
|
||||
}
|
||||
elsif ($read_mode) {
|
||||
# Handle merge conflicts in the .gitperms file
|
||||
if (-e "$gitdir/MERGE_MSG") {
|
||||
if (`grep ====== $gitmeta`) {
|
||||
# Conflict not resolved -- abort the commit
|
||||
print "PERMISSIONS/OWNERSHIP CONFLICT\n";
|
||||
print " Resolve the conflict in the $gitmeta file and then run\n";
|
||||
print " `.git/hooks/setgitperms.perl --write` to reconcile.\n";
|
||||
exit 1;
|
||||
}
|
||||
elsif (`grep $gitmeta $gitdir/MERGE_MSG`) {
|
||||
# A conflict in .gitmeta has been manually resolved. Verify that
|
||||
# the working dir perms matches the current .gitmeta perms for
|
||||
# each file/dir that conflicted.
|
||||
# This is here because a `setgitperms.perl --write` was not
|
||||
# performed due to a merge conflict, so permissions/ownership
|
||||
# may not be consistent with the manually merged .gitmeta file.
|
||||
my @conflict_diff = `git show \$(cat $gitdir/MERGE_HEAD)`;
|
||||
my @conflict_files;
|
||||
my $metadiff = 0;
|
||||
|
||||
# Build a list of files that conflicted from the .gitmeta diff
|
||||
foreach my $line (@conflict_diff) {
|
||||
if ($line =~ m|^diff --git a/$gitmeta b/$gitmeta|) {
|
||||
$metadiff = 1;
|
||||
}
|
||||
elsif ($line =~ /^diff --git/) {
|
||||
$metadiff = 0;
|
||||
}
|
||||
elsif ($metadiff && $line =~ /^\+(.*) mode=/) {
|
||||
push @conflict_files, $1;
|
||||
}
|
||||
}
|
||||
|
||||
# Verify that each conflict file now has permissions consistent
|
||||
# with the .gitmeta file
|
||||
foreach my $file (@conflict_files) {
|
||||
my $absfile = $topdir . $file;
|
||||
my $gm_entry = `grep "^$file mode=" $gitmeta`;
|
||||
if ($gm_entry =~ /mode=(\d+) uid=(\d+) gid=(\d+)/) {
|
||||
my ($gm_mode, $gm_uid, $gm_gid) = ($1, $2, $3);
|
||||
my (undef,undef,$mode,undef,$uid,$gid) = lstat("$absfile");
|
||||
$mode = sprintf("%04o", $mode & 07777);
|
||||
if (($gm_mode ne $mode) || ($gm_uid != $uid)
|
||||
|| ($gm_gid != $gid)) {
|
||||
print "PERMISSIONS/OWNERSHIP CONFLICT\n";
|
||||
print " Mismatch found for file: $file\n";
|
||||
print " Run `.git/hooks/setgitperms.perl --write` to reconcile.\n";
|
||||
exit 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print "Warning! Permissions/ownership no longer being tracked for file: $file\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# No merge conflicts -- write out perms/ownership data to .gitmeta file
|
||||
unless ($stdout) {
|
||||
open (OUT, ">$gitmeta.tmp") or die "Could not open $gitmeta.tmp for writing: $!\n";
|
||||
}
|
||||
|
||||
my @files = `git-ls-files`;
|
||||
my %dirs;
|
||||
|
||||
foreach my $path (@files) {
|
||||
chomp $path;
|
||||
# We have to manually add stats for parent directories
|
||||
my $parent = dirname($path);
|
||||
while (!exists $dirs{$parent}) {
|
||||
$dirs{$parent} = 1;
|
||||
next if $parent eq '.';
|
||||
printstats($parent);
|
||||
$parent = dirname($parent);
|
||||
}
|
||||
# Now the git-tracked file
|
||||
printstats($path);
|
||||
}
|
||||
|
||||
# diff the temporary metadata file to see if anything has changed
|
||||
# If no metadata has changed, don't overwrite the real file
|
||||
# This is just so `git commit -a` doesn't try to commit a bogus update
|
||||
unless ($stdout) {
|
||||
if (! -e $gitmeta) {
|
||||
rename "$gitmeta.tmp", $gitmeta;
|
||||
}
|
||||
else {
|
||||
my $diff = `diff -U 0 $gitmeta $gitmeta.tmp`;
|
||||
if ($diff ne '') {
|
||||
rename "$gitmeta.tmp", $gitmeta;
|
||||
}
|
||||
else {
|
||||
unlink "$gitmeta.tmp";
|
||||
}
|
||||
if ($showdiff) {
|
||||
print $diff;
|
||||
}
|
||||
}
|
||||
close OUT;
|
||||
}
|
||||
# Make sure the .gitmeta file is tracked
|
||||
system("git add $gitmeta");
|
||||
}
|
||||
|
||||
|
||||
sub printstats {
|
||||
my $path = $_[0];
|
||||
$path =~ s/@/\@/g;
|
||||
my (undef,undef,$mode,undef,$uid,$gid) = lstat($path);
|
||||
$path =~ s/%/\%/g;
|
||||
if ($stdout) {
|
||||
print $path;
|
||||
printf " mode=%04o uid=$uid gid=$gid\n", $mode & 07777;
|
||||
}
|
||||
else {
|
||||
print OUT $path;
|
||||
printf OUT " mode=%04o uid=$uid gid=$gid\n", $mode & 07777;
|
||||
}
|
||||
}
|
13
git-merge.sh
13
git-merge.sh
@ -97,6 +97,19 @@ finish () {
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Run a post-merge hook
|
||||
if test -x "$GIT_DIR"/hooks/post-merge
|
||||
then
|
||||
case "$squash" in
|
||||
t)
|
||||
"$GIT_DIR"/hooks/post-merge 1
|
||||
;;
|
||||
'')
|
||||
"$GIT_DIR"/hooks/post-merge 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
merge_name () {
|
||||
|
56
t/t5402-post-merge-hook.sh
Executable file
56
t/t5402-post-merge-hook.sh
Executable file
@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2006 Josh England
|
||||
#
|
||||
|
||||
test_description='Test the post-merge hook.'
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success setup '
|
||||
echo Data for commit0. >a &&
|
||||
git update-index --add a &&
|
||||
tree0=$(git write-tree) &&
|
||||
commit0=$(echo setup | git commit-tree $tree0) &&
|
||||
echo Changed data for commit1. >a &&
|
||||
git update-index a &&
|
||||
tree1=$(git write-tree) &&
|
||||
commit1=$(echo modify | git commit-tree $tree1 -p $commit0) &&
|
||||
git update-ref refs/heads/master $commit0 &&
|
||||
git-clone ./. clone1 &&
|
||||
GIT_DIR=clone1/.git git update-index --add a &&
|
||||
git-clone ./. clone2 &&
|
||||
GIT_DIR=clone2/.git git update-index --add a
|
||||
'
|
||||
|
||||
for clone in 1 2; do
|
||||
cat >clone${clone}/.git/hooks/post-merge <<'EOF'
|
||||
#!/bin/sh
|
||||
echo $@ >> $GIT_DIR/post-merge.args
|
||||
EOF
|
||||
chmod u+x clone${clone}/.git/hooks/post-merge
|
||||
done
|
||||
|
||||
test_expect_failure 'post-merge does not run for up-to-date ' '
|
||||
GIT_DIR=clone1/.git git merge $commit0 &&
|
||||
test -e clone1/.git/post-merge.args
|
||||
'
|
||||
|
||||
test_expect_success 'post-merge runs as expected ' '
|
||||
GIT_DIR=clone1/.git git merge $commit1 &&
|
||||
test -e clone1/.git/post-merge.args
|
||||
'
|
||||
|
||||
test_expect_success 'post-merge from normal merge receives the right argument ' '
|
||||
grep 0 clone1/.git/post-merge.args
|
||||
'
|
||||
|
||||
test_expect_success 'post-merge from squash merge runs as expected ' '
|
||||
GIT_DIR=clone2/.git git merge --squash $commit1 &&
|
||||
test -e clone2/.git/post-merge.args
|
||||
'
|
||||
|
||||
test_expect_success 'post-merge from squash merge receives the right argument ' '
|
||||
grep 1 clone2/.git/post-merge.args
|
||||
'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user