Class: Amp::Revlog

Inherits:
Object show all
Includes:
Amp::RevlogSupport::Node, Enumerable
Defined in:
lib/amp/revlogs/revlog.rb

Overview

Revlog

A revlog is a generic file that represents a revision history. This class, while generic, is extremely importantly and highly functional. While the Manifest and ChangeLog classes inherit from Revlog, one can open either file using the base Revlog class.

A Revision log is based on two things: an index, which stores some meta-data about each revision in the repository’s history, and some data associated with each revision. The data is stored as a (possibly zlib-compressed) diff.

There are two versions of revision logs - version 0 and version NG. This information is handled by the Amp::RevlogSupport:Index classes.

Sometimes the data is stored in a separate file from the index. This is up to the system to decide.

Direct Known Subclasses

ChangeLog, FileLog, Manifest

Constant Summary

Constants included from Amp::RevlogSupport::Node

Amp::RevlogSupport::Node::NULL_ID, Amp::RevlogSupport::Node::NULL_REV

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Amp::RevlogSupport::Node

#short

Methods included from Enumerable

#inject, #select_map

Constructor Details

#initialize(opener, indexfile) ⇒ Revlog Also known as: revlog_initialize

Initializes the revision log with an opener object (which handles how the interface to opening the files) and the path to the index itself.

Parameters:

  • opener (Amp::Opener)

    an object that will handle opening the file

  • indexfile (String)

    the path to the index file



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/amp/revlogs/revlog.rb', line 38

def initialize(opener, indexfile)
  @opener = opener
  @index_file = indexfile
  @data_file  = indexfile[0..-3] + ".d"
  @chunk_cache = nil
  @index = RevlogSupport::Index.parse(opener, indexfile)
  
  # add the null, terminating index entry if it isn't already there
  if @index.index.empty? || @index.is_a?(RevlogSupport::LazyIndex) || 
                           @index.index[-1].node_id.not_null?
    # the use of @index.index is deliberate!
    @index.index << RevlogSupport::IndexEntry.new(0,0,0,-1,-1,-1,-1,NULL_ID)
  end
end

Instance Attribute Details

#data_fileObject (readonly)

the file paths to the index and data files



28
29
30
# File 'lib/amp/revlogs/revlog.rb', line 28

def data_file
  @data_file
end

#indexObject (readonly)

The actual Index object.



30
31
32
# File 'lib/amp/revlogs/revlog.rb', line 30

def index
  @index
end

#index_fileObject (readonly)

the file paths to the index and data files



28
29
30
# File 'lib/amp/revlogs/revlog.rb', line 28

def index_file
  @index_file
end

Instance Method Details

#[](idx) ⇒ IndexEntry

Returns the requested node as an IndexEntry. Takes either a string or a fixnum index value.

Parameters:

  • the (String, Fixnum)

    index or node ID to look up in the revlog

Returns:

  • (IndexEntry)

    the requested index entry.



66
67
68
69
70
71
72
73
74
75
# File 'lib/amp/revlogs/revlog.rb', line 66

def [](idx)
  if idx.is_a? String
    return @index[@index.node_map[idx]]
  elsif idx.is_a? Array
    STDERR.puts idx.inspect # KILLME
    idx
  else
    return @index[idx]
  end
end

#add_group(revisions, link_mapper, journal) ⇒ Object

Adds a changelog to the index

Parameters:

  • revisions (StringIO, #string)

    something we can iterate over (Usually a StringIO)

  • link_mapper (Proc, #call, #[])
  • journal (Amp::Journal)

    to start a transaction



859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
# File 'lib/amp/revlogs/revlog.rb', line 859

def add_group(revisions, link_mapper, journal)
  r = index_size
  t = r - 1
  node = nil
  
  base = prev = RevlogSupport::Node::NULL_REV
  start = endpt = text_len = 0
  endpt = data_end_for_index t if r != 0
  
  index_file_handle = open(@index_file, "a+")
  index_size = r * @index.entry_size
  if @index.inline?
    journal << [@index_file, endpt + index_size, r]
    data_file_handle = nil
  else
    journal << [@index_file, index_size, r]
    journal << [@data_file, endpt]
    data_file_handle = open(@data_file, "a")
  end
  
  begin #errors abound here i guess
    chain = nil
    
    Amp::RevlogSupport::ChangeGroup.each_chunk(revisions) do |chunk|
      node, parent1, parent2, cs = chunk[0..79].unpack("a20a20a20a20")
      link = link_mapper.call(cs)
      
      if @index.node_map[node]
        chain = node
        next
      end
      delta = chunk[80..-1]
      [parent1, parent2].each do |parent|
        unless @index.node_map[parent]
          raise RevlogSupport::LookupError.new("unknown parent #{parent}"+
                                                     " in #{@index_file}")
        end
      end
      
      unless chain
        chain = parent1
        unless @index.node_map[chain]
          raise RevlogSupport::LookupError.new("unknown parent #{chain}"+
                                      " from #{chain} in #{@index_file}")
        end
      end
      
      if chain == prev
        cdelta = RevlogSupport::Support.compress delta
        cdeltalen = cdelta[:compression].size + cdelta[:text].size
        text_len = Diffs::MercurialPatch.patched_size text_len, delta
      end
      
      if chain != prev || (endpt - start + cdeltalen) > text_len * 2
        #flush our writes here so we can read it in revision
        data_file_handle.flush if data_file_handle
        index_file_handle.flush
        text = decompress_revision(chain)
        if text.size == 0
          text = delta[12..-1]
        else
          text = Diffs::MercurialPatch.apply_patches(text, [delta])
        end
        chk = add_revision(text, journal, link, parent1, parent2, 
                              nil, index_file_handle)
        # if (! data_file_handle) && (! @index.inline?)
        #   data_file_handle = open(@data_file, "a")
        #   index_file_handle = open(@index_file, "a")
        # end
        if chk != node
          raise RevlogSupport::RevlogError.new("consistency error "+
                    "adding group")
        end
        text_len = text.size
      else
        entry = RevlogSupport::IndexEntry.new(RevlogSupport::Support.offset_version(endpt, 0),
                   cdeltalen,text_len, base, link, rev(parent1), rev(parent2), node)
        @index << entry
        @index.node_map[node] = r
        @index.write_entry(@index_file, entry, journal, cdelta, index_file_handle)
      end
      
      
      t, r, chain, prev = r, r + 1, node, node
      base  = self[t].base_rev
      start = data_start_for_index base
      endpt = data_end_for_index t
    end
  rescue Exception => e
    puts e
    puts e.backtrace
  ensure
    if data_file_handle && !(data_file_handle.closed?)
      data_file_handle.close
    end
    index_file_handle.close
  end
  node
end

#add_revision(text, transaction, link, p1, p2, d = nil, index_file_handle = nil) ⇒ String

add a revision to the log

Parameters:

  • text (String)

    the revision data to add

  • transaction

    the transaction object used for rollback

  • link

    the linkrev data to add

  • p1 (String)

    the parent nodeids of the revision

  • p2 (String)

    the parent nodeids of the revision

  • d (defaults to: nil)

    an optional precomputed delta

Returns:

  • (String)

    the digest ID referring to the node in the log



751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
# File 'lib/amp/revlogs/revlog.rb', line 751

def add_revision(text, transaction, link, p1, p2, d=nil, index_file_handle=nil)
  node = RevlogSupport::Support.history_hash(text, p1, p2)
  return node if @index.node_map[node]
  curr = index_size
  prev = curr - 1
  base = self[prev].base_rev
  offset = data_end_for_index prev
  if curr > 0
    if d.nil? || d.empty?
      ptext = decompress_revision node_id_for_index(prev)
      d = Diffs::MercurialDiff.text_diff(ptext, text)
    end
    data = RevlogSupport::Support.compress d
    len = data[:compression].size + data[:text].size
    dist = len + offset - data_start_for_index(base)
  end
  # Compressed diff > size of actual file
  if curr == 0 || dist > text.size * 2
    data = RevlogSupport::Support.compress text
    len = data[:compression].size + data[:text].size
    base = curr
  end
  entry = RevlogSupport::IndexEntry.new(RevlogSupport::Support.offset_version(offset, 0), 
            len, text.size, base, link, rev(p1), rev(p2), node)
  @index << entry
  @index.node_map[node] = curr
  @index.write_entry(@index_file, entry, transaction, data, index_file_handle)
  @index.cache = [node, curr, text]
  node
end

#all_indicesArray

Returns all of the indices for all revisions.

Returns:

  • (Array)

    all indicies



195
196
197
# File 'lib/amp/revlogs/revlog.rb', line 195

def all_indices
  (0..size).to_a
end

#ancestor(a, b) ⇒ Object

Finds the most-recent common ancestor for the two nodes.



784
785
786
787
788
789
790
791
792
793
# File 'lib/amp/revlogs/revlog.rb', line 784

def ancestor(a, b)
  parent_func = proc do |rev| 
    self.parent_indices_for_index(rev).select {|i| i != NULL_REV }
  end
  c = Graphs::AncestorCalculator.ancestors(revision_index_for_node(a),
                                           revision_index_for_node(b),
                                           parent_func)
  return NULL_ID if c.nil?
  node_id_for_index c
end

#ancestors(revisions) ⇒ Object

Allows the user to operate on all the ancestors of the given revisions. One can pass a block, or just call it and get a Set.



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/amp/revlogs/revlog.rb', line 227

def ancestors(revisions)
  revisions = [revisions] unless revisions.kind_of? Array
  to_visit = revisions.dup
  seen = Set.new([NULL_REV])
  until to_visit.empty?
    parent_indices_for_index(to_visit.shift).each do |parent|
      unless seen.include? parent
        to_visit << parent
        seen     << parent
        yield parent if block_given?
      end
    end
  end
  seen.delete NULL_REV
  seen
end

#base_revision_for_index(index) ⇒ Object

Returns the “base revision” index for the revision at index.



163
164
165
# File 'lib/amp/revlogs/revlog.rb', line 163

def base_revision_for_index(index)
  self[index].base_rev
end

#check_inline_size(tr, fp = nil) ⇒ Object

TODO:

FINISH THIS METHOD

TODO:

FIXME

TODO FINISH THIS METHOD TODO FIXME



704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
# File 'lib/amp/revlogs/revlog.rb', line 704

def check_inline_size(tr, fp=nil)
  return unless @index.inline?
  if fp.nil?
    fp = open(@index_file, "r")
    fp.seek(0, IO::SEEK_END)
  end
  size = fp.tell
  return if size < 131072
  
  trinfo = tr.find(@index_file)
  if trinfo.nil?
    raise RevlogSupport::RevlogError.new("#{@index_file} not found in the"+
                                         "transaction")
  end
  trindex = trinfo[:data]
  data_offset = data_start_for_index trindex
  tr.add @data_file, data_offset
  df = open(@data_file, 'w')
  begin
    calc = @index.entry_size
    self.size.times do |r|
      start = data_start_for_index(r) + (r + 1) * calc
      length = self[r].compressed_len
      fp.seek(start)
      d = fp.read length
      df.write d
    end
  ensure
    df.close
  end
  fp.close
  
  ############ TODO
  # FINISH THIS METHOD
  ############ TODO
end

#checksizeObject

Checks to make sure our data and index files are the right size. Returns the differences between expected and actual sizes.



991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
# File 'lib/amp/revlogs/revlog.rb', line 991

def checksize
  expected = 0
  expected = [0, data_end_for_index(self.index_size - 1)].max if self.index_size > 0
  
  
  
  
  f = open(@index_file)
  f.seek(0, IO::SEEK_END)
  actual = f.tell
  s = @index.entry_size
  i = [0, actual / s].max
  di = actual - (i * s)
  
  if @index.inline?
    databytes = 0
    self.index_size.times do |r|
      databytes += [0, self[r].compressed_len].max
    end
    dd = 0
    di = actual - (self.index_size * s) - databytes
  else
    f = open(@data_file)
    f.seek(0, IO::SEEK_END)
    actual = f.tell
    dd = actual - expected
    f.close
  end
  
  return {:data_diff => dd, :index_diff => di}
end

#children(node) ⇒ Object

Returns the children of the node with ID node.



505
506
507
508
509
510
511
512
513
514
# File 'lib/amp/revlogs/revlog.rb', line 505

def children(node)
  c = []
  p = revision_index_for_node node
  (p+1).upto(self.size - 1) do |r|
    prevs = parent_indices_for_index(r).select {|pr| pr != NULL_REV}
    prevs.each {|pr| c << node_id_for_index(r) if pr == p} if prevs.any?
    c << node_id_for_index(r) if p == NULL_REV
  end
  c
end

#cmp(node, text) ⇒ Object

Compares a node with the provided text, as a consistency check. Works using <=> semantics.



560
561
562
563
564
# File 'lib/amp/revlogs/revlog.rb', line 560

def cmp(node, text)
  
  p1, p2 = parents_for_node node
  return RevlogSupport::Support.history_hash(text, p1, p2) != node
end

#data_end_for_index(index) ⇒ Object

Returns the offset where the data ends for the revision at index.



157
158
159
# File 'lib/amp/revlogs/revlog.rb', line 157

def data_end_for_index(index)
  data_start_for_index(index) + self[index].compressed_len
end

#data_size_for_index(index) ⇒ Object

Returns the size of the data for the revision at index.



135
136
137
# File 'lib/amp/revlogs/revlog.rb', line 135

def data_size_for_index(index)
  self[index].compressed_len
end

#data_start_for_index(index) ⇒ Object

Returns the offset where the data begins for the revision at index.



151
152
153
# File 'lib/amp/revlogs/revlog.rb', line 151

def data_start_for_index(index)
  RevlogSupport::Support.get_offset self[index].offset_flags
end

#decompress_revision(node) ⇒ String

Given a node ID, extracts that revision and decompresses it. What you get back will the pristine revision data!

Parameters:

  • node (String)

    the Node ID of the revision to extract.

Returns:

  • (String)

    the pristine revision data.



664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
# File 'lib/amp/revlogs/revlog.rb', line 664

def decompress_revision(node)
  return "" if node.nil? || node.null?
  return @index.cache[2] if @index.cache && @index.cache[0] == node
  
  
  text = nil
  rev = revision_index_for_node node
  base = @index[rev].base_rev
  
  if @index[rev].offset_flags & 0xFFFF  > 0
    raise RevlogSupport::RevlogError.new("incompatible revision flag %x" %
                                    (self.index[rev].offset_flags & 0xFFFF))
  end
  data_file = nil
  
  if @index.cache && @index.cache[1].is_a?(Numeric) && @index.cache[1] >= base && @index.cache[1] < rev
    base = @index.cache[1]
    text = @index.cache[2]
    # load the index if we're lazy (base, rev + 1)
  end
  data_file = open(@data_file) if !(@index.inline?) && rev > base + 1
  text = get_chunk(base, data_file) if text.nil?
  bins = ((base + 1)..rev).map {|r| get_chunk(r, data_file)}
  text = Diffs::MercurialPatch.apply_patches(text, bins)
  
  p1, p2 = parents_for_node node
  if node != RevlogSupport::Support.history_hash(text, p1, p2)
    raise RevlogSupport::RevlogError.new("integrity check failed on %s:%d, data:%s" % 
                                         [(@index.inline? ? @index_file : @data_file), rev, text.inspect])
  end
  @index.cache = [node, rev, text]
  text
end

#descendants(revisions) ⇒ Object

Allows the user to operate on all the descendants of the given revisions. One can pass a block, or just call it and get a Set. Revisions are passed as indices.



248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/amp/revlogs/revlog.rb', line 248

def descendants(revisions)
  seen = Set.new revisions
  start = revisions.min + 1
  start.upto self.size do |i|
    parent_indices_for_index(i).each do |x|
      if x != NULL_REV && seen.include?(x)
        seen << i
        yield i if block_given?
        break 1
      end
    end
  end
  seen - revisions
end

#each(&b) ⇒ Object

Returns each revision as a Amp::RevlogSupport::IndexEntry. Don’t iterate over the extra revision -1!



189
# File 'lib/amp/revlogs/revlog.rb', line 189

def each(&b); @index[0..-2].each(&b); end

#empty?Boolean

Returns true if size is 0

Returns:



182
183
184
# File 'lib/amp/revlogs/revlog.rb', line 182

def empty?
  index_size.zero?
end

#filesObject

Returns all the files this object is concerned with.



1025
1026
1027
1028
1029
# File 'lib/amp/revlogs/revlog.rb', line 1025

def files
  res = [ @index_file ]
  res << @data_file unless @index.inline?
  res
end

#find_missing(common = [NULL_ID], heads = self.heads) ⇒ Object

Returns the topologically sorted list of nodes from the set: missing = (ancestors(heads) \ ancestors(common))



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/amp/revlogs/revlog.rb', line 266

def find_missing(common=[NULL_ID], heads=self.heads)
  common.map! {|r| revision_index_for_node r}
  heads.map!  {|r| revision_index_for_node r}
  
  has = {}
  ancestors(common) {|a| has[a] = true}
  has[NULL_REV] = true
  common.each {|r| has[r] = true}
  
  missing = {}
  to_visit = heads.reject {|r| has[r]}
  until to_visit.empty?
    r = to_visit.shift
    next if missing.include? r
    missing[r] = true
    parent_indices_for_index(r).each do |p|
      to_visit << p unless has[p]
    end
  end
  
  missing.keys.sort.map {|rev| node_id_for_index rev}
end

#get_chunk(rev, data_file = nil) ⇒ String

Gets a chunk of data from the datafile (or, if inline, from the index file). Just give it a revision index and which data file to use

Parameters:

  • rev (Fixnum)

    the revision index to extract

  • data_file (IO) (defaults to: nil)

    The IO file descriptor for loading data

Returns:

  • (String)

    the raw data from the index (posssibly compressed)



599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/amp/revlogs/revlog.rb', line 599

def get_chunk(rev, data_file = nil)
  begin
    start, length = self.data_start_for_index(rev), self[rev].compressed_len
  rescue
    Amp::UI.debug "Failed get_chunk: #{@index_file}:#{rev}"
    raise
  end
  
  #puts "The starting point for the data is: #{data_start_for_index(rev)}"   # KILLME
  #puts "We're reading #{length} bytes. Look at data_start_for_index" # KILLME
  
  start += ((rev + 1) * @index.entry_size) if @index.inline?
  
  endpt = start + length
  offset = 0
  if @chunk_cache.nil?
    cache_length = [65536, length].max
    data_file = load_cache data_file, start, cache_length
  else
    cache_start = @chunk_cache[0]
    cache_length = @chunk_cache[1].size
    cache_end = cache_start + cache_length
    if start >= cache_start && endpt <= cache_end
      offset = start - cache_start
    else
      cache_length = [65536, length].max
      data_file = load_cache data_file, start, cache_length
    end
  end
  
  c = @chunk_cache[1]
  return "" if c.nil? || c.empty? || length == 0
  c = c[offset..(offset + length - 1)] if cache_length != length
  
  RevlogSupport::Support.decompress c
end

#group(nodelist, lookup, info_collect = nil) {|RevlogSupport::ChangeGroup.closing_chunk| ... } ⇒ Object

Yields chunks of change-group data for writing to disk, given a nodelist, a method to lookup stuff. Given a list of changset revs, return a set of deltas and metadata corresponding to nodes. the first delta is parent(nodes) -> nodes the receiver is guaranteed to have this parent as it has all history before these changesets. parent is parent

FIXME – could be the cause of our failures with #pre_push!

Parameters:

  • nodelist ([String])
  • lookup (Proc, #[], #call)
  • info_collect (Proc, #[], #call) (defaults to: nil)

    can be left nil

Yields:



807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
# File 'lib/amp/revlogs/revlog.rb', line 807

def group(nodelist, lookup, info_collect=nil)
  revs = nodelist.map {|n| rev n }

  # if we don't have any revisions touched by these changesets, bail
  if revs.empty?
    yield RevlogSupport::ChangeGroup.closing_chunk
    return
  end
  
  # add the parent of the first rev
  parent1 = parents_for_node(node(revs[0]))[0]
  revs.unshift rev(parent1)
  
  # build deltas
  0.upto(revs.size - 2) do |d|
    a, b = revs[d], revs[d + 1]
    nb = node b
    
    info_collect[nb] if info_collect
    
    p = parents(nb)
    meta = nb + p[0] + p[1] + lookup[nb]
    
    if a == -1
      data = decompress_revision nb
      meta += Diffs::MercurialDiff.trivial_diff_header(d.size)
    else
      
      data = revision_diff(a, b)
    end
    
    yield RevlogSupport::ChangeGroup.chunk_header(meta.size + data.size)
    yield meta
    if data.size > 1048576
      pos = 0
      while pos < data.size
        pos2 = pos + 262144
        yield data[pos..(pos2-1)]
        pos = pos2
      end
    else
      yield data
    end
  end
  yield RevlogSupport::ChangeGroup.closing_chunk
end

#heads(start = nil, stop = nil) ⇒ Object

Return the list of all nodes that have no children.

if start is specified, only heads that are descendants of start will be returned if stop is specified, it will consider all the revs from stop as if they had no children



472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/amp/revlogs/revlog.rb', line 472

def heads(start=nil, stop=nil)
  if start.nil? && stop.nil?
    count = self.size
    return [NULL_ID] if count == 0
    is_head = [true] * (count + 1)
    count.times do |r|
      e = @index[r]
      is_head[e.parent_one_rev] = is_head[e.parent_two_rev] = false
    end
    return (0..(count-1)).to_a.select {|r| is_head[r]}.map {|r| node_id_for_index r}
  end
  start = NULL_ID if start.nil?
  stop  = [] if stop.nil?
  stop_revs = {}  
  stop.each {|r| stop_revs[revision_index_for_node(r)] = true }
  start_rev = revision_index_for_node start
  reachable = {start_rev => 1}
  heads     = {start_rev => 1}
  (start_rev + 1).upto(self.size - 1) do |r|
    parent_indices_for_index(r).each do |p|
      if reachable[p]
        reachable[r] = 1 unless stop_revs[r]
        heads[r] = 1
      end
      heads.delete p if heads[p] && stop_revs[p].nil?
    end
  end
  
  heads.map {|k,v| node_id_for_index k}
end

#id_match(id) ⇒ Object

Tries to find an exact match for a node with ID id. If no match is, found, then the id is treated as an index number - if that doesn’t work, the revlog will try treating the ID supplied as node_id in hex form.



520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/amp/revlogs/revlog.rb', line 520

def id_match(id)
  return node_id_for_index(id) if id.is_a? Integer
  return id if id.size == 20 && revision_index_for_node(id)
  rev = id.to_i
  rev = self.size + rev if rev < 0
  if id.size == 40
    node = id.unhexlify
    r = revision_index_for_node node
    return node if r
  end
  nil
end

Returns the “link revision” index for the given revision index



112
113
114
# File 'lib/amp/revlogs/revlog.rb', line 112

def link_revision_for_index(index)
  self[index].link_rev
end

#load_cache(data_file, start, cache_length) ⇒ Object

Loads a block of data into the cache.



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# File 'lib/amp/revlogs/revlog.rb', line 568

def load_cache(data_file, start, cache_length)

  if data_file.nil?
    data_file = open(@index_file) if @index.inline?
    data_file = open(@data_file)  unless @index.inline?
  end
  
  # data_file.seek(start, IO::SEEK_SET)
  # sz = data_file.read.length
  # data_file.seek(0, IO::SEEK_SET)
  # $zs = data_file.read.length
  # puts(@index.inline? ? "------- INLINE" : "-------NOT INLINE") #killme
  # puts "------- CACHE_LENGTH = #{cache_length}" # KILLME
  # puts "===" # KILLME
  # puts "We are going to read #{cache_length} bytes starting at #{start}" # KILLME
  # puts "Wait a minute... on Ari's machine, there's only #{sz} bytes to read..." # KILLME
  # puts "Filesize: #{$zs}" # KILLME
  # puts "===" # KILLME
  
  data_file.seek(start, IO::SEEK_SET)
  @chunk_cache = [start, data_file.read(cache_length)]
  data_file
end

#lookup_id(id) ⇒ Object

This method will, given an id (or an index) or an ID in hex form, try to find the given node in the index.



549
550
551
552
553
554
555
# File 'lib/amp/revlogs/revlog.rb', line 549

def lookup_id(id)
  n = id_match id
  return n unless n.nil?
  n = partial_id_match id
  return n unless n.nil?
  raise RevlogSupport::LookupError.new("no match found #{id}")
end

#node_id_for_index(index) ⇒ String Also known as: node

Returns the unique node_id (a string) for a given revision at index.

Parameters:

  • index (Fixnum)

    the index into the list, from 0-(num_revisions - 1).

Returns:

  • (String)

    the node’s ID



82
83
84
85
86
87
# File 'lib/amp/revlogs/revlog.rb', line 82

def node_id_for_index(index)
  unless @index[index]
    raise RevlogSupport::LookupError.new("Couldn't find node for id '#{index}'")
  end
  @index[index].node_id
end

#nodes_between(roots = nil, heads = nil) ⇒ {:heads => Array<String>, :roots => Array<String>, :between => Array<String>}

Return a tuple containing three elements. Elements 1 and 2 contain a final list bases and heads after all the unreachable ones have been pruned. Element 0 contains a topologically sorted list of all

nodes that satisfy these constraints:

  1. All nodes must be descended from a node in roots (the nodes on roots are considered descended from themselves).

  2. All nodes must also be ancestors of a node in heads (the nodes in heads are considered to be their own ancestors).

If roots is unspecified, nullid is assumed as the only root. If heads is unspecified, it is taken to be the output of the heads method (i.e. a list of all nodes in the repository that have no children).

Parameters:

Returns:



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'lib/amp/revlogs/revlog.rb', line 308

def nodes_between(roots=nil, heads=nil)
  no_nodes = {:roots => [], :heads => [], :between => []}
  return no_nodes if roots != nil && roots.empty?
  return no_nodes if heads != nil && heads.empty?
  
  if roots.nil?
    roots = [NULL_ID] # Everybody's a descendent of nullid
    lowest_rev = NULL_REV
  else
    roots = roots.dup
    lowest_rev = roots.map {|r| revision_index_for_node r}.min
  end
  
  if lowest_rev == NULL_REV && heads.nil?
    # We want _all_ the nodes!
    return {:between => all_indices.map {|i| node_id_for_index i },
            :roots => [NULL_ID], :heads => self.heads}
  end
  
  if heads.nil?
    # All nodes are ancestors, so the latest ancestor is the last
    # node.
    highest_rev = self.size - 1
    # Set ancestors to None to signal that every node is an ancestor.
    ancestors = nil
    # Set heads to an empty dictionary for later discovery of heads
    heads = {}
  else
    heads = heads.dup
    ancestors = {}
    
    # Turn heads into a dictionary so we can remove 'fake' heads.
    # Also, later we will be using it to filter out the heads we can't
    # find from roots.
    heads = Hash.with_keys heads, false
    
    # Start at the top and keep marking parents until we're done.
    nodes_to_tag = heads.keys
    highest_rev = nodes_to_tag.map {|r| revision_index_for_node r }.max
    
    until nodes_to_tag.empty?
      # grab a node to tag
      node = nodes_to_tag.pop
      # Never tag nullid
      next if node.null?
      
      # A node's revision number represents its place in a
      # topologically sorted list of nodes.
      r = revision_index_for_node node
      if r >= lowest_rev
        if !ancestors.include?(node)
          # If we are possibly a descendent of one of the roots
          # and we haven't already been marked as an ancestor
          ancestors[node] = true # mark as ancestor
          # Add non-nullid parents to list of nodes to tag.
          nodes_to_tag += parents_for_node(node).reject {|p| p.null? }
        elsif heads.include? node # We've seen it before, is it a fake head?
          # So it is, real heads should not be the ancestors of
          # any other heads.
          heads.delete_at node
        end
      end
    end
    
    return no_nodes if ancestors.empty?
    
    # Now that we have our set of ancestors, we want to remove any
    # roots that are not ancestors.

    # If one of the roots was nullid, everything is included anyway.
    if lowest_rev > NULL_REV
      # But, since we weren't, let's recompute the lowest rev to not
      # include roots that aren't ancestors.

      # Filter out roots that aren't ancestors of heads
      roots = roots.select {|rev| ancestors.include? rev}
      
      return no_nodes if roots.empty? # No more roots?  Return empty list
      
      # Recompute the lowest revision
      lowest_rev = roots.map {|rev| revision_index_for_node rev}.min
    else
      lowest_rev = NULL_REV
      roots = [NULL_ID]
    end
  end
  
  # Transform our roots list into a 'set' (i.e. a dictionary where the
  # values don't matter.
  descendents = Hash.with_keys roots
  
  # Also, keep the original roots so we can filter out roots that aren't
  # 'real' roots (i.e. are descended from other roots).
  roots = descendents.dup
  
  # Our topologically sorted list of output nodes.
  ordered_output = []
  
  # Don't start at nullid since we don't want nullid in our output list,
  # and if nullid shows up in descedents, empty parents will look like
  # they're descendents.
  [lowest_rev, 0].max.upto(highest_rev) do |rev|
    node = node_id_for_index rev
    is_descendent = false
    
    if lowest_rev == NULL_REV # Everybody is a descendent of nullid
      is_descendent = true
    elsif descendents.include? node
      # n is already a descendent
      is_descendent = true
      
      # This check only needs to be done here because all the roots
      # will start being marked is descendents before the loop.
      if roots.include? node
        # If n was a root, check if it's a 'real' root.
        par = parents_for_node node
        # If any of its parents are descendents, it's not a root.
        if descendents.include?(par[0]) || descendents.include?(par[1])
          roots.delete_at node
        end
      end
    else
      # A node is a descendent if either of its parents are
      # descendents. (We seeded the dependents list with the roots
      # up there, remember?)
      par = parents_for_node node
      if descendents.include?(par[0]) || descendents.include?(par[1])
        descendents[node] = true
        is_descendent     = true
      end
    end
    
    if is_descendent && (ancestors.nil? || ancestors.include?(node))
      # Only include nodes that are both descendents and ancestors.
      ordered_output << node
      if !ancestors.nil? && heads.include?(node)
        # We're trying to figure out which heads are reachable
        # from roots.
        # Mark this head as having been reached
        heads[node] = true
      elsif ancestors.nil?
        # Otherwise, we're trying to discover the heads.
        # Assume this is a head because if it isn't, the next step
        # will eventually remove it.
        heads[node] = true
        
        # But, obviously its parents aren't.
        parents_for_node(node).each {|parent| heads.delete parent }
      end
    end
  end
  
  heads = heads.keys.select {|k| heads[k] }
  roots = roots.keys
  {:heads => heads, :roots => roots, :between => ordered_output}
end

#open(path, mode = "r") ⇒ Object

Actually opens the file.



56
57
58
# File 'lib/amp/revlogs/revlog.rb', line 56

def open(path, mode="r")
  @opener.open(path, mode)
end

#parent_indices_for_index(index) ⇒ Object

Returns the indicies of the parents (1 or 2) of the node at index



128
129
130
131
# File 'lib/amp/revlogs/revlog.rb', line 128

def parent_indices_for_index(index)
  [ self[index].parent_one_rev ,
    self[index].parent_two_rev ]
end

#parents_for_node(id) ⇒ Object Also known as: parents

Returns the node_id’s of the parents (1 or 2) of the given node ID.



118
119
120
121
122
123
# File 'lib/amp/revlogs/revlog.rb', line 118

def parents_for_node(id)
  #index = revision_index_for_node id
  entry = self[id]
  [ @index[entry.parent_one_rev].node_id , 
    @index[entry.parent_two_rev].node_id ]
end

#partial_id_match(id) ⇒ Object

Tries to find a partial match for a node_id in hex form.



535
536
537
538
539
540
541
542
543
544
# File 'lib/amp/revlogs/revlog.rb', line 535

def partial_id_match(id)
  return nil if id.size >= 40
  l = id.size / 2
  bin_id = id[0..(l*2 - 1)].unhexlify
  nl = @index.node_map.keys.select {|k| k[0..(l-1)] == bin_id}
  nl = nl.select {|n| n.hexlify =~ /^#{id}/}
  return nl.first if nl.size == 1
  raise RevlogSupport::LookupError.new("ambiguous ID #{id}") if nl.size > 1
  nil
end

#reachable_nodes_for_node(node, stop = nil) ⇒ Object

Returns a hash of all ancestral nodes that can be reached from the given node ID. Just do [node_id] on the result to check if it’s reachable.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/amp/revlogs/revlog.rb', line 203

def reachable_nodes_for_node(node, stop=nil)
  reachable = {}
  to_visit = [node]
  reachable[node] = true
  stop_idx = stop ? revision_index_for_node(stop) : 0
  
  until to_visit.empty?
    node = to_visit.shift
    next if node == stop || node.null?
    parents_for_node(node).each do |parent|
      next if revision_index_for_node(parent) < stop_idx
      unless reachable[parent]
        reachable[parent] = true
        to_visit << parent
      end
    end
  end
    
  reachable
end

#revision_diff(rev1, rev2) ⇒ String

Diffs 2 revisions, based on their indices. They are returned in BinaryDiff format.

Parameters:

  • rev1 (Fixnum)

    the index of the source revision

  • rev2 (Fixnum)

    the index of the destination revision

Returns:

  • (String)

    The diff of the 2 revisions.



651
652
653
654
655
656
# File 'lib/amp/revlogs/revlog.rb', line 651

def revision_diff(rev1, rev2)
  return get_chunk(rev2) if (rev1 + 1 == rev2) && 
         self[rev1].base_rev == self[rev2].base_rev
  Diffs::MercurialDiff.text_diff( decompress_revision(node_id_for_index(rev1)),
                                  decompress_revision(node_id_for_index(rev2)))
end

#revision_index_for_node(id) ⇒ Integer Also known as: rev

Returns the index number for the given node ID.

Parameters:

  • id (String)

    the node_id to lookup

Returns:

  • (Integer)

    the index into the revision index where you can find the requested node.



98
99
100
101
102
103
104
# File 'lib/amp/revlogs/revlog.rb', line 98

def revision_index_for_node(id)
  unless @index.node_map[id]
    p id
    raise StandardError.new("Couldn't find node for id '#{id}'")
  end
  @index.node_map[id]
end

#sizeObject Also known as: index_size

Returns the number of entries in this revision log.



175
176
177
# File 'lib/amp/revlogs/revlog.rb', line 175

def size
  @index.size - 1
end

#strip(min_link) ⇒ Object

Strips all revisions after (and including) a given link_index



961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
# File 'lib/amp/revlogs/revlog.rb', line 961

def strip(min_link)
  return if size == 0
  
  load_index_map if @index.is_a? RevlogSupport::LazyIndex
  
  rev = 0
  all_indices.each {|_rev| rev = _rev; break if @index[rev].link_rev >= min_link }
  return if rev > all_indices.max
  
  endpt = data_start_for_index rev
  unless @index.inline?
    df = File.open(@data_file, "a")
    df.truncate(endpt)
    endpt = rev * @index.entry_size
  else
    endpt += rev * @index.entry_size
  end
  
  indexf = File.open(@index_file, "a")
  indexf.truncate(endpt)
  
  @cache = @index.cache = nil
  @chunk_cache = nil
  rev.upto(self.size-1) {|x| @index.node_map.delete(self.node(x)) }
  @index.index = @index.index[0..rev-1]
end

#tipObject

Returns the node ID for the index’s tip-most revision



169
170
171
# File 'lib/amp/revlogs/revlog.rb', line 169

def tip
  node_id_for_index(@index.size - 2)
end

#uncompressed_size_for_index(index) ⇒ Object

Returns the uncompressed size of the data for the revision at index.



141
142
143
144
145
146
147
# File 'lib/amp/revlogs/revlog.rb', line 141

def uncompressed_size_for_index(index)
  len = self[index].uncompressed_len
  return len if len >= 0
  
  text = decompress_revision node_id_for_index(index)
  return text.size
end

#unified_revision_diff(rev1, rev2) ⇒ Object

Unified diffs 2 revisions, based on their indices. They are returned in a sexified unified diff format.



639
640
641
642
# File 'lib/amp/revlogs/revlog.rb', line 639

def unified_revision_diff(rev1, rev2)
  Diffs::MercurialDiff.unified_diff( decompress_revision(self.node_id_for_index(rev1)),
                                  decompress_revision(self.node_id_for_index(rev2)))
end