Class: Rails::Credentials::Conflict::MergeStrategy
- Inherits:
-
Object
- Object
- Rails::Credentials::Conflict::MergeStrategy
- Defined in:
- lib/rails/credentials/conflict/merge_strategy.rb,
sig/rails/credentials/conflict/merge_strategy.rbs
Overview
Performs three-way merge of decrypted credentials using git merge-file.
Handles creating conflict markers for manual resolution, detecting unresolved markers, validating YAML, and opening an editor for interactive conflict resolution.
Constant Summary collapse
- CONFLICT_MARKER_START =
"<<<<<<<"- CONFLICT_MARKER_END =
">>>>>>>"
Instance Method Summary collapse
-
#create_conflict_markers(ours_content, base_content, theirs_content, labels:) ⇒ Object
Runs a three-way merge via
git merge-fileand returns the result. -
#has_conflict_markers?(content) ⇒ Boolean
Returns true if the content contains unresolved git conflict markers.
-
#initialize(encryption_service) ⇒ MergeStrategy
constructor
A new instance of MergeStrategy.
-
#open_editor_for_resolution(merged_content) ⇒ String
Writes
merged_contentto a temp file, opens it in$EDITOR, and returns the edited content after the editor exits. -
#validate_yaml!(content) ⇒ void
Parses content as YAML and raises
Errorif it is invalid. - #with_temp_files(*contents) {|paths| ... } ⇒ Object
Constructor Details
#initialize(encryption_service) ⇒ MergeStrategy
19 20 21 |
# File 'lib/rails/credentials/conflict/merge_strategy.rb', line 19 def initialize(encryption_service) @encryption_service = encryption_service end |
Instance Method Details
#create_conflict_markers(ours_content, base_content, theirs_content, labels:) ⇒ Object
Runs a three-way merge via git merge-file and returns the result.
Returns a Hash with :content (merged text) and :has_conflicts
(true when conflict markers are present).
Raises Error when git merge-file exits with a status greater
than 1, which indicates an actual error rather than conflicts.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/rails/credentials/conflict/merge_strategy.rb', line 30 def create_conflict_markers(ours_content, base_content, theirs_content, labels:) with_temp_files(ours_content, base_content, theirs_content) do |ours_path, base_path, theirs_path| merged_content, status = Open3.capture2( "git", "merge-file", "-p", "--diff3", "-L", labels[:ours], "-L", labels[:base], "-L", labels[:theirs], ours_path, base_path, theirs_path, err: File::NULL ) exit_code = status.exitstatus raise Error, "git merge-file failed (exit #{exit_code || "unknown"})" if exit_code.nil? || exit_code > 1 { content: merged_content, has_conflicts: exit_code == 1 } end end |
#has_conflict_markers?(content) ⇒ Boolean
Returns true if the content contains unresolved git conflict markers.
50 51 52 |
# File 'lib/rails/credentials/conflict/merge_strategy.rb', line 50 def has_conflict_markers?(content) content.include?(CONFLICT_MARKER_START) || content.include?(CONFLICT_MARKER_END) end |
#open_editor_for_resolution(merged_content) ⇒ String
Writes merged_content to a temp file, opens it in $EDITOR,
and returns the edited content after the editor exits.
63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/rails/credentials/conflict/merge_strategy.rb', line 63 def open_editor_for_resolution(merged_content) Tempfile.create(["credentials_conflict", ".yml"]) do |tempfile| tempfile.write(merged_content) tempfile.flush editor = ENV.fetch("EDITOR", "vim") system(editor, tempfile.path) File.read(tempfile.path) end end |
#validate_yaml!(content) ⇒ void
This method returns an undefined value.
Parses content as YAML and raises Error if it is invalid.
55 56 57 58 59 |
# File 'lib/rails/credentials/conflict/merge_strategy.rb', line 55 def validate_yaml!(content) YAML.safe_load(content) rescue Psych::SyntaxError => e raise Error, "Resolved content is not valid YAML: #{e.message}" end |
#with_temp_files(*contents) {|paths| ... } ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/rails/credentials/conflict/merge_strategy.rb', line 77 def with_temp_files(*contents) temp_files = contents.map.with_index do |content, index| file = Tempfile.new(["temp_#{index}", ".yml"]) file.write(content) file.flush file end yield(*temp_files.map(&:path)) ensure temp_files&.each do |file| file.close file.unlink end end |