Permalink
Browse files

Initial commit: syncronize a single file

  • Loading branch information...
0 parents commit cd49bc92af7a7370488bbf78af7e8675a16c8bb4 @jferris jferris committed Sep 10, 2010
Showing with 285 additions and 0 deletions.
  1. +5 −0 .gitignore
  2. +5 −0 bin/trout
  3. +15 −0 features/support/env.rb
  4. +54 −0 features/sync_gemfile.feature
  5. +2 −0 lib/trout.rb
  6. +36 −0 lib/trout/cli.rb
  7. +124 −0 lib/trout/managed_file.rb
  8. +44 −0 lib/trout/version_list.rb
@@ -0,0 +1,5 @@
+tmp
+.swo
+*~
+*.swp
+
@@ -0,0 +1,5 @@
+#!/usr/bin/env ruby
+
+require 'trout'
+Trout::CLI.run(ARGV)
+
@@ -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
@@ -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
+ """
+
@@ -0,0 +1,2 @@
+require 'trout/cli'
+
@@ -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
@@ -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
@@ -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

0 comments on commit cd49bc9

Please sign in to comment.