Skip to content
Browse files

Use a different strategy to update so we get handling conflicts

Test handles conflicts
  • Loading branch information...
1 parent 6bfbf69 commit d7e8ccc2b06f3cb53fb72991ee7522980dd35100 Sergio Cambra committed with Oct 10, 2008
View
4 TODO
@@ -1,4 +1,6 @@
= Integration tests to verify
-* Handles conflicts (currently when there is a conflict doesn't update from git repositories, update from svn repositories but ignores the conflict)
+* Port conflicts in temporary checkout directory to working copy:
+It detects and marks conflicts with changes after last update. But when some changes, which were made before last update, conflict when merging in temporary directory, file is merged correctly but conflicts are not marked
+In Piston 1.x old changes were lost, even without conflicts
* Force addition of Git repositories, because the files might be ignored in .gitignore
View
5 lib/piston/git/client.rb
@@ -54,8 +54,7 @@ def run_real(cmd)
begin
pid, stdin, stdout, stderr = Open4::popen4(cmd)
_, cmdstatus = Process.waitpid2(pid)
- return stdout.read if cmd =~ /status/ && cmdstatus.exitstatus == 1
- raise CommandError, "#{cmd.inspect} exited with status: #{cmdstatus.exitstatus}\n#{stderr.read}" unless cmdstatus.success?
+ raise CommandError, "#{cmd.inspect} exited with status: #{cmdstatus.exitstatus}\n#{stderr.read}" unless cmdstatus.success? || cmdstatus.exitstatus == 1
return stdout.read
rescue Errno::ENOENT
raise BadCommand, cmd.inspect
@@ -68,7 +67,7 @@ def run_real(cmd)
def run_real(cmd)
out = `#{cmd}`
raise BadCommand, cmd.inspect if $?.exitstatus == 127
- raise Failed, "#{cmd.inspect} exited with status: #{$?.exitstatus}" unless $?.success? || (cmd =~ /status/ && $?.exitstatus == 1)
+ raise Failed, "#{cmd.inspect} exited with status: #{$?.exitstatus}" unless $?.success? || $?.exitstatus == 1
out
end
end
View
7 lib/piston/git/commit.rb
@@ -49,7 +49,6 @@ def checkout_to(dir)
super
git(:clone, repository.url, @dir)
Dir.chdir(@dir) do
- logger.debug {"in dir #{@dir}"}
git(:checkout, "-b", branch_name, commit)
response = git(:log, "-n", "1")
@sha1 = $1 if response =~ /commit\s+([a-f\d]{40})/i
@@ -60,10 +59,10 @@ def update_to(commit)
raise ArgumentError, "Commit #{self.commit} of #{repository.url} was never checked out -- can't update" unless @dir
Dir.chdir(@dir) do
- logger.debug {"in dir #{@dir}"}
- git(:commit, '-a', '-m', 'local changes') unless git(:status) =~ /nothing to commit/
+ logger.debug {"Saving old changes before updating"}
+ git(:commit, '-a', '-m', 'old changes')
+ logger.debug {"Merging old changes with #{commit}"}
git(:merge, '--squash', commit)
-
output = git(:status)
added = output.scan(/new file:\s+(.*)$/).flatten
deleted = output.scan(/deleted:\s+(.*)$/).flatten
View
47 lib/piston/git/working_copy.rb
@@ -62,11 +62,6 @@ def finalize
Dir.chdir(path) { git(:add, ".") }
end
- def copy_from(revision)
- super
- Dir.chdir(path) { git(:add, "-u") }
- end
-
def add(added)
Dir.chdir(path) do
added.each { |item| git(:add, item) }
@@ -84,12 +79,36 @@ def rename(renamed)
renamed.each { |from, to| git(:mv, from, to) }
end
end
+
+ def downgrade_to(revision)
+ logger.debug {"Creating a branch to copy changes from remote repository"}
+ Dir.chdir(path) { git(:checkout, '-b', "my-#{revision}", revision) }
+ end
+ def merge_local_changes(revision)
+ from_revision = current_revision
+ Dir.chdir(path) do
+ begin
+ logger.debug {"Saving changes in temporary branch"}
+ git(:commit, '-a', '-m', 'merging')
+ logger.debug {"Return to previous branch"}
+ git(:checkout, revision)
+ logger.debug {"Merge changes from temporary branch"}
+ git(:merge, '--squash', from_revision)
+ rescue Piston::Git::Client::CommandError
+ git(:checkout, revision)
+ ensure
+ logger.debug {"Deleting temporary branch"}
+ git(:branch, '-D', from_revision)
+ end
+ end
+ end
+
def update(revision, to, lock)
tmpdir = temp_dir_name
begin
- logger.info {"Checking out the repository at #{to.revision}"}
- to.checkout_to(tmpdir)
+ logger.debug {"Checking out the repository at #{to.revision}"}
+ to.checkout_to(tmpdir) # is needed to remember new remote revision
ensure
logger.debug {"Removing temporary directory: #{tmpdir}"}
tmpdir.rmtree rescue nil
@@ -100,13 +119,23 @@ def update(revision, to, lock)
def locally_modified
Dir.chdir(path) do
# get latest commit for .piston.yml
- data = git(:log, '-n', '1', yaml_path.relative_path_from(path))
- initial_revision = data.match(/commit\s+(.*)$/)[1]
+ initial_revision = last_changed_revision(yaml_path)
# get latest revisions for this working copy since last update
log = git(:log, '-n', '1', "#{initial_revision}..")
not log.empty?
end
end
+
+ protected
+ def current_revision
+ Dir.chdir(path) { git(:branch).match(/^\*\s+(.+)$/)[1] }
+ end
+
+ def last_changed_revision(path)
+ path = Pathname.new(path) unless path.is_a? Pathname
+ path = path.relative_path_from(self.path) unless path.relative?
+ Dir.chdir(self.path) { git(:log, '-n', '1', path).match(/commit\s+(.*)$/)[1] }
+ end
end
end
end
View
1 lib/piston/revision.rb
@@ -38,6 +38,7 @@ def checkout_to(dir)
# Update a copy of this repository to revision +to+.
def update_to(to, lock)
+ raise SubclassResponsibilityError, "Piston::Revision#update_to should be implemented by a subclass."
end
# What values does this revision want to remember for the future ?
View
2 lib/piston/svn/revision.rb
@@ -37,7 +37,7 @@ def checkout_to(dir)
def update_to(revision)
raise ArgumentError, "Revision #{self.revision} of #{repository.url} was never checked out -- can't update" unless @dir
- answer = svn(:update, "--revision", revision, @dir)
+ answer = svn(:update, "--non-interactive", "--revision", revision, @dir)
if answer =~ /(Updated to|At) revision (\d+)[.]/ then
if revision == "HEAD" then
@revision = $1.to_i
View
25 lib/piston/svn/working_copy.rb
@@ -85,6 +85,16 @@ def rename(renamed)
end
end
+ def downgrade_to(revision)
+ logger.debug {"Downgrading to revision when last update was made"}
+ svn(:update, '--revision', revision, path)
+ end
+
+ def merge_local_changes(revision)
+ logger.debug {"Update to #{revision} in order to merge local changes"}
+ svn(:update, "--non-interactive", path)
+ end
+
# Returns all defined externals (recursively) of this WC.
# Returns a Hash:
# {"vendor/rails" => {:revision => :head, :url => "http://dev.rubyonrails.org/svn/rails/trunk"},
@@ -119,9 +129,7 @@ def remove_external_references(*targets)
def locally_modified
# get latest revision for .piston.yml
- data = svn(:info, yaml_path)
- info = YAML.load(data)
- initial_revision = info["Last Changed Rev"].to_i
+ initial_revision = last_changed_revision(yaml_path)
# get latest revisions for this working copy since last update
log = svn(:log, '--revision', "#{initial_revision}:HEAD", '--quiet', '--limit', '2', path)
log.count("\n") > 3
@@ -137,6 +145,17 @@ def upgrade
end
remember({:repository_url => props[Piston::Svn::ROOT], :lock => props[Piston::Svn::LOCKED], :repository_class => Piston::Svn::Repository.name}, {Piston::Svn::REMOTE_REV => props[Piston::Svn::REMOTE_REV], Piston::Svn::UUID => props[Piston::Svn::UUID]})
end
+
+ protected
+ def current_revision
+ data = svn(:info, path)
+ YAML.load(data)["Revision"].to_i
+ end
+
+ def last_changed_revision(path)
+ data = svn(:info, yaml_path)
+ YAML.load(data)["Last Changed Rev"].to_i
+ end
end
end
end
View
44 lib/piston/working_copy.rb
@@ -114,14 +114,27 @@ def copy_to(revision)
# add some files to working copy
def add(added)
+ raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
end
# delete some files from working copy
def delete(deleted)
+ raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
end
# rename some files in working copy
def rename(renamed)
+ raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
+ end
+
+ # Downgrade this working copy to +revision+.
+ def downgrade_to(revision)
+ raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
+ end
+
+ # Merge remote changes with local changes in +revision+.
+ def merge_local_changes(revision)
+ raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
end
# Stores a Hash of values that can be retrieved later.
@@ -197,18 +210,31 @@ def update(revision, to, lock)
logger.info {"Checking out the repository at #{revision.revision}"}
revision.checkout_to(tmpdir)
- logger.info {"Copying local changes to temporary directory"}
+ revision_to_return = current_revision
+ revision_to_downgrade = last_changed_revision(yaml_path)
+ logger.debug {"Downgrading to #{revision_to_downgrade}"}
+ downgrade_to(revision_to_downgrade)
+
+ logger.debug {"Copying old changes to temporary directory in order to keep them"}
copy_to(revision)
- logger.info {"Updating to #{to.revision}"}
+ logger.info {"Looking changes from #{revision.revision} to #{to.revision}"}
added, deleted, renamed = revision.update_to(to.revision)
+ logger.info {"Updating working copy"}
+ logger.debug {"Renaming files"}
+ # rename before copy because copy_from will copy these files
+ rename(renamed)
logger.debug {"Copying files from temporary directory"}
- rename(renamed) # rename before copy because copy_from will copy these files
copy_from(revision)
+ logger.debug {"Adding new files to version control"}
add(added)
+ logger.debug {"Deleting files from version control"}
delete(deleted)
-
+ logger.debug {"Merging local changes"}
+ # merge local changes updating to revision before downgrade was made
+ merge_local_changes(revision_to_return)
+
remember(recall.merge(:lock => lock), to.remember_values)
ensure
logger.debug {"Removing temporary directory: #{tmpdir}"}
@@ -233,5 +259,15 @@ def remotely_modified
def yaml_path
path + ".piston.yml"
end
+
+ # The current revision of this working copy.
+ def current_revision
+ raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
+ end
+
+ # The last revision which +path+ was changed in
+ def last_changed_revision(path)
+ raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
+ end
end
end
View
105 test/integration/test_git_git.rb
@@ -19,6 +19,7 @@ def setup
File.open("file_in_first_commit", "wb") {|f| f.write "file_in_first_commit"}
File.open("file_to_rename", "wb") {|f| f.write "file_to_rename"}
File.open("file_to_copy", "wb") {|f| f.write "file_to_copy"}
+ File.open("conflicting_file", "wb") {|f| f.write "conflicting_file\n"}
git(:add, ".")
git(:commit, "-m", "'first commit'")
end
@@ -61,25 +62,30 @@ def test_import
#\tnew file: vendor/parent/file_in_first_commit
#\tnew file: vendor/parent/file_to_rename
#\tnew file: vendor/parent/file_to_copy
+#\tnew file: vendor/parent/conflicting_file
#
)
def test_update
Dir.chdir(wc_path) do
piston(:import, parent_path, "vendor/parent")
+ git(:commit, "-m", "'import'")
end
- # change mode to "ab" to get a conflict when it's implemented
+
File.open(wc_path + "vendor/parent/README", "wb") do |f|
f.write "Readme - modified after imported\nReadme - first commit\n"
end
-
+ File.open(wc_path + "vendor/parent/conflicting_file", "ab") do |f|
+ f.write "working copy\n"
+ end
Dir.chdir(wc_path) do
git(:add, ".")
git(:commit, "-m", "'next commit'")
end
Dir.chdir(parent_path) do
File.open("README", "ab") {|f| f.write "Readme - second commit\n"}
+ File.open("conflicting_file", "ab") {|f| f.write "parent repository\n"}
git(:rm, "file_in_first_commit")
File.open("file_in_second_commit", "wb") {|f| f.write "file_in_second_commit"}
FileUtils.cp("file_to_copy", "copied_file")
@@ -93,25 +99,112 @@ def test_update
end
Dir.chdir(wc_path) do
- assert_equal CHANGE_STATUS.split("\n").sort, git(:status).split("\n").sort
+ assert_equal CHANGE_STATUS.split("\n"), git(:status).split("\n")
end
- assert_equal README, File.readlines(wc_path + "vendor/parent/README").join
+ assert_equal README, File.read(wc_path + "vendor/parent/README")
+ assert_match CONFLICT, File.read(wc_path + "vendor/parent/conflicting_file")
end
- CHANGE_STATUS = %Q(# On branch master
+ CHANGE_STATUS = %Q(vendor/parent/conflicting_file: needs merge
+# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
#\tmodified: vendor/parent/.piston.yml
#\tmodified: vendor/parent/README
+#\tnew file: vendor/parent/copied_file
#\tdeleted: vendor/parent/file_in_first_commit
#\tnew file: vendor/parent/file_in_second_commit
-#\tnew file: vendor/parent/copied_file
#\trenamed: vendor/parent/file_to_rename -> vendor/parent/renamed_file
#
+# Changed but not updated:
+# (use "git add <file>..." to update what will be committed)
+#
+#\tunmerged: vendor/parent/conflicting_file
+#\tmodified: vendor/parent/conflicting_file
+#
)
README = %Q(Readme - modified after imported
Readme - first commit
Readme - second commit
)
+ CONFLICT = /conflicting_file
+<<<<<<< HEAD:#{Regexp.quote('vendor/parent/conflicting_file')}
+working copy
+=======
+parent repository
+>>>>>>> my-[0-9a-f]{40}:#{Regexp.quote('vendor/parent/conflicting_file')}
+/
+
+ def test_double_update
+ Dir.chdir(wc_path) do
+ piston(:import, parent_path, "vendor/parent")
+ git(:commit, "-m", "'import'")
+ end
+
+ File.open(wc_path + "vendor/parent/conflicting_file", "wb") do |f|
+ f.write "modified after imported\n"
+ end
+ Dir.chdir(wc_path) do
+ git(:add, ".")
+ git(:commit, "-m", "'next commit'")
+ end
+
+ Dir.chdir(parent_path) do
+ File.open("file_in_first_commit", "ab") {|f| f.write "\nmodified in parent repository\n"}
+ git(:add, ".")
+ git(:commit, "-m", "'second commit'")
+ end
+
+ Dir.chdir(wc_path) do
+ piston(:update, "vendor/parent")
+ git(:commit, "-m", "'updated'")
+ end
+
+ Dir.chdir(parent_path) do
+ File.open("conflicting_file", "ab") {|f| f.write "modified in parent repository\n"}
+ git(:add, ".")
+ git(:commit, "-m", "'third commit'")
+ end
+ Dir.chdir(wc_path) do
+ piston(:update, "vendor/parent")
+ end
+
+ Dir.chdir(wc_path) do
+ # It isn't implemented, unmerge status should be copied from temp dir to working copy
+ #assert_equal DOUBLE_CHANGE_STATUS.split("\n"), git(:status).split("\n")
+ # Use this assert so test doesn't fail while port conflicts is not implemented
+ assert_equal DOUBLE_CHANGE_STATUS_NO_UNMERGED.split("\n"), git(:status).split("\n")
+ end
+ assert_equal DOUBLE_CONFLICT, File.read(wc_path + "vendor/parent/conflicting_file")
+ end
+ DOUBLE_CHANGE_STATUS = %Q(vendor/parent/conflicting_file: needs merge
+# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+#\tmodified: vendor/parent/.piston.yml
+#
+# Changed but not updated:
+# (use "git add <file>..." to update what will be committed)
+#
+#\tunmerged: vendor/parent/conflicting_file
+#\tmodified: vendor/parent/conflicting_file
+#
+)
+ DOUBLE_CHANGE_STATUS_NO_UNMERGED = %Q(# On branch master
+# Changes to be committed:
+# (use "git reset HEAD <file>..." to unstage)
+#
+#\tmodified: vendor/parent/.piston.yml
+#\tmodified: vendor/parent/conflicting_file
+#
+)
+ DOUBLE_CONFLICT = %Q(<<<<<<< HEAD:conflicting_file
+modified after imported
+=======
+conflicting_file
+modified in parent repository
+>>>>>>> master:conflicting_file
+)
end
View
31 test/integration/test_git_svn.rb
@@ -17,10 +17,11 @@ def setup
Dir.chdir(parent_path) do
git(:init)
- File.open(parent_path + "README", "wb") {|f| f.write "Readme - first commit\n"}
- File.open(parent_path + "file_in_first_commit", "wb") {|f| f.write "file_in_first_commit"}
+ File.open("README", "wb") {|f| f.write "Readme - first commit\n"}
+ File.open("file_in_first_commit", "wb") {|f| f.write "file_in_first_commit"}
File.open("file_to_rename", "wb") {|f| f.write "file_to_rename"}
File.open("file_to_copy", "wb") {|f| f.write "file_to_copy"}
+ File.open("conflicting_file", "wb") {|f| f.write "conflicting_file\n"}
git(:add, ".")
git(:commit, "-m", "'first commit'")
end
@@ -96,21 +97,25 @@ def test_import_from_tag
A vendor/parent/file_in_first_commit
A vendor/parent/file_to_rename
A vendor/parent/file_to_copy
+A vendor/parent/conflicting_file
)
def test_update
piston(:import, parent_path, wc_path + "trunk/vendor/parent")
+ svn(:commit, "-m", "'import'", wc_path)
+
# change mode to "ab" to get a conflict when it's implemented
File.open(wc_path + "trunk/vendor/parent/README", "wb") do |f|
f.write "Readme - modified after imported\nReadme - first commit\n"
end
-
- Dir.chdir(wc_path) do
- svn(:commit, "-m", "'next commit'")
+ File.open(wc_path + "trunk/vendor/parent/conflicting_file", "ab") do |f|
+ f.write "working copy\n"
end
+ svn(:commit, "-m", "'next commit'", wc_path)
Dir.chdir(parent_path) do
File.open("README", "ab") {|f| f.write "Readme - second commit\n"}
+ File.open("conflicting_file", "ab") {|f| f.write "parent repository\n"}
git(:rm, "file_in_first_commit")
File.open("file_in_second_commit", "wb") {|f| f.write "file_in_second_commit"}
FileUtils.cp("file_to_copy", "copied_file")
@@ -119,10 +124,11 @@ def test_update
git(:commit, "-m", "'second commit'")
end
- piston(:update, wc_path + "trunk/vendor/parent", '-v', '2')
+ piston(:update, wc_path + "trunk/vendor/parent")
assert_equal CHANGE_STATUS.split("\n").sort, svn(:status, wc_path + "trunk/vendor").gsub((wc_path + "trunk/").to_s, "").split("\n").sort
- assert_equal README, File.readlines(wc_path + "trunk/vendor/parent/README").join
+ assert_equal README, File.read(wc_path + "trunk/vendor/parent/README")
+ assert_equal CONFLICT, File.read(wc_path + "trunk/vendor/parent/conflicting_file")
end
CHANGE_STATUS = %Q(M vendor/parent/.piston.yml
@@ -132,9 +138,20 @@ def test_update
A vendor/parent/copied_file
D vendor/parent/file_to_rename
A + vendor/parent/renamed_file
+C vendor/parent/conflicting_file
+? vendor/parent/conflicting_file.mine
+? vendor/parent/conflicting_file.r2
+? vendor/parent/conflicting_file.r3
)
README = %Q(Readme - modified after imported
Readme - first commit
Readme - second commit
)
+ CONFLICT = %Q(conflicting_file
+<<<<<<< .mine
+parent repository
+=======
+working copy
+>>>>>>> .r3
+)
end
View
23 test/integration/test_svn_svn.rb
@@ -18,6 +18,7 @@ def setup
File.open("parent/file_in_first_commit", "wb") {|f| f.write "file_in_first_commit"}
File.open("parent/file_to_rename", "wb") {|f| f.write "file_to_rename"}
File.open("parent/file_to_copy", "wb") {|f| f.write "file_to_copy"}
+ File.open("parent/conflicting_file", "wb") {|f| f.write "conflicting_file\n"}
svn :add, "parent/*"
end
svn :commit, wc_path, "--message", "'first commit'"
@@ -52,15 +53,19 @@ def test_update
Dir.chdir(wc_path) do
piston(:import, "file://#{repos_path}/parent", "trunk/vendor/parent")
end
- # change mode to "ab" to get a conflict when it's implemented
+ svn(:commit, "-m", "'import'", wc_path)
+
File.open(wc_path + "trunk/vendor/parent/README", "wb") do |f|
f.write "Readme - modified after imported\nReadme - first commit\n"
end
-
+ File.open(wc_path + "trunk/vendor/parent/conflicting_file", "ab") do |f|
+ f.write "working copy\n"
+ end
svn(:commit, wc_path, "-m", "'next commit'")
Dir.chdir(parent_path) do
File.open("README", "ab") {|f| f.write "Readme - second commit\n"}
+ File.open("conflicting_file", "ab") {|f| f.write "parent repository\n"}
svn(:rm, "file_in_first_commit")
File.open("file_in_second_commit", "wb") {|f| f.write "file_in_second_commit"}
svn(:add, "file_in_second_commit")
@@ -76,7 +81,8 @@ def test_update
Dir.chdir(wc_path) do
assert_equal CHANGE_STATUS.split("\n").sort, svn(:status).gsub("trunk/", "").split("\n").sort
end
- assert_equal README, File.readlines(wc_path + "trunk/vendor/parent/README").join
+ assert_equal README, File.read(wc_path + "trunk/vendor/parent/README")
+ assert_equal CONFLICT, File.read(wc_path + "trunk/vendor/parent/conflicting_file")
end
CHANGE_STATUS = %Q(M vendor/parent/.piston.yml
@@ -86,9 +92,20 @@ def test_update
A vendor/parent/copied_file
D vendor/parent/file_to_rename
A vendor/parent/renamed_file
+C vendor/parent/conflicting_file
+? vendor/parent/conflicting_file.mine
+? vendor/parent/conflicting_file.r2
+? vendor/parent/conflicting_file.r4
)
README = %Q(Readme - modified after imported
Readme - first commit
Readme - second commit
)
+ CONFLICT = %Q(conflicting_file
+<<<<<<< .mine
+parent repository
+=======
+working copy
+>>>>>>> .r4
+)
end

0 comments on commit d7e8ccc

Please sign in to comment.
Something went wrong with that request. Please try again.