cvsserver: generalize getmeta() to recognize commit refs

This allows getmeta() to recognize any commitish (sha1, tag/branch name, etc). Signed-off-by: Matthew Ogilvie <mmogilvi_git@miniinfo.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Matthew Ogilvie committed Oct 13, 2012 at 23:42 UTC bfdafa099eb9fd8c3b99f5debdddfa251dcdd855
1 file changed +145 -11
git-cvsserver.perl
+145 -11
@@ -4041,6 +4041,19 @@ sub getlog
4041 This function takes a filename (with path) argument and returns a hashref of
4042 metadata for that file.
4043
4044 +There are several ways $revision can be specified:
4045 +
4046 + - A reference to hash that contains a "tag" that is the
4047 + actual revision (one of the below). TODO: Also allow it to
4048 + specify a "date" in the hash.
4049 + - undef, to refer to the latest version on the main branch.
4050 + - Full CVS client revision number (mapped to integer in DB, without the
4051 + "1." prefix),
4052 + - Complex CVS-compatible "special" revision number for
4053 + non-linear history (see comment below)
4054 + - git commit sha1 hash
4055 + - branch or tag name
4056 +
4057 =cut
4058
4059 sub getmeta
@@ -4051,23 +4064,144 @@ sub getmeta
4064 my $tablename_rev = $self->tablename("revision");
4065 my $tablename_head = $self->tablename("head");
4066
4054 - my $db_query;
4055 - if ( defined($revision) and $revision =~ /^1\.(\d+)$/ )
4067 + if ( ref($revision) eq "HASH" )
4068 {
4057 - my ($intRev) = $1;
4058 - $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_rev WHERE name=? AND revision=?",{},1);
4059 - $db_query->execute($filename, $intRev);
4069 + $revision = $revision->{tag};
4070 + }
4071 +
4072 + # Overview of CVS revision numbers:
4073 + #
4074 + # General CVS numbering scheme:
4075 + # - Basic mainline branch numbers: "1.1", "1.2", "1.3", etc.
4076 + # - Result of "cvs checkin -r" (possible, but not really
4077 + # recommended): "2.1", "2.2", etc
4078 + # - Branch tag: "1.2.0.n", where "1.2" is revision it was branched
4079 + # from, "0" is a magic placeholder that identifies it as a
4080 + # branch tag instead of a version tag, and n is 2 times the
4081 + # branch number off of "1.2", starting with "2".
4082 + # - Version on a branch: "1.2.n.x", where "1.2" is branch-from, "n"
4083 + # is branch number off of "1.2" (like n above), and "x" is
4084 + # the version number on the branch.
4085 + # - Branches can branch off of branches: "1.3.2.7.4.1" (even number
4086 + # of components).
4087 + # - Odd "n"s are used by "vendor branches" that result
4088 + # from "cvs import". Vendor branches have additional
4089 + # strangeness in the sense that the main rcs "head" of the main
4090 + # branch will (temporarily until first normal commit) point
4091 + # to the version on the vendor branch, rather than the actual
4092 + # main branch. (FUTURE: This may provide an opportunity
4093 + # to use "strange" revision numbers for fast-forward-merged
4094 + # branch tip when CVS client is asking for the main branch.)
4095 + #
4096 + # git-cvsserver CVS-compatible special numbering schemes:
4097 + # - Currently git-cvsserver only tries to be identical to CVS for
4098 + # simple "1.x" numbers on the "main" branch (as identified
4099 + # by the module name that was originally cvs checkout'ed).
4100 + # - The database only stores the "x" part, for historical reasons.
4101 + # But most of the rest of the cvsserver preserves
4102 + # and thinks using the full revision number.
4103 + # - To handle non-linear history, it uses a version of the form
4104 + # "2.1.1.2000.b.b.b."..., where the 2.1.1.2000 is to help uniquely
4105 + # identify this as a special revision number, and there are
4106 + # 20 b's that together encode the sha1 git commit from which
4107 + # this version of this file originated. Each b is
4108 + # the numerical value of the corresponding byte plus
4109 + # 100.
4110 + # - "plus 100" avoids "0"s, and also reduces the
4111 + # likelyhood of a collision in the case that someone someday
4112 + # writes an import tool that tries to preserve original
4113 + # CVS revision numbers, and the original CVS data had done
4114 + # lots of branches off of branches and other strangeness to
4115 + # end up with a real version number that just happens to look
4116 + # like this special revision number form. Also, if needed
4117 + # there are several ways to extend/identify alternative encodings
4118 + # within the "2.1.1.2000" part if necessary.
4119 + # - Unlike real CVS revisions, you can't really reconstruct what
4120 + # relation a revision of this form has to other revisions.
4121 + # - FUTURE: TODO: Rework database somehow to make up and remember
4122 + # fully-CVS-compatible branches and branch version numbers.
4123 +
4124 + my $meta;
4125 + if ( defined($revision) )
4126 + {
4127 + if ( $revision =~ /^1\.(\d+)$/ )
4128 + {
4129 + my ($intRev) = $1;
4130 + my $db_query;
4131 + $db_query = $self->{dbh}->prepare_cached(
4132 + "SELECT * FROM $tablename_rev WHERE name=? AND revision=?",
4133 + {},1);
4134 + $db_query->execute($filename, $intRev);
4135 + $meta = $db_query->fetchrow_hashref;
4136 + }
4137 + elsif ( $revision =~ /^2\.1\.1\.2000(\.[1-3][0-9][0-9]){20}$/ )
4138 + {
4139 + my ($commitHash)=($revision=~/^2\.1\.1\.2000(.*)$/);
4140 + $commitHash=~s/\.([0-9]+)/sprintf("%02x",$1-100)/eg;
4141 + if($commitHash=~/^[0-9a-f]{40}$/)
4142 + {
4143 + return $self->getMetaFromCommithash($filename,$commitHash);
4144 + }
4145 +
4146 + # error recovery: fall back on head version below
4147 + print "E Failed to find $filename version=$revision or commit=$commitHash\n";
4148 + $log->warning("failed get $revision with commithash=$commitHash");
4149 + undef $revision;
4150 + }
4151 + elsif ( $revision =~ /^[0-9a-f]{40}$/ )
4152 + {
4153 + # Try DB first. This is mostly only useful for req_annotate(),
4154 + # which only calls this for stuff that should already be in
4155 + # the DB. It is fairly likely to be a waste of time
4156 + # in most other cases [unless the file happened to be
4157 + # modified in $revision specifically], but
4158 + # it is probably in the noise compared to how long
4159 + # getMetaFromCommithash() will take.
4160 + my $db_query;
4161 + $db_query = $self->{dbh}->prepare_cached(
4162 + "SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",
4163 + {},1);
4164 + $db_query->execute($filename, $revision);
4165 + $meta = $db_query->fetchrow_hashref;
4166 +
4167 + if(! $meta)
4168 + {
4169 + my($revCommit)=$self->lookupCommitRef($revision);
4170 + if($revCommit=~/^[0-9a-f]{40}$/)
4171 + {
4172 + return $self->getMetaFromCommithash($filename,$revCommit);
4173 + }
4174 +
4175 + # error recovery: nothing found:
4176 + print "E Failed to find $filename version=$revision\n";
4177 + $log->warning("failed get $revision");
4178 + return $meta;
4179 + }
4180 + }
4181 + else
4182 + {
4183 + my($revCommit)=$self->lookupCommitRef($revision);
4184 + if($revCommit=~/^[0-9a-f]{40}$/)
4185 + {
4186 + return $self->getMetaFromCommithash($filename,$revCommit);
4187 + }
4188 +
4189 + # error recovery: fall back on head version below
4190 + print "E Failed to find $filename version=$revision\n";
4191 + $log->warning("failed get $revision");
4192 + undef $revision; # Allow fallback
4193 + }
4194 }
4061 - elsif ( defined($revision) and $revision =~ /^[a-zA-Z0-9]{40}$/ )
4195 +
4196 + if(!defined($revision))
4197 {
4063 - $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",{},1);
4064 - $db_query->execute($filename, $revision);
4065 - } else {
4066 - $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_head WHERE name=?",{},1);
4198 + my $db_query;
4199 + $db_query = $self->{dbh}->prepare_cached(
4200 + "SELECT * FROM $tablename_head WHERE name=?",{},1);
4201 $db_query->execute($filename);
4202 + $meta = $db_query->fetchrow_hashref;
4203 }
4204
4070 - my $meta = $db_query->fetchrow_hashref;
4205 if($meta)
4206 {
4207 $meta->{revision} = "1.$meta->{revision}";