VCS::LibCVS::Datum::RevisionNumber - A CVS revision number.


VCS-LibCVS documentation Contained in the VCS-LibCVS distribution.

Index


Code Index:

NAME

Top

VCS::LibCVS::Datum::RevisionNumber - A CVS revision number.

SYNOPSIS

Top

DESCRIPTION

Top

Represents a CVS revision number, either a branch or regular one.

It accepts both regular branch numbers (1.4.2) and magic ones (1.4.0.2). This means that numbers of the form "0.x" are ambiguous. They are treated as revision numbers on the branch "0".

The revision number "0" is used for added files, as well as for one of the trunk branches.

I use the term "depth" to refer to the number of fields in a revision. I use the terms "ancestor" and "descendant" to refer to a relationship between two revisions, whereby the intervening revisions increase monotonically. Eg. 1.6 is an ancestor of 1.19 and of 1.6.4.5, but 1.19 and 1.6.4.5 are neither ancestors nor descendants of each other.

SUPERCLASS

Top

VCS::LibCVS::Datum

CLASS ROUTINES

Top

new()

$rev_num = VCS::LibCVS::Datum::RevisionNumber->new("1.2.4.5") $rev_num = VCS::LibCVS::Datum::RevisionNumber->new("1.2.0.4") $rev_num = VCS::LibCVS::Datum::RevisionNumber->new("1.2.4")

return type: VCS::LibCVS::Datum::RevisionNumber
argument 1 type: scalar string

Must be a valid CVS revision number.

INSTANCE ROUTINES

Top

as_string()

$rev_str = $rev_num->as_string()

return type: string scalar

Returns the revision number as a string.

equals()

if ($rev_num1->equals($rev_num2)) {

return type: boolean
argument 1 type: VCS::LibCVS::Datum::RevisionNumber

Returns true if the revision numbers contain the same information.

is_branch()

if ($rev_num->is_branch()) { . . .

return type: boolean scalar

Return true if this is a branch revision number, false otherwise.

is_trunk()

if ($rev_num->is_trunk()) { . . .

return type: boolean scalar

Return true if this is a revision number for the trunk, false otherwise. A trunk revision number is one with only one field, like: "1", "2", . . .

is_import_branch()

if ($rev_num->is_import_branch()) { . . .

return type: boolean scalar

Return true if this is a branch revision number for an import branch, false otherwise. An import branch is one with an odd revision number, which is assumed to be the result of an import.

branch_of()

$branch_num = $rev_num->branch_of()

return type: VCS::LibCVS::Datum::RevisionNumber

Get the RevisionNumber for the branch on which this revision lives. If it's a branch revision number it throws an exception.

base_of()

$branch_num = $rev_num->base_of()

return type: VCS::LibCVS::Datum::RevisionNumber

Returns the RevisionNumber from which this branch starts. If it's the trunk or a non-branch revision, an exception is thrown.

first_revision_of()

$rev_num = $branch_num->first_revision_of()

return type: VCS::LibCVS::Datum::RevisionNumber

Returns the RevisionNumber of the first revision committed to this branch. This is not the same as the base of the branch, it's the branch number with a .1 appended.

get_predecessor()

$p_rev_num = $rev_num->get_predecessor()

return type: VCS::LibCVS::Datum::RevisionNumber

Return the revision number that immediately preceeds this one, it's youngest ancestor. Return undef if there isn't one. Throw an exception if this is a branch revision.

get_successor()

$p_rev_num = $rev_num->get_successor()

return type: VCS::LibCVS::Datum::RevisionNumber

Return the revision number on the same branch that immediately follows this one, it's eldest descendant. Throw an exception if this is a branch revision.

compare()

$diff = $rev_num1->compare($rev_num2)

return type: integer, one of VCS::LibCVS::Datum::RevisionNumber::COMPARE_*
argument 1 type: string or RevisionNumber object

Compares this revision number with the argument.

The meanings of the return values are:

COMPARE_EQUAL

They are the same revision number.

COMPARE_ANCESTOR

The argument is an ancestor of this.

COMPARE_DESCENDANT

The argument is a descendant of this.

COMPARE_POSSIBLE_ANCESTOR

The argument is possibly an ancestor of this.

COMPARE_POSSIBLE_DESCENDANT

The argument is possibly a descendant of this.

COMPARE_INCOMPARABLE

The argument is neither an ancestor, nor a descendant of this, and they aren't equal.

Both branch and regular revision numbers can be compared this way. A branch revision number is an ancestor of all revisions on it (except its base revision) and its subbranches. So 1.6.2 is an ancestor of 1.6.2.4, a descendant of 1.6, and incomparable with 1.7.

Branches that sprout from the same revision are possibly ancestors or descendants. However, this can't be known because of CVS's lazy branching. In this case, the values returned are COMPARE_POSSIBLE_ANCESTOR and COMPARE_POSSIBLE_DESCENDANT. For example, 1.11.4 is possibly and ancestor of 1.11.6.

It would be nice if 1.6 were a descendant of 1.6.2, since it's on that branch, but then we'd lose transitivity, because then we'd have 1.6.2 < 1.6, 1.6 < 1.7, but 1.6.2 and 1.7 are incomparable.

SEE ALSO

Top

  VCS::LibCVS::Datum


VCS-LibCVS documentation Contained in the VCS-LibCVS distribution.
#
# Copyright (c) 2003,2004,2005 Alexander Taler (dissent@0--0.org)
#
# All rights reserved. This program is free software; you can redistribute it
# and/or modify it under the same terms as Perl itself.
#

package VCS::LibCVS::Datum::RevisionNumber;

use strict;
use Carp;

###############################################################################
# Class constants
###############################################################################

use constant REVISION => '$Header: /cvsroot/libcvs-perl/libcvs-perl/VCS/LibCVS/Datum/RevisionNumber.pm,v 1.16 2005/10/10 12:52:12 dissent Exp $ ';

use vars ('@ISA');
@ISA = ("VCS::LibCVS::Datum");

# COMPARE_* constants are documented in compare() routine
use constant COMPARE_EQUAL => 0;
use constant COMPARE_ANCESTOR => 1;
use constant COMPARE_DESCENDANT => 2;
use constant COMPARE_INCOMPARABLE => 3;
use constant COMPARE_POSSIBLE_ANCESTOR => 4;
use constant COMPARE_POSSIBLE_DESCENDANT => 5;

###############################################################################
# Class variables
###############################################################################

###############################################################################
# Private variables
###############################################################################

# $self->{Number} is the number as a string, with magic branch numbers
#                 converted to regular branch numbers.
# $self->{IsBranch} true if it's a branch revision number

###############################################################################
# Class routines
###############################################################################

sub new {
  my ($class, $num) = @_;
  my $that = bless {}, $class;

  # 0 may only appear by itself, or as the first field or the second to last
  confess "Bad revision number $num"
    unless ($num =~ /^0$|^(0\.)?([1-9][0-9]*\.)*(0\.)?[1-9][0-9]*$/);

  # Convert magic branch numbers to regular ones by removing the 0 in the
  # second to last place, unless it's the first number.
  $num =~ s/\.0(\.[0-9])*$/$1/;
  $that->{Number} = $num;

  # It's a branch if there are an odd number of fields
  $that->{IsBranch} = ($that->_depth() % 2);

  return $that;
}

###############################################################################
# Instance routines
###############################################################################

sub as_string {
  my $self = shift;
  return $self->{Number};
}

sub equals {
  my $self = shift;
  return 0 unless $self->SUPER::equals(@_);
  my $other = shift;
  return $self->{Number} eq $other->{Number};
}

sub is_branch {
  my $self = shift;
  return $self->{IsBranch};
}

sub is_trunk {
  my $self = shift;
  return ($self->_depth() == 1);
}

sub is_import_branch {
  my $self = shift;
  return (   $self->{IsBranch}
          && ($self->_depth() >= 3)
          && ($self->_last_field() % 2 == 1));
}

sub branch_of {
  my $self = shift;
  confess "No branch_of() for a branch RevisionNumber" if $self->is_branch();
  return $self->_subrevision();
}

sub base_of {
  my $self = shift;
  confess "Only branch revisions have a base" unless $self->is_branch();
  confess "Main branch has no base" if $self->_depth == 1;
  return $self->_subrevision();
}

sub first_revision_of {
  my $self = shift;
  confess "Only branches have a first revision" unless $self->is_branch();
  return VCS::LibCVS::Datum::RevisionNumber->new($self->as_string() . ".1");
}

sub get_predecessor {
  my $self = shift;
  if ($self->is_branch()) {
    confess "Branches don't have predecessors.";
  }
  if ($self->_last_field != 1) {
    my $rvstr = $self->_subrevision_string() . "." . ($self->_last_field() - 1);
    return VCS::LibCVS::Datum::RevisionNumber->new($rvstr);
  } else {
    if ($self->branch_of()->is_trunk()) {
      return;
    } else {
      return $self->branch_of()->base_of();
    }
  }
}

sub get_successor {
  my $self = shift;
  if ($self->is_branch()) {
    confess "Branches don't have successors.";
  }
  my $rvstr = $self->_subrevision_string() . "." . ($self->_last_field() + 1);
  return VCS::LibCVS::Datum::RevisionNumber->new($rvstr);
}

sub compare {
  my ($self, $other) = @_;

  if (!ref($other)) {
    $other = new VCS::LibCVS::Datum::RevisionNumber($other);
  }

  # Check revision numbers that are of the same depth.
  # Since they are of the same depth, they are either both branches, or both
  # revisions
  if ($self->_depth() == $other->_depth()) {
    # Check for trivial equality
    if ($self->equals($other)) {
      return COMPARE_EQUAL;
    }
    # Branches of the same depth that sprout from different revisions are
    # incomparable.  If they sprout from the same revision they are possibly
    # related.
    if ($self->is_branch()) {
      if (! $self->base_of()->equals($other->base_of())) {
        return COMPARE_INCOMPARABLE;
      } else {
        return ($other->_last_field > $self->_last_field)
          ? COMPARE_POSSIBLE_DESCENDANT : COMPARE_POSSIBLE_ANCESTOR;
      }
    }
    # For revisions, check that they are on the same branch, then check their
    # last field
    if ($self->branch_of()->equals($other->branch_of)) {
      return ($other->_last_field > $self->_last_field)
        ? COMPARE_DESCENDANT : COMPARE_ANCESTOR;
    }
    # They are revisions on different branches
    return COMPARE_INCOMPARABLE;
  }

  # The revision numbers are of different depths

  # Reduce the deep revision to the same depth as the shallow revision, and
  # then compare them.
  my $shallow_rev = ($self->_depth > $other->_depth) ? $other : $self;
  my $deep_rev = ($self->_depth > $other->_depth) ? $self : $other;
  # $less_deep_rev is the ancestor of $deep_rev with the same depth as
  # $shallow_rev
  my $less_deep_rev = $deep_rev;
  while ($shallow_rev->_depth() != $less_deep_rev->_depth()) {
    $less_deep_rev = $less_deep_rev->_subrevision();
  }

  # Compare $shallow_rev and $less_deep_rev
  my $result = $shallow_rev->compare($less_deep_rev);

  # Adjust $result to be correct for $shallow_rev->compare($deep_rev)
  # eg $shallow_rev is 1.6, $deep_rev is 1.6.4.3
  $result = COMPARE_DESCENDANT if ($result == COMPARE_EQUAL);

  # eg $shallow_rev is 1.4, $deep_rev is 1.6.4.3
  $result = COMPARE_DESCENDANT if ($result == COMPARE_DESCENDANT);

  # eg $shallow_rev is 1.8, $deep_rev is 1.6.4.3
  $result = COMPARE_INCOMPARABLE if ($result == COMPARE_ANCESTOR);

  $result = COMPARE_INCOMPARABLE if ($result == COMPARE_INCOMPARABLE);

  # Adjust $result to be correct for $self->compare($other)
  # Only COMPARE_DESCENDANT and COMPARE_INCOMPARABLE are possible
  if (($self->_depth > $other->_depth) && $result == COMPARE_DESCENDANT) {
    $result = COMPARE_ANCESTOR;
  }
  return $result;
}

###############################################################################
# Private routines
###############################################################################

# Create new revision number with one less field, as a string
sub _subrevision_string {
  my $self = shift;
  my ($sub_num) = ($self->{Number} =~ /^(.*)\.[0-9]+$/);
  return $sub_num;
}

# Create new RevisionNumber with one less field
sub _subrevision {
  return VCS::LibCVS::Datum::RevisionNumber->new(shift->_subrevision_string());
}

# Get the depth of the revision, ie. the number of fields.
sub _depth {
  my $self = shift;
  my $depth = (my @t = split(/\./, "$self->{Number}"));
  return $depth;
}

# The last field of this revision number
sub _last_field {
  my $self = shift;
  my ($self_field) = ($self->{Number} =~ /.*\.([0-9]+)/);
  return $self_field;
}

1;