Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 6163365142f63ee6115ea53b2c426a372ec578e8 0 parents
@harleypig authored
90 build_metafile
@@ -0,0 +1,90 @@
+#! /usr/bin/perl
+
+eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
+ if 0; #$running_under_some_shell
+
+use strict;
+use warnings;
+#no warnings 'File::Find';
+
+use Data::Dumper ();
+use Fcntl ':mode';
+use File::Find ();
+use IPC::Run3::Simple;
+
+IPC::Run3::Simple::croak_on_err( 1 );
+IPC::Run3::Simple::chomp_out( 1 );
+IPC::Run3::Simple::default_stderr( \my $err );
+
+run3({ 'cmd' => [qw( git rev-parse --show-toplevel )], 'stdout' => \my $base_dir });
+
+chdir $base_dir
+ or die "Unable to change to git top level directory: $!\n";
+
+run3({ 'cmd' => [qw( git ls-files )], 'stdout' => \my @git_files });
+my %git_file; @git_file{ @git_files } = undef;
+
+my $metafile = shift
+ or die "Expecting file to save metadata to.\n";
+
+open my $METAFILE, '>', $metafile
+ or die "Unable to open $metafile for writing: $!\n";
+
+my %inode;
+
+File::Find::find({
+
+ 'wanted' => \&wanted,
+ 'no_chdir' => 1
+
+}, $base_dir );
+
+for my $inode ( keys %inode ) {
+
+ my $meta = $inode{ $inode }{ 'meta' };
+ my $names = join "\0", @{ $inode{ $inode }{ 'names' } };
+ printf $METAFILE "HL: %s\0%s\n", $meta, $names;
+
+}
+
+exit 0;
+
+
+sub wanted {
+
+ return if $File::Find::name eq $base_dir;
+
+ ( my $name = $File::Find::name ) =~ s{^$base_dir/}{};
+
+ return unless exists $git_file{ $name };
+
+ my ( $device, $inode, $mode, $nlink, $uid, $gid ) = stat( $File::Find::name );
+
+ if ( $nlink > 1 ) {
+
+ my $meta = sprintf "%d\0%d\0%d", $uid, $gid, $mode;
+
+ ( die sprintf "This shouldn't happen! $name is a hardlink and has different meta data ($meta) than %s\n", Data::Dumper::Dumper $inode{ $inode } )
+ if exists $inode{ $inode } && $meta ne $inode{ $inode }{ 'meta' };
+
+ $inode{ $inode }{ 'meta' } = $meta;
+ push @{ $inode{ $inode }{ 'names' } }, $name;
+ return;
+
+ }
+
+# warn "$File::Find::name has no dev\n" if ! $device;
+
+ # Don't traverse filesystems
+ if ( ! $device || $device != $File::Find::topdev ) {
+
+ $File::Find::prune = 1;
+ return;
+
+ }
+
+ my $link = S_ISLNK( $mode ) ? do { readlink $mode } : '';
+
+ printf $METAFILE "%d\0%d\0%d\0%s\0%s\n", $uid, $gid, $mode, $name, $link;
+
+}
3  post-checkout
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$(git rev-parse --show-toplevel)/.git/hooks/validate_metadata .metafile
13 post-commit
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Add calls to specific programs you wish to run in this hook ... don't add
+# actual code handling here.
+
+OLDPWD=$PWD
+
+cd $(git rev-parse --show-toplevel)
+.git/hooks/build_metafile .metafile
+git add .metafile
+git commit -m 'automatic commit for .metafile' .metafile
+
+exit 0
1  post-merge
250 setgitperms.perl
@@ -0,0 +1,250 @@
+#!/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
+# SUBDIRECTORY_OK=1 . 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` and `post-checkout` hook with the
+# following lines:
+
+# #!/bin/sh
+# SUBDIRECTORY_OK=1 . git-sh-setup
+# $GIT_DIR/hooks/setgitperms.perl -w
+
+use strict;
+
+use Getopt::Long;
+use File::Find;
+use File::Basename;
+
+exit if exists $ENV{ 'GIT_COMMIT_RECURSED' };
+
+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";
+ }
+ } ## end while ( defined( $_ ...))
+
+ 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";
+ }
+ } ## end foreach my $file ( @conflict_files)
+ } ## end elsif ( `grep $gitmeta $gitdir/MERGE_MSG`)
+ } ## end if ( -e "$gitdir/MERGE_MSG")
+
+ # 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";
+ }
+
+ print $diff if $showdiff;
+
+ }
+
+ close OUT;
+
+ }
+
+ # Make sure the .gitmeta file is tracked
+ #system( "git add $gitmeta ; GIT_COMMIT_RECURSED=1 git commit -m 'auto commit of $gitmeta' $gitmeta ; git update-index" );
+ system( "git add $gitmeta ; git update-index --really-refresh -q" );
+
+} ## end elsif ( $read_mode )
+
+sub printstats {
+
+ my $path = $_[ 0 ];
+ $path =~ s/@/\@/g;
+ my ( undef, undef, $mode, undef, $uid, $gid ) = lstat( $path );
+ $path =~ s/%/\%/g;
+
+ if ( $stdout ) {
+ printf "%s mode=%04o uid=$uid gid=$gid\n", $path, $mode & 07777;
+ } else {
+ printf OUT "%s mode=%04o uid=$uid gid=$gid\n", $path, $mode & 07777;
+ }
+}
82 validate_metadata
@@ -0,0 +1,82 @@
+#!/usr/bin/perl -w
+
+# This file is expecting to be run from a git hook (at the moment, either
+# post-checkout or post-merge).
+
+use Carp;
+use IPC::Run3::Simple;
+use Text::Diff;
+
+IPC::Run3::Simple::chomp_out( 1 );
+IPC::Run3::Simple::default_stderr( \my $err );
+
+run3({ 'cmd' => [qw( git rev-parse --show-toplevel )], 'stdout' => \my $base_dir });
+
+chdir $base_dir
+ or die "Unable to change to git top level directory: $!\n";
+
+my $git_version = shift;
+
+die "Git version of metadata file does not exist\n"
+ unless -e $git_version;
+
+my $validate_file = '.validate_metafile';
+
+run3( [ '.git/hooks/build_metafile', $validate_file ] );
+
+die "$validate_file was not created"
+ unless -e $validate_file;
+
+my @diff = split /\n/, diff $validate_file, $git_version, { 'CONTEXT' => 0 };
+
+croak "Incorrect order in diff ($validate_file should be first) or something else is wrong\n"
+ unless ( shift @diff ) =~ /--- $validate_file/;
+
+croak "Incorrect order in diff ($git_version should be second) or something else is wrong\n"
+ unless ( shift @diff ) =~ /\+\+\+ $git_version/;
+
+for my $fix ( @diff ) {
+
+ next unless $fix =~ s/^\+//;
+
+ my ( $uid, $gid, $mode, $name, $link ) = split /\0/, $fix, 5;
+
+ if ( ! -e $name ) {
+
+ warn "$name does not exist, skipping\n";
+ next;
+
+ }
+
+ if ( $uid =~ s/^HL: // ) {
+
+ my @links = split /\0/, $link;
+
+ my $links = join ' ', @links;
+
+ warn "Deleting $links\n";
+ my $removed = unlink @links;
+ warn "Unable to remove all files.\n"
+ if $removed != @links;
+
+ warn "Hardlinking $links to $name\n";
+ link $name, $_ for @links;
+
+ }
+
+ $mode = sprintf '%04o', $mode & 07777;
+
+ warn "Setting owner, group and mode for $name\n";
+
+ my $chowned = chown $uid, $gid, $name;
+ warn "Unable to change owner and/or group for $name: $!\n"
+ unless $chowned == 1;
+
+ my $chmodded = chmod oct($mode), $name;
+ warn "Unable to change mode for $name: $!\n"
+ unless $chmodded;
+
+}
+
+unlink $validate_file
+ or warn "Unable to remove $validate_file: $!\n";
Please sign in to comment.
Something went wrong with that request. Please try again.