Merge branch 'je/hooks'
* je/hooks: Added example hook script to save/restore permissions/ownership. Add post-merge hook, related documentation, and tests.maint
						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
 | 
			
		||||
-----------
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 () {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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