#### Definition and mappings of functions ####
+# NOTE: Despite the existence of req_CATCHALL and req_EMPTY unimplemented
+# requests, this list is incomplete. It is missing many rarer/optional
+# requests. Perhaps some clients require a claim of support for
+# these specific requests for main functionality to work?
my $methods = {
'Root' => \&req_Root,
'Valid-responses' => \&req_Validresponses,
'noop' => \&req_EMPTY,
'annotate' => \&req_annotate,
'Global_option' => \&req_Globaloption,
- #'annotate' => \&req_CATCHALL,
};
##############################################
if ( $meta->{filehash} eq "deleted" )
{
+ # TODO: If it has been modified in the sandbox, error out
+ # with the appropriate message, rather than deleting a modified
+ # file.
+
my ( $filepart, $dirpart ) = filenamesplit($filename,1);
$log->info("Removing '$filename' from working copy (no longer in the repo)");
#$log->debug("status state : " . Dumper($state));
# Grab a handle to the SQLite db and do any necessary updates
- my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+ my $updater;
+ $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
$updater->update();
- # if no files were specified, we need to work out what files we should be providing status on ...
+ # if no files were specified, we need to work out what files we should
+ # be providing status on ...
argsfromdir($updater);
# foreach file specified on the command line ...
{
$filename = filecleanup($filename);
- next if exists($state->{opt}{l}) && index($filename, '/', length($state->{prependdir})) >= 0;
+ if ( exists($state->{opt}{l}) &&
+ index($filename, '/', length($state->{prependdir})) >= 0 )
+ {
+ next;
+ }
my $meta = $updater->getmeta($filename);
my $oldmeta = $meta;
my $wrev = revparse($filename);
- # If the working copy is an old revision, lets get that version too for comparison.
+ # If the working copy is an old revision, lets get that
+ # version too for comparison.
if ( defined($wrev) and $wrev != $meta->{revision} )
{
$oldmeta = $updater->getmeta($filename, $wrev);
# TODO : All possible statuses aren't yet implemented
my $status;
- # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified
- $status = "Up-to-date" if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision}
- and
- ( ( $state->{entries}{$filename}{unchanged} and ( not defined ( $state->{entries}{$filename}{conflict} ) or $state->{entries}{$filename}{conflict} !~ /^\+=/ ) )
- or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $meta->{filehash} ) )
- );
-
- # Need checkout if the working copy has an older revision than the repo copy, and the working copy is unmodified
- $status ||= "Needs Checkout" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and $meta->{revision} > $wrev
- and
- ( $state->{entries}{$filename}{unchanged}
- or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) )
- );
-
- # Need checkout if it exists in the repo but doesn't have a working copy
- $status ||= "Needs Checkout" if ( not defined ( $wrev ) and defined ( $meta->{revision} ) );
-
- # Locally modified if working copy and repo copy have the same revision but there are local changes
- $status ||= "Locally Modified" if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{modified_filename} );
-
- # Needs Merge if working copy revision is less than repo copy and there are local changes
- $status ||= "Needs Merge" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and $meta->{revision} > $wrev and $state->{entries}{$filename}{modified_filename} );
-
- $status ||= "Locally Added" if ( defined ( $state->{entries}{$filename}{revision} ) and not defined ( $meta->{revision} ) );
- $status ||= "Locally Removed" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and -$wrev == $meta->{revision} );
- $status ||= "Unresolved Conflict" if ( defined ( $state->{entries}{$filename}{conflict} ) and $state->{entries}{$filename}{conflict} =~ /^\+=/ );
- $status ||= "File had conflicts on merge" if ( 0 );
+ # Files are up to date if the working copy and repo copy have
+ # the same revision, and the working copy is unmodified
+ if ( defined ( $wrev ) and defined($meta->{revision}) and
+ $wrev == $meta->{revision} and
+ ( ( $state->{entries}{$filename}{unchanged} and
+ ( not defined ( $state->{entries}{$filename}{conflict} ) or
+ $state->{entries}{$filename}{conflict} !~ /^\+=/ ) ) or
+ ( defined($state->{entries}{$filename}{modified_hash}) and
+ $state->{entries}{$filename}{modified_hash} eq
+ $meta->{filehash} ) ) )
+ {
+ $status = "Up-to-date";
+ }
+
+ # Need checkout if the working copy has an older revision than
+ # the repo copy, and the working copy is unmodified
+ if ( defined ( $wrev ) and defined ( $meta->{revision} ) and
+ $meta->{revision} > $wrev and
+ ( $state->{entries}{$filename}{unchanged} or
+ ( defined($state->{entries}{$filename}{modified_hash}) and
+ $state->{entries}{$filename}{modified_hash} eq
+ $oldmeta->{filehash} ) ) )
+ {
+ $status ||= "Needs Checkout";
+ }
+
+ # Need checkout if it exists in the repo but doesn't have a working
+ # copy
+ if ( not defined ( $wrev ) and defined ( $meta->{revision} ) )
+ {
+ $status ||= "Needs Checkout";
+ }
+
+ # Locally modified if working copy and repo copy have the
+ # same revision but there are local changes
+ if ( defined ( $wrev ) and defined($meta->{revision}) and
+ $wrev == $meta->{revision} and
+ $state->{entries}{$filename}{modified_filename} )
+ {
+ $status ||= "Locally Modified";
+ }
+
+ # Needs Merge if working copy revision is less than repo copy
+ # and there are local changes
+ if ( defined ( $wrev ) and defined ( $meta->{revision} ) and
+ $meta->{revision} > $wrev and
+ $state->{entries}{$filename}{modified_filename} )
+ {
+ $status ||= "Needs Merge";
+ }
+
+ if ( defined ( $state->{entries}{$filename}{revision} ) and
+ not defined ( $meta->{revision} ) )
+ {
+ $status ||= "Locally Added";
+ }
+ if ( defined ( $wrev ) and defined ( $meta->{revision} ) and
+ -$wrev == $meta->{revision} )
+ {
+ $status ||= "Locally Removed";
+ }
+ if ( defined ( $state->{entries}{$filename}{conflict} ) and
+ $state->{entries}{$filename}{conflict} =~ /^\+=/ )
+ {
+ $status ||= "Unresolved Conflict";
+ }
+ if ( 0 )
+ {
+ $status ||= "File had conflicts on merge";
+ }
$status ||= "Unknown";
my ($filepart) = filenamesplit($filename);
- print "M ===================================================================\n";
+ print "M =======" . ( "=" x 60 ) . "\n";
print "M File: $filepart\tStatus: $status\n";
if ( defined($state->{entries}{$filename}{revision}) )
{
- print "M Working revision:\t" . $state->{entries}{$filename}{revision} . "\n";
+ print "M Working revision:\t" .
+ $state->{entries}{$filename}{revision} . "\n";
} else {
print "M Working revision:\tNo entry for $filename\n";
}
if ( defined($meta->{revision}) )
{
- print "M Repository revision:\t1." . $meta->{revision} . "\t$state->{CVSROOT}/$state->{module}/$filename,v\n";
+ print "M Repository revision:\t1." .
+ $meta->{revision} .
+ "\t$state->{CVSROOT}/$state->{module}/$filename,v\n";
print "M Sticky Tag:\t\t(none)\n";
print "M Sticky Date:\t\t(none)\n";
print "M Sticky Options:\t\t(none)\n";
$revision1 =~ s/^1\.// if ( defined ( $revision1 ) );
$revision2 =~ s/^1\.// if ( defined ( $revision2 ) );
- $log->debug("Diffing revisions " . ( defined($revision1) ? $revision1 : "[NULL]" ) . " and " . ( defined($revision2) ? $revision2 : "[NULL]" ) );
+ $log->debug("Diffing revisions " .
+ ( defined($revision1) ? $revision1 : "[NULL]" ) .
+ " and " . ( defined($revision2) ? $revision2 : "[NULL]" ) );
# Grab a handle to the SQLite db and do any necessary updates
- my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+ my $updater;
+ $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
$updater->update();
- # if no files were specified, we need to work out what files we should be providing status on ...
+ # if no files were specified, we need to work out what files we should
+ # be providing status on ...
argsfromdir($updater);
# foreach file specified on the command line ...
$file2 = $state->{entries}{$filename}{modified_filename};
}
- # if we have been given -r, and we don't have a $file2 yet, lets get one
+ # if we have been given -r, and we don't have a $file2 yet, lets
+ # get one
if ( defined ( $revision1 ) and not defined ( $file2 ) )
{
( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
# We need to have retrieved something useful
next unless ( defined ( $meta1 ) );
- # Files to date if the working copy and repo copy have the same revision, and the working copy is unmodified
- next if ( not defined ( $meta2 ) and $wrev == $meta1->{revision}
- and
- ( ( $state->{entries}{$filename}{unchanged} and ( not defined ( $state->{entries}{$filename}{conflict} ) or $state->{entries}{$filename}{conflict} !~ /^\+=/ ) )
- or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $meta1->{filehash} ) )
- );
+ # Files to date if the working copy and repo copy have the same
+ # revision, and the working copy is unmodified
+ if ( not defined ( $meta2 ) and $wrev == $meta1->{revision} and
+ ( ( $state->{entries}{$filename}{unchanged} and
+ ( not defined ( $state->{entries}{$filename}{conflict} ) or
+ $state->{entries}{$filename}{conflict} !~ /^\+=/ ) ) or
+ ( defined($state->{entries}{$filename}{modified_hash}) and
+ $state->{entries}{$filename}{modified_hash} eq
+ $meta1->{filehash} ) ) )
+ {
+ next;
+ }
# Apparently we only show diffs for locally modified files
- next unless ( defined($meta2) or defined ( $state->{entries}{$filename}{modified_filename} ) );
+ unless ( defined($meta2) or
+ defined ( $state->{entries}{$filename}{modified_filename} ) )
+ {
+ next;
+ }
print "M Index: $filename\n";
- print "M ===================================================================\n";
+ print "M =======" . ( "=" x 60 ) . "\n";
print "M RCS file: $state->{CVSROOT}/$state->{module}/$filename,v\n";
- print "M retrieving revision 1.$meta1->{revision}\n" if ( defined ( $meta1 ) );
- print "M retrieving revision 1.$meta2->{revision}\n" if ( defined ( $meta2 ) );
+ if ( defined ( $meta1 ) )
+ {
+ print "M retrieving revision 1.$meta1->{revision}\n"
+ }
+ if ( defined ( $meta2 ) )
+ {
+ print "M retrieving revision 1.$meta2->{revision}\n"
+ }
print "M diff ";
foreach my $opt ( keys %{$state->{opt}} )
{
}
} else {
print "-$opt ";
- print "$state->{opt}{$opt} " if ( defined ( $state->{opt}{$opt} ) );
+ if ( defined ( $state->{opt}{$opt} ) )
+ {
+ print "$state->{opt}{$opt} "
+ }
}
}
print "$filename\n";
- $log->info("Diffing $filename -r $meta1->{revision} -r " . ( $meta2->{revision} or "workingcopy" ));
+ $log->info("Diffing $filename -r $meta1->{revision} -r " .
+ ( $meta2->{revision} or "workingcopy" ));
( $fh, $filediff ) = tempfile ( DIR => $TEMP_DIR );
if ( exists $state->{opt}{u} )
{
- system("diff -u -L '$filename revision 1.$meta1->{revision}' -L '$filename " . ( defined($meta2->{revision}) ? "revision 1.$meta2->{revision}" : "working copy" ) . "' $file1 $file2 > $filediff");
+ system("diff -u -L '$filename revision 1.$meta1->{revision}'" .
+ " -L '$filename " .
+ ( defined($meta2->{revision}) ?
+ "revision 1.$meta2->{revision}" :
+ "working copy" ) .
+ "' $file1 $file2 > $filediff" );
} else {
system("diff $file1 $file2 > $filediff");
}
#$log->debug("log state : " . Dumper($state));
my ( $minrev, $maxrev );
- if ( defined ( $state->{opt}{r} ) and $state->{opt}{r} =~ /([\d.]+)?(::?)([\d.]+)?/ )
+ if ( defined ( $state->{opt}{r} ) and
+ $state->{opt}{r} =~ /([\d.]+)?(::?)([\d.]+)?/ )
{
my $control = $2;
$minrev = $1;
}
# Grab a handle to the SQLite db and do any necessary updates
- my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+ my $updater;
+ $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
$updater->update();
- # if no files were specified, we need to work out what files we should be providing status on ...
+ # if no files were specified, we need to work out what files we
+ # should be providing status on ...
argsfromdir($updater);
# foreach file specified on the command line ...
if ( defined ( $minrev ) )
{
$log->debug("Removing revisions less than $minrev");
- while ( scalar(@$revisions) > 0 and $revisions->[-1]{revision} < $minrev )
+ while ( scalar(@$revisions) > 0 and
+ $revisions->[-1]{revision} < $minrev )
{
pop @$revisions;
}
if ( defined ( $maxrev ) )
{
$log->debug("Removing revisions greater than $maxrev");
- while ( scalar(@$revisions) > 0 and $revisions->[0]{revision} > $maxrev )
+ while ( scalar(@$revisions) > 0 and
+ $revisions->[0]{revision} > $maxrev )
{
shift @$revisions;
}
print "M access list:\n";
print "M symbolic names:\n";
print "M keyword substitution: kv\n";
- print "M total revisions: $totalrevisions;\tselected revisions: " . scalar(@$revisions) . "\n";
+ print "M total revisions: $totalrevisions;\tselected revisions: " .
+ scalar(@$revisions) . "\n";
print "M description:\n";
foreach my $revision ( @$revisions )
print "M ----------------------------\n";
print "M revision 1.$revision->{revision}\n";
# reformat the date for log output
- $revision->{modified} = sprintf('%04d/%02d/%02d %s', $3, $DATE_LIST->{$2}, $1, $4 ) if ( $revision->{modified} =~ /(\d+)\s+(\w+)\s+(\d+)\s+(\S+)/ and defined($DATE_LIST->{$2}) );
+ if ( $revision->{modified} =~ /(\d+)\s+(\w+)\s+(\d+)\s+(\S+)/ and
+ defined($DATE_LIST->{$2}) )
+ {
+ $revision->{modified} = sprintf('%04d/%02d/%02d %s',
+ $3, $DATE_LIST->{$2}, $1, $4 );
+ }
$revision->{author} = cvs_author($revision->{author});
- print "M date: $revision->{modified}; author: $revision->{author}; state: " . ( $revision->{filehash} eq "deleted" ? "dead" : "Exp" ) . "; lines: +2 -3\n";
- my $commitmessage = $updater->commitmessage($revision->{commithash});
+ print "M date: $revision->{modified};" .
+ " author: $revision->{author}; state: " .
+ ( $revision->{filehash} eq "deleted" ? "dead" : "Exp" ) .
+ "; lines: +2 -3\n";
+ my $commitmessage;
+ $commitmessage = $updater->commitmessage($revision->{commithash});
$commitmessage =~ s/^/M /mg;
print $commitmessage . "\n";
}
- print "M =============================================================================\n";
+ print "M =======" . ( "=" x 70 ) . "\n";
}
print "ok\n";
$state->{entries} = {};
}
+# Return working directory revision int "X" from CVS revision "1.X" out
+# of the the working directory "entries" state, for the given filename.
+# Return negative "X" to represent the file is scheduled for removal
+# when it is committed.
sub revparse
{
my $filename = shift;
}
elsif( ($cfg->{gitcvs}{allbinary} =~ /^\s*guess\s*$/i) )
{
- if( $srcType eq "sha1Or-k" &&
- !defined($name) )
+ if( is_binary($srcType,$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;
+ $log->debug("... as binary");
+ return "-kb";
}
else
{
- if( is_binary($srcType,$name) )
- {
- $log->debug("... as binary");
- return "-kb";
- }
- else
- {
- $log->debug("... as text");
- }
+ $log->debug("... as text");
}
}
}
die "Unable to open file $name: $!\n";
}
}
- elsif( $srcType eq "sha1" || $srcType eq "sha1Or-k" )
+ elsif( $srcType eq "sha1" )
{
unless ( defined ( $name ) and $name =~ /^[a-zA-Z0-9]{40}$/ )
{
}
# Construct the revision table if required
+ # The revision table stores an entry for each file, each time that file
+ # changes.
+ # numberOfRecords = O( numCommits * averageNumChangedFilesPerCommit )
+ # This is not sufficient to support "-r {commithash}" for any
+ # files except files that were modified by that commit (also,
+ # some places in the code ignore/effectively strip out -r in
+ # some cases, before it gets passed to getmeta()).
+ # The "filehash" field typically has a git blob hash, but can also
+ # be set to "dead" to indicate that the given version of the file
+ # should not exist in the sandbox.
unless ( $self->{tables}{$self->tablename("revision")} )
{
my $tablename = $self->tablename("revision");
}
# Construct the head table if required
+ # The head table (along with the "last_commit" entry in the property
+ # table) is the persisted working state of the "sub update" subroutine.
+ # All of it's data is read entirely first, and completely recreated
+ # last, every time "sub update" runs.
+ # This is also used by "sub getmeta" when it is asked for the latest
+ # version of a file (as opposed to some specific version).
+ # Another way of thinking about it is as a single slice out of
+ # "revisions", giving just the most recent revision information for
+ # each file.
unless ( $self->{tables}{$self->tablename("head")} )
{
my $tablename = $self->tablename("head");
}
# Construct the properties table if required
+ # - "last_commit" - Used by "sub update".
unless ( $self->{tables}{$self->tablename("properties")} )
{
my $tablename = $self->tablename("properties");
}
# Construct the commitmsgs table if required
+ # The commitmsgs table is only used for merge commits, since
+ # "sub update" will only keep one branch of parents. Shortlogs
+ # for ignored commits (i.e. not on the chosen branch) will be used
+ # to construct a replacement "collapsed" merge commit message,
+ # which will be stored in this table. See also "sub commitmessage".
unless ( $self->{tables}{$self->tablename("commitmsgs")} )
{
my $tablename = $self->tablename("commitmsgs");
=head2 update
+Bring the database up to date with the latest changes from
+the git repository.
+
+Internal working state is read out of the "head" table and the
+"last_commit" property, then it updates "revisions" based on that, and
+finally it writes the new internal state back to the "head" table
+so it can be used as a starting point the next time update is called.
+
=cut
sub update
{
next;
} elsif (@{$commit->{parents}} > 1) {
# it is a merge commit, for each parent that is
- # not $lastpicked, see if we can get a log
+ # 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) {
- # git-merge-base can potentially (but rarely) throw
- # several candidate merge bases. let's assume
- # that the first one is the best one.
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);
$insert_head->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
}
-sub _headrev
-{
- my $self = shift;
- my $filename = shift;
- my $tablename = $self->tablename("head");
-
- my $db_query = $self->{dbh}->prepare_cached("SELECT filehash, revision, mode FROM $tablename WHERE name=?",{},1);
- $db_query->execute($filename);
- my ( $hash, $revision, $mode ) = $db_query->fetchrow_array;
-
- return ( $hash, $revision, $mode );
-}
-
sub _get_prop
{
my $self = shift;
=head2 getlog
+See also gethistorydense().
+
=cut
sub getlog
return $message;
}
-=head2 gethistory
-
-This function takes a filename (with path) argument and returns an arrayofarrays
-containing revision,filehash,commithash ordered by revision descending
-
-=cut
-sub gethistory
-{
- my $self = shift;
- my $filename = shift;
- my $tablename = $self->tablename("revision");
-
- my $db_query;
- $db_query = $self->{dbh}->prepare_cached("SELECT revision, filehash, commithash FROM $tablename WHERE name=? ORDER BY revision DESC",{},1);
- $db_query->execute($filename);
-
- return $db_query->fetchall_arrayref;
-}
-
=head2 gethistorydense
This function takes a filename (with path) argument and returns an arrayofarrays
The 'dense' part is a reference to a '--dense' option available for git-rev-list
and other git tools that depend on it.
+See also getlog().
+
=cut
sub gethistorydense
{