use strict;
use warnings;
+use bytes;
use Fcntl;
use File::Temp qw/tempdir tempfile/;
# $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 = {};
+my $state = { prependdir => '' };
$log->info("--------------- STARTING -----------------");
my $TEMP_DIR = tempdir( CLEANUP => 1 );
return 0;
}
- my @gitvars = `git-repo-config -l`;
+ my @gitvars = `git-config -l`;
if ($?) {
- print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
+ print "E problems executing git-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
print "E \n";
- print "error 1 - problem executing git-repo-config\n";
+ print "error 1 - problem executing git-config\n";
return 0;
}
foreach my $line ( @gitvars )
{
my ( $cmd, $data ) = @_;
$log->debug("req_Globaloption : $data");
-
- # TODO : is this data useful ???
+ $state->{globaloptions}{$data} = 1;
}
# Valid-responses request-list \n
$state->{localdir} = $data;
$state->{repository} = $repository;
- $state->{directory} = $repository;
- $state->{directory} =~ s/^$state->{CVSROOT}\///;
- $state->{module} = $1 if ($state->{directory} =~ s/^(.*?)(\/|$)//);
+ $state->{path} = $repository;
+ $state->{path} =~ s/^$state->{CVSROOT}\///;
+ $state->{module} = $1 if ($state->{path} =~ s/^(.*?)(\/|$)//);
+ $state->{path} .= "/" if ( $state->{path} =~ /\S/ );
+
+ $state->{directory} = $state->{localdir};
+ $state->{directory} = "" if ( $state->{directory} eq "." );
$state->{directory} .= "/" if ( $state->{directory} =~ /\S/ );
- $log->debug("req_Directory : localdir=$data repository=$repository directory=$state->{directory} module=$state->{module}");
+ if ( (not defined($state->{prependdir}) or $state->{prependdir} eq '') and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
+ {
+ $log->info("Setting prepend to '$state->{path}'");
+ $state->{prependdir} = $state->{path};
+ foreach my $entry ( keys %{$state->{entries}} )
+ {
+ $state->{entries}{$state->{prependdir} . $entry} = $state->{entries}{$entry};
+ delete $state->{entries}{$entry};
+ }
+ }
+
+ if ( defined ( $state->{prependdir} ) )
+ {
+ $log->debug("Prepending '$state->{prependdir}' to state|directory");
+ $state->{directory} = $state->{prependdir} . $state->{directory}
+ }
+ $log->debug("req_Directory : localdir=$data repository=$repository path=$state->{path} directory=$state->{directory} module=$state->{module}");
}
# Entry entry-line \n
{
my ( $cmd, $data ) = @_;
- $log->debug("req_Entry : $data");
+ #$log->debug("req_Entry : $data");
my @data = split(/\//, $data);
options => $data[4],
tag_or_date => $data[5],
};
+
+ $log->info("Received entry line '$data' => '" . $state->{directory} . $data[1] . "'");
+}
+
+# Questionable filename \n
+# Response expected: no. Additional data: no. Tell the server to check
+# whether filename should be ignored, and if not, next time the server
+# sends responses, send (in a M response) `?' followed by the directory and
+# filename. filename must not contain `/'; it needs to be a file in the
+# directory named by the most recent Directory request.
+sub req_Questionable
+{
+ my ( $cmd, $data ) = @_;
+
+ $log->debug("req_Questionable : $data");
+ $state->{entries}{$state->{directory}.$data}{questionable} = 1;
}
# add \n
next;
}
-
- my ( $filepart, $dirpart ) = filenamesplit($filename);
+ my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
print "E cvs add: scheduling file `$filename' for addition\n";
print "Checked-in $dirpart\n";
print "$filename\n";
- print "/$filepart/0///\n";
+ my $kopts = kopts_from_path($filepart);
+ print "/$filepart/0//$kopts/\n";
$addcount++;
}
}
- my ( $filepart, $dirpart ) = filenamesplit($filename);
+ my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
print "E cvs remove: scheduling `$filename' for removal\n";
print "Checked-in $dirpart\n";
print "$filename\n";
- print "/$filepart/-1.$wrev///\n";
+ my $kopts = kopts_from_path($filepart);
+ print "/$filepart/-1.$wrev//$kopts/\n";
$rmcount++;
}
#$log->debug("req_Unchanged : $data");
}
-# Questionable filename \n
-# Response expected: no. Additional data: no.
-# Tell the server to check whether filename should be ignored,
-# and if not, next time the server sends responses, send (in
-# a M response) `?' followed by the directory and filename.
-# filename must not contain `/'; it needs to be a file in the
-# directory named by the most recent Directory request.
-sub req_Questionable
-{
- my ( $cmd, $data ) = @_;
-
- $state->{entries}{$state->{directory}.$data}{questionable} = 1;
-
- #$log->debug("req_Questionable : $data");
-}
-
# Argument text \n
# Response expected: no. Save argument for use in a subsequent command.
# Arguments accumulate until an argument-using command is given, at which
{
my ( $cmd, $data ) = @_;
- # TODO : Not quite sure how Argument and Argumentx differ, but I assume
- # it's for multi-line arguments ... somehow ...
+ # Argumentx means: append to last Argument (with a newline in front)
$log->debug("$cmd : $data");
- push @{$state->{arguments}}, $data;
+ if ( $cmd eq 'Argumentx') {
+ ${$state->{arguments}}[$#{$state->{arguments}}] .= "\n" . $data;
+ } else {
+ push @{$state->{arguments}}, $data;
+ }
}
# expand-modules \n
print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
# this is an "entries" line
- print "/$git->{name}/1.$git->{revision}///\n";
+ my $kopts = kopts_from_path($git->{name});
+ print "/$git->{name}/1.$git->{revision}//$kopts/\n";
# permissions
print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n";
$updater->update();
- # if no files were specified, we need to work out what files we should be providing status on ...
- argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+ argsfromdir($updater);
#$log->debug("update state : " . Dumper($state));
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
$filename = filecleanup($filename);
+ $log->debug("Processing file $filename");
+
# if we have a -C we should pretend we never saw modified stuff
if ( exists ( $state->{opt}{C} ) )
{
$meta = $updater->getmeta($filename);
}
- next unless ( $meta->{revision} );
+ if ( ! defined $meta )
+ {
+ $meta = {
+ name => $filename,
+ revision => 0,
+ filehash => 'added'
+ };
+ }
my $oldmeta = $meta;
and not exists ( $state->{opt}{C} ) )
{
$log->info("Tell the client the file is modified");
- print "MT text U\n";
+ print "MT text M \n";
print "MT fname $filename\n";
print "MT newline\n";
next;
if ( $meta->{filehash} eq "deleted" )
{
- my ( $filepart, $dirpart ) = filenamesplit($filename);
+ my ( $filepart, $dirpart ) = filenamesplit($filename,1);
$log->info("Removing '$filename' from working copy (no longer in the repo)");
print "E cvs update: `$filename' is no longer in the repository\n";
- print "Removed $dirpart\n";
- print "$filepart\n";
+ # Don't want to actually _DO_ the update if -n specified
+ unless ( $state->{globaloptions}{-n} ) {
+ print "Removed $dirpart\n";
+ print "$filepart\n";
+ }
}
elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
- or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
+ or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash}
+ or $meta->{filehash} eq 'added' )
{
- $log->info("Updating '$filename'");
- # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove)
- print "MT +updated\n";
- print "MT text U\n";
- print "MT fname $filename\n";
- print "MT newline\n";
- print "MT -updated\n";
-
- my ( $filepart, $dirpart ) = filenamesplit($filename);
- $dirpart =~ s/^$state->{directory}//;
-
- if ( defined ( $wrev ) )
- {
- # instruct client we're sending a file to put in this path as a replacement
- print "Update-existing $dirpart\n";
- $log->debug("Updating existing file 'Update-existing $dirpart'");
- } else {
- # instruct client we're sending a file to put in this path as a new file
- print "Created $dirpart\n";
- $log->debug("Creating new file 'Created $dirpart'");
- }
- print $state->{CVSROOT} . "/$state->{module}/$filename\n";
-
- # this is an "entries" line
- $log->debug("/$filepart/1.$meta->{revision}///");
- print "/$filepart/1.$meta->{revision}///\n";
+ # normal update, just send the new revision (either U=Update,
+ # or A=Add, or R=Remove)
+ if ( defined($wrev) && $wrev < 0 )
+ {
+ $log->info("Tell the client the file is scheduled for removal");
+ print "MT text R \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ next;
+ }
+ elsif ( (!defined($wrev) || $wrev == 0) && (!defined($meta->{revision}) || $meta->{revision} == 0) )
+ {
+ $log->info("Tell the client the file is scheduled for addition");
+ print "MT text A \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ next;
- # permissions
- $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
- print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+ }
+ else {
+ $log->info("Updating '$filename' to ".$meta->{revision});
+ print "MT +updated\n";
+ print "MT text U \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ print "MT -updated\n";
+ }
- # transmit file
- transmitfile($meta->{filehash});
+ my ( $filepart, $dirpart ) = filenamesplit($filename,1);
+
+ # Don't want to actually _DO_ the update if -n specified
+ unless ( $state->{globaloptions}{-n} )
+ {
+ if ( defined ( $wrev ) )
+ {
+ # instruct client we're sending a file to put in this path as a replacement
+ print "Update-existing $dirpart\n";
+ $log->debug("Updating existing file 'Update-existing $dirpart'");
+ } else {
+ # instruct client we're sending a file to put in this path as a new file
+ print "Clear-static-directory $dirpart\n";
+ print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+ print "Clear-sticky $dirpart\n";
+ print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+
+ $log->debug("Creating new file 'Created $dirpart'");
+ print "Created $dirpart\n";
+ }
+ print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+
+ # this is an "entries" line
+ my $kopts = kopts_from_path($filepart);
+ $log->debug("/$filepart/1.$meta->{revision}//$kopts/");
+ print "/$filepart/1.$meta->{revision}//$kopts/\n";
+
+ # permissions
+ $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+ print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+ # transmit file
+ transmitfile($meta->{filehash});
+ }
} else {
$log->info("Updating '$filename'");
- my ( $filepart, $dirpart ) = filenamesplit($meta->{name});
+ my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);
my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
$log->debug("Temporary directory for merge is $dir");
- my $return = system("merge", $file_local, $file_old, $file_new);
+ my $return = system("git", "merge-file", $file_local, $file_old, $file_new);
$return >>= 8;
if ( $return == 0 )
$log->info("Merged successfully");
print "M M $filename\n";
$log->debug("Update-existing $dirpart");
- print "Update-existing $dirpart\n";
- $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
- print $state->{CVSROOT} . "/$state->{module}/$filename\n";
- $log->debug("/$filepart/1.$meta->{revision}///");
- print "/$filepart/1.$meta->{revision}///\n";
+
+ # Don't want to actually _DO_ the update if -n specified
+ unless ( $state->{globaloptions}{-n} )
+ {
+ print "Update-existing $dirpart\n";
+ $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
+ print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+ my $kopts = kopts_from_path($filepart);
+ $log->debug("/$filepart/1.$meta->{revision}//$kopts/");
+ print "/$filepart/1.$meta->{revision}//$kopts/\n";
+ }
}
elsif ( $return == 1 )
{
$log->info("Merged with conflicts");
print "M C $filename\n";
- print "Update-existing $dirpart\n";
- print $state->{CVSROOT} . "/$state->{module}/$filename\n";
- print "/$filepart/1.$meta->{revision}/+//\n";
+
+ # Don't want to actually _DO_ the update if -n specified
+ unless ( $state->{globaloptions}{-n} )
+ {
+ print "Update-existing $dirpart\n";
+ print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+ my $kopts = kopts_from_path($filepart);
+ print "/$filepart/1.$meta->{revision}/+/$kopts/\n";
+ }
}
else
{
next;
}
- # permissions
- $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
- print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
-
- # 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`;
- $log->debug("File size : " . length($data));
- print length($data) . "\n";
- print $data;
+ # Don't want to actually _DO_ the update if -n specified
+ unless ( $state->{globaloptions}{-n} )
+ {
+ # permissions
+ $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+ print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+ # 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`;
+ $log->debug("File size : " . length($data));
+ print length($data) . "\n";
+ print $data;
+ }
chdir "/";
}
if ( -e $state->{CVSROOT} . "/index" )
{
+ $log->warn("file 'index' already exists in the git repository");
print "error 1 Index already exists in git repo\n";
exit;
}
- my $lockfile = "$state->{CVSROOT}/refs/heads/$state->{module}.lock";
- unless ( sysopen(LOCKFILE,$lockfile,O_EXCL|O_CREAT|O_WRONLY) )
- {
- print "error 1 Lock file '$lockfile' already exists, please try again\n";
- exit;
- }
-
# Grab a handle to the SQLite db and do any necessary updates
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("Lock successful, basing commit on '$tmpdir', index file is '$file_index'");
+ $log->info("Lockless commit start, basing commit on '$tmpdir', index file is '$file_index'");
$ENV{GIT_DIR} = $state->{CVSROOT} . "/";
$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";
+ exit;
+ }
+
chdir $tmpdir;
# populate the temporary index based
- system("git-read-tree", $state->{module});
+ system("git-read-tree", $parenthash);
unless ($? == 0)
{
die "Error running git-read-tree $state->{module} $file_index $!";
}
$log->info("Created index '$file_index' with for head $state->{module} - exit status $?");
-
my @committedfiles = ();
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
+ my $committedfile = $filename;
$filename = filecleanup($filename);
next unless ( exists $state->{entries}{$filename}{modified_filename} or not $state->{entries}{$filename}{unchanged} );
{
# fail everything if an up to date check fails
print "error 1 Up to date check failed for $filename\n";
- close LOCKFILE;
- unlink($lockfile);
chdir "/";
exit;
}
- push @committedfiles, $filename;
+ push @committedfiles, $committedfile;
$log->info("Committing $filename");
system("mkdir","-p",$dirpart) unless ( -d $dirpart );
{
print "E No files to commit\n";
print "ok\n";
- close LOCKFILE;
- unlink($lockfile);
chdir "/";
return;
}
my $treehash = `git-write-tree`;
- my $parenthash = `cat $ENV{GIT_DIR}refs/heads/$state->{module}`;
chomp $treehash;
- chomp $parenthash;
$log->debug("Treehash : $treehash, Parenthash : $parenthash");
close $msg_fh;
my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`;
+ chomp($commithash);
$log->info("Commit hash : $commithash");
unless ( $commithash =~ /[a-zA-Z0-9]{40}/ )
{
$log->warn("Commit failed (Invalid commit hash)");
print "error 1 Commit failed (unknown reason)\n";
- close LOCKFILE;
- unlink($lockfile);
chdir "/";
exit;
}
- open FILE, ">", "$ENV{GIT_DIR}refs/heads/$state->{module}";
- print FILE $commithash;
- close FILE;
+ # Check that this is allowed, just as we would with a receive-pack
+ my @cmd = ( $ENV{GIT_DIR}.'hooks/update', "refs/heads/$state->{module}",
+ $parenthash, $commithash );
+ if( -x $cmd[0] ) {
+ unless( system( @cmd ) == 0 )
+ {
+ $log->warn("Commit failed (update hook declined to update ref)");
+ print "error 1 Commit failed (update hook declined)\n";
+ chdir "/";
+ exit;
+ }
+ }
+
+ if (system(qw(git update-ref -m), "cvsserver ci",
+ "refs/heads/$state->{module}", $commithash, $parenthash)) {
+ $log->warn("update-ref for $state->{module} failed.");
+ print "error 1 Cannot commit -- update first\n";
+ exit;
+ }
$updater->update();
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @committedfiles )
{
$filename = filecleanup($filename);
my $meta = $updater->getmeta($filename);
+ unless (defined $meta->{revision}) {
+ $meta->{revision} = 1;
+ }
- my ( $filepart, $dirpart ) = filenamesplit($filename);
+ my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
$log->debug("Checked-in $dirpart : $filename");
- if ( $meta->{filehash} eq "deleted" )
+ if ( defined $meta->{filehash} && $meta->{filehash} eq "deleted" )
{
print "Remove-entry $dirpart\n";
print "$filename\n";
} else {
print "Checked-in $dirpart\n";
print "$filename\n";
- print "/$filepart/1.$meta->{revision}///\n";
+ my $kopts = kopts_from_path($filepart);
+ print "/$filepart/1.$meta->{revision}//$kopts/\n";
}
}
- close LOCKFILE;
- unlink($lockfile);
chdir "/";
-
print "ok\n";
}
$updater->update();
# if no files were specified, we need to work out what files we should be providing status on ...
- argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+ argsfromdir($updater);
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
$filename = filecleanup($filename);
$updater->update();
# if no files were specified, we need to work out what files we should be providing status on ...
- argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+ argsfromdir($updater);
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
$filename = filecleanup($filename);
$updater->update();
# if no files were specified, we need to work out what files we should be providing status on ...
- argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+ argsfromdir($updater);
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
$filename = filecleanup($filename);
$updater->update();
# if no files were specified, we need to work out what files we should be providing annotate on ...
- argsfromdir($updater) if ( scalar ( @{$state->{args}} ) == 0 );
+ argsfromdir($updater);
# we'll need a temporary checkout dir
my $tmpdir = tempdir ( DIR => $TEMP_DIR );
chdir $tmpdir;
- # foreach file specified on the commandline ...
+ # foreach file specified on the command line ...
foreach my $filename ( @{$state->{args}} )
{
$filename = filecleanup($filename);
{
my $updater = shift;
- $state->{args} = [];
+ $state->{args} = [] if ( scalar(@{$state->{args}}) == 1 and $state->{args}[0] eq "." );
+
+ return if ( scalar ( @{$state->{args}} ) > 1 );
+
+ my @gethead = @{$updater->gethead};
- foreach my $file ( @{$updater->gethead} )
+ # push added files
+ foreach my $file (keys %{$state->{entries}}) {
+ if ( exists $state->{entries}{$file}{revision} &&
+ $state->{entries}{$file}{revision} == 0 )
+ {
+ push @gethead, { name => $file, filehash => 'added' };
+ }
+ }
+
+ if ( scalar(@{$state->{args}}) == 1 )
{
- next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
- next unless ( $file->{name} =~ s/^$state->{directory}// );
- push @{$state->{args}}, $file->{name};
+ my $arg = $state->{args}[0];
+ $arg .= $state->{prependdir} if ( defined ( $state->{prependdir} ) );
+
+ $log->info("Only one arg specified, checking for directory expansion on '$arg'");
+
+ foreach my $file ( @gethead )
+ {
+ next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+ next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg );
+ push @{$state->{args}}, $file->{name};
+ }
+
+ shift @{$state->{args}} if ( scalar(@{$state->{args}}) > 1 );
+ } else {
+ $log->info("Only one arg specified, populating file list automatically");
+
+ $state->{args} = [];
+
+ foreach my $file ( @gethead )
+ {
+ next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+ next unless ( $file->{name} =~ s/^$state->{prependdir}// );
+ push @{$state->{args}}, $file->{name};
+ }
}
}
sub filenamesplit
{
my $filename = shift;
+ my $fixforlocaldir = shift;
my ( $filepart, $dirpart ) = ( $filename, "." );
( $filepart, $dirpart ) = ( $2, $1 ) if ( $filename =~ /(.*)\/(.*)/ );
$dirpart .= "/";
+ if ( $fixforlocaldir )
+ {
+ $dirpart =~ s/^$state->{prependdir}//;
+ }
+
return ( $filepart, $dirpart );
}
}
$filename =~ s/^\.\///g;
- $filename = $state->{directory} . $filename;
-
+ $filename = $state->{prependdir} . $filename;
return $filename;
}
+# 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.
+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.
+
+ # 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
+ 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";
+ }
+}
+
package GITCVS::log;
####
mode TEXT NOT NULL
)
");
+ $self->{dbh}->do("
+ CREATE INDEX revision_ix1
+ ON revision (name,revision)
+ ");
+ $self->{dbh}->do("
+ CREATE INDEX revision_ix2
+ ON revision (name,commithash)
+ ");
}
- # Construct the revision table if required
+ # Construct the head table if required
unless ( $self->{tables}{head} )
{
$self->{dbh}->do("
mode TEXT NOT NULL
)
");
+ $self->{dbh}->do("
+ CREATE INDEX head_ix1
+ ON head (name)
+ ");
}
# Construct the properties table if required
# first lets get the commit list
$ENV{GIT_DIR} = $self->{git_path};
- # prepare database queries
- my $db_insert_rev = $self->{dbh}->prepare_cached("INSERT INTO revision (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
- my $db_insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO commitmsgs (key, value) VALUES (?,?)",{},1);
- my $db_delete_head = $self->{dbh}->prepare_cached("DELETE FROM head",{},1);
- my $db_insert_head = $self->{dbh}->prepare_cached("INSERT INTO head (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+ my $commitsha1 = `git rev-parse $self->{module}`;
+ chomp $commitsha1;
- my $commitinfo = `git-cat-file commit $self->{module} 2>&1`;
+ my $commitinfo = `git cat-file commit $self->{module} 2>&1`;
unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{40}/ )
{
die("Invalid module '$self->{module}'");
my $git_log;
my $lastcommit = $self->_get_prop("last_commit");
+ if (defined $lastcommit && $lastcommit eq $commitsha1) { # up-to-date
+ return 1;
+ }
+
# Start exclusive lock here...
$self->{dbh}->begin_work() or die "Cannot lock database for BEGIN";
if ( defined ( $lastpicked ) )
{
- my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
+ my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
+ local ($/) = "\0";
while ( <FILELIST> )
{
- unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)\s+(.*)$/o )
+ chomp;
+ unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)$/o )
{
die("Couldn't process git-diff-tree line : $_");
}
+ my ($mode, $hash, $change) = ($1, $2, $3);
+ my $name = <FILELIST>;
+ chomp($name);
- # $log->debug("File mode=$1, hash=$2, change=$3, name=$4");
+ # $log->debug("File mode=$mode, hash=$hash, change=$change, name=$name");
my $git_perms = "";
- $git_perms .= "r" if ( $1 & 4 );
- $git_perms .= "w" if ( $1 & 2 );
- $git_perms .= "x" if ( $1 & 1 );
+ $git_perms .= "r" if ( $mode & 4 );
+ $git_perms .= "w" if ( $mode & 2 );
+ $git_perms .= "x" if ( $mode & 1 );
$git_perms = "rw" if ( $git_perms eq "" );
- if ( $3 eq "D" )
+ if ( $change eq "D" )
{
- #$log->debug("DELETE $4");
- $head->{$4} = {
- name => $4,
- revision => $head->{$4}{revision} + 1,
+ #$log->debug("DELETE $name");
+ $head->{$name} = {
+ name => $name,
+ revision => $head->{$name}{revision} + 1,
filehash => "deleted",
commithash => $commit->{hash},
modified => $commit->{date},
author => $commit->{author},
mode => $git_perms,
};
- $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
- elsif ( $3 eq "M" )
+ elsif ( $change eq "M" )
{
- #$log->debug("MODIFIED $4");
- $head->{$4} = {
- name => $4,
- revision => $head->{$4}{revision} + 1,
- filehash => $2,
+ #$log->debug("MODIFIED $name");
+ $head->{$name} = {
+ name => $name,
+ revision => $head->{$name}{revision} + 1,
+ filehash => $hash,
commithash => $commit->{hash},
modified => $commit->{date},
author => $commit->{author},
mode => $git_perms,
};
- $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
- elsif ( $3 eq "A" )
+ elsif ( $change eq "A" )
{
- #$log->debug("ADDED $4");
- $head->{$4} = {
- name => $4,
+ #$log->debug("ADDED $name");
+ $head->{$name} = {
+ name => $name,
revision => 1,
- filehash => $2,
+ filehash => $hash,
commithash => $commit->{hash},
modified => $commit->{date},
author => $commit->{author},
mode => $git_perms,
};
- $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
else
{
- $log->warn("UNKNOWN FILE CHANGE mode=$1, hash=$2, change=$3, name=$4");
+ $log->warn("UNKNOWN FILE CHANGE mode=$mode, hash=$hash, change=$change, name=$name");
die;
}
}
# this is used to detect files removed from the repo
my $seen_files = {};
- my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
+ my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
+ local $/ = "\0";
while ( <FILELIST> )
{
- unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\s+(.*)$/o )
+ chomp;
+ unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
{
die("Couldn't process git-ls-tree line : $_");
}
};
- $db_insert_rev->execute($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+ $self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
}
}
close FILELIST;
$head->{$file}{modified} = $commit->{date};
$head->{$file}{author} = $commit->{author};
- $db_insert_rev->execute($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
+ $self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
}
}
# END : "Detect deleted files"
if (exists $commit->{mergemsg})
{
- $db_insert_mergelog->execute($commit->{hash}, $commit->{mergemsg});
+ $self->insert_mergelog($commit->{hash}, $commit->{mergemsg});
}
$lastpicked = $commit->{hash};
$self->_set_prop("last_commit", $commit->{hash});
}
- $db_delete_head->execute();
+ $self->delete_head();
foreach my $file ( keys %$head )
{
- $db_insert_head->execute(
+ $self->insert_head(
$file,
$head->{$file}{revision},
$head->{$file}{filehash},
$self->{dbh}->commit() or die "Failed to commit changes to SQLite";
}
+sub insert_rev
+{
+ my $self = shift;
+ my $name = shift;
+ my $revision = shift;
+ my $filehash = shift;
+ my $commithash = shift;
+ my $modified = shift;
+ my $author = shift;
+ my $mode = shift;
+
+ my $insert_rev = $self->{dbh}->prepare_cached("INSERT INTO revision (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+ $insert_rev->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
+}
+
+sub insert_mergelog
+{
+ my $self = shift;
+ my $key = shift;
+ my $value = shift;
+
+ my $insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO commitmsgs (key, value) VALUES (?,?)",{},1);
+ $insert_mergelog->execute($key, $value);
+}
+
+sub delete_head
+{
+ my $self = shift;
+
+ my $delete_head = $self->{dbh}->prepare_cached("DELETE FROM head",{},1);
+ $delete_head->execute();
+}
+
+sub insert_head
+{
+ my $self = shift;
+ my $name = shift;
+ my $revision = shift;
+ my $filehash = shift;
+ my $commithash = shift;
+ my $modified = shift;
+ my $author = shift;
+ my $mode = shift;
+
+ my $insert_head = $self->{dbh}->prepare_cached("INSERT INTO head (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+ $insert_head->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
+}
+
sub _headrev
{
my $self = shift;