Module: Amp::Bundles::BundleRevlog

Includes:
RevlogSupport::Node
Included in:
BundleChangeLog, BundleFileLog, BundleManifest
Defined in:
lib/amp/revlogs/bundle_revlogs.rb

Overview

This module handles revlogs passed to our client (or server) through the bundle file format. Thing is, this revision log spans both a physical filelog, and a bundle (the new revisions), and we might need to get stuff from both. It’s kind of like the DelayedOpener/FakeAppender for changelogs.

Constant Summary collapse

BUNDLED_INDEX_ENTRY_SIZE =
80

Constants included from RevlogSupport::Node

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

Instance Method Summary collapse

Methods included from RevlogSupport::Node

#short

Instance Method Details

#add_group(revisions, link_mapper, journal) ⇒ Object

Give an error to enforce read-only behavior

Raises:

  • (NotImplementedError)


191
192
193
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 191

def add_group(revisions, link_mapper, journal)
  raise NotImplementedError.new
end

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

Give an error to enforce read-only behavior

Raises:

  • (NotImplementedError)


185
186
187
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 185

def add_revision(text, transaction, link, p1, p2, d=nil, index_file_handle = nil)
  raise NotImplementedError.new
end

#bundle_initialize(opener, index_file, bundle_file, link_mapper = nil) ⇒ Object

Initializes a bundle revlog. Takes, in addition to the normal revlog arguments, a bundle_file. This is any IO we can read from that will give us additional revisions, aside from the revisions stored in the real Revlog. It also takes a link_mapper that will connect things to the changelog revisions (including changelog revisions in the bundle).

Parameters:

  • opener (Opener)

    the opener to use for openinf up the index_file

  • index_file (String)

    the name of the file containing the revlog’s index

  • bundle_file (IO)

    an IO that we can #read from

  • link_mapper (Proc, #call) (defaults to: nil)

    a function that will give us the link-index to connect revisions to changelog revisions based on node_ids



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 27

def bundle_initialize(opener, index_file, bundle_file, link_mapper = nil)
  @bundle_file = bundle_file
  @base_map = {}
  
  num_revs = self.index_size
  previous = nil
  all_chunk_positions do |chunk, start|
    chunk_size = chunk.size
    
    # each chunk starts with 4 node IDs: the new node's ID, its 2 parent node IDs,
    # and the node ID of the corresponding revision in the changelog. In that order.
    # If we have less than 80 bytes (BUNDLED_INDEX_ENTRY_SIZE), then we're fucked.
    if chunk_size < BUNDLED_INDEX_ENTRY_SIZE
      raise abort("invalid changegroup")
    end
    
    start      += BUNDLED_INDEX_ENTRY_SIZE
    chunk_size -= BUNDLED_INDEX_ENTRY_SIZE
    
    # Get the aforementioned node IDs
    node, parent_1, parent_2, changeset = chunk[0..79].unpack("a20a20a20a20")
    
    # Do we already have this node? Skip it.
    if @index.has_node? node
      previous = node
      next
    end
    
    # make sure we have the new node's parents, or all of our operations will fail!
    # at least, the interesting ones.
    [parent_1, parent_2].each do |parent|
      unless @index.has_node? parent
        raise abort("Unknown parent: #{parent}@#{index_file}")
      end
    end
    
    link_rev = (link_mapper && link_mapper[changeset]) || num_revs
    previous ||= parent_1
    
    @index << [RevlogSupport::Support.offset_version(start, 0), chunk_size, -1, -1, link_rev,
               revision_index_for_node(parent_1), revision_index_for_node(parent_2),
               node]
    
    @index.node_map[node] = num_revs
    @base_map[num_revs] = previous
    
    previous = node
    num_revs += 1
  end
end

#bundled_base_revision_for_index(revision) ⇒ String Also known as: bundled_base

Returns the base revision for the revision at the given index, while being cognizant of the bundle-ness of this revlog.

Parameters:

  • revision (Fixnum)

    the revision index to lookup the base-revision of

Returns:

  • (String)

    the revision node ID of the base for the requested revision



95
96
97
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 95

def bundled_base_revision_for_index(revision)
  @base_map[revision] || base_revision_for_index(revision)
end

#bundled_revision?(revision) ⇒ Boolean

Returns whether the revision index is in the bundle part of this revlog, or if it’s in the actual, stored revlog file.

Parameters:

  • revision (Fixnum)

    the revision index to lookup

Returns:

  • (Boolean)

    is the revision in the bundle section?



84
85
86
87
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 84

def bundled_revision?(revision)
  return false if revision < 0
  !!@base_map[revision]
end

#decompress_revision(node) ⇒ String

Given a node ID, extracts that revision and decompresses it. What you get back will the pristine revision data! Checks for bundle-ness when we access a node.

Parameters:

  • node (String)

    the Node ID of the revision to extract.

Returns:

  • (String)

    the pristine revision data.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 147

def decompress_revision(node)
  return "" if node == NULL_ID
  
  text = nil
  chain = []
  iter_node = node
  rev = revision_index_for_node(node)
  # walk backwards down the chain. Every single node is going
  # to be a diff, because it's from a bundle.
  while bundled_revision? rev
    if @index.cache && @index.cache[0] == iter_node
      text = @index.cache[2]
      break
    end
    chain << rev
    iter_node = bundled_base_revision_for_index rev
    rev = revision_index_for_node iter_node
  end
  # done walking back, see if we have a stored cache!
  text = super(iter_node) if text.nil? || text.empty?
  
  while chain.any?
    delta = get_chunk(chain.pop)
    text = Diffs::MercurialPatch.apply_patches(text, [delta])
  end
  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(node), text.inspect])
  end
  
  @index.cache = [node, revision_index_for_node(node), text]
  text
end

#get_chunk(revision, datafile = nil, cache_len = 4096) ⇒ 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. Only difference is that this will check the bundlefile if necessary.

Parameters:

  • rev (Fixnum)

    the revision index to extract

  • data_file (IO)

    The IO file descriptor for loading data

Returns:

  • (String)

    the raw data from the index (posssibly compressed)



108
109
110
111
112
113
114
115
116
117
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 108

def get_chunk(revision, datafile=nil, cache_len = 4096)
  # Warning: in case of bundle, the diff is against bundlebase, not against
  # rev - 1
  # TODO: could use some caching
  unless bundled_revision?(revision)
    return super(revision, datafile)
  end
  @bundle_file.seek data_start_for_index(revision)
  @bundle_file.read self[revision].compressed_len
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.



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/amp/revlogs/bundle_revlogs.rb', line 126

def revision_diff(rev1, rev2)
  both_bundled = bundled_revision?(rev1) && bundled_revision?(rev2)
  if both_bundled
    # super-quick path if both are bundled and rev2 == rev1 + 1 diff
    revision_base = self.revision_index_for_node bundled_base_revision_for_index(rev2)
    if revision_base == rev1
      # if rev2 = rev1 + a diff, just get the diff!
      return get_chunk(revision2)
    end
  end
  # normal style
  return super(rev1, rev2)
end