Class: Amp::Revlog
- 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.
Constant Summary
Constants included from Amp::RevlogSupport::Node
Amp::RevlogSupport::Node::NULL_ID, Amp::RevlogSupport::Node::NULL_REV
Instance Attribute Summary collapse
-
#data_file ⇒ Object
readonly
the file paths to the index and data files.
-
#index ⇒ Object
readonly
The actual Index object.
-
#index_file ⇒ Object
readonly
the file paths to the index and data files.
Instance Method Summary collapse
-
#[](idx) ⇒ IndexEntry
Returns the requested node as an IndexEntry.
-
#add_group(revisions, link_mapper, journal) ⇒ Object
Adds a changelog to the index.
-
#add_revision(text, transaction, link, p1, p2, d = nil, index_file_handle = nil) ⇒ String
add a revision to the log.
-
#all_indices ⇒ Array
Returns all of the indices for all revisions.
-
#ancestor(a, b) ⇒ Object
Finds the most-recent common ancestor for the two nodes.
-
#ancestors(revisions) ⇒ Object
Allows the user to operate on all the ancestors of the given revisions.
-
#base_revision_for_index(index) ⇒ Object
Returns the “base revision” index for the revision at index.
-
#check_inline_size(tr, fp = nil) ⇒ Object
TODO FINISH THIS METHOD TODO FIXME.
-
#checksize ⇒ Object
Checks to make sure our data and index files are the right size.
-
#children(node) ⇒ Object
Returns the children of the node with ID node.
-
#cmp(node, text) ⇒ Object
Compares a node with the provided text, as a consistency check.
-
#data_end_for_index(index) ⇒ Object
Returns the offset where the data ends for the revision at index.
-
#data_size_for_index(index) ⇒ Object
Returns the size of the data for the revision at index.
-
#data_start_for_index(index) ⇒ Object
Returns the offset where the data begins for the revision at index.
-
#decompress_revision(node) ⇒ String
Given a node ID, extracts that revision and decompresses it.
-
#descendants(revisions) ⇒ Object
Allows the user to operate on all the descendants of the given revisions.
-
#each(&b) ⇒ Object
Returns each revision as a Amp::RevlogSupport::IndexEntry.
-
#empty? ⇒ Boolean
Returns true if size is 0.
-
#files ⇒ Object
Returns all the files this object is concerned with.
-
#find_missing(common = [NULL_ID], heads = self.heads) ⇒ Object
Returns the topologically sorted list of nodes from the set: missing = (ancestors(heads) \ ancestors(common)).
-
#get_chunk(rev, data_file = nil) ⇒ String
Gets a chunk of data from the datafile (or, if inline, from the index file).
-
#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.
-
#heads(start = nil, stop = nil) ⇒ Object
Return the list of all nodes that have no children.
-
#id_match(id) ⇒ Object
Tries to find an exact match for a node with ID id.
-
#initialize(opener, indexfile) ⇒ Revlog
(also: #revlog_initialize)
constructor
Initializes the revision log with an opener object (which handles how the interface to opening the files) and the path to the index itself.
-
#link_revision_for_index(index) ⇒ Object
Returns the “link revision” index for the given revision index.
-
#load_cache(data_file, start, cache_length) ⇒ Object
Loads a block of data into the cache.
-
#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.
-
#node_id_for_index(index) ⇒ String
(also: #node)
Returns the unique node_id (a string) for a given revision at index.
-
#nodes_between(roots = nil, heads = nil) ⇒ {:heads => Array<String>, :roots => Array<String>, :between => Array<String>}
Return a tuple containing three elements.
-
#open(path, mode = "r") ⇒ Object
Actually opens the file.
-
#parent_indices_for_index(index) ⇒ Object
Returns the indicies of the parents (1 or 2) of the node at index.
-
#parents_for_node(id) ⇒ Object
(also: #parents)
Returns the node_id’s of the parents (1 or 2) of the given node ID.
-
#partial_id_match(id) ⇒ Object
Tries to find a partial match for a node_id in hex form.
-
#reachable_nodes_for_node(node, stop = nil) ⇒ Object
Returns a hash of all ancestral nodes that can be reached from the given node ID.
-
#revision_diff(rev1, rev2) ⇒ String
Diffs 2 revisions, based on their indices.
-
#revision_index_for_node(id) ⇒ Integer
(also: #rev)
Returns the index number for the given node ID.
-
#size ⇒ Object
(also: #index_size)
Returns the number of entries in this revision log.
-
#strip(min_link) ⇒ Object
Strips all revisions after (and including) a given link_index.
-
#tip ⇒ Object
Returns the node ID for the index’s tip-most revision.
-
#uncompressed_size_for_index(index) ⇒ Object
Returns the uncompressed size of the data for the revision at index.
-
#unified_revision_diff(rev1, rev2) ⇒ Object
Unified diffs 2 revisions, based on their indices.
Methods included from Amp::RevlogSupport::Node
Methods included from Enumerable
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.
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_file ⇒ Object (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 |
#index ⇒ Object (readonly)
The actual Index object.
30 31 32 |
# File 'lib/amp/revlogs/revlog.rb', line 30 def index @index end |
#index_file ⇒ Object (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.
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
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
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_indices ⇒ Array
Returns all of the indices for all revisions.
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
FINISH THIS METHOD
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 |
#checksize ⇒ Object
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!
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
182 183 184 |
# File 'lib/amp/revlogs/revlog.rb', line 182 def empty? index_size.zero? end |
#files ⇒ Object
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
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!
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) = nb + p[0] + p[1] + lookup[nb] if a == -1 data = decompress_revision nb += Diffs::MercurialDiff.trivial_diff_header(d.size) else data = revision_diff(a, b) end yield RevlogSupport::ChangeGroup.chunk_header(.size + data.size) yield 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 |
#link_revision_for_index(index) ⇒ Object
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.
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:
-
All nodes must be descended from a node in roots (the nodes on roots are considered descended from themselves).
-
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).
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.
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.
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 |
#size ⇒ Object 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 |
#tip ⇒ Object
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 |