-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
346 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,167 +1,48 @@ | ||
# Copyright (c) 2021 Lightricks. All rights reserved. | ||
# Created by Ben Yohay. | ||
# frozen_string_literal: true | ||
|
||
require "json" | ||
require "tmpdir" | ||
require "tempfile" | ||
require "xcodeproj" | ||
# Copyright (c) 2022 Lightricks. All rights reserved. | ||
# Created by Ben Yohay. | ||
|
||
require_relative "kintsugi/xcodeproj_extensions" | ||
require_relative "kintsugi/apply_change_to_project" | ||
require_relative "kintsugi/cli" | ||
require_relative "kintsugi/error" | ||
require_relative "kintsugi/merge" | ||
|
||
module Kintsugi | ||
class << self | ||
# Resolves git conflicts of a pbxproj file specified by `project_file_path`. | ||
# | ||
# @param [String] project_file_path | ||
# Project to which to apply the changes. | ||
# | ||
# @param [String] changes_output_path | ||
# Path to where the changes to apply to the project are written in JSON format. | ||
# | ||
# @raise [ArgumentError] | ||
# If the file extension is not `pbxproj`, or the file doesn't exist, or if no rebase, | ||
# cherry-pick, or merge is in progress | ||
# | ||
# @raise [MergeError] | ||
# If there was an error applying the change to the project. | ||
# | ||
# @return [void] | ||
def resolve_conflicts(project_file_path, changes_output_path) | ||
validate_project(project_file_path) | ||
|
||
project_in_temp_directory = | ||
open_project_of_current_commit_in_temporary_directory(project_file_path) | ||
|
||
change = change_of_conflicting_commit_with_parent(project_file_path) | ||
|
||
if changes_output_path | ||
File.write(changes_output_path, JSON.pretty_generate(change)) | ||
end | ||
|
||
apply_change_and_copy_to_original_path(project_in_temp_directory, change, project_file_path) | ||
end | ||
|
||
# Merges the changes done between `theirs_project_path` and `base_project_path` to the file at | ||
# `ours_project_path`. The files may not be at the original path, and therefore the | ||
# `original_project_path` is required in order for the project metadata to be written properly. | ||
# | ||
# @param [String] base_project_path | ||
# Path to the base version of the project. | ||
# | ||
# @param [String] ours_project_path | ||
# Path to ours version of the project. | ||
# | ||
# @param [String] theirs_project_path | ||
# Path to theirs version of the project. | ||
# | ||
# @param [String] original_project_path | ||
# Path to the original path of the file. | ||
# | ||
# @raise [MergeError] | ||
# If there was an error applying the change to the project. | ||
# | ||
# @return [void] | ||
def three_way_merge(base_project_path, ours_project_path, theirs_project_path, | ||
original_project_path) | ||
original_directory_name = File.basename(File.dirname(original_project_path)) | ||
base_temporary_project = | ||
copy_project_to_temporary_path_in_directory_with_name(base_project_path, | ||
original_directory_name) | ||
ours_temporary_project = | ||
copy_project_to_temporary_path_in_directory_with_name(ours_project_path, | ||
original_directory_name) | ||
theirs_temporary_project = | ||
copy_project_to_temporary_path_in_directory_with_name(theirs_project_path, | ||
original_directory_name) | ||
|
||
change = | ||
Xcodeproj::Differ.project_diff(theirs_temporary_project, base_temporary_project, | ||
:added, :removed) | ||
|
||
apply_change_and_copy_to_original_path(ours_temporary_project, change, ours_project_path) | ||
end | ||
|
||
private | ||
|
||
PROJECT_FILE_NAME = "project.pbxproj" | ||
|
||
def apply_change_and_copy_to_original_path(project, change, original_project_file_path) | ||
apply_change_to_project(project, change) | ||
project.save | ||
FileUtils.cp(File.join(project.path, PROJECT_FILE_NAME), original_project_file_path) | ||
end | ||
|
||
def validate_project(project_file_path) | ||
unless File.exist?(project_file_path) | ||
raise ArgumentError, "File '#{project_file_path}' doesn't exist" | ||
end | ||
|
||
if File.extname(project_file_path) != ".pbxproj" | ||
raise ArgumentError, "Wrong file extension, please provide file with extension .pbxproj\"" | ||
end | ||
|
||
Dir.chdir(File.dirname(project_file_path)) do | ||
unless file_has_base_ours_and_theirs_versions?(project_file_path) | ||
raise ArgumentError, "File '#{project_file_path}' doesn't have conflicts, " \ | ||
"or a 3-way merge is not possible." | ||
def run(arguments) | ||
first_argument = arguments[0] | ||
cli = CLI.new | ||
command = | ||
if name_of_subcommand?(cli.subcommands, first_argument) | ||
arguments.shift | ||
cli.subcommands[first_argument] | ||
else | ||
cli.root_command | ||
end | ||
end | ||
end | ||
|
||
def copy_project_to_temporary_path_in_directory_with_name(project_file_path, directory_name) | ||
temp_directory_name = File.join(Dir.mktmpdir, directory_name) | ||
Dir.mkdir(temp_directory_name) | ||
temp_project_file_path = File.join(temp_directory_name, PROJECT_FILE_NAME) | ||
FileUtils.cp(project_file_path, temp_project_file_path) | ||
Xcodeproj::Project.open(File.dirname(temp_project_file_path)) | ||
end | ||
options = parse_options!(command, arguments) | ||
|
||
def open_project_of_current_commit_in_temporary_directory(project_file_path) | ||
project_directory_name = File.basename(File.dirname(project_file_path)) | ||
temp_directory_name = File.join(Dir.mktmpdir, project_directory_name) | ||
Dir.mkdir(temp_directory_name) | ||
temp_project_file_path = File.join(temp_directory_name, PROJECT_FILE_NAME) | ||
Dir.chdir(File.dirname(project_file_path)) do | ||
`git show HEAD:./project.pbxproj > "#{temp_project_file_path}"` | ||
begin | ||
command.action.call(options, arguments) | ||
rescue ArgumentError => e | ||
puts "#{e.class}: #{e}" | ||
raise | ||
rescue Kintsugi::MergeError => e | ||
puts e | ||
raise | ||
end | ||
Xcodeproj::Project.open(File.dirname(temp_project_file_path)) | ||
end | ||
|
||
def file_has_base_ours_and_theirs_versions?(file_path) | ||
Dir.chdir(`git rev-parse --show-toplevel`.strip) do | ||
file_has_version_in_stage_numbers?(file_path, [1, 2, 3]) | ||
end | ||
end | ||
private | ||
|
||
def file_has_version_in_stage_numbers?(file_path, stage_numbers) | ||
file_absolute_path = File.absolute_path(file_path) | ||
actual_stage_numbers = | ||
`git ls-files -u -- "#{file_absolute_path}"`.split("\n").map do |git_file_status| | ||
git_file_status.split[2] | ||
end | ||
(stage_numbers - actual_stage_numbers.map(&:to_i)).empty? | ||
def parse_options!(command, arguments) | ||
options = {} | ||
command.option_parser.parse!(arguments, into: options) | ||
options | ||
end | ||
|
||
def change_of_conflicting_commit_with_parent(project_file_path) | ||
Dir.chdir(File.dirname(project_file_path)) do | ||
conflicting_commit_project_file_path = File.join(Dir.mktmpdir, PROJECT_FILE_NAME) | ||
`git show :3:./#{PROJECT_FILE_NAME} > #{conflicting_commit_project_file_path}` | ||
|
||
conflicting_commit_parent_project_file_path = File.join(Dir.mktmpdir, PROJECT_FILE_NAME) | ||
`git show :1:./#{PROJECT_FILE_NAME} > #{conflicting_commit_parent_project_file_path}` | ||
|
||
conflicting_commit_project = Xcodeproj::Project.open( | ||
File.dirname(conflicting_commit_project_file_path) | ||
) | ||
conflicting_commit_parent_project = | ||
Xcodeproj::Project.open(File.dirname(conflicting_commit_parent_project_file_path)) | ||
|
||
Xcodeproj::Differ.project_diff(conflicting_commit_project, | ||
conflicting_commit_parent_project, :added, :removed) | ||
end | ||
def name_of_subcommand?(subcommands, argument) | ||
subcommands.include?(argument) | ||
end | ||
end | ||
end |
Oops, something went wrong.