Class: Amp::ChangeLog

Inherits:
Revlog show all
Defined in:
lib/amp/revlogs/changelog.rb

Overview

A ChangeLog is a special revision log that stores the actual commit data, including usernames, dates, messages, all kinds of stuff.

This version of the revision log is special though, because sometimes we have to hold off on writing until all other updates are done, for example during merges that might fail. So we have to actually have a real Opener and a fake one, which will save the data in memory. When you call #finalize, the fake file will replace the real deal.

Direct Known Subclasses

Bundles::BundleChangeLog

Constant Summary

Constants included from RevlogSupport::Node

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

Instance Attribute Summary collapse

Attributes inherited from Revlog

#data_file, #index

Instance Method Summary collapse

Methods inherited from Revlog

#[], #add_group, #add_revision, #all_indices, #ancestor, #ancestors, #base_revision_for_index, #checksize, #children, #cmp, #data_end_for_index, #data_size_for_index, #data_start_for_index, #decompress_revision, #descendants, #each, #empty?, #files, #find_missing, #get_chunk, #group, #heads, #id_match, #link_revision_for_index, #load_cache, #lookup_id, #node_id_for_index, #nodes_between, #open, #parent_indices_for_index, #parents_for_node, #partial_id_match, #reachable_nodes_for_node, #revision_diff, #revision_index_for_node, #size, #strip, #tip, #uncompressed_size_for_index, #unified_revision_diff

Methods included from RevlogSupport::Node

#short

Methods included from Enumerable

#inject, #select_map

Constructor Details

#initialize(opener) ⇒ ChangeLog Also known as: changelog_initialize

Initializes the revision log. Just pass in an Opener. Amp::Opener.new(path) will do just fine.

Parameters:

  • opener (Amp::Opener)

    an object that knows how to open and return files based on a root directory.



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

def initialize(opener)
  super(opener, "00changelog.i")
  @node_map = @index.node_map
end

Instance Attribute Details

#delay_bufferObject

Returns the value of attribute delay_buffer.



155
156
157
# File 'lib/amp/revlogs/changelog.rb', line 155

def delay_buffer
  @delay_buffer
end

#delay_countObject

Returns the value of attribute delay_count.



155
156
157
# File 'lib/amp/revlogs/changelog.rb', line 155

def delay_count
  @delay_count
end

#delay_nameObject

Returns the value of attribute delay_name.



155
156
157
# File 'lib/amp/revlogs/changelog.rb', line 155

def delay_name
  @delay_name
end

#index_fileObject

Returns the value of attribute index_file.



155
156
157
# File 'lib/amp/revlogs/changelog.rb', line 155

def index_file
  @index_file
end

#node_mapObject

Returns the value of attribute node_map.



155
156
157
# File 'lib/amp/revlogs/changelog.rb', line 155

def node_map
  @node_map
end

Instance Method Details

#add(manifest, files, desc, journal, p1 = nil, p2 = nil, user = nil, date = nil, extra = {}) ⇒ Object

TODO:

Handle text encodings

Adds the given commit to the changelog.

Parameters:

  • Manifest (Amp::Manifest)

    a hex-version of a node_id or something?

  • files (String)

    the files relevant to the commit, to be included

  • desc (String)

    the commit message from the user

  • journal (Amp::Journal)

    the transaction journal to write to for rollbacks if something goes horribly wrong

  • p1 (String) (defaults to: nil)

    the first parent of this node

  • p2 (String) (defaults to: nil)

    the second parent of this node

  • user (Strng) (defaults to: nil)

    the username of the committer

  • date (Time) (defaults to: nil)

    the date of the commit

  • extra (Hash) (defaults to: {})

    any extra data

Raises:



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/amp/revlogs/changelog.rb', line 317

def add(manifest, files, desc, journal, p1=nil, p2=nil, user=nil, date=nil, extra={})
  user = user.strip
  raise RevlogSupport::RevlogError.new("no \\n in username") if user=~ /\n/
  user, desc = user, desc #TODO: encoding!
  
  date = Time.now unless date
  parsed_date = "#{date.to_i} #{-1 * date.utc_offset}"
  
  if extra && ["default", "", nil].include?(extra["branch"])
    extra.delete "branch"
  end
  if extra
    extra = (extra.any?) ? encode_extra(extra) : ""
    parsed_date = "#{parsed_date}#{extra}"
  end
  
  l = [manifest.hexlify, user, parsed_date] + files.sort + ["", desc]
  text = l.join "\n"
  add_revision text, journal, self.size, p1, p2
end

#check_inline_size(journal, fp = nil) ⇒ Object

Does a check on our size, but knows enough to quit if we’re still in delayed-writing mode.

Parameters:

  • journal (Amp::Journal)

    the journal to use to keep track of our transaction

  • fp (File) (defaults to: nil)

    the file pointer to use to check our size



236
237
238
239
# File 'lib/amp/revlogs/changelog.rb', line 236

def check_inline_size(journal, fp=nil)
  return if @opener.is_a? DelayedOpener
  super(journal, fp)
end

#decode_extra(text) ⇒ Hash

Decodes the extra data stored with the commit, such as requirements or just about anything else we need to save

Parameters:

  • text (String)

    the data in the revision, decompressed

Returns:

  • (Hash)

    key-value pairs, joining each file with its extra data



246
247
248
249
250
251
252
# File 'lib/amp/revlogs/changelog.rb', line 246

def decode_extra(text)
  extra = {}
  text.split("\0").select {|l| l.any? }.
                   map {|l| l.remove_slashes.split(":",2) }.
                   each {|k,v| extra[k]=v }
  extra
end

#delay_updateObject

Tells the changelog to stop writing updates directly to the file, and start saving any new info to memory/other files. Used when the changelog has to be the last file saved.



173
174
175
176
177
178
179
# File 'lib/amp/revlogs/changelog.rb', line 173

def delay_update
  @_real_opener = @opener
  @opener = DelayedOpener.new(@_real_opener, self) # Our fake Opener
  @delay_count = self.size
  @delay_buffer = []
  @delay_name = nil
end

#encode_extra(data) ⇒ String

Encodes the extra data in a format we can use for writing.

Parameters:

  • data (Hash)

    the extra data to format

Returns:

  • (String)

    the encoded data



258
259
260
# File 'lib/amp/revlogs/changelog.rb', line 258

def encode_extra(data)
  " " + data.sort.map {|k| "#{k}:#{data[k]}".add_slashes }.join("\0")
end

#finalize(journal) ⇒ Object

Finalizes the changelog by swapping out the fake file if it has to. If there’s any other data left in the buffer, it will be written as well.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/amp/revlogs/changelog.rb', line 185

def finalize(journal)
  if @delay_name
    src = @_real_opener.join(@index_file+".a")
    dest = @_real_opener.join(@index_file)
    @opener = @_real_opener # switch back to normal mode....
    return File.amp_force_rename(src, dest)
  end
  
  if @delay_buffer && @delay_buffer.any?
    @fp = open(@index_file, "a")
    @fp.write @delay_buffer.join
    @fp.close
    @delay_buffer = []
  end
  # check_inline_size journal
end

#read(node) ⇒ [String, String, [Float, Integer], [String], String, Hash]

TODO:

Text encodings, I hate you. but i must do them

Reads the revision at the given node_id. It returns it in a format that tells us everything about the revision - the manifest, the user who committed it, timestamps, the relevant filenames, the description message, and any extra data.

Parameters:

  • node (Fixnum)

    the node ID to lookup into the revision log

Returns:



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/amp/revlogs/changelog.rb', line 273

def read(node)
  text = decompress_revision node
  if text.nil? || text.empty?
    return [NULL_ID, "", [0,0], [], "", {"branch" => "default"}]
  end
  #p text
  last = text.index("\n\n")
  desc = text[last+2..-1] #TODO: encoding
  l = text[0..last].split("\n")
  manifest = l[0].unhexlify
  user = l[1] #TODO: encoding
  extra_data = l[2].split(' ', 3)
  if extra_data.size != 3
    time = extra_data.shift.to_f
    timezone = extra_data.shift.to_i
    extra = {}
  else
    time, timezone, extra = extra_data
    time, timezone = time.to_f, timezone.to_i
    extra = decode_extra text
  end
  extra["branch"] = "default" unless extra["branch"]
  
  files = l[3..-1]
  
  #puts(">> Ari's tipmost changeset: "+[manifest, user, [time, timezone], files, desc, extra].inspect) #killme
  
  [manifest, user, [time, timezone], files, desc, extra]
end

#read_pending(file) ⇒ Object

Reads while we’re blocking this changelog’s output.

Parameters:

  • file

    the file to read in as a revision log



205
206
207
208
209
210
# File 'lib/amp/revlogs/changelog.rb', line 205

def read_pending(file)
  r = Revlog.new(@opener, file)
  @index = r.index
  @node_map = r.index.node_map
  @chunk_cache = r.chunk_cache
end

#write_pendingObject

Writes our data, while being aware of the delay buffer when we’re holding off on finalizing the changelog.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/amp/revlogs/changelog.rb', line 215

def write_pending
  if @delay_buffer && @delay_buffer.size > 0
    fp1 = @_real_opener.open(@index_file)
    fp2 = @_real_opener.open(@index_file + ".a", "w+")
    UI.debug "trying to open #{@index_file + ".a"}..."
    fp2.write fp1.read
    fp2.write @delay_buffer.join
    fp2.close
    fp1.close
    @delay_buffer = []
    @delay_name = @index_file
  end
  return true if @delay_name && @delay_name.any?
  false
end