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.