From cd49bc92af7a7370488bbf78af7e8675a16c8bb4 Mon Sep 17 00:00:00 2001 From: Joe Ferris Date: Thu, 9 Sep 2010 23:42:36 -0400 Subject: [PATCH] Initial commit: syncronize a single file --- .gitignore | 5 ++ bin/trout | 5 ++ features/support/env.rb | 15 ++++ features/sync_gemfile.feature | 54 +++++++++++++++ lib/trout.rb | 2 + lib/trout/cli.rb | 36 ++++++++++ lib/trout/managed_file.rb | 124 ++++++++++++++++++++++++++++++++++ lib/trout/version_list.rb | 44 ++++++++++++ 8 files changed, 285 insertions(+) create mode 100644 .gitignore create mode 100755 bin/trout create mode 100644 features/support/env.rb create mode 100644 features/sync_gemfile.feature create mode 100644 lib/trout.rb create mode 100644 lib/trout/cli.rb create mode 100644 lib/trout/managed_file.rb create mode 100644 lib/trout/version_list.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..230a50c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +tmp +.swo +*~ +*.swp + diff --git a/bin/trout b/bin/trout new file mode 100755 index 0000000..d9c510f --- /dev/null +++ b/bin/trout @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +require 'trout' +Trout::CLI.run(ARGV) + diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..3073131 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,15 @@ +require 'rubygems' +require 'aruba' +require 'fileutils' + +Before do + FileUtils.rm_rf("tmp") + FileUtils.mkdir("tmp") +end + +PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze +BIN_PATH = File.join(PROJECT_ROOT, 'bin').freeze +LIB_PATH = File.join(PROJECT_ROOT, 'lib').freeze + +ENV['PATH'] = [BIN_PATH, ENV['PATH']].join(':') +ENV['RUBYLIB'] = LIB_PATH diff --git a/features/sync_gemfile.feature b/features/sync_gemfile.feature new file mode 100644 index 0000000..1b1ed8a --- /dev/null +++ b/features/sync_gemfile.feature @@ -0,0 +1,54 @@ +Feature: sync a Gemfile between two repositories + + @puts @announce + Scenario: sync a Gemfile + Given a directory named "upstream_repo" + And a directory named "child_repo" + And a file named "upstream_repo/Gemfile" with: + """ + source "http://rubygems.org" + gem "rails" + gem "mysql" + """ + When I cd to "upstream_repo" + And I run "git init" + And I run "git add Gemfile" + And I run "git commit -m 'Added gemfile'" + And I cd to "../child_repo" + And I run "trout checkout Gemfile ../upstream_repo" + And I run "cat Gemfile" + Then the output should contain: + """ + source "http://rubygems.org" + gem "rails" + gem "mysql" + """ + When I cd to "../upstream_repo" + And I write to "Gemfile" with: + """ + source "http://rubygems.org" + gem "rails" + gem "postgresql" + """ + When I run "git add Gemfile" + And I run "git commit -m 'Changed to postgres'" + And I cd to "../child_repo" + When I append to "Gemfile" with: + """ + + gem "redcloth" + """ + When I run "trout update Gemfile" + And I run "cat Gemfile" + Then the output should contain: + """ + source "http://rubygems.org" + gem "rails" + <<<<<<< Gemfile + gem "mysql" + gem "redcloth" + ======= + gem "postgresql" + >>>>>>> .trout/upstream + """ + diff --git a/lib/trout.rb b/lib/trout.rb new file mode 100644 index 0000000..94c0974 --- /dev/null +++ b/lib/trout.rb @@ -0,0 +1,2 @@ +require 'trout/cli' + diff --git a/lib/trout/cli.rb b/lib/trout/cli.rb new file mode 100644 index 0000000..8eff28b --- /dev/null +++ b/lib/trout/cli.rb @@ -0,0 +1,36 @@ +require 'trout/managed_file' + +module Trout + class CLI + def self.run(arguments) + new(arguments).run + end + + attr_reader :arguments, :git_url, :filename, :file, :command + + def initialize(arguments) + self.arguments = arguments + self.file = ManagedFile.new(filename) + end + + def run + case command + when 'checkout' + self.git_url = arguments[2] + file.copy_from(git_url) + when 'update' + file.update + end + end + + private + + def arguments=(arguments) + @arguments = arguments + self.command = arguments[0] + self.filename = arguments[1] + end + + attr_writer :git_url, :filename, :file, :command + end +end diff --git a/lib/trout/managed_file.rb b/lib/trout/managed_file.rb new file mode 100644 index 0000000..b3d8b26 --- /dev/null +++ b/lib/trout/managed_file.rb @@ -0,0 +1,124 @@ +require 'fileutils' +require 'trout/version_list' + +module Trout + class ManagedFile + attr_reader :filename, :checked_out_url + + def initialize(filename) + @filename = filename + end + + def copy_from(git_url) + checkout(git_url) + copy_to_destination + write_url_and_version + ensure + cleanup + end + + def update + checkout(previous_git_url) + merge_to_destination + ensure + cleanup + end + + private + + def checkout(git_url) + run_or_fail("git clone #{git_url} #{working('git')}") + @checked_out_url = git_url + end + + def copy_to_destination + FileUtils.cp(working('git', filename), filename) + end + + def merge_to_destination + upstream = working('upstream') + at_last_update = working('at_last_update') + merge = working('merge') + + FileUtils.cp(working('git', filename), upstream) + + checkout_last_version + FileUtils.cp(working('git', filename), at_last_update) + + enforce_newline(upstream) + enforce_newline(at_last_update) + enforce_newline(filename) + + run("diff3 -mX #{filename} #{at_last_update} #{upstream} > #{merge}") + FileUtils.mv(merge, filename) + ensure + FileUtils.rm_rf(upstream) + FileUtils.rm_rf(at_last_update) + end + + def cleanup + FileUtils.rm_rf(working('git')) + @checked_out_url = nil + end + + def prepare_working_directory + FileUtils.mkdir(working_root) + end + + def working_root + '.trout' + end + + def write_url_and_version + version_list.update(filename, + 'git_url' => checked_out_url, + 'version' => checked_out_version) + end + + def checked_out_version + git_command("rev-parse HEAD") + end + + def checkout_last_version + git_command("checkout #{previous_git_version}") + end + + def git_command(command) + run_or_fail("git --git-dir=#{working('git/.git')} --work-tree=#{working('git')} #{command}").strip + end + + def previous_git_url + version_list.git_url_for(filename) + end + + def previous_git_version + version_list.version_for(filename) + end + + def version_list + @version_list ||= VersionList.new(working('versions')) + end + + def working(*paths) + File.join(working_root, *paths) + end + + def enforce_newline(path) + if IO.read(path)[-1].chr != "\n" + File.open(path, "a") { |file| file.puts } + end + end + + def run(command) + `#{command} 2>&1` + end + + def run_or_fail(command) + output = run(command) + unless $? == 0 + raise "Command failed with status #{$?}:\n#{command}\n#{output}" + end + output + end + end +end diff --git a/lib/trout/version_list.rb b/lib/trout/version_list.rb new file mode 100644 index 0000000..e3e7635 --- /dev/null +++ b/lib/trout/version_list.rb @@ -0,0 +1,44 @@ +require 'yaml' + +module Trout + class VersionList + attr_reader :path, :entries + + def initialize(path) + @path = path + end + + def git_url_for(filename) + read + entries[filename]['git_url'] + end + + def version_for(filename) + read + entries[filename]['version'] + end + + def update(filename, info) + read + entries[filename] ||= {} + entries[filename].update(info) + write + end + + private + + def read + if File.exist?(path) + @entries = YAML.load(IO.read(path)) + else + @entries = {} + end + end + + def write + File.open(path, 'w') do |file| + file.write(YAML.dump(entries)) + end + end + end +end