in git-cvsserver.perl [3832:4138]
sub update
{
my $self = shift;
# first lets get the commit list
$ENV{GIT_DIR} = $self->{git_path};
my $commitsha1 = ::safe_pipe_capture('git', 'rev-parse', $self->{module});
chomp $commitsha1;
my $commitinfo = ::safe_pipe_capture('git', 'cat-file', 'commit', $self->{module});
unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{$state->{hexsz}}/ )
{
die("Invalid module '$self->{module}'");
}
my $git_log;
my $lastcommit = $self->_get_prop("last_commit");
if (defined $lastcommit && $lastcommit eq $commitsha1) { # up-to-date
# invalidate the gethead cache
$self->clearCommitRefCaches();
return 1;
}
# Start exclusive lock here...
$self->{dbh}->begin_work() or die "Cannot lock database for BEGIN";
# TODO: log processing is memory bound
# if we can parse into a 2nd file that is in reverse order
# we can probably do something really efficient
my @git_log_params = ('--pretty', '--parents', '--topo-order');
if (defined $lastcommit) {
push @git_log_params, "$lastcommit..$self->{module}";
} else {
push @git_log_params, $self->{module};
}
# git-rev-list is the backend / plumbing version of git-log
open(my $gitLogPipe, '-|', 'git', 'rev-list', @git_log_params)
or die "Cannot call git-rev-list: $!";
my @commits=readCommits($gitLogPipe);
close $gitLogPipe;
# Now all the commits are in the @commits bucket
# ordered by time DESC. for each commit that needs processing,
# determine whether it's following the last head we've seen or if
# it's on its own branch, grab a file list, and add whatever's changed
# NOTE: $lastcommit refers to the last commit from previous run
# $lastpicked is the last commit we picked in this run
my $lastpicked;
my $head = {};
if (defined $lastcommit) {
$lastpicked = $lastcommit;
}
my $committotal = scalar(@commits);
my $commitcount = 0;
# Load the head table into $head (for cached lookups during the update process)
foreach my $file ( @{$self->gethead(1)} )
{
$head->{$file->{name}} = $file;
}
foreach my $commit ( @commits )
{
$self->{log}->debug("GITCVS::updater - Processing commit $commit->{hash} (" . (++$commitcount) . " of $committotal)");
if (defined $lastpicked)
{
if (!in_array($lastpicked, @{$commit->{parents}}))
{
# skip, we'll see this delta
# as part of a merge later
# warn "skipping off-track $commit->{hash}\n";
next;
} elsif (@{$commit->{parents}} > 1) {
# it is a merge commit, for each parent that is
# not $lastpicked (not given a CVS revision number),
# see if we can get a log
# from the merge-base to that parent to put it
# in the message as a merge summary.
my @parents = @{$commit->{parents}};
foreach my $parent (@parents) {
if ($parent eq $lastpicked) {
next;
}
# git-merge-base can potentially (but rarely) throw
# several candidate merge bases. let's assume
# that the first one is the best one.
my $base = eval {
::safe_pipe_capture('git', 'merge-base',
$lastpicked, $parent);
};
# The two branches may not be related at all,
# in which case merge base simply fails to find
# any, but that's Ok.
next if ($@);
chomp $base;
if ($base) {
my @merged;
# print "want to log between $base $parent \n";
open(GITLOG, '-|', 'git', 'log', '--pretty=medium', "$base..$parent")
or die "Cannot call git-log: $!";
my $mergedhash;
while (<GITLOG>) {
chomp;
if (!defined $mergedhash) {
if (m/^commit\s+(.+)$/) {
$mergedhash = $1;
} else {
next;
}
} else {
# grab the first line that looks non-rfc822
# aka has content after leading space
if (m/^\s+(\S.*)$/) {
my $title = $1;
$title = substr($title,0,100); # truncate
unshift @merged, "$mergedhash $title";
undef $mergedhash;
}
}
}
close GITLOG;
if (@merged) {
$commit->{mergemsg} = $commit->{message};
$commit->{mergemsg} .= "\nSummary of merged commits:\n\n";
foreach my $summary (@merged) {
$commit->{mergemsg} .= "\t$summary\n";
}
$commit->{mergemsg} .= "\n\n";
# print "Message for $commit->{hash} \n$commit->{mergemsg}";
}
}
}
}
}
# convert the date to CVS-happy format
my $cvsDate = convertToCvsDate($commit->{date});
if ( defined ( $lastpicked ) )
{
my $filepipe = open(FILELIST, '-|', 'git', 'diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
local ($/) = "\0";
while ( <FILELIST> )
{
chomp;
unless ( /^:\d{6}\s+([0-7]{6})\s+[a-f0-9]{$state->{hexsz}}\s+([a-f0-9]{$state->{hexsz}})\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=$mode, hash=$hash, change=$change, name=$name");
my $dbMode = convertToDbMode($mode);
if ( $change eq "D" )
{
#$log->debug("DELETE $name");
$head->{$name} = {
name => $name,
revision => $head->{$name}{revision} + 1,
filehash => "deleted",
commithash => $commit->{hash},
modified => $cvsDate,
author => $commit->{author},
mode => $dbMode,
};
$self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
}
elsif ( $change eq "M" || $change eq "T" )
{
#$log->debug("MODIFIED $name");
$head->{$name} = {
name => $name,
revision => $head->{$name}{revision} + 1,
filehash => $hash,
commithash => $commit->{hash},
modified => $cvsDate,
author => $commit->{author},
mode => $dbMode,
};
$self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
}
elsif ( $change eq "A" )
{
#$log->debug("ADDED $name");
$head->{$name} = {
name => $name,
revision => $head->{$name}{revision} ? $head->{$name}{revision}+1 : 1,
filehash => $hash,
commithash => $commit->{hash},
modified => $cvsDate,
author => $commit->{author},
mode => $dbMode,
};
$self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
}
else
{
$log->warn("UNKNOWN FILE CHANGE mode=$mode, hash=$hash, change=$change, name=$name");
die;
}
}
close FILELIST;
} else {
# this is used to detect files removed from the repo
my $seen_files = {};
my $filepipe = open(FILELIST, '-|', 'git', 'ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
local $/ = "\0";
while ( <FILELIST> )
{
chomp;
unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
{
die("Couldn't process git-ls-tree line : $_");
}
my ( $mode, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
$seen_files->{$git_filename} = 1;
my ( $oldhash, $oldrevision, $oldmode ) = (
$head->{$git_filename}{filehash},
$head->{$git_filename}{revision},
$head->{$git_filename}{mode}
);
my $dbMode = convertToDbMode($mode);
# unless the file exists with the same hash, we need to update it ...
unless ( defined($oldhash) and $oldhash eq $git_hash and defined($oldmode) and $oldmode eq $dbMode )
{
my $newrevision = ( $oldrevision or 0 ) + 1;
$head->{$git_filename} = {
name => $git_filename,
revision => $newrevision,
filehash => $git_hash,
commithash => $commit->{hash},
modified => $cvsDate,
author => $commit->{author},
mode => $dbMode,
};
$self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
}
}
close FILELIST;
# Detect deleted files
foreach my $file ( sort keys %$head )
{
unless ( exists $seen_files->{$file} or $head->{$file}{filehash} eq "deleted" )
{
$head->{$file}{revision}++;
$head->{$file}{filehash} = "deleted";
$head->{$file}{commithash} = $commit->{hash};
$head->{$file}{modified} = $cvsDate;
$head->{$file}{author} = $commit->{author};
$self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $cvsDate, $commit->{author}, $head->{$file}{mode});
}
}
# END : "Detect deleted files"
}
if (exists $commit->{mergemsg})
{
$self->insert_mergelog($commit->{hash}, $commit->{mergemsg});
}
$lastpicked = $commit->{hash};
$self->_set_prop("last_commit", $commit->{hash});
}
$self->delete_head();
foreach my $file ( sort keys %$head )
{
$self->insert_head(
$file,
$head->{$file}{revision},
$head->{$file}{filehash},
$head->{$file}{commithash},
$head->{$file}{modified},
$head->{$file}{author},
$head->{$file}{mode},
);
}
# invalidate the gethead cache
$self->clearCommitRefCaches();
# Ending exclusive lock here
$self->{dbh}->commit() or die "Failed to commit changes to SQLite";
}