Class: Amp::Repositories::BundleRepository

Inherits:
LocalRepository show all
Defined in:
lib/amp/repository/repositories/bundle_repository.rb

Overview

BundleRepository

This class represents a read-only repository that combines both local repository data with a bundle file. The bundle file contains un-merged-in changesets - this is useful for, say, previewing the results of a pull action.

A bundle is stored in the following manner:

- Changelog entries
- Manifest  entries
- Modified File entry #1
- Modified File entry #2
- ...
- Modified file entry #N

Constant Summary

Constants included from Amp::RevlogSupport::Node

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

Constants included from TagManager

TagManager::TAG_FORBIDDEN_LETTERS

Instance Attribute Summary

Attributes inherited from LocalRepository

#branch_manager, #config, #hg, #hg_opener, #root, #root_pathname, #store, #store_opener

Instance Method Summary collapse

Methods inherited from LocalRepository

#[], #add, #add_changegroup, #add_file, #annotate, #between, #branches, #changed?, #changegroup, #changegroup_info, #changegroup_subset, #clone, #commit, #commit_changeset, #commit_file, #common_nodes, #copy, #cwd, #dirstate, #dirstate_copy, #each, #find_incoming_roots, #find_outgoing_roots, #forget, #get_changegroup, #heads, #init, #inspect, #invalidate!, #join, #living_parents, #local?, #lock_store, #lock_working, #lock_working_and_store, #lookup, #make_lock, #merge_state, #open, #parents, #path_to, #pristine?, #pull, #push, #push_add_changegroup, #push_unbundle, #relative_join, #remove, #revert, #run_hook, #size, #status, #store_join, #stream_in, #undelete, #versioned_file, #walk, #working_file, #working_join, #working_read, #working_write

Methods included from Verification

#verify

Methods included from Updatable

#clean, #merge, #update

Methods included from Amp::RevlogSupport::Node

#short

Methods included from TagManager

#apply_tag, #invalidate_tag_cache!, #tag_list, #tag_type, #tags, #tags_for_node

Methods included from BranchManager

#branch_heads, #branch_tags, #save_branch_cache, #save_branches

Methods inherited from Repository

#add_path, #capable?, #get_capabilities, #local?, #require_capability

Constructor Details

#initialize(path = "", config = nil, bundle_name = "") ⇒ BundleRepository

Returns a new instance of BundleRepository.



19
20
21
22
23
24
25
26
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 19

def initialize(path="", config=nil, bundle_name="")
  @temp_parent = nil
  # Figure out what to do here - if there's no current local repository, that
  # takes some special work.
  begin
    super(path, false, config) # don't create, just look for a repository
  rescue
    # Ok, no local repository. Let's make one really quickly.
    @temp_parent = File.join(Dir.tmpdir, File.amp_make_tmpname("bundlerepo"))
    File.mkdir(@temp_parent)
    tmprepo = LocalRepository.new(@temp_parent, true, config) # true -> create
    super(@temp_parent, false, config) # and proceed as scheduled!
  end
  
  # Set up our URL variable, if anyone asks us what it is
  if path
    @url = "bundle:#{path}+#{bundle_name}"
  else
    @url = "bundle:#{bundle_name}"
  end
  
  @temp_file = nil
  @bundle_file = File.open(bundle_name, "r")
  
  @bundle_file.seek(0, IO::SEEK_END)
  Amp::UI.debug "Bundle File Size: #{@bundle_file.tell}"
  @bundle_file.seek(0, IO::SEEK_SET)
  
  # OK, now for the fun part - check the header to see if we're compressed.
  header = @bundle_file.read(6)
  # And switch based on that header
  if !header.start_with?("HG")
    # Not even an HG file. FML. Bail
    raise abort("#{bundle_name}: not a Mercurial bundle file")
  elsif not header.start_with?("HG10")
    # Not a version we understand, bail
    raise abort("#{bundle_name}: unknown bundle version")
  elsif header == "HG10BZ" || header == "HG10GZ"
    # Compressed! We'll have to save to a new file, because this could get messy.
    temp_file = Tempfile.new("hg-bundle-hg10un", @root)
    @temp_file_path = temp_file.path
    # Are we BZip, or GZip?
    case header
    when "HG10BZ"
      # fuck BZip. Seriously.
      headerio = StringIO.new "BZ", (ruby_19? ? "w+:ASCII-8BIT" : "w+")
      input = Amp::Support::MultiIO.new(headerio, @bundle_file)
      decomp = BZ2::Reader.new(input)
    when "HG10GZ"
      # Gzip is much nicer.
      decomp = Zlib::GzipReader.new(@bundle_file)
    end
    
    # We're writing this in an uncompressed fashion, of course.
    @temp_file.write("HG10UN")
    # While we can uncompressed....
    while !r.eof? do
      # Write the uncompressed data to our new file!
      @temp_file.write decomp.read(4096)
    end
    # and close 'er up
    @temp_file.close
    
    # Close the compressed bundle file
    @bundle_file.close
    # And re-open the uncompressed bundle file!
    @bundle_file = File.open(@temp_file_path, "r")
    # Skip the header.
    @bundle_file.seek(6)
  elsif header == "HG10UN"
    # uncompressed, do nothing
  else
    # We have no idae what's going on
    raise abort("#{bundle_name}: unknown bundle compression type")
  end
  # This hash stores pairs of {filename => position_in_bundle_file_of_this_file} 
  @bundle_files_positions = {}
end

Instance Method Details

#can_copy?Boolean

We can’t copy files. Read-only.

Returns:



208
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 208

def can_copy?; false; end

#changelogBundleChangeLog

Gets the changelog of the repository. This is different from LocalRepository#changelog in that it uses a BundleChangeLog. Also, since the manifest is stored in the bundle directly after the changelog, by checking our position in the bundle file, we can save where the bundle_file is stored.

Returns:

  • (BundleChangeLog)

    the changelog for this repository.



105
106
107
108
109
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 105

def changelog
  @changelog      ||= Bundles::BundleChangeLog.new(@store.opener, @bundle_file)
  @manifest_start ||= @bundle_file.tell
  @changelog
end

#closeObject

Closes the repository - in this case, it closes the bundle_file. Analogous to closing an SSHRepository’s socket.



203
204
205
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 203

def close
  @bundle_file.close
end

#file(filename) ⇒ FileLog

Gets the file-log for the given path, so we can look at an individual file’s history, for example. However, we need to be cognizant of files that traverse the local repository’s history as well as the bundle file.

Parameters:

  • f (String)

    the path to the file

Returns:

  • (FileLog)

    a filelog (a type of revision log) for the given file



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 159

def file(filename)
  
  # Load the file-log positions now - we didn't do this in the constructor for a reason
  # (if they don't ask for them, don't load them!)
  if @bundle_files_positions.empty?
    # Jump to the file position
    @bundle_file.seek file_start
    while true
      # get a changegroup chunk - it'll be the filename
      chunk = RevlogSupport::ChangeGroup.get_chunk @bundle_file
      # no filename? bail
      break if chunk.nil? || chunk.empty?
      
      # Now that we've read the filename, we're at the start of the changelogs for that
      # file. So let's save this position for later.
      @bundle_files_positions[chunk] = @bundle_file.tell
      # Then read chunks until we get to the next file!
      RevlogSupport::ChangeGroup.each_chunk(@bundle_file) {|c|}
    end
  end
  
  # Remove leading slash
  filename = filename.shift("/")
  
  # Does this file cross local history as well as the bundle?
  if @bundle_files_positions[filename]
    # If so, we'll need to make a BundleFileLog. Meh. 
    @bundle_file.seek @bundle_files_positions[filename]
    Bundles::BundleFileLog.new @store.opener, filename, @bundle_file, proc {|n| changelog.rev(n) }
  else
    # Nope? Make a normal FileLog!
    FileLog.new(@store.opener, filename)
  end
end

#file_startInteger

Returns the position in the bundle file where the file log changesets are located. This involves loading the changelog and the manifest first - see #manifest.

Returns:

  • (Integer)

    the position in the bundle file where we can find the file-log changesets.



148
149
150
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 148

def file_start
  manifest && @file_start
end

#get_cwdObject

Gets the current working directory. Not sure why we need this.



210
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 210

def get_cwd; Dir.pwd; end

#manifestBundleChangeLog

Gets the manifest of the repository. This is different from LocalRepository#manifest in that it uses a BundleManifest. The file logs are stored in the bundle directly after the manifest, so once we load the manifest, we save where the file logs start when we are done loading the manifest.

This has the side-effect of loading the changelog, if it hasn’t been loaded already -# this is necessary because the manifest changesets are stored after the changelog changesets, and we must fully load the changelog changesets to know where to look for the manifest changesets.

Don’t look at me, I didn’t design the file format.

Returns:

  • (BundleChangeLog)

    the changelog for this repository.



124
125
126
127
128
129
130
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 124

def manifest
  return @manifest if @manifest
  @bundle_file.seek manifest_start
  @manifest   ||= Bundles::BundleManifest.new @store.opener, @bundle_file, proc {|n| changelog.rev(n) }
  @file_start ||= @bundle_file.tell
  @manifest
end

#manifest_startInteger

Returns the position in the bundle file where the manifest changesets are located. This involves loading the changelog first - see #manifest

Returns:

  • (Integer)

    the position in the bundle file where we can find the manifest changesets.



138
139
140
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 138

def manifest_start
  changelog && @manifest_start
end

#urlString

Gets the URL for this repository - unused, I believe.

Returns:

  • (String)

    the URL for the repository



198
# File 'lib/amp/repository/repositories/bundle_repository.rb', line 198

def url; @url; end