1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/irb/ext/save-history.rb
Jeremy Evans 14e1739ff3 [ruby/irb] Make save-history extension safe for concurrent use
This makes the save-history extension check for modifications to
the history file before saving it.  If the history file was modified
after the history was loaded and before it was saved, append only
the new history lines to the history file.

This can result in more lines in the history file than SAVE_HISTORY
allows.  However, that will be fixed the next time irb is run and
the history is saved.

Fixes [Bug #13654]

https://github.com/ruby/irb/commit/041ef53845
2021-03-06 00:18:32 +09:00

130 lines
3.8 KiB
Ruby

# frozen_string_literal: false
# save-history.rb -
# $Release Version: 0.9.6$
# $Revision$
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
#
#
module IRB
module HistorySavingAbility # :nodoc:
end
class Context
def init_save_history# :nodoc:
unless (class<<@io;self;end).include?(HistorySavingAbility)
@io.extend(HistorySavingAbility)
end
end
# A copy of the default <code>IRB.conf[:SAVE_HISTORY]</code>
def save_history
IRB.conf[:SAVE_HISTORY]
end
remove_method(:save_history=) if method_defined?(:save_history=)
# Sets <code>IRB.conf[:SAVE_HISTORY]</code> to the given +val+ and calls
# #init_save_history with this context.
#
# Will store the number of +val+ entries of history in the #history_file
#
# Add the following to your +.irbrc+ to change the number of history
# entries stored to 1000:
#
# IRB.conf[:SAVE_HISTORY] = 1000
def save_history=(val)
IRB.conf[:SAVE_HISTORY] = val
if val
main_context = IRB.conf[:MAIN_CONTEXT]
main_context = self unless main_context
main_context.init_save_history
end
end
# A copy of the default <code>IRB.conf[:HISTORY_FILE]</code>
def history_file
IRB.conf[:HISTORY_FILE]
end
# Set <code>IRB.conf[:HISTORY_FILE]</code> to the given +hist+.
def history_file=(hist)
IRB.conf[:HISTORY_FILE] = hist
end
end
module HistorySavingAbility # :nodoc:
def HistorySavingAbility.extended(obj)
IRB.conf[:AT_EXIT].push proc{obj.save_history}
obj.load_history
obj
end
def load_history
return unless self.class.const_defined?(:HISTORY)
history = self.class::HISTORY
if history_file = IRB.conf[:HISTORY_FILE]
history_file = File.expand_path(history_file)
end
history_file = IRB.rc_file("_history") unless history_file
if File.exist?(history_file)
open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f|
f.each { |l|
l = l.chomp
if self.class == ReidlineInputMethod and history.last&.end_with?("\\")
history.last.delete_suffix!("\\")
history.last << "\n" << l
else
history << l
end
}
end
@loaded_history_lines = history.size
@loaded_history_mtime = File.mtime(history_file)
end
end
def save_history
return unless self.class.const_defined?(:HISTORY)
history = self.class::HISTORY
if num = IRB.conf[:SAVE_HISTORY] and (num = num.to_i) != 0
if history_file = IRB.conf[:HISTORY_FILE]
history_file = File.expand_path(history_file)
end
history_file = IRB.rc_file("_history") unless history_file
# Change the permission of a file that already exists[BUG #7694]
begin
if File.stat(history_file).mode & 066 != 0
File.chmod(0600, history_file)
end
rescue Errno::ENOENT
rescue Errno::EPERM
return
rescue
raise
end
if File.exist?(history_file) && @loaded_history_mtime &&
File.mtime(history_file) != @loaded_history_mtime
history = history[@loaded_history_lines..-1]
append_history = true
end
open(history_file, "#{append_history ? 'a' : 'w'}:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f|
hist = history.map{ |l| l.split("\n").join("\\\n") }
unless append_history
begin
hist = hist.last(num) if hist.size > num and num > 0
rescue RangeError # bignum too big to convert into `long'
# Do nothing because the bignum should be treated as inifinity
end
end
f.puts(hist)
end
end
end
end
end