From 044182ef82f1a371c469a26a6d64c7b823aea764 Mon Sep 17 00:00:00 2001 From: Matthew Ogilvie Date: Wed, 14 May 2008 22:35:46 -0600 Subject: [PATCH 1/4] 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 Signed-off-by: Junio C Hamano --- git-cvsserver.perl | 252 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 213 insertions(+), 39 deletions(-) diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 29dbfc940b..674892b816 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -21,6 +21,7 @@ use bytes; use Fcntl; use File::Temp qw/tempdir tempfile/; +use File::Path qw/rmtree/; use File::Basename; use Getopt::Long qw(:config require_order no_ignore_case); @@ -86,6 +87,17 @@ my $methods = { # $state holds all the bits of information the clients sends us that could # potentially be useful when it comes to actually _doing_ something. my $state = { prependdir => '' }; + +# Work is for managing temporary working directory +my $work = + { + state => undef, # undef, 1 (empty), 2 (with stuff) + workDir => undef, + index => undef, + emptyDir => undef, + tmpDir => undef + }; + $log->info("--------------- STARTING -----------------"); my $usage = @@ -189,6 +201,9 @@ while () $log->debug("Processing time : user=" . (times)[0] . " system=" . (times)[1]); $log->info("--------------- FINISH -----------------"); +chdir '/'; +exit 0; + # Magic catchall method. # This is the method that will handle all commands we haven't yet # implemented. It simply sends a warning to the log file indicating a @@ -1101,10 +1116,10 @@ sub req_update $log->info("Updating '$filename'"); my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1); - my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/"; + my $mergeDir = setupTmpDir(); - chdir $dir; my $file_local = $filepart . ".mine"; + my $mergedFile = "$mergeDir/$file_local"; system("ln","-s",$state->{entries}{$filename}{modified_filename}, $file_local); my $file_old = $filepart . "." . $oldmeta->{revision}; transmitfile($oldmeta->{filehash}, { targetfile => $file_old }); @@ -1115,11 +1130,13 @@ sub req_update $log->info("Merging $file_local, $file_old, $file_new"); print "M Merging differences between 1.$oldmeta->{revision} and 1.$meta->{revision} into $filename\n"; - $log->debug("Temporary directory for merge is $dir"); + $log->debug("Temporary directory for merge is $mergeDir"); my $return = system("git", "merge-file", $file_local, $file_old, $file_new); $return >>= 8; + cleanupTmpDir(); + if ( $return == 0 ) { $log->info("Merged successfully"); @@ -1168,13 +1185,11 @@ sub req_update # transmit file, format is single integer on a line by itself (file # size) followed by the file contents # TODO : we should copy files in blocks - my $data = `cat $file_local`; + my $data = `cat $mergedFile`; $log->debug("File size : " . length($data)); print length($data) . "\n"; print $data; } - - chdir "/"; } } @@ -1195,6 +1210,7 @@ sub req_ci if ( $state->{method} eq 'pserver') { print "error 1 pserver access cannot commit\n"; + cleanupWorkTree(); exit; } @@ -1202,6 +1218,7 @@ sub req_ci { $log->warn("file 'index' already exists in the git repository"); print "error 1 Index already exists in git repo\n"; + cleanupWorkTree(); exit; } @@ -1209,31 +1226,20 @@ sub req_ci my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); $updater->update(); - my $tmpdir = tempdir ( DIR => $TEMP_DIR ); - my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 ); - $log->info("Lockless commit start, basing commit on '$tmpdir', index file is '$file_index'"); - - $ENV{GIT_DIR} = $state->{CVSROOT} . "/"; - $ENV{GIT_WORK_TREE} = "."; - $ENV{GIT_INDEX_FILE} = $file_index; - # Remember where the head was at the beginning. my $parenthash = `git show-ref -s refs/heads/$state->{module}`; chomp $parenthash; if ($parenthash !~ /^[0-9a-f]{40}$/) { print "error 1 pserver cannot find the current HEAD of module"; + cleanupWorkTree(); exit; } - chdir $tmpdir; + setupWorkTree($parenthash); - # populate the temporary index - system("git-read-tree", $parenthash); - unless ($? == 0) - { - die "Error running git-read-tree $state->{module} $file_index $!"; - } - $log->info("Created index '$file_index' for head $state->{module} - exit status $?"); + $log->info("Lockless commit start, basing commit on '$work->{workDir}', index file is '$work->{index}'"); + + $log->info("Created index '$work->{index}' for head $state->{module} - exit status $?"); my @committedfiles = (); my %oldmeta; @@ -1271,7 +1277,7 @@ sub req_ci { # fail everything if an up to date check fails print "error 1 Up to date check failed for $filename\n"; - chdir "/"; + cleanupWorkTree(); exit; } @@ -1313,7 +1319,7 @@ sub req_ci { print "E No files to commit\n"; print "ok\n"; - chdir "/"; + cleanupWorkTree(); return; } @@ -1336,7 +1342,7 @@ sub req_ci { $log->warn("Commit failed (Invalid commit hash)"); print "error 1 Commit failed (unknown reason)\n"; - chdir "/"; + cleanupWorkTree(); exit; } @@ -1348,7 +1354,7 @@ sub req_ci { $log->warn("Commit failed (update hook declined to update ref)"); print "error 1 Commit failed (update hook declined)\n"; - chdir "/"; + cleanupWorkTree(); exit; } } @@ -1358,6 +1364,7 @@ sub req_ci "refs/heads/$state->{module}", $commithash, $parenthash)) { $log->warn("update-ref for $state->{module} failed."); print "error 1 Cannot commit -- update first\n"; + cleanupWorkTree(); exit; } @@ -1414,7 +1421,7 @@ sub req_ci } } - chdir "/"; + cleanupWorkTree(); print "ok\n"; } @@ -1757,15 +1764,9 @@ sub req_annotate argsfromdir($updater); # we'll need a temporary checkout dir - my $tmpdir = tempdir ( DIR => $TEMP_DIR ); - my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 ); - $log->info("Temp checkoutdir creation successful, basing annotate session work on '$tmpdir', index file is '$file_index'"); - - $ENV{GIT_DIR} = $state->{CVSROOT} . "/"; - $ENV{GIT_WORK_TREE} = "."; - $ENV{GIT_INDEX_FILE} = $file_index; + setupWorkTree(); - chdir $tmpdir; + $log->info("Temp checkoutdir creation successful, basing annotate session work on '$work->{workDir}', index file is '$ENV{GIT_INDEX_FILE}'"); # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) @@ -1789,10 +1790,10 @@ sub req_annotate system("git-read-tree", $lastseenin); unless ($? == 0) { - print "E error running git-read-tree $lastseenin $file_index $!\n"; + print "E error running git-read-tree $lastseenin $ENV{GIT_INDEX_FILE} $!\n"; return; } - $log->info("Created index '$file_index' with commit $lastseenin - exit status $?"); + $log->info("Created index '$ENV{GIT_INDEX_FILE}' with commit $lastseenin - exit status $?"); # do a checkout of the file system('git-checkout-index', '-f', '-u', $filename); @@ -1808,7 +1809,7 @@ sub req_annotate # git-jsannotate telling us about commits we are hiding # from the client. - my $a_hints = "$tmpdir/.annotate_hints"; + my $a_hints = "$work->{workDir}/.annotate_hints"; if (!open(ANNOTATEHINTS, '>', $a_hints)) { print "E failed to open '$a_hints' for writing: $!\n"; return; @@ -1862,7 +1863,7 @@ sub req_annotate } # done; get out of the tempdir - chdir "/"; + cleanupWorkDir(); print "ok\n"; @@ -2115,6 +2116,179 @@ sub filecleanup return $filename; } +sub validateGitDir +{ + if( !defined($state->{CVSROOT}) ) + { + print "error 1 CVSROOT not specified\n"; + cleanupWorkTree(); + exit; + } + if( $ENV{GIT_DIR} ne ($state->{CVSROOT} . '/') ) + { + print "error 1 Internally inconsistent CVSROOT\n"; + cleanupWorkTree(); + exit; + } +} + +# Setup working directory in a work tree with the requested version +# loaded in the index. +sub setupWorkTree +{ + my ($ver) = @_; + + validateGitDir(); + + if( ( defined($work->{state}) && $work->{state} != 1 ) || + defined($work->{tmpDir}) ) + { + $log->warn("Bad work tree state management"); + print "error 1 Internal setup multiple work trees without cleanup\n"; + cleanupWorkTree(); + exit; + } + + $work->{workDir} = tempdir ( DIR => $TEMP_DIR ); + + if( !defined($work->{index}) ) + { + (undef, $work->{index}) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 ); + } + + chdir $work->{workDir} or + die "Unable to chdir to $work->{workDir}\n"; + + $log->info("Setting up GIT_WORK_TREE as '.' in '$work->{workDir}', index file is '$work->{index}'"); + + $ENV{GIT_WORK_TREE} = "."; + $ENV{GIT_INDEX_FILE} = $work->{index}; + $work->{state} = 2; + + if($ver) + { + system("git","read-tree",$ver); + unless ($? == 0) + { + $log->warn("Error running git-read-tree"); + die "Error running git-read-tree $ver in $work->{workDir} $!\n"; + } + } + # else # req_annotate reads tree for each file +} + +# Ensure current directory is in some kind of working directory, +# with a recent version loaded in the index. +sub ensureWorkTree +{ + if( defined($work->{tmpDir}) ) + { + $log->warn("Bad work tree state management [ensureWorkTree()]"); + print "error 1 Internal setup multiple dirs without cleanup\n"; + cleanupWorkTree(); + exit; + } + if( $work->{state} ) + { + return; + } + + validateGitDir(); + + if( !defined($work->{emptyDir}) ) + { + $work->{emptyDir} = tempdir ( DIR => $TEMP_DIR, OPEN => 0); + } + chdir $work->{emptyDir} or + die "Unable to chdir to $work->{emptyDir}\n"; + + my $ver = `git show-ref -s refs/heads/$state->{module}`; + chomp $ver; + if ($ver !~ /^[0-9a-f]{40}$/) + { + $log->warn("Error from git show-ref -s refs/head$state->{module}"); + print "error 1 cannot find the current HEAD of module"; + cleanupWorkTree(); + exit; + } + + if( !defined($work->{index}) ) + { + (undef, $work->{index}) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 ); + } + + $ENV{GIT_WORK_TREE} = "."; + $ENV{GIT_INDEX_FILE} = $work->{index}; + $work->{state} = 1; + + system("git","read-tree",$ver); + unless ($? == 0) + { + die "Error running git-read-tree $ver $!\n"; + } +} + +# Cleanup working directory that is not needed any longer. +sub cleanupWorkTree +{ + if( ! $work->{state} ) + { + return; + } + + chdir "/" or die "Unable to chdir '/'\n"; + + if( defined($work->{workDir}) ) + { + rmtree( $work->{workDir} ); + undef $work->{workDir}; + } + undef $work->{state}; +} + +# Setup a temporary directory (not a working tree), typically for +# merging dirty state as in req_update. +sub setupTmpDir +{ + $work->{tmpDir} = tempdir ( DIR => $TEMP_DIR ); + chdir $work->{tmpDir} or die "Unable to chdir $work->{tmpDir}\n"; + + return $work->{tmpDir}; +} + +# Clean up a previously setupTmpDir. Restore previous work tree if +# appropriate. +sub cleanupTmpDir +{ + if ( !defined($work->{tmpDir}) ) + { + $log->warn("cleanup tmpdir that has not been setup"); + die "Cleanup tmpDir that has not been setup\n"; + } + if( defined($work->{state}) ) + { + if( $work->{state} == 1 ) + { + chdir $work->{emptyDir} or + die "Unable to chdir to $work->{emptyDir}\n"; + } + elsif( $work->{state} == 2 ) + { + chdir $work->{workDir} or + die "Unable to chdir to $work->{emptyDir}\n"; + } + else + { + $log->warn("Inconsistent work dir state"); + die "Inconsistent work dir state\n"; + } + } + else + { + chdir "/" or die "Unable to chdir '/'\n"; + } +} + # Given a path, this function returns a string containing the kopts # that should go into that path's Entries line. For example, a binary # file should get -kb. From 8a06a632976ead891115ad9ac9dea7b99c52158e Mon Sep 17 00:00:00 2001 From: Matthew Ogilvie Date: Wed, 14 May 2008 22:35:47 -0600 Subject: [PATCH 2/4] implement gitcvs.usecrlfattr If gitcvs.usecrlfattr is set to true, git-cvsserver will consult the "crlf" for each file to determine if it should mark the file as binary (-kb). Signed-off-by: Matthew Ogilvie Signed-off-by: Junio C Hamano --- Documentation/config.txt | 23 +++-- Documentation/git-cvsserver.txt | 26 ++++- git-cvsserver.perl | 71 +++++++++++-- t/t9401-git-cvsserver-crlf.sh | 178 ++++++++++++++++++++++++++++++++ 4 files changed, 276 insertions(+), 22 deletions(-) create mode 100755 t/t9401-git-cvsserver-crlf.sh diff --git a/Documentation/config.txt b/Documentation/config.txt index 217980f48d..036e61e2f6 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -660,11 +660,21 @@ gitcvs.logfile:: Path to a log file where the CVS server interface well... logs various stuff. See linkgit:git-cvsserver[1]. +gitcvs.usecrlfattr + If true, the server will look up the `crlf` attribute for + files to determine the '-k' modes to use. If `crlf` is set, + the '-k' mode will be left blank, so cvs clients will + treat it as text. If `crlf` is explicitly unset, the file + will be set with '-kb' mode, which supresses any newline munging + the client might otherwise do. If `crlf` is not specified, + then 'gitcvs.allbinary' is used. See linkgit:gitattribute[5]. + gitcvs.allbinary:: - If true, all files are sent to the client in mode '-kb'. This - causes the client to treat all files as binary files which suppresses - any newline munging it otherwise might do. A work-around for the - fact that there is no way yet to set single files to mode '-kb'. + If true, all files not otherwise specified using + 'gitcvs.usecrlfattr' and an explicitly set or unset `crlf` + attribute are sent to the client in mode '-kb'. This + causes the client to treat them as binary files which + suppresses any newline munging it otherwise might do. gitcvs.dbname:: Database used by git-cvsserver to cache revision information @@ -695,8 +705,9 @@ gitcvs.dbTableNamePrefix:: linkgit:git-cvsserver[1] for details). Any non-alphabetic characters will be replaced with underscores. -All gitcvs variables except for 'gitcvs.allbinary' can also be -specified as 'gitcvs..' (where 'access_method' +All gitcvs variables except for 'gitcvs.usecrlfattr' and +'gitcvs.allbinary' can also be specified as +'gitcvs..' (where 'access_method' is one of "ext" and "pserver") to make them apply only for the given access method. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index b1106714b2..4888b8604d 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -301,11 +301,27 @@ checkout, diff, status, update, log, add, remove, commit. Legacy monitoring operations are not supported (edit, watch and related). Exports and tagging (tags and branches) are not supported at this stage. -The server should set the '-k' mode to binary when relevant, however, -this is not really implemented yet. For now, you can force the server -to set '-kb' for all files by setting the `gitcvs.allbinary` config -variable. In proper GIT tradition, the contents of the files are -always respected. No keyword expansion or newline munging is supported. +CRLF Line Ending Conversions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default the server leaves the '-k' mode blank for all files, +which causes the cvs client to treat them as a text files, subject +to crlf conversion on some platforms. + +You can make the server use `crlf` attributes to set the '-k' modes +for files by setting the `gitcvs.usecrlfattr` config variable. +In this case, if `crlf` is explicitly unset ('-crlf'), then the +will set '-kb' mode, for binary files. If it `crlf` is set, +then the '-k' mode will explicitly be left blank. See +also linkgit:gitattributes[5] for more information about the `crlf` +attribute. + +Alternatively, if `gitcvs.usecrlfattr` config is not enabled +or if the `crlf` attribute is unspecified for a filename, then +the server uses the `gitcvs.allbinary` for the default setting. +If `gitcvs.allbinary` is set, then the files not otherwise +specified will default to '-kb' mode. Otherwise the '-k' mode +is left blank. Dependencies ------------ diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 674892b816..58206aed7c 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -502,7 +502,7 @@ sub req_add print $state->{CVSROOT} . "/$state->{module}/$filename\n"; # this is an "entries" line - my $kopts = kopts_from_path($filepart); + my $kopts = kopts_from_path($filename); $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); print "/$filepart/1.$meta->{revision}//$kopts/\n"; # permissions @@ -533,9 +533,25 @@ sub req_add print "Checked-in $dirpart\n"; print "$filename\n"; - my $kopts = kopts_from_path($filepart); + my $kopts = kopts_from_path($filename); print "/$filepart/0//$kopts/\n"; + my $requestedKopts = $state->{opt}{k}; + if(defined($requestedKopts)) + { + $requestedKopts = "-k$requestedKopts"; + } + else + { + $requestedKopts = ""; + } + if( $kopts ne $requestedKopts ) + { + $log->warn("Ignoring requested -k='$requestedKopts'" + . " for '$filename'; detected -k='$kopts' instead"); + #TODO: Also have option to send warning to user? + } + $addcount++; } @@ -615,7 +631,7 @@ sub req_remove print "Checked-in $dirpart\n"; print "$filename\n"; - my $kopts = kopts_from_path($filepart); + my $kopts = kopts_from_path($filename); print "/$filepart/-1.$wrev//$kopts/\n"; $rmcount++; @@ -785,6 +801,7 @@ sub req_co argsplit("co"); my $module = $state->{args}[0]; + $state->{module} = $module; my $checkout_path = $module; # use the user specified directory if we're given it @@ -862,6 +879,7 @@ sub req_co # Don't want to check out deleted files next if ( $git->{filehash} eq "deleted" ); + my $fullName = $git->{name}; ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name}); if (length($git->{dir}) && $git->{dir} ne './' @@ -892,7 +910,7 @@ sub req_co print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; # this is an "entries" line - my $kopts = kopts_from_path($git->{name}); + my $kopts = kopts_from_path($fullName); print "/$git->{name}/1.$git->{revision}//$kopts/\n"; # permissions print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n"; @@ -1101,7 +1119,7 @@ sub req_update print $state->{CVSROOT} . "/$state->{module}/$filename\n"; # this is an "entries" line - my $kopts = kopts_from_path($filepart); + my $kopts = kopts_from_path($filename); $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); print "/$filepart/1.$meta->{revision}//$kopts/\n"; @@ -1149,7 +1167,7 @@ sub req_update print "Merged $dirpart\n"; $log->debug($state->{CVSROOT} . "/$state->{module}/$filename"); print $state->{CVSROOT} . "/$state->{module}/$filename\n"; - my $kopts = kopts_from_path($filepart); + my $kopts = kopts_from_path("$dirpart/$filepart"); $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); print "/$filepart/1.$meta->{revision}//$kopts/\n"; } @@ -1165,7 +1183,7 @@ sub req_update { print "Merged $dirpart\n"; print $state->{CVSROOT} . "/$state->{module}/$filename\n"; - my $kopts = kopts_from_path($filepart); + my $kopts = kopts_from_path("$dirpart/$filepart"); print "/$filepart/1.$meta->{revision}/+/$kopts/\n"; } } @@ -1416,7 +1434,7 @@ sub req_ci } print "Checked-in $dirpart\n"; print "$filename\n"; - my $kopts = kopts_from_path($filepart); + my $kopts = kopts_from_path($filename); print "/$filepart/1.$meta->{revision}//$kopts/\n"; } } @@ -2296,10 +2314,24 @@ sub kopts_from_path { my ($path) = @_; - # Once it exists, the git attributes system should be used to look up - # what attributes apply to this path. + if ( defined ( $cfg->{gitcvs}{usecrlfattr} ) and + $cfg->{gitcvs}{usecrlfattr} =~ /\s*(1|true|yes)\s*$/i ) + { + my ($val) = check_attr( "crlf", $path ); + if ( $val eq "set" ) + { + return ""; + } + elsif ( $val eq "unset" ) + { + return "-kb" + } + else + { + $log->info("Unrecognized check_attr crlf $path : $val"); + } + } - # Until then, take the setting from the config file unless ( defined ( $cfg->{gitcvs}{allbinary} ) and $cfg->{gitcvs}{allbinary} =~ /^\s*(1|true|yes)\s*$/i ) { # Return "" to give no special treatment to any path @@ -2311,6 +2343,23 @@ sub kopts_from_path } } +sub check_attr +{ + my ($attr,$path) = @_; + ensureWorkTree(); + if ( open my $fh, '-|', "git", "check-attr", $attr, "--", $path ) + { + my $val = <$fh>; + close $fh; + $val =~ s/.*: ([^:\r\n]*)\s*$/$1/; + return $val; + } + else + { + return undef; + } +} + # Generate a CVS author name from Git author information, by taking # the first eight characters of the user part of the email address. sub cvs_author diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh new file mode 100755 index 0000000000..b7a779b788 --- /dev/null +++ b/t/t9401-git-cvsserver-crlf.sh @@ -0,0 +1,178 @@ +#!/bin/sh +# +# Copyright (c) 2008 Matthew Ogilvie +# Parts adapted from other tests. +# + +test_description='git-cvsserver -kb modes + +tests -kb mode for binary files when accessing a git +repository using cvs CLI client via git-cvsserver server' + +. ./test-lib.sh + +q_to_nul () { + perl -pe 'y/Q/\000/' +} + +q_to_cr () { + tr Q '\015' +} + +marked_as () { + foundEntry="$(grep "^/$2/" "$1/CVS/Entries")" + if [ x"$foundEntry" = x"" ] ; then + echo "NOT FOUND: $1 $2 1 $3" >> "${WORKDIR}/marked.log" + return 1 + fi + test x"$(grep "^/$2/" "$1/CVS/Entries" | cut -d/ -f5)" = x"$3" + stat=$? + echo "$1 $2 $stat '$3'" >> "${WORKDIR}/marked.log" + return $stat +} + +not_present() { + foundEntry="$(grep "^/$2/" "$1/CVS/Entries")" + if [ -r "$1/$2" ] ; then + echo "Error: File still exists: $1 $2" >> "${WORKDIR}/marked.log" + return 1; + fi + if [ x"$foundEntry" != x"" ] ; then + echo "Error: should not have found: $1 $2" >> "${WORKDIR}/marked.log" + return 1; + else + echo "Correctly not found: $1 $2" >> "${WORKDIR}/marked.log" + return 0; + fi +} + +cvs >/dev/null 2>&1 +if test $? -ne 1 +then + test_expect_success 'skipping git-cvsserver tests, cvs not found' : + test_done + exit +fi +perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { + test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' : + test_done + exit +} + +unset GIT_DIR GIT_CONFIG +WORKDIR=$(pwd) +SERVERDIR=$(pwd)/gitcvs.git +git_config="$SERVERDIR/config" +CVSROOT=":fork:$SERVERDIR" +CVSWORK="$(pwd)/cvswork" +CVS_SERVER=git-cvsserver +export CVSROOT CVS_SERVER + +rm -rf "$CVSWORK" "$SERVERDIR" +test_expect_success 'setup' ' + echo "Simple text file" >textfile.c && + echo "File with embedded NUL: Q <- there" | q_to_nul > binfile.bin && + mkdir subdir && + echo "Another text file" > subdir/file.h && + echo "Another binary: Q (this time CR)" | q_to_cr > subdir/withCr.bin && + echo "Mixed up NUL, but marked text: Q <- there" | q_to_nul > mixedUp.c + echo "Unspecified" > subdir/unspecified.other && + echo "/*.bin -crlf" > .gitattributes && + echo "/*.c crlf" >> .gitattributes && + echo "subdir/*.bin -crlf" >> .gitattributes && + echo "subdir/*.c crlf" >> .gitattributes && + echo "subdir/file.h crlf" >> .gitattributes && + git add .gitattributes textfile.c binfile.bin mixedUp.c subdir/* && + git commit -q -m "First Commit" && + git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && + GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && + GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" +' + +test_expect_success 'cvs co (default crlf)' ' + GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 && + test x"$(grep '/-k' cvswork/CVS/Entries cvswork/subdir/CVS/Entries)" = x"" +' + +rm -rf cvswork +test_expect_success 'cvs co (allbinary)' ' + GIT_DIR="$SERVERDIR" git config --bool gitcvs.allbinary true && + GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 && + marked_as cvswork textfile.c -kb && + marked_as cvswork binfile.bin -kb && + marked_as cvswork .gitattributes -kb && + marked_as cvswork mixedUp.c -kb && + marked_as cvswork/subdir withCr.bin -kb && + marked_as cvswork/subdir file.h -kb && + marked_as cvswork/subdir unspecified.other -kb +' + +rm -rf cvswork cvs.log +test_expect_success 'cvs co (use attributes/allbinary)' ' + GIT_DIR="$SERVERDIR" git config --bool gitcvs.usecrlfattr true && + GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 && + marked_as cvswork textfile.c "" && + marked_as cvswork binfile.bin -kb && + marked_as cvswork .gitattributes -kb && + marked_as cvswork mixedUp.c "" && + marked_as cvswork/subdir withCr.bin -kb && + marked_as cvswork/subdir file.h "" && + marked_as cvswork/subdir unspecified.other -kb +' + +rm -rf cvswork +test_expect_success 'cvs co (use attributes)' ' + GIT_DIR="$SERVERDIR" git config --bool gitcvs.allbinary false && + GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 && + marked_as cvswork textfile.c "" && + marked_as cvswork binfile.bin -kb && + marked_as cvswork .gitattributes "" && + marked_as cvswork mixedUp.c "" && + marked_as cvswork/subdir withCr.bin -kb && + marked_as cvswork/subdir file.h "" && + marked_as cvswork/subdir unspecified.other "" +' + +test_expect_success 'adding files' ' + cd cvswork/subdir && + echo "more text" > src.c && + GIT_CONFIG="$git_config" cvs -Q add src.c >cvs.log 2>&1 && + marked_as . src.c "" && + echo "psuedo-binary" > temp.bin && + cd .. && + GIT_CONFIG="$git_config" cvs -Q add subdir/temp.bin >cvs.log 2>&1 && + marked_as subdir temp.bin "-kb" && + cd subdir && + GIT_CONFIG="$git_config" cvs -Q ci -m "adding files" >cvs.log 2>&1 && + marked_as . temp.bin "-kb" && + marked_as . src.c "" +' + +cd "$WORKDIR" +test_expect_success 'updating' ' + git pull gitcvs.git && + echo 'hi' > subdir/newfile.bin && + echo 'junk' > subdir/file.h && + echo 'hi' > subdir/newfile.c && + echo 'hello' >> binfile.bin && + git add subdir/newfile.bin subdir/file.h subdir/newfile.c binfile.bin && + git commit -q -m "Add and change some files" && + git push gitcvs.git >/dev/null && + cd cvswork && + GIT_CONFIG="$git_config" cvs -Q update && + cd .. && + marked_as cvswork textfile.c "" && + marked_as cvswork binfile.bin -kb && + marked_as cvswork .gitattributes "" && + marked_as cvswork mixedUp.c "" && + marked_as cvswork/subdir withCr.bin -kb && + marked_as cvswork/subdir file.h "" && + marked_as cvswork/subdir unspecified.other "" && + marked_as cvswork/subdir newfile.bin -kb && + marked_as cvswork/subdir newfile.c "" && + echo "File with embedded NUL: Q <- there" | q_to_nul > tmpExpect1 && + echo "hello" >> tmpExpect1 && + cmp cvswork/binfile.bin tmpExpect1 +' + +test_done From 90948a42892779734f77d62f20326c868392fd8f Mon Sep 17 00:00:00 2001 From: Matthew Ogilvie Date: Wed, 14 May 2008 22:35:48 -0600 Subject: [PATCH 3/4] git-cvsserver: add ability to guess -kb from contents If "gitcvs.allbinary" is set to "guess", then any file that has not been explicitly marked as binary or text using the "crlf" attribute and the "gitcvs.usecrlfattr" config will guess binary based on the contents of the file. Signed-off-by: Matthew Ogilvie Signed-off-by: Junio C Hamano --- Documentation/config.txt | 13 ++- Documentation/git-cvsserver.txt | 14 ++- git-cvsserver.perl | 193 +++++++++++++++++++++++++++++--- t/t9401-git-cvsserver-crlf.sh | 159 ++++++++++++++++++++++++++ 4 files changed, 354 insertions(+), 25 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 036e61e2f6..917ef5b4fe 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -670,11 +670,14 @@ gitcvs.usecrlfattr then 'gitcvs.allbinary' is used. See linkgit:gitattribute[5]. gitcvs.allbinary:: - If true, all files not otherwise specified using - 'gitcvs.usecrlfattr' and an explicitly set or unset `crlf` - attribute are sent to the client in mode '-kb'. This - causes the client to treat them as binary files which - suppresses any newline munging it otherwise might do. + This is used if 'gitcvs.usecrlfattr' does not resolve + the correct '-kb' mode to use. If true, all + unresolved files are sent to the client in + mode '-kb'. This causes the client to treat them + as binary files, which suppresses any newline munging it + otherwise might do. Alternatively, if it is set to "guess", + then the contents of the file are examined to decide if + it is binary, similar to 'core.autocrlf'. gitcvs.dbname:: Database used by git-cvsserver to cache revision information diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 4888b8604d..cd0685ea67 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -311,17 +311,23 @@ to crlf conversion on some platforms. You can make the server use `crlf` attributes to set the '-k' modes for files by setting the `gitcvs.usecrlfattr` config variable. In this case, if `crlf` is explicitly unset ('-crlf'), then the -will set '-kb' mode, for binary files. If it `crlf` is set, +server will set '-kb' mode for binary files. If `crlf` is set, then the '-k' mode will explicitly be left blank. See also linkgit:gitattributes[5] for more information about the `crlf` attribute. Alternatively, if `gitcvs.usecrlfattr` config is not enabled or if the `crlf` attribute is unspecified for a filename, then -the server uses the `gitcvs.allbinary` for the default setting. -If `gitcvs.allbinary` is set, then the files not otherwise +the server uses the `gitcvs.allbinary` config for the default setting. +If `gitcvs.allbinary` is set, then file not otherwise specified will default to '-kb' mode. Otherwise the '-k' mode -is left blank. +is left blank. But if `gitcvs.allbinary` is set to "guess", then +the correct '-k' mode will be guessed based on the contents of +the file. + +For best consistency with cvs, it is probably best to override the +defaults by setting `gitcvs.usecrlfattr` to true, +and `gitcvs.allbinary` to "guess". Dependencies ------------ diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 58206aed7c..920bbe15a3 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -502,7 +502,7 @@ sub req_add print $state->{CVSROOT} . "/$state->{module}/$filename\n"; # this is an "entries" line - my $kopts = kopts_from_path($filename); + my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); print "/$filepart/1.$meta->{revision}//$kopts/\n"; # permissions @@ -533,7 +533,8 @@ sub req_add print "Checked-in $dirpart\n"; print "$filename\n"; - my $kopts = kopts_from_path($filename); + my $kopts = kopts_from_path($filename,"file", + $state->{entries}{$filename}{modified_filename}); print "/$filepart/0//$kopts/\n"; my $requestedKopts = $state->{opt}{k}; @@ -631,7 +632,7 @@ sub req_remove print "Checked-in $dirpart\n"; print "$filename\n"; - my $kopts = kopts_from_path($filename); + my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); print "/$filepart/-1.$wrev//$kopts/\n"; $rmcount++; @@ -910,7 +911,7 @@ sub req_co print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; # this is an "entries" line - my $kopts = kopts_from_path($fullName); + my $kopts = kopts_from_path($fullName,"sha1",$git->{filehash}); print "/$git->{name}/1.$git->{revision}//$kopts/\n"; # permissions print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n"; @@ -1119,7 +1120,7 @@ sub req_update print $state->{CVSROOT} . "/$state->{module}/$filename\n"; # this is an "entries" line - my $kopts = kopts_from_path($filename); + my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); print "/$filepart/1.$meta->{revision}//$kopts/\n"; @@ -1167,7 +1168,8 @@ sub req_update print "Merged $dirpart\n"; $log->debug($state->{CVSROOT} . "/$state->{module}/$filename"); print $state->{CVSROOT} . "/$state->{module}/$filename\n"; - my $kopts = kopts_from_path("$dirpart/$filepart"); + my $kopts = kopts_from_path("$dirpart/$filepart", + "file",$mergedFile); $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); print "/$filepart/1.$meta->{revision}//$kopts/\n"; } @@ -1183,7 +1185,8 @@ sub req_update { print "Merged $dirpart\n"; print $state->{CVSROOT} . "/$state->{module}/$filename\n"; - my $kopts = kopts_from_path("$dirpart/$filepart"); + my $kopts = kopts_from_path("$dirpart/$filepart", + "file",$mergedFile); print "/$filepart/1.$meta->{revision}/+/$kopts/\n"; } } @@ -1434,7 +1437,7 @@ sub req_ci } print "Checked-in $dirpart\n"; print "$filename\n"; - my $kopts = kopts_from_path($filename); + my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); print "/$filepart/1.$meta->{revision}//$kopts/\n"; } } @@ -2312,7 +2315,7 @@ sub cleanupTmpDir # file should get -kb. sub kopts_from_path { - my ($path) = @_; + my ($path, $srcType, $name) = @_; if ( defined ( $cfg->{gitcvs}{usecrlfattr} ) and $cfg->{gitcvs}{usecrlfattr} =~ /\s*(1|true|yes)\s*$/i ) @@ -2332,15 +2335,55 @@ sub kopts_from_path } } - unless ( defined ( $cfg->{gitcvs}{allbinary} ) and $cfg->{gitcvs}{allbinary} =~ /^\s*(1|true|yes)\s*$/i ) + if ( defined ( $cfg->{gitcvs}{allbinary} ) ) { - # Return "" to give no special treatment to any path - return ""; - } else { - # Alternatively, to have all files treated as if they are binary (which - # is more like git itself), always return the "-kb" option - return "-kb"; + if( ($cfg->{gitcvs}{allbinary} =~ /^\s*(1|true|yes)\s*$/i) ) + { + return "-kb"; + } + elsif( ($cfg->{gitcvs}{allbinary} =~ /^\s*guess\s*$/i) ) + { + if( $srcType eq "sha1Or-k" && + !defined($name) ) + { + my ($ret)=$state->{entries}{$path}{options}; + if( !defined($ret) ) + { + $ret=$state->{opt}{k}; + if(defined($ret)) + { + $ret="-k$ret"; + } + else + { + $ret=""; + } + } + if( ! ($ret=~/^(|-kb|-kkv|-kkvl|-kk|-ko|-kv)$/) ) + { + print "E Bad -k option\n"; + $log->warn("Bad -k option: $ret"); + die "Error: Bad -k option: $ret\n"; + } + + return $ret; + } + else + { + if( is_binary($srcType,$name) ) + { + $log->debug("... as binary"); + return "-kb"; + } + else + { + $log->debug("... as text"); + } + } + } } + # Return "" to give no special treatment to any path + return ""; } sub check_attr @@ -2360,6 +2403,124 @@ sub check_attr } } +# This should have the same heuristics as convert.c:is_binary() and related. +# Note that the bare CR test is done by callers in convert.c. +sub is_binary +{ + my ($srcType,$name) = @_; + $log->debug("is_binary($srcType,$name)"); + + # Minimize amount of interpreted code run in the inner per-character + # loop for large files, by totalling each character value and + # then analyzing the totals. + my @counts; + my $i; + for($i=0;$i<256;$i++) + { + $counts[$i]=0; + } + + my $fh = open_blob_or_die($srcType,$name); + my $line; + while( defined($line=<$fh>) ) + { + # Any '\0' and bare CR are considered binary. + if( $line =~ /\0|(\r[^\n])/ ) + { + close($fh); + return 1; + } + + # Count up each character in the line: + my $len=length($line); + for($i=0;$i<$len;$i++) + { + $counts[ord(substr($line,$i,1))]++; + } + } + close $fh; + + # Don't count CR and LF as either printable/nonprintable + $counts[ord("\n")]=0; + $counts[ord("\r")]=0; + + # Categorize individual character count into printable and nonprintable: + my $printable=0; + my $nonprintable=0; + for($i=0;$i<256;$i++) + { + if( $i < 32 && + $i != ord("\b") && + $i != ord("\t") && + $i != 033 && # ESC + $i != 014 ) # FF + { + $nonprintable+=$counts[$i]; + } + elsif( $i==127 ) # DEL + { + $nonprintable+=$counts[$i]; + } + else + { + $printable+=$counts[$i]; + } + } + + return ($printable >> 7) < $nonprintable; +} + +# Returns open file handle. Possible invocations: +# - open_blob_or_die("file",$filename); +# - open_blob_or_die("sha1",$filehash); +sub open_blob_or_die +{ + my ($srcType,$name) = @_; + my ($fh); + if( $srcType eq "file" ) + { + if( !open $fh,"<",$name ) + { + $log->warn("Unable to open file $name: $!"); + die "Unable to open file $name: $!\n"; + } + } + elsif( $srcType eq "sha1" || $srcType eq "sha1Or-k" ) + { + unless ( defined ( $name ) and $name =~ /^[a-zA-Z0-9]{40}$/ ) + { + $log->warn("Need filehash"); + die "Need filehash\n"; + } + + my $type = `git cat-file -t $name`; + chomp $type; + + unless ( defined ( $type ) and $type eq "blob" ) + { + $log->warn("Invalid type '$type' for '$name'"); + die ( "Invalid type '$type' (expected 'blob')" ) + } + + my $size = `git cat-file -s $name`; + chomp $size; + + $log->debug("open_blob_or_die($name) size=$size, type=$type"); + + unless( open $fh, '-|', "git", "cat-file", "blob", $name ) + { + $log->warn("Unable to open sha1 $name"); + die "Unable to open sha1 $name\n"; + } + } + else + { + $log->warn("Unknown type of blob source: $srcType"); + die "Unknown type of blob source: $srcType\n"; + } + return $fh; +} + # Generate a CVS author name from Git author information, by taking # the first eight characters of the user part of the email address. sub cvs_author diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index b7a779b788..e27a1c5f85 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -175,4 +175,163 @@ test_expect_success 'updating' ' cmp cvswork/binfile.bin tmpExpect1 ' +rm -rf cvswork +test_expect_success 'cvs co (use attributes/guess)' ' + GIT_DIR="$SERVERDIR" git config gitcvs.allbinary guess && + GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 && + marked_as cvswork textfile.c "" && + marked_as cvswork binfile.bin -kb && + marked_as cvswork .gitattributes "" && + marked_as cvswork mixedUp.c "" && + marked_as cvswork/subdir withCr.bin -kb && + marked_as cvswork/subdir file.h "" && + marked_as cvswork/subdir unspecified.other "" && + marked_as cvswork/subdir newfile.bin -kb && + marked_as cvswork/subdir newfile.c "" +' + +test_expect_success 'setup multi-line files' ' + ( echo "line 1" && + echo "line 2" && + echo "line 3" && + echo "line 4 with NUL: Q <-" ) | q_to_nul > multiline.c && + git add multiline.c && + ( echo "line 1" && + echo "line 2" && + echo "line 3" && + echo "line 4" ) | q_to_nul > multilineTxt.c && + git add multilineTxt.c && + git commit -q -m "multiline files" && + git push gitcvs.git >/dev/null +' + +rm -rf cvswork +test_expect_success 'cvs co (guess)' ' + GIT_DIR="$SERVERDIR" git config --bool gitcvs.usecrlfattr false && + GIT_CONFIG="$git_config" cvs -Q co -d cvswork master >cvs.log 2>&1 && + marked_as cvswork textfile.c "" && + marked_as cvswork binfile.bin -kb && + marked_as cvswork .gitattributes "" && + marked_as cvswork mixedUp.c -kb && + marked_as cvswork multiline.c -kb && + marked_as cvswork multilineTxt.c "" && + marked_as cvswork/subdir withCr.bin -kb && + marked_as cvswork/subdir file.h "" && + marked_as cvswork/subdir unspecified.other "" && + marked_as cvswork/subdir newfile.bin "" && + marked_as cvswork/subdir newfile.c "" +' + +test_expect_success 'cvs co another copy (guess)' ' + GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 && + marked_as cvswork2 textfile.c "" && + marked_as cvswork2 binfile.bin -kb && + marked_as cvswork2 .gitattributes "" && + marked_as cvswork2 mixedUp.c -kb && + marked_as cvswork2 multiline.c -kb && + marked_as cvswork2 multilineTxt.c "" && + marked_as cvswork2/subdir withCr.bin -kb && + marked_as cvswork2/subdir file.h "" && + marked_as cvswork2/subdir unspecified.other "" && + marked_as cvswork2/subdir newfile.bin "" && + marked_as cvswork2/subdir newfile.c "" +' + +test_expect_success 'add text (guess)' ' + cd cvswork && + echo "simpleText" > simpleText.c && + GIT_CONFIG="$git_config" cvs -Q add simpleText.c && + cd .. && + marked_as cvswork simpleText.c "" +' + +test_expect_success 'add bin (guess)' ' + cd cvswork && + echo "simpleBin: NUL: Q <- there" | q_to_nul > simpleBin.bin && + GIT_CONFIG="$git_config" cvs -Q add simpleBin.bin && + cd .. && + marked_as cvswork simpleBin.bin -kb +' + +test_expect_success 'remove files (guess)' ' + cd cvswork && + GIT_CONFIG="$git_config" cvs -Q rm -f subdir/file.h && + cd subdir && + GIT_CONFIG="$git_config" cvs -Q rm -f withCr.bin && + cd ../.. && + marked_as cvswork/subdir withCr.bin -kb && + marked_as cvswork/subdir file.h "" +' + +test_expect_success 'cvs ci (guess)' ' + cd cvswork && + GIT_CONFIG="$git_config" cvs -Q ci -m "add/rm files" >cvs.log 2>&1 && + cd .. && + marked_as cvswork textfile.c "" && + marked_as cvswork binfile.bin -kb && + marked_as cvswork .gitattributes "" && + marked_as cvswork mixedUp.c -kb && + marked_as cvswork multiline.c -kb && + marked_as cvswork multilineTxt.c "" && + not_present cvswork/subdir withCr.bin && + not_present cvswork/subdir file.h && + marked_as cvswork/subdir unspecified.other "" && + marked_as cvswork/subdir newfile.bin "" && + marked_as cvswork/subdir newfile.c "" && + marked_as cvswork simpleBin.bin -kb && + marked_as cvswork simpleText.c "" +' + +test_expect_success 'update subdir of other copy (guess)' ' + cd cvswork2/subdir && + GIT_CONFIG="$git_config" cvs -Q update && + cd ../.. && + marked_as cvswork2 textfile.c "" && + marked_as cvswork2 binfile.bin -kb && + marked_as cvswork2 .gitattributes "" && + marked_as cvswork2 mixedUp.c -kb && + marked_as cvswork2 multiline.c -kb && + marked_as cvswork2 multilineTxt.c "" && + not_present cvswork2/subdir withCr.bin && + not_present cvswork2/subdir file.h && + marked_as cvswork2/subdir unspecified.other "" && + marked_as cvswork2/subdir newfile.bin "" && + marked_as cvswork2/subdir newfile.c "" && + not_present cvswork2 simpleBin.bin && + not_present cvswork2 simpleText.c +' + +echo "starting update/merge" >> "${WORKDIR}/marked.log" +test_expect_success 'update/merge full other copy (guess)' ' + git pull gitcvs.git master && + sed "s/3/replaced_3/" < multilineTxt.c > ml.temp && + mv ml.temp multilineTxt.c && + git add multilineTxt.c && + git commit -q -m "modify multiline file" >> "${WORKDIR}/marked.log" && + git push gitcvs.git >/dev/null && + cd cvswork2 && + sed "s/1/replaced_1/" < multilineTxt.c > ml.temp && + mv ml.temp multilineTxt.c && + GIT_CONFIG="$git_config" cvs update > cvs.log 2>&1 && + cd .. && + marked_as cvswork2 textfile.c "" && + marked_as cvswork2 binfile.bin -kb && + marked_as cvswork2 .gitattributes "" && + marked_as cvswork2 mixedUp.c -kb && + marked_as cvswork2 multiline.c -kb && + marked_as cvswork2 multilineTxt.c "" && + not_present cvswork2/subdir withCr.bin && + not_present cvswork2/subdir file.h && + marked_as cvswork2/subdir unspecified.other "" && + marked_as cvswork2/subdir newfile.bin "" && + marked_as cvswork2/subdir newfile.c "" && + marked_as cvswork2 simpleBin.bin -kb && + marked_as cvswork2 simpleText.c "" && + echo "line replaced_1" > tmpExpect2 && + echo "line 2" >> tmpExpect2 && + echo "line replaced_3" >> tmpExpect2 && + echo "line 4" | q_to_nul >> tmpExpect2 && + cmp cvswork2/multilineTxt.c tmpExpect2 +' + test_done From b592d88fb21fb3e3216d8eface9b748b2868323b Mon Sep 17 00:00:00 2001 From: Gustaf Hendeby Date: Thu, 22 May 2008 04:28:43 +0200 Subject: [PATCH 4/4] Documentation: Fix skipped section level With xmlto 0.0.18 it seems to demand that no section levels are skipped. The commit 'implement gitcvs.usecrlfattr' (8a06a632976ead) one such skip, which here is removed by increasing the level of the offender. Signed-off-by: Gustaf Hendeby Signed-off-by: Junio C Hamano --- Documentation/git-cvsserver.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index cd0685ea67..a33382ec2d 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -302,7 +302,7 @@ Legacy monitoring operations are not supported (edit, watch and related). Exports and tagging (tags and branches) are not supported at this stage. CRLF Line Ending Conversions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default the server leaves the '-k' mode blank for all files, which causes the cvs client to treat them as a text files, subject