]>
Commit | Line | Data |
---|---|---|
af6fb4c8 JE |
1 | #!/usr/bin/perl |
2 | # | |
3 | # Copyright (c) 2006 Josh England | |
4 | # | |
5 | # This script can be used to save/restore full permissions and ownership data | |
6 | # within a git working tree. | |
7 | # | |
8 | # To save permissions/ownership data, place this script in your .git/hooks | |
9 | # directory and enable a `pre-commit` hook with the following lines: | |
10 | # #!/bin/sh | |
03618b9d | 11 | # SUBDIRECTORY_OK=1 . git-sh-setup |
af6fb4c8 JE |
12 | # $GIT_DIR/hooks/setgitperms.perl -r |
13 | # | |
14 | # To restore permissions/ownership data, place this script in your .git/hooks | |
03618b9d JE |
15 | # directory and enable a `post-merge` and `post-checkout` hook with the |
16 | # following lines: | |
af6fb4c8 | 17 | # #!/bin/sh |
03618b9d | 18 | # SUBDIRECTORY_OK=1 . git-sh-setup |
af6fb4c8 JE |
19 | # $GIT_DIR/hooks/setgitperms.perl -w |
20 | # | |
21 | use strict; | |
22 | use Getopt::Long; | |
23 | use File::Find; | |
24 | use File::Basename; | |
25 | ||
26 | my $usage = | |
00eae5ef | 27 | "usage: setgitperms.perl [OPTION]... <--read|--write> |
af6fb4c8 JE |
28 | This program uses a file `.gitmeta` to store/restore permissions and uid/gid |
29 | info for all files/dirs tracked by git in the repository. | |
30 | ||
31 | ---------------------------------Read Mode------------------------------------- | |
32 | -r, --read Reads perms/etc from working dir into a .gitmeta file | |
33 | -s, --stdout Output to stdout instead of .gitmeta | |
34 | -d, --diff Show unified diff of perms file (XOR with --stdout) | |
35 | ||
36 | ---------------------------------Write Mode------------------------------------ | |
37 | -w, --write Modify perms/etc in working dir to match the .gitmeta file | |
38 | -v, --verbose Be verbose | |
39 | ||
40 | \n"; | |
41 | ||
42 | my ($stdout, $showdiff, $verbose, $read_mode, $write_mode); | |
43 | ||
44 | if ((@ARGV < 0) || !GetOptions( | |
45 | "stdout", \$stdout, | |
46 | "diff", \$showdiff, | |
47 | "read", \$read_mode, | |
48 | "write", \$write_mode, | |
49 | "verbose", \$verbose, | |
50 | )) { die $usage; } | |
51 | die $usage unless ($read_mode xor $write_mode); | |
52 | ||
18309f4c | 53 | my $topdir = `git rev-parse --show-cdup` or die "\n"; chomp $topdir; |
af6fb4c8 JE |
54 | my $gitdir = $topdir . '.git'; |
55 | my $gitmeta = $topdir . '.gitmeta'; | |
56 | ||
57 | if ($write_mode) { | |
58 | # Update the working dir permissions/ownership based on data from .gitmeta | |
59 | open (IN, "<$gitmeta") or die "Could not open $gitmeta for reading: $!\n"; | |
60 | while (defined ($_ = <IN>)) { | |
61 | chomp; | |
62 | if (/^(.*) mode=(\S+)\s+uid=(\d+)\s+gid=(\d+)/) { | |
63 | # Compare recorded perms to actual perms in the working dir | |
64 | my ($path, $mode, $uid, $gid) = ($1, $2, $3, $4); | |
65 | my $fullpath = $topdir . $path; | |
66 | my (undef,undef,$wmode,undef,$wuid,$wgid) = lstat($fullpath); | |
67 | $wmode = sprintf "%04o", $wmode & 07777; | |
68 | if ($mode ne $wmode) { | |
69 | $verbose && print "Updating permissions on $path: old=$wmode, new=$mode\n"; | |
70 | chmod oct($mode), $fullpath; | |
71 | } | |
72 | if ($uid != $wuid || $gid != $wgid) { | |
73 | if ($verbose) { | |
74 | # Print out user/group names instead of uid/gid | |
75 | my $pwname = getpwuid($uid); | |
76 | my $grpname = getgrgid($gid); | |
77 | my $wpwname = getpwuid($wuid); | |
78 | my $wgrpname = getgrgid($wgid); | |
79 | $pwname = $uid if !defined $pwname; | |
80 | $grpname = $gid if !defined $grpname; | |
81 | $wpwname = $wuid if !defined $wpwname; | |
82 | $wgrpname = $wgid if !defined $wgrpname; | |
83 | ||
84 | print "Updating uid/gid on $path: old=$wpwname/$wgrpname, new=$pwname/$grpname\n"; | |
85 | } | |
86 | chown $uid, $gid, $fullpath; | |
87 | } | |
88 | } | |
89 | else { | |
90 | warn "Invalid input format in $gitmeta:\n\t$_\n"; | |
91 | } | |
92 | } | |
93 | close IN; | |
94 | } | |
95 | elsif ($read_mode) { | |
96 | # Handle merge conflicts in the .gitperms file | |
97 | if (-e "$gitdir/MERGE_MSG") { | |
98 | if (`grep ====== $gitmeta`) { | |
99 | # Conflict not resolved -- abort the commit | |
100 | print "PERMISSIONS/OWNERSHIP CONFLICT\n"; | |
101 | print " Resolve the conflict in the $gitmeta file and then run\n"; | |
102 | print " `.git/hooks/setgitperms.perl --write` to reconcile.\n"; | |
103 | exit 1; | |
104 | } | |
105 | elsif (`grep $gitmeta $gitdir/MERGE_MSG`) { | |
106 | # A conflict in .gitmeta has been manually resolved. Verify that | |
107 | # the working dir perms matches the current .gitmeta perms for | |
108 | # each file/dir that conflicted. | |
109 | # This is here because a `setgitperms.perl --write` was not | |
110 | # performed due to a merge conflict, so permissions/ownership | |
111 | # may not be consistent with the manually merged .gitmeta file. | |
112 | my @conflict_diff = `git show \$(cat $gitdir/MERGE_HEAD)`; | |
113 | my @conflict_files; | |
114 | my $metadiff = 0; | |
115 | ||
116 | # Build a list of files that conflicted from the .gitmeta diff | |
117 | foreach my $line (@conflict_diff) { | |
118 | if ($line =~ m|^diff --git a/$gitmeta b/$gitmeta|) { | |
119 | $metadiff = 1; | |
120 | } | |
121 | elsif ($line =~ /^diff --git/) { | |
122 | $metadiff = 0; | |
123 | } | |
124 | elsif ($metadiff && $line =~ /^\+(.*) mode=/) { | |
125 | push @conflict_files, $1; | |
126 | } | |
127 | } | |
128 | ||
129 | # Verify that each conflict file now has permissions consistent | |
130 | # with the .gitmeta file | |
131 | foreach my $file (@conflict_files) { | |
132 | my $absfile = $topdir . $file; | |
133 | my $gm_entry = `grep "^$file mode=" $gitmeta`; | |
134 | if ($gm_entry =~ /mode=(\d+) uid=(\d+) gid=(\d+)/) { | |
135 | my ($gm_mode, $gm_uid, $gm_gid) = ($1, $2, $3); | |
136 | my (undef,undef,$mode,undef,$uid,$gid) = lstat("$absfile"); | |
137 | $mode = sprintf("%04o", $mode & 07777); | |
138 | if (($gm_mode ne $mode) || ($gm_uid != $uid) | |
139 | || ($gm_gid != $gid)) { | |
140 | print "PERMISSIONS/OWNERSHIP CONFLICT\n"; | |
141 | print " Mismatch found for file: $file\n"; | |
142 | print " Run `.git/hooks/setgitperms.perl --write` to reconcile.\n"; | |
143 | exit 1; | |
144 | } | |
145 | } | |
146 | else { | |
147 | print "Warning! Permissions/ownership no longer being tracked for file: $file\n"; | |
148 | } | |
149 | } | |
150 | } | |
151 | } | |
152 | ||
153 | # No merge conflicts -- write out perms/ownership data to .gitmeta file | |
154 | unless ($stdout) { | |
155 | open (OUT, ">$gitmeta.tmp") or die "Could not open $gitmeta.tmp for writing: $!\n"; | |
156 | } | |
157 | ||
18309f4c | 158 | my @files = `git ls-files`; |
af6fb4c8 JE |
159 | my %dirs; |
160 | ||
161 | foreach my $path (@files) { | |
162 | chomp $path; | |
163 | # We have to manually add stats for parent directories | |
164 | my $parent = dirname($path); | |
165 | while (!exists $dirs{$parent}) { | |
166 | $dirs{$parent} = 1; | |
167 | next if $parent eq '.'; | |
168 | printstats($parent); | |
169 | $parent = dirname($parent); | |
170 | } | |
171 | # Now the git-tracked file | |
172 | printstats($path); | |
173 | } | |
174 | ||
175 | # diff the temporary metadata file to see if anything has changed | |
176 | # If no metadata has changed, don't overwrite the real file | |
177 | # This is just so `git commit -a` doesn't try to commit a bogus update | |
178 | unless ($stdout) { | |
179 | if (! -e $gitmeta) { | |
180 | rename "$gitmeta.tmp", $gitmeta; | |
181 | } | |
182 | else { | |
183 | my $diff = `diff -U 0 $gitmeta $gitmeta.tmp`; | |
184 | if ($diff ne '') { | |
185 | rename "$gitmeta.tmp", $gitmeta; | |
186 | } | |
187 | else { | |
188 | unlink "$gitmeta.tmp"; | |
189 | } | |
190 | if ($showdiff) { | |
191 | print $diff; | |
192 | } | |
193 | } | |
194 | close OUT; | |
195 | } | |
196 | # Make sure the .gitmeta file is tracked | |
197 | system("git add $gitmeta"); | |
198 | } | |
199 | ||
200 | ||
201 | sub printstats { | |
202 | my $path = $_[0]; | |
203 | $path =~ s/@/\@/g; | |
204 | my (undef,undef,$mode,undef,$uid,$gid) = lstat($path); | |
205 | $path =~ s/%/\%/g; | |
206 | if ($stdout) { | |
207 | print $path; | |
208 | printf " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; | |
209 | } | |
210 | else { | |
211 | print OUT $path; | |
212 | printf OUT " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; | |
213 | } | |
214 | } |