@matheusbsbs / matheus-bagliano / commits / 044182ef82
Fork 0

git-cvsserver: add mechanism for managing working tree and current directory

There are various reasons git-cvsserver needs to manipulate the current directory, and this patch attempts to clarify and validate such changes: 1. Temporary empty working directory (with index) for certain operations that require an index file to work. 2. Use a temporary directory with temporary file names for doing merges of user's dirty sandbox state with latest changes in repository. 3. Coming up soon: Set up an index and either a valid or empty working directory when calling git-check-attr to decide if a file should be marked binary (-kb). Signed-off-by: Matthew Ogilvie <mmogilvi_git@miniinfo.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Matthew Ogilvie committed May 14, 2008 at 22:35 UTC 044182ef82f1a371c469a26a6d64c7b823aea764
1 file changed +213 -39
git-cvsserver.perl
+213 -39
@@ -21,6 +21,7 @@ use bytes;
21
22 use Fcntl;
23 use File::Temp qw/tempdir tempfile/;
24 +use File::Path qw/rmtree/;
25 use File::Basename;
26 use Getopt::Long qw(:config require_order no_ignore_case);
27
@@ -86,6 +87,17 @@ my $methods = {
87 # $state holds all the bits of information the clients sends us that could
88 # potentially be useful when it comes to actually _doing_ something.
89 my $state = { prependdir => '' };
90 +
91 +# Work is for managing temporary working directory
92 +my $work =
93 + {
94 + state => undef, # undef, 1 (empty), 2 (with stuff)
95 + workDir => undef,
96 + index => undef,
97 + emptyDir => undef,
98 + tmpDir => undef
99 + };
100 +
101 $log->info("--------------- STARTING -----------------");
102
103 my $usage =
@@ -189,6 +201,9 @@ while (<STDIN>)
201 $log->debug("Processing time : user=" . (times)[0] . " system=" . (times)[1]);
202 $log->info("--------------- FINISH -----------------");
203
204 +chdir '/';
205 +exit 0;
206 +
207 # Magic catchall method.
208 # This is the method that will handle all commands we haven't yet
209 # implemented. It simply sends a warning to the log file indicating a
@@ -1101,10 +1116,10 @@ sub req_update
1116 $log->info("Updating '$filename'");
1117 my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);
1118
1104 - my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
1119 + my $mergeDir = setupTmpDir();
1120
1106 - chdir $dir;
1121 my $file_local = $filepart . ".mine";
1122 + my $mergedFile = "$mergeDir/$file_local";
1123 system("ln","-s",$state->{entries}{$filename}{modified_filename}, $file_local);
1124 my $file_old = $filepart . "." . $oldmeta->{revision};
1125 transmitfile($oldmeta->{filehash}, { targetfile => $file_old });
@@ -1115,11 +1130,13 @@ sub req_update
1130 $log->info("Merging $file_local, $file_old, $file_new");
1131 print "M Merging differences between 1.$oldmeta->{revision} and 1.$meta->{revision} into $filename\n";
1132
1118 - $log->debug("Temporary directory for merge is $dir");
1133 + $log->debug("Temporary directory for merge is $mergeDir");
1134
1135 my $return = system("git", "merge-file", $file_local, $file_old, $file_new);
1136 $return >>= 8;
1137
1138 + cleanupTmpDir();
1139 +
1140 if ( $return == 0 )
1141 {
1142 $log->info("Merged successfully");
@@ -1168,13 +1185,11 @@ sub req_update
1185 # transmit file, format is single integer on a line by itself (file
1186 # size) followed by the file contents
1187 # TODO : we should copy files in blocks
1171 - my $data = `cat $file_local`;
1188 + my $data = `cat $mergedFile`;
1189 $log->debug("File size : " . length($data));
1190 print length($data) . "\n";
1191 print $data;
1192 }
1176 -
1177 - chdir "/";
1193 }
1194
1195 }
@@ -1195,6 +1210,7 @@ sub req_ci
1210 if ( $state->{method} eq 'pserver')
1211 {
1212 print "error 1 pserver access cannot commit\n";
1213 + cleanupWorkTree();
1214 exit;
1215 }
1216
@@ -1202,6 +1218,7 @@ sub req_ci
1218 {
1219 $log->warn("file 'index' already exists in the git repository");
1220 print "error 1 Index already exists in git repo\n";
1221 + cleanupWorkTree();
1222 exit;
1223 }
1224
@@ -1209,31 +1226,20 @@ sub req_ci
1226 my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
1227 $updater->update();
1228
1212 - my $tmpdir = tempdir ( DIR => $TEMP_DIR );
1213 - my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
1214 - $log->info("Lockless commit start, basing commit on '$tmpdir', index file is '$file_index'");
1215 -
1216 - $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
1217 - $ENV{GIT_WORK_TREE} = ".";
1218 - $ENV{GIT_INDEX_FILE} = $file_index;
1219 -
1229 # Remember where the head was at the beginning.
1230 my $parenthash = `git show-ref -s refs/heads/$state->{module}`;
1231 chomp $parenthash;
1232 if ($parenthash !~ /^[0-9a-f]{40}$/) {
1233 print "error 1 pserver cannot find the current HEAD of module";
1234 + cleanupWorkTree();
1235 exit;
1236 }
1237
1228 - chdir $tmpdir;
1238 + setupWorkTree($parenthash);
1239
1230 - # populate the temporary index
1231 - system("git-read-tree", $parenthash);
1232 - unless ($? == 0)
1233 - {
1234 - die "Error running git-read-tree $state->{module} $file_index $!";
1235 - }
1236 - $log->info("Created index '$file_index' for head $state->{module} - exit status $?");
1240 + $log->info("Lockless commit start, basing commit on '$work->{workDir}', index file is '$work->{index}'");
1241 +
1242 + $log->info("Created index '$work->{index}' for head $state->{module} - exit status $?");
1243
1244 my @committedfiles = ();
1245 my %oldmeta;
@@ -1271,7 +1277,7 @@ sub req_ci
1277 {
1278 # fail everything if an up to date check fails
1279 print "error 1 Up to date check failed for $filename\n";
1274 - chdir "/";
1280 + cleanupWorkTree();
1281 exit;
1282 }
1283
@@ -1313,7 +1319,7 @@ sub req_ci
1319 {
1320 print "E No files to commit\n";
1321 print "ok\n";
1316 - chdir "/";
1322 + cleanupWorkTree();
1323 return;
1324 }
1325
@@ -1336,7 +1342,7 @@ sub req_ci
1342 {
1343 $log->warn("Commit failed (Invalid commit hash)");
1344 print "error 1 Commit failed (unknown reason)\n";
1339 - chdir "/";
1345 + cleanupWorkTree();
1346 exit;
1347 }
1348
@@ -1348,7 +1354,7 @@ sub req_ci
1354 {
1355 $log->warn("Commit failed (update hook declined to update ref)");
1356 print "error 1 Commit failed (update hook declined)\n";
1351 - chdir "/";
1357 + cleanupWorkTree();
1358 exit;
1359 }
1360 }
@@ -1358,6 +1364,7 @@ sub req_ci
1364 "refs/heads/$state->{module}", $commithash, $parenthash)) {
1365 $log->warn("update-ref for $state->{module} failed.");
1366 print "error 1 Cannot commit -- update first\n";
1367 + cleanupWorkTree();
1368 exit;
1369 }
1370
@@ -1414,7 +1421,7 @@ sub req_ci
1421 }
1422 }
1423
1417 - chdir "/";
1424 + cleanupWorkTree();
1425 print "ok\n";
1426 }
1427
@@ -1757,15 +1764,9 @@ sub req_annotate
1764 argsfromdir($updater);
1765
1766 # we'll need a temporary checkout dir
1760 - my $tmpdir = tempdir ( DIR => $TEMP_DIR );
1761 - my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
1762 - $log->info("Temp checkoutdir creation successful, basing annotate session work on '$tmpdir', index file is '$file_index'");
1763 -
1764 - $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
1765 - $ENV{GIT_WORK_TREE} = ".";
1766 - $ENV{GIT_INDEX_FILE} = $file_index;
1767 + setupWorkTree();
1768
1768 - chdir $tmpdir;
1769 + $log->info("Temp checkoutdir creation successful, basing annotate session work on '$work->{workDir}', index file is '$ENV{GIT_INDEX_FILE}'");
1770
1771 # foreach file specified on the command line ...
1772 foreach my $filename ( @{$state->{args}} )
@@ -1789,10 +1790,10 @@ sub req_annotate
1790 system("git-read-tree", $lastseenin);
1791 unless ($? == 0)
1792 {
1792 - print "E error running git-read-tree $lastseenin $file_index $!\n";
1793 + print "E error running git-read-tree $lastseenin $ENV{GIT_INDEX_FILE} $!\n";
1794 return;
1795 }
1795 - $log->info("Created index '$file_index' with commit $lastseenin - exit status $?");
1796 + $log->info("Created index '$ENV{GIT_INDEX_FILE}' with commit $lastseenin - exit status $?");
1797
1798 # do a checkout of the file
1799 system('git-checkout-index', '-f', '-u', $filename);
@@ -1808,7 +1809,7 @@ sub req_annotate
1809 # git-jsannotate telling us about commits we are hiding
1810 # from the client.
1811
1811 - my $a_hints = "$tmpdir/.annotate_hints";
1812 + my $a_hints = "$work->{workDir}/.annotate_hints";
1813 if (!open(ANNOTATEHINTS, '>', $a_hints)) {
1814 print "E failed to open '$a_hints' for writing: $!\n";
1815 return;
@@ -1862,7 +1863,7 @@ sub req_annotate
1863 }
1864
1865 # done; get out of the tempdir
1865 - chdir "/";
1866 + cleanupWorkDir();
1867
1868 print "ok\n";
1869
@@ -2115,6 +2116,179 @@ sub filecleanup
2116 return $filename;
2117 }
2118
2119 +sub validateGitDir
2120 +{
2121 + if( !defined($state->{CVSROOT}) )
2122 + {
2123 + print "error 1 CVSROOT not specified\n";
2124 + cleanupWorkTree();
2125 + exit;
2126 + }
2127 + if( $ENV{GIT_DIR} ne ($state->{CVSROOT} . '/') )
2128 + {
2129 + print "error 1 Internally inconsistent CVSROOT\n";
2130 + cleanupWorkTree();
2131 + exit;
2132 + }
2133 +}
2134 +
2135 +# Setup working directory in a work tree with the requested version
2136 +# loaded in the index.
2137 +sub setupWorkTree
2138 +{
2139 + my ($ver) = @_;
2140 +
2141 + validateGitDir();
2142 +
2143 + if( ( defined($work->{state}) && $work->{state} != 1 ) ||
2144 + defined($work->{tmpDir}) )
2145 + {
2146 + $log->warn("Bad work tree state management");
2147 + print "error 1 Internal setup multiple work trees without cleanup\n";
2148 + cleanupWorkTree();
2149 + exit;
2150 + }
2151 +
2152 + $work->{workDir} = tempdir ( DIR => $TEMP_DIR );
2153 +
2154 + if( !defined($work->{index}) )
2155 + {
2156 + (undef, $work->{index}) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
2157 + }
2158 +
2159 + chdir $work->{workDir} or
2160 + die "Unable to chdir to $work->{workDir}\n";
2161 +
2162 + $log->info("Setting up GIT_WORK_TREE as '.' in '$work->{workDir}', index file is '$work->{index}'");
2163 +
2164 + $ENV{GIT_WORK_TREE} = ".";
2165 + $ENV{GIT_INDEX_FILE} = $work->{index};
2166 + $work->{state} = 2;
2167 +
2168 + if($ver)
2169 + {
2170 + system("git","read-tree",$ver);
2171 + unless ($? == 0)
2172 + {
2173 + $log->warn("Error running git-read-tree");
2174 + die "Error running git-read-tree $ver in $work->{workDir} $!\n";
2175 + }
2176 + }
2177 + # else # req_annotate reads tree for each file
2178 +}
2179 +
2180 +# Ensure current directory is in some kind of working directory,
2181 +# with a recent version loaded in the index.
2182 +sub ensureWorkTree
2183 +{
2184 + if( defined($work->{tmpDir}) )
2185 + {
2186 + $log->warn("Bad work tree state management [ensureWorkTree()]");
2187 + print "error 1 Internal setup multiple dirs without cleanup\n";
2188 + cleanupWorkTree();
2189 + exit;
2190 + }
2191 + if( $work->{state} )
2192 + {
2193 + return;
2194 + }
2195 +
2196 + validateGitDir();
2197 +
2198 + if( !defined($work->{emptyDir}) )
2199 + {
2200 + $work->{emptyDir} = tempdir ( DIR => $TEMP_DIR, OPEN => 0);
2201 + }
2202 + chdir $work->{emptyDir} or
2203 + die "Unable to chdir to $work->{emptyDir}\n";
2204 +
2205 + my $ver = `git show-ref -s refs/heads/$state->{module}`;
2206 + chomp $ver;
2207 + if ($ver !~ /^[0-9a-f]{40}$/)
2208 + {
2209 + $log->warn("Error from git show-ref -s refs/head$state->{module}");
2210 + print "error 1 cannot find the current HEAD of module";
2211 + cleanupWorkTree();
2212 + exit;
2213 + }
2214 +
2215 + if( !defined($work->{index}) )
2216 + {
2217 + (undef, $work->{index}) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
2218 + }
2219 +
2220 + $ENV{GIT_WORK_TREE} = ".";
2221 + $ENV{GIT_INDEX_FILE} = $work->{index};
2222 + $work->{state} = 1;
2223 +
2224 + system("git","read-tree",$ver);
2225 + unless ($? == 0)
2226 + {
2227 + die "Error running git-read-tree $ver $!\n";
2228 + }
2229 +}
2230 +
2231 +# Cleanup working directory that is not needed any longer.
2232 +sub cleanupWorkTree
2233 +{
2234 + if( ! $work->{state} )
2235 + {
2236 + return;
2237 + }
2238 +
2239 + chdir "/" or die "Unable to chdir '/'\n";
2240 +
2241 + if( defined($work->{workDir}) )
2242 + {
2243 + rmtree( $work->{workDir} );
2244 + undef $work->{workDir};
2245 + }
2246 + undef $work->{state};
2247 +}
2248 +
2249 +# Setup a temporary directory (not a working tree), typically for
2250 +# merging dirty state as in req_update.
2251 +sub setupTmpDir
2252 +{
2253 + $work->{tmpDir} = tempdir ( DIR => $TEMP_DIR );
2254 + chdir $work->{tmpDir} or die "Unable to chdir $work->{tmpDir}\n";
2255 +
2256 + return $work->{tmpDir};
2257 +}
2258 +
2259 +# Clean up a previously setupTmpDir. Restore previous work tree if
2260 +# appropriate.
2261 +sub cleanupTmpDir
2262 +{
2263 + if ( !defined($work->{tmpDir}) )
2264 + {
2265 + $log->warn("cleanup tmpdir that has not been setup");
2266 + die "Cleanup tmpDir that has not been setup\n";
2267 + }
2268 + if( defined($work->{state}) )
2269 + {
2270 + if( $work->{state} == 1 )
2271 + {
2272 + chdir $work->{emptyDir} or
2273 + die "Unable to chdir to $work->{emptyDir}\n";
2274 + }
2275 + elsif( $work->{state} == 2 )
2276 + {
2277 + chdir $work->{workDir} or
2278 + die "Unable to chdir to $work->{emptyDir}\n";
2279 + }
2280 + else
2281 + {
2282 + $log->warn("Inconsistent work dir state");
2283 + die "Inconsistent work dir state\n";
2284 + }
2285 + }
2286 + else
2287 + {
2288 + chdir "/" or die "Unable to chdir '/'\n";
2289 + }
2290 +}
2291 +
2292 # Given a path, this function returns a string containing the kopts
2293 # that should go into that path's Entries line. For example, a binary
2294 # file should get -kb.