Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72bfc5a2fd | ||
|
|
f5054cf41d | ||
|
|
b60703d496 | ||
|
|
9a8788fb80 | ||
|
|
1a44edcde1 | ||
|
|
383f2a9f32 | ||
|
|
f55828f1d4 | ||
|
|
d4f9633a0c | ||
|
|
b5138bcdd1 | ||
|
|
d9ee74bf14 | ||
|
|
2148697864 | ||
|
|
1c3403064e | ||
|
|
03d87807e0 | ||
|
|
f6c4e5e42e | ||
|
|
705a416d74 | ||
|
|
41f3f9d374 | ||
|
|
74cfd29272 | ||
|
|
9a3268b7c3 | ||
|
|
70c1666606 | ||
|
|
53ac09a5e9 | ||
|
|
c790e34b39 | ||
|
|
8428ad1c9c | ||
|
|
efea18327b | ||
|
|
84fb1d1462 | ||
|
|
5dc7b5068d | ||
|
|
ab603240e4 | ||
|
|
2e5c2ec018 | ||
|
|
349e75584f | ||
|
|
bea3a0b680 | ||
|
|
8b6bf92e9a | ||
|
|
133c3613e9 | ||
|
|
ff2e5ee064 | ||
|
|
22aed48d4e | ||
|
|
4c7e45a1d5 | ||
|
|
ca41ae7f85 | ||
|
|
fa61e7b10e | ||
|
|
8397dcacc5 | ||
|
|
3f2d343161 | ||
|
|
d91628f811 | ||
|
|
94bff3aa9d | ||
|
|
7253bdd634 | ||
|
|
8c6a17404f | ||
|
|
98edb54ca4 | ||
|
|
9b780ffac6 | ||
|
|
59f6239ea0 | ||
|
|
c2cb6081e1 | ||
|
|
95943deb82 | ||
|
|
08a71f657f | ||
|
|
2b48544e32 | ||
|
|
f1191d4b3c | ||
|
|
8173429131 | ||
|
|
82be04ad8a | ||
|
|
bb735763c6 | ||
|
|
0bbb82f9ba | ||
|
|
e42eff4e10 | ||
|
|
7e659f11fe | ||
|
|
c667cefd4c | ||
|
|
571c5799e9 | ||
|
|
604a3b2a20 | ||
|
|
7d36460851 | ||
|
|
8f634b9d07 | ||
|
|
12244abb56 | ||
|
|
fc2bbb1d6e | ||
|
|
f03e7670cf | ||
|
|
e202b7eae7 | ||
|
|
3fcbd21104 |
19
.rubocop.yml
Normal file
19
.rubocop.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
# TODO: Eval is required for the .homesickrc feature. This should eventually be
|
||||
# removed if the feature is implemented in a more secure way.
|
||||
Eval:
|
||||
Enabled: false
|
||||
|
||||
# TODO: The following settings disable reports about issues that can be fixed
|
||||
# through refactoring. Remove these as offenses are removed from the code base.
|
||||
|
||||
ClassLength:
|
||||
Enabled: false
|
||||
|
||||
CyclomaticComplexity:
|
||||
Max: 13
|
||||
|
||||
LineLength:
|
||||
Enabled: false
|
||||
|
||||
MethodLength:
|
||||
Max: 36
|
||||
@@ -1,4 +1,5 @@
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.1.0
|
||||
- 2.0.0
|
||||
- 1.9.3
|
||||
|
||||
4
Gemfile
4
Gemfile
@@ -13,8 +13,8 @@ group :development do
|
||||
gem "guard-rspec"
|
||||
gem "rb-readline", "~> 0.5.0"
|
||||
gem "jeweler", ">= 1.6.2"
|
||||
gem "rcov", :platforms => :mri_18
|
||||
gem "simplecov", :platforms => :mri_19
|
||||
#gem "simplecov"
|
||||
gem 'coveralls', require: false
|
||||
gem "test_construct"
|
||||
gem "capture-output", "~> 1.0.0"
|
||||
if RbConfig::CONFIG['host_os'] =~ /linux|freebsd|openbsd|sunos|solaris/
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
[](http://badge.fury.io/rb/homesick)
|
||||
[](https://travis-ci.org/technicalpickles/homesick)
|
||||
[](https://gemnasium.com/technicalpickles/homesick)
|
||||
[](https://coveralls.io/r/technicalpickles/homesick)
|
||||
[](https://codeclimate.com/github/technicalpickles/homesick)
|
||||
[](https://gitter.im/technicalpickles/homesick)
|
||||
|
||||
Your home directory is your castle. Don't leave your dotfiles behind.
|
||||
|
||||
@@ -64,6 +66,14 @@ To open your default editor in the root of a castle (the $EDITOR environment var
|
||||
|
||||
homesick open CASTLE
|
||||
|
||||
To execute a shell command inside the root directory of a given castle:
|
||||
|
||||
homesick exec CASTLE COMMAND
|
||||
|
||||
To execute a shell command inside the root directory of every cloned castle:
|
||||
|
||||
homesick exec_all COMMAND
|
||||
|
||||
Not sure what else homesick has up its sleeve? There's always the built in help:
|
||||
|
||||
homesick help
|
||||
@@ -156,6 +166,7 @@ Homesick is tested on the following Ruby versions:
|
||||
|
||||
* 1.9.3
|
||||
* 2.0.0
|
||||
* 2.1.0
|
||||
|
||||
## Note on Patches/Pull Requests
|
||||
|
||||
|
||||
@@ -6,4 +6,4 @@ $LOAD_PATH.unshift lib.to_s
|
||||
|
||||
require 'homesick'
|
||||
|
||||
Homesick.start
|
||||
Homesick::CLI.start
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
||||
# -*- encoding: utf-8 -*-
|
||||
# stub: homesick 1.0.0 ruby lib
|
||||
# stub: homesick 1.1.0 ruby lib
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "homesick"
|
||||
s.version = "1.0.0"
|
||||
s.version = "1.1.0"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.require_paths = ["lib"]
|
||||
s.authors = ["Joshua Nichols", "Yusuke Murata"]
|
||||
s.date = "2014-01-16"
|
||||
s.date = "2014-04-28"
|
||||
s.description = "\n Your home directory is your castle. Don't leave your dotfiles behind.\n \n\n Homesick is sorta like rip, but for dotfiles. It uses git to clone a repository containing dotfiles, and saves them in ~/.homesick. It then allows you to symlink all the dotfiles into place with a single command. \n\n "
|
||||
s.email = ["josh@technicalpickles.com", "info@muratayusuke.com"]
|
||||
s.executables = ["homesick"]
|
||||
@@ -22,6 +23,7 @@ Gem::Specification.new do |s|
|
||||
s.files = [
|
||||
".document",
|
||||
".rspec",
|
||||
".rubocop.yml",
|
||||
".travis.yml",
|
||||
"ChangeLog.markdown",
|
||||
"Gemfile",
|
||||
@@ -32,17 +34,19 @@ Gem::Specification.new do |s|
|
||||
"bin/homesick",
|
||||
"homesick.gemspec",
|
||||
"lib/homesick.rb",
|
||||
"lib/homesick/actions.rb",
|
||||
"lib/homesick/actions/file_actions.rb",
|
||||
"lib/homesick/actions/git_actions.rb",
|
||||
"lib/homesick/cli.rb",
|
||||
"lib/homesick/shell.rb",
|
||||
"lib/homesick/utils.rb",
|
||||
"lib/homesick/version.rb",
|
||||
"spec/homesick_spec.rb",
|
||||
"spec/homesick_cli_spec.rb",
|
||||
"spec/spec.opts",
|
||||
"spec/spec_helper.rb"
|
||||
]
|
||||
s.homepage = "http://github.com/technicalpickles/homesick"
|
||||
s.licenses = ["MIT"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubygems_version = "2.1.11"
|
||||
s.rubygems_version = "2.2.2"
|
||||
s.summary = "Your home directory is your castle. Don't leave your dotfiles behind."
|
||||
|
||||
if s.respond_to? :specification_version then
|
||||
@@ -56,11 +60,9 @@ Gem::Specification.new do |s|
|
||||
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
||||
s.add_development_dependency(%q<rb-readline>, ["~> 0.5.0"])
|
||||
s.add_development_dependency(%q<jeweler>, [">= 1.6.2"])
|
||||
s.add_development_dependency(%q<rcov>, [">= 0"])
|
||||
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
||||
s.add_development_dependency(%q<coveralls>, [">= 0"])
|
||||
s.add_development_dependency(%q<test_construct>, [">= 0"])
|
||||
s.add_development_dependency(%q<capture-output>, ["~> 1.0.0"])
|
||||
s.add_development_dependency(%q<libnotify>, [">= 0"])
|
||||
s.add_development_dependency(%q<rubocop>, [">= 0"])
|
||||
else
|
||||
s.add_dependency(%q<thor>, [">= 0.14.0"])
|
||||
@@ -70,11 +72,9 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
||||
s.add_dependency(%q<rb-readline>, ["~> 0.5.0"])
|
||||
s.add_dependency(%q<jeweler>, [">= 1.6.2"])
|
||||
s.add_dependency(%q<rcov>, [">= 0"])
|
||||
s.add_dependency(%q<simplecov>, [">= 0"])
|
||||
s.add_dependency(%q<coveralls>, [">= 0"])
|
||||
s.add_dependency(%q<test_construct>, [">= 0"])
|
||||
s.add_dependency(%q<capture-output>, ["~> 1.0.0"])
|
||||
s.add_dependency(%q<libnotify>, [">= 0"])
|
||||
s.add_dependency(%q<rubocop>, [">= 0"])
|
||||
end
|
||||
else
|
||||
@@ -85,11 +85,9 @@ Gem::Specification.new do |s|
|
||||
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
||||
s.add_dependency(%q<rb-readline>, ["~> 0.5.0"])
|
||||
s.add_dependency(%q<jeweler>, [">= 1.6.2"])
|
||||
s.add_dependency(%q<rcov>, [">= 0"])
|
||||
s.add_dependency(%q<simplecov>, [">= 0"])
|
||||
s.add_dependency(%q<coveralls>, [">= 0"])
|
||||
s.add_dependency(%q<test_construct>, [">= 0"])
|
||||
s.add_dependency(%q<capture-output>, ["~> 1.0.0"])
|
||||
s.add_dependency(%q<libnotify>, [">= 0"])
|
||||
s.add_dependency(%q<rubocop>, [">= 0"])
|
||||
end
|
||||
end
|
||||
|
||||
451
lib/homesick.rb
451
lib/homesick.rb
@@ -1,450 +1,15 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
require 'thor'
|
||||
|
||||
class Homesick < Thor
|
||||
autoload :Shell, 'homesick/shell'
|
||||
autoload :Actions, 'homesick/actions'
|
||||
autoload :Version, 'homesick/version'
|
||||
|
||||
include Thor::Actions
|
||||
include Homesick::Actions
|
||||
include Homesick::Version
|
||||
|
||||
add_runtime_options!
|
||||
require 'homesick/shell'
|
||||
require 'homesick/actions/file_actions'
|
||||
require 'homesick/actions/git_actions'
|
||||
require 'homesick/version'
|
||||
require 'homesick/utils'
|
||||
require 'homesick/cli'
|
||||
|
||||
# Homesick's top-level module
|
||||
module Homesick
|
||||
GITHUB_NAME_REPO_PATTERN = /\A([A-Za-z0-9_-]+\/[A-Za-z0-9_-]+)\Z/
|
||||
SUBDIR_FILENAME = '.homesick_subdir'
|
||||
|
||||
DEFAULT_CASTLE_NAME = 'dotfiles'
|
||||
|
||||
map '-v' => :version
|
||||
map '--version' => :version
|
||||
|
||||
def initialize(args = [], options = {}, config = {})
|
||||
super
|
||||
self.shell = Homesick::Shell.new
|
||||
end
|
||||
|
||||
desc 'clone URI', 'Clone +uri+ as a castle for homesick'
|
||||
def clone(uri)
|
||||
inside repos_dir do
|
||||
destination = nil
|
||||
if File.exist?(uri)
|
||||
uri = Pathname.new(uri).expand_path
|
||||
if uri.to_s.start_with?(repos_dir.to_s)
|
||||
raise "Castle already cloned to #{uri}"
|
||||
end
|
||||
|
||||
destination = uri.basename
|
||||
|
||||
ln_s uri, destination
|
||||
elsif uri =~ GITHUB_NAME_REPO_PATTERN
|
||||
destination = Pathname.new(uri).basename
|
||||
git_clone "https://github.com/#{$1}.git", :destination => destination
|
||||
elsif uri =~ /%r([^%r]*?)(\.git)?\Z/
|
||||
destination = Pathname.new($1)
|
||||
git_clone uri
|
||||
elsif uri =~ /[^:]+:([^:]+)(\.git)?\Z/
|
||||
destination = Pathname.new($1)
|
||||
git_clone uri
|
||||
else
|
||||
raise "Unknown URI format: #{uri}"
|
||||
end
|
||||
|
||||
if destination.join('.gitmodules').exist?
|
||||
inside destination do
|
||||
git_submodule_init
|
||||
git_submodule_update
|
||||
end
|
||||
end
|
||||
|
||||
rc(destination)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'rc CASTLE', 'Run the .homesickrc for the specified castle'
|
||||
def rc(name = DEFAULT_CASTLE_NAME)
|
||||
inside repos_dir do
|
||||
destination = Pathname.new(name)
|
||||
homesickrc = destination.join('.homesickrc').expand_path
|
||||
if homesickrc.exist?
|
||||
proceed = shell.yes?("#{name} has a .homesickrc. Proceed with evaling it? (This could be destructive)")
|
||||
if proceed
|
||||
shell.say_status 'eval', homesickrc
|
||||
inside destination do
|
||||
eval homesickrc.read, binding, homesickrc.expand_path.to_s
|
||||
end
|
||||
else
|
||||
shell.say_status 'eval skip', "not evaling #{homesickrc}, #{destination} may need manual configuration", :blue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'pull CASTLE', 'Update the specified castle'
|
||||
method_option :all, :type => :boolean, :default => false, :required => false, :desc => 'Update all cloned castles'
|
||||
def pull(name = DEFAULT_CASTLE_NAME)
|
||||
if options[:all]
|
||||
inside_each_castle do |castle|
|
||||
shell.say castle.to_s.gsub(repos_dir.to_s + '/', '') + ':'
|
||||
update_castle castle
|
||||
end
|
||||
else
|
||||
update_castle name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
desc 'commit CASTLE MESSAGE', "Commit the specified castle's changes"
|
||||
def commit(name = DEFAULT_CASTLE_NAME, message = nil)
|
||||
commit_castle name, message
|
||||
|
||||
end
|
||||
|
||||
desc 'push CASTLE', 'Push the specified castle'
|
||||
def push(name = DEFAULT_CASTLE_NAME)
|
||||
push_castle name
|
||||
end
|
||||
|
||||
desc 'unlink CASTLE', 'Unsymlinks all dotfiles from the specified castle'
|
||||
def unlink(name = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(name, 'symlink')
|
||||
|
||||
inside castle_dir(name) do
|
||||
subdirs = subdirs(name)
|
||||
|
||||
# unlink files
|
||||
unsymlink_each(name, castle_dir(name), subdirs)
|
||||
|
||||
# unlink files in subdirs
|
||||
subdirs.each do |subdir|
|
||||
unsymlink_each(name, subdir, subdirs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'symlink CASTLE', 'Symlinks all dotfiles from the specified castle'
|
||||
method_option :force, :default => false, :desc => 'Overwrite existing conflicting symlinks without prompting.'
|
||||
def symlink(name = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(name, 'symlink')
|
||||
|
||||
inside castle_dir(name) do
|
||||
subdirs = subdirs(name)
|
||||
|
||||
# link files
|
||||
symlink_each(name, castle_dir(name), subdirs)
|
||||
|
||||
# link files in subdirs
|
||||
subdirs.each do |subdir|
|
||||
symlink_each(name, subdir, subdirs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'track FILE CASTLE', 'add a file to a castle'
|
||||
def track(file, castle = DEFAULT_CASTLE_NAME)
|
||||
castle = Pathname.new(castle)
|
||||
file = Pathname.new(file.chomp('/'))
|
||||
check_castle_existance(castle, 'track')
|
||||
|
||||
absolute_path = file.expand_path
|
||||
relative_dir = absolute_path.relative_path_from(home_dir).dirname
|
||||
castle_path = Pathname.new(castle_dir(castle)).join(relative_dir)
|
||||
FileUtils.mkdir_p castle_path
|
||||
|
||||
# Are we already tracking this or anything inside it?
|
||||
target = Pathname.new(castle_path.join(file.basename))
|
||||
if target.exist?
|
||||
if absolute_path.directory?
|
||||
move_dir_contents(target, absolute_path)
|
||||
absolute_path.rmtree
|
||||
subdir_remove(castle, relative_dir + file.basename)
|
||||
|
||||
elsif more_recent? absolute_path, target
|
||||
target.delete
|
||||
mv absolute_path, castle_path
|
||||
else
|
||||
shell.say_status(:track, "#{target} already exists, and is more recent than #{file}. Run 'homesick SYMLINK CASTLE' to create symlinks.", :blue) unless options[:quiet]
|
||||
end
|
||||
else
|
||||
mv absolute_path, castle_path
|
||||
end
|
||||
|
||||
inside home_dir do
|
||||
absolute_path = castle_path + file.basename
|
||||
home_path = home_dir + relative_dir + file.basename
|
||||
ln_s absolute_path, home_path
|
||||
end
|
||||
|
||||
inside castle_path do
|
||||
git_add absolute_path
|
||||
end
|
||||
|
||||
# are we tracking something nested? Add the parent dir to the manifest
|
||||
subdir_add(castle, relative_dir) unless relative_dir.eql?(Pathname.new('.'))
|
||||
end
|
||||
|
||||
desc 'list', 'List cloned castles'
|
||||
def list
|
||||
inside_each_castle do |castle|
|
||||
say_status castle.relative_path_from(repos_dir).to_s, `git config remote.origin.url`.chomp, :cyan
|
||||
end
|
||||
end
|
||||
|
||||
desc 'status CASTLE', 'Shows the git status of a castle'
|
||||
def status(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(castle, 'status')
|
||||
inside repos_dir.join(castle) do
|
||||
git_status
|
||||
end
|
||||
end
|
||||
|
||||
desc 'diff CASTLE', 'Shows the git diff of uncommitted changes in a castle'
|
||||
def diff(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(castle, 'diff')
|
||||
inside repos_dir.join(castle) do
|
||||
git_diff
|
||||
end
|
||||
end
|
||||
|
||||
desc 'show_path CASTLE', 'Prints the path of a castle'
|
||||
def show_path(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(castle, 'show_path')
|
||||
say repos_dir.join(castle)
|
||||
end
|
||||
|
||||
desc 'generate PATH', 'generate a homesick-ready git repo at PATH'
|
||||
def generate(castle)
|
||||
castle = Pathname.new(castle).expand_path
|
||||
|
||||
github_user = `git config github.user`.chomp
|
||||
github_user = nil if github_user == ''
|
||||
github_repo = castle.basename
|
||||
|
||||
empty_directory castle
|
||||
inside castle do
|
||||
git_init
|
||||
if github_user
|
||||
url = "git@github.com:#{github_user}/#{github_repo}.git"
|
||||
git_remote_add 'origin', url
|
||||
end
|
||||
|
||||
empty_directory 'home'
|
||||
end
|
||||
end
|
||||
|
||||
desc "destroy CASTLE", "Delete all symlinks and remove the cloned repository"
|
||||
def destroy(name)
|
||||
check_castle_existance name, "destroy"
|
||||
|
||||
if shell.yes?("This will destroy your castle irreversible! Are you sure?")
|
||||
unlink(name)
|
||||
rm_rf repos_dir.join(name)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
desc "cd CASTLE", "Open a new shell in the root of the given castle"
|
||||
def cd(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance castle, "cd"
|
||||
castle_dir = repos_dir.join(castle)
|
||||
say_status "cd #{castle_dir.realpath}", "Opening a new shell in castle '#{castle}'. To return to the original one exit from the new shell.", :green
|
||||
inside castle_dir do
|
||||
system(ENV['SHELL'])
|
||||
end
|
||||
end
|
||||
|
||||
desc "open CASTLE", "Open your default editor in the root of the given castle"
|
||||
def open(castle = DEFAULT_CASTLE_NAME)
|
||||
if ! ENV['EDITOR']
|
||||
say_status :error,"The $EDITOR environment variable must be set to use this command", :red
|
||||
|
||||
exit(1)
|
||||
end
|
||||
check_castle_existance castle, "open"
|
||||
castle_dir = repos_dir.join(castle)
|
||||
say_status "#{ENV['EDITOR']} #{castle_dir.realpath}", "Opening the root directory of castle '#{castle}' in editor '#{ENV['EDITOR']}'.", :green
|
||||
inside castle_dir do
|
||||
system(ENV['EDITOR'])
|
||||
end
|
||||
end
|
||||
|
||||
desc 'version', 'Display the current version of homesick'
|
||||
def version
|
||||
say Homesick::Version::STRING
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def home_dir
|
||||
@home_dir ||= Pathname.new(ENV['HOME'] || '~').expand_path
|
||||
end
|
||||
|
||||
def repos_dir
|
||||
@repos_dir ||= home_dir.join('.homesick', 'repos').expand_path
|
||||
end
|
||||
|
||||
def castle_dir(name)
|
||||
repos_dir.join(name, 'home')
|
||||
end
|
||||
|
||||
def check_castle_existance(name, action)
|
||||
unless castle_dir(name).exist?
|
||||
say_status :error, "Could not #{action} #{name}, expected #{castle_dir(name)} exist and contain dotfiles", :red
|
||||
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
def all_castles
|
||||
dirs = Pathname.glob("#{repos_dir}/**/.git", File::FNM_DOTMATCH)
|
||||
# reject paths that lie inside another castle, like git submodules
|
||||
return dirs.reject do |dir|
|
||||
dirs.any? do |other|
|
||||
dir != other && dir.fnmatch(other.parent.join('*').to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def inside_each_castle(&block)
|
||||
all_castles.each do |git_dir|
|
||||
castle = git_dir.dirname
|
||||
Dir.chdir castle do # so we can call git config from the right contxt
|
||||
yield castle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_castle(castle)
|
||||
check_castle_existance(castle, 'pull')
|
||||
inside repos_dir.join(castle) do
|
||||
git_pull
|
||||
git_submodule_init
|
||||
git_submodule_update
|
||||
end
|
||||
end
|
||||
|
||||
def commit_castle(castle, message)
|
||||
check_castle_existance(castle, 'commit')
|
||||
inside repos_dir.join(castle) do
|
||||
git_commit_all :message => message
|
||||
end
|
||||
end
|
||||
|
||||
def push_castle(castle)
|
||||
check_castle_existance(castle, 'push')
|
||||
inside repos_dir.join(castle) do
|
||||
git_push
|
||||
end
|
||||
end
|
||||
|
||||
def subdir_file(castle)
|
||||
repos_dir.join(castle, SUBDIR_FILENAME)
|
||||
end
|
||||
|
||||
def subdirs(castle)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
subdirs = []
|
||||
if subdir_filepath.exist?
|
||||
subdir_filepath.readlines.each do |subdir|
|
||||
subdirs.push(subdir.chomp)
|
||||
end
|
||||
end
|
||||
subdirs
|
||||
end
|
||||
|
||||
def subdir_add(castle, path)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
File.open(subdir_filepath, 'a+') do |subdir|
|
||||
subdir.puts path unless subdir.readlines.reduce(false) do |memo, line|
|
||||
line.eql?("#{path.to_s}\n") || memo
|
||||
end
|
||||
end
|
||||
|
||||
inside castle_dir(castle) do
|
||||
git_add subdir_filepath
|
||||
end
|
||||
end
|
||||
|
||||
def subdir_remove(castle, path)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
if subdir_filepath.exist?
|
||||
lines = IO.readlines(subdir_filepath).delete_if { |line| line == "#{path}\n" }
|
||||
File.open(subdir_filepath, 'w') { |manfile| manfile.puts lines }
|
||||
end
|
||||
|
||||
inside castle_dir(castle) do
|
||||
git_add subdir_filepath
|
||||
end
|
||||
end
|
||||
|
||||
def move_dir_contents(target, dir_path)
|
||||
child_files = dir_path.children
|
||||
child_files.each do |child|
|
||||
|
||||
target_path = target.join(child.basename)
|
||||
if target_path.exist?
|
||||
if more_recent?(child, target_path) && target.file?
|
||||
target_path.delete
|
||||
mv child, target
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
mv child, target
|
||||
end
|
||||
end
|
||||
|
||||
def more_recent?(first, second)
|
||||
first_p = Pathname.new(first)
|
||||
second_p = Pathname.new(second)
|
||||
first_p.mtime > second_p.mtime && !first_p.symlink?
|
||||
end
|
||||
|
||||
def each_file(castle, basedir, subdirs)
|
||||
absolute_basedir = Pathname.new(basedir).expand_path
|
||||
inside basedir do
|
||||
files = Pathname.glob('{.*,*}').reject{ |a| ['.', '..'].include?(a.to_s) }
|
||||
files.each do |path|
|
||||
absolute_path = path.expand_path
|
||||
castle_home = castle_dir(castle)
|
||||
|
||||
# make ignore dirs
|
||||
ignore_dirs = []
|
||||
subdirs.each do |subdir|
|
||||
# ignore all parent of each line in subdir file
|
||||
Pathname.new(subdir).ascend do |p|
|
||||
ignore_dirs.push(p)
|
||||
end
|
||||
end
|
||||
|
||||
# ignore dirs written in subdir file
|
||||
matched = false
|
||||
ignore_dirs.uniq.each do |ignore_dir|
|
||||
if absolute_path == castle_home.join(ignore_dir)
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
next if matched
|
||||
|
||||
relative_dir = absolute_basedir.relative_path_from(castle_home)
|
||||
home_path = home_dir.join(relative_dir).join(path)
|
||||
|
||||
yield(absolute_path, home_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unsymlink_each(castle, basedir, subdirs)
|
||||
each_file(castle, basedir, subdirs) do |absolute_path, home_path|
|
||||
rm_link home_path
|
||||
end
|
||||
end
|
||||
|
||||
def symlink_each(castle, basedir, subdirs)
|
||||
each_file(castle, basedir, subdirs) do |absolute_path, home_path|
|
||||
ln_s absolute_path, home_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
class Homesick
|
||||
module Actions
|
||||
# TODO move this to be more like thor's template, empty_directory, etc
|
||||
def git_clone(repo, config = {})
|
||||
config ||= {}
|
||||
destination = config[:destination] || File.basename(repo, '.git')
|
||||
|
||||
destination = Pathname.new(destination) unless destination.kind_of?(Pathname)
|
||||
FileUtils.mkdir_p destination.dirname
|
||||
|
||||
if ! destination.directory?
|
||||
say_status 'git clone', "#{repo} to #{destination.expand_path}", :green unless options[:quiet]
|
||||
system "git clone -q --config push.default=upstream --recursive #{repo} #{destination}" unless options[:pretend]
|
||||
else
|
||||
say_status :exist, destination.expand_path, :blue unless options[:quiet]
|
||||
end
|
||||
end
|
||||
|
||||
def git_init(path = '.')
|
||||
path = Pathname.new(path)
|
||||
|
||||
inside path do
|
||||
if !path.join('.git').exist?
|
||||
say_status 'git init', '' unless options[:quiet]
|
||||
system 'git init >/dev/null' unless options[:pretend]
|
||||
else
|
||||
say_status 'git init', 'already initialized', :blue unless options[:quiet]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def git_remote_add(name, url)
|
||||
existing_remote = `git config remote.#{name}.url`.chomp
|
||||
existing_remote = nil if existing_remote == ''
|
||||
|
||||
if !existing_remote
|
||||
say_status 'git remote', "add #{name} #{url}" unless options[:quiet]
|
||||
system "git remote add #{name} #{url}" unless options[:pretend]
|
||||
else
|
||||
say_status 'git remote', "#{name} already exists", :blue unless options[:quiet]
|
||||
end
|
||||
end
|
||||
|
||||
def git_submodule_init(config = {})
|
||||
say_status 'git submodule', 'init', :green unless options[:quiet]
|
||||
system 'git submodule --quiet init' unless options[:pretend]
|
||||
end
|
||||
|
||||
def git_submodule_update(config = {})
|
||||
say_status 'git submodule', 'update', :green unless options[:quiet]
|
||||
system 'git submodule --quiet update --init --recursive >/dev/null 2>&1' unless options[:pretend]
|
||||
end
|
||||
|
||||
def git_pull(config = {})
|
||||
say_status 'git pull', '', :green unless options[:quiet]
|
||||
system 'git pull --quiet' unless options[:pretend]
|
||||
end
|
||||
|
||||
def git_push(config = {})
|
||||
say_status 'git push', '', :green unless options[:quiet]
|
||||
system 'git push' unless options[:pretend]
|
||||
end
|
||||
|
||||
def git_commit_all(config = {})
|
||||
say_status 'git commit all', '', :green unless options[:quiet]
|
||||
if config[:message]
|
||||
system "git commit -a -m '#{config[:message]}'" unless options[:pretend]
|
||||
else
|
||||
system 'git commit -v -a' unless options[:pretend]
|
||||
end
|
||||
end
|
||||
|
||||
def git_add(file, config = {})
|
||||
say_status 'git add file', '', :green unless options[:quiet]
|
||||
system "git add '#{file}'" unless options[:pretend]
|
||||
end
|
||||
|
||||
def git_status(config = {})
|
||||
say_status 'git status', '', :green unless options[:quiet]
|
||||
system "git status" unless options[:pretend]
|
||||
end
|
||||
|
||||
def git_diff(config = {})
|
||||
say_status 'git diff', '', :green unless options[:quiet]
|
||||
system "git diff" unless options[:pretend]
|
||||
end
|
||||
|
||||
def mv(source, destination, config = {})
|
||||
source = Pathname.new(source)
|
||||
destination = Pathname.new(destination + source.basename)
|
||||
|
||||
if destination.exist?
|
||||
say_status :conflict, "#{destination} exists", :red unless options[:quiet]
|
||||
|
||||
if options[:force] || shell.file_collision(destination) { source }
|
||||
system "mv '#{source}' '#{destination}'" unless options[:pretend]
|
||||
end
|
||||
else
|
||||
# this needs some sort of message here.
|
||||
system "mv '#{source}' '#{destination}'" unless options[:pretend]
|
||||
end
|
||||
end
|
||||
|
||||
def rm_link(target)
|
||||
target = Pathname.new(target)
|
||||
|
||||
if target.symlink?
|
||||
say_status :unlink, "#{target.expand_path}", :green unless options[:quiet]
|
||||
FileUtils.rm_rf target
|
||||
else
|
||||
say_status :conflict, "#{target} is not a symlink", :red unless options[:quiet]
|
||||
end
|
||||
end
|
||||
|
||||
def rm(file)
|
||||
say_status "rm #{file}", '', :green unless options[:quiet]
|
||||
system "rm #{file}" if File.exists?(file)
|
||||
end
|
||||
|
||||
def rm_rf(dir)
|
||||
say_status "rm -rf #{dir}", '', :green unless options[:quiet]
|
||||
system "rm -rf #{dir}"
|
||||
end
|
||||
|
||||
def rm_link(target)
|
||||
target = Pathname.new(target)
|
||||
|
||||
if target.symlink?
|
||||
say_status :unlink, "#{target.expand_path}", :green unless options[:quiet]
|
||||
FileUtils.rm_rf target
|
||||
else
|
||||
say_status :conflict, "#{target} is not a symlink", :red unless options[:quiet]
|
||||
end
|
||||
end
|
||||
|
||||
def rm(file)
|
||||
say_status "rm #{file}", '', :green unless options[:quiet]
|
||||
system "rm #{file}"
|
||||
end
|
||||
|
||||
def rm_r(dir)
|
||||
say_status "rm -r #{dir}", '', :green unless options[:quiet]
|
||||
system "rm -r #{dir}"
|
||||
end
|
||||
|
||||
def ln_s(source, destination, config = {})
|
||||
source = Pathname.new(source)
|
||||
destination = Pathname.new(destination)
|
||||
FileUtils.mkdir_p destination.dirname
|
||||
|
||||
if destination.symlink?
|
||||
if destination.readlink == source
|
||||
say_status :identical, destination.expand_path, :blue unless options[:quiet]
|
||||
else
|
||||
say_status :conflict, "#{destination} exists and points to #{destination.readlink}", :red unless options[:quiet]
|
||||
|
||||
if options[:force] || shell.file_collision(destination) { source }
|
||||
system "ln -nsf '#{source}' '#{destination}'" unless options[:pretend]
|
||||
end
|
||||
end
|
||||
elsif destination.exist?
|
||||
say_status :conflict, "#{destination} exists", :red unless options[:quiet]
|
||||
|
||||
if options[:force] || shell.file_collision(destination) { source }
|
||||
system "rm -rf '#{destination}'" unless options[:pretend]
|
||||
system "ln -sf '#{source}' '#{destination}'" unless options[:pretend]
|
||||
end
|
||||
else
|
||||
say_status :symlink, "#{source.expand_path} to #{destination.expand_path}", :green unless options[:quiet]
|
||||
system "ln -s '#{source}' '#{destination}'" unless options[:pretend]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
91
lib/homesick/actions/file_actions.rb
Normal file
91
lib/homesick/actions/file_actions.rb
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
module Homesick
|
||||
module Actions
|
||||
# File-related helper methods for Homesick
|
||||
module FileActions
|
||||
def mv(source, destination, config = {})
|
||||
source = Pathname.new(source)
|
||||
destination = Pathname.new(destination + source.basename)
|
||||
|
||||
if destination.exist?
|
||||
say_status :conflict, "#{destination} exists", :red
|
||||
|
||||
FileUtils.mv source, destination if (options[:force] || shell.file_collision(destination) { source }) && !options[:pretend]
|
||||
else
|
||||
# this needs some sort of message here.
|
||||
FileUtils.mv source, destination unless options[:pretend]
|
||||
end
|
||||
end
|
||||
|
||||
def rm_rf(dir)
|
||||
say_status "rm -rf #{dir}", '', :green
|
||||
FileUtils.rm_r dir, force: true
|
||||
end
|
||||
|
||||
def rm_link(target)
|
||||
target = Pathname.new(target)
|
||||
|
||||
if target.symlink?
|
||||
say_status :unlink, "#{target.expand_path}", :green
|
||||
FileUtils.rm_rf target
|
||||
else
|
||||
say_status :conflict, "#{target} is not a symlink", :red
|
||||
end
|
||||
end
|
||||
|
||||
def rm(file)
|
||||
say_status "rm #{file}", '', :green
|
||||
FileUtils.rm file, force: true
|
||||
end
|
||||
|
||||
def rm_r(dir)
|
||||
say_status "rm -r #{dir}", '', :green
|
||||
FileUtils.rm_r dir
|
||||
end
|
||||
|
||||
def ln_s(source, destination, config = {})
|
||||
source = Pathname.new(source)
|
||||
destination = Pathname.new(destination)
|
||||
FileUtils.mkdir_p destination.dirname
|
||||
|
||||
action = if destination.symlink? && destination.readlink == source
|
||||
:identical
|
||||
elsif destination.symlink?
|
||||
:symlink_conflict
|
||||
elsif destination.exist?
|
||||
:conflict
|
||||
else
|
||||
:success
|
||||
end
|
||||
|
||||
handle_symlink_action action, source, destination
|
||||
end
|
||||
|
||||
def handle_symlink_action(action, source, destination)
|
||||
case action
|
||||
when :identical
|
||||
say_status :identical, destination.expand_path, :blue
|
||||
when :symlink_conflict
|
||||
say_status :conflict,
|
||||
"#{destination} exists and points to #{destination.readlink}",
|
||||
:red
|
||||
|
||||
FileUtils.rm destination
|
||||
FileUtils.ln_s source, destination, force: true unless options[:pretend]
|
||||
when :conflict
|
||||
say_status :conflict, "#{destination} exists", :red
|
||||
|
||||
if collision_accepted?(destination)
|
||||
FileUtils.rm_r destination, force: true unless options[:pretend]
|
||||
FileUtils.ln_s source, destination, force: true unless options[:pretend]
|
||||
end
|
||||
else
|
||||
say_status :symlink,
|
||||
"#{source.expand_path} to #{destination.expand_path}",
|
||||
:green
|
||||
FileUtils.ln_s source, destination unless options[:pretend]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
94
lib/homesick/actions/git_actions.rb
Normal file
94
lib/homesick/actions/git_actions.rb
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
module Homesick
|
||||
module Actions
|
||||
# Git-related helper methods for Homesick
|
||||
module GitActions
|
||||
# TODO: move this to be more like thor's template, empty_directory, etc
|
||||
def git_clone(repo, config = {})
|
||||
config ||= {}
|
||||
destination = config[:destination] || File.basename(repo, '.git')
|
||||
|
||||
destination = Pathname.new(destination) unless destination.kind_of?(Pathname)
|
||||
FileUtils.mkdir_p destination.dirname
|
||||
|
||||
if destination.directory?
|
||||
say_status :exist, destination.expand_path, :blue
|
||||
else
|
||||
say_status 'git clone',
|
||||
"#{repo} to #{destination.expand_path}",
|
||||
:green
|
||||
system "git clone -q --config push.default=upstream --recursive #{repo} #{destination}"
|
||||
end
|
||||
end
|
||||
|
||||
def git_init(path = '.')
|
||||
path = Pathname.new(path)
|
||||
|
||||
inside path do
|
||||
if path.join('.git').exist?
|
||||
say_status 'git init', 'already initialized', :blue
|
||||
else
|
||||
say_status 'git init', ''
|
||||
system 'git init >/dev/null'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def git_remote_add(name, url)
|
||||
existing_remote = `git config remote.#{name}.url`.chomp
|
||||
existing_remote = nil if existing_remote == ''
|
||||
|
||||
if existing_remote
|
||||
say_status 'git remote', "#{name} already exists", :blue
|
||||
else
|
||||
say_status 'git remote', "add #{name} #{url}"
|
||||
system "git remote add #{name} #{url}"
|
||||
end
|
||||
end
|
||||
|
||||
def git_submodule_init(config = {})
|
||||
say_status 'git submodule', 'init', :green
|
||||
system 'git submodule --quiet init'
|
||||
end
|
||||
|
||||
def git_submodule_update(config = {})
|
||||
say_status 'git submodule', 'update', :green
|
||||
system 'git submodule --quiet update --init --recursive >/dev/null 2>&1'
|
||||
end
|
||||
|
||||
def git_pull(config = {})
|
||||
say_status 'git pull', '', :green
|
||||
system 'git pull --quiet'
|
||||
end
|
||||
|
||||
def git_push(config = {})
|
||||
say_status 'git push', '', :green
|
||||
system 'git push'
|
||||
end
|
||||
|
||||
def git_commit_all(config = {})
|
||||
say_status 'git commit all', '', :green
|
||||
if config[:message]
|
||||
system "git commit -a -m '#{config[:message]}'"
|
||||
else
|
||||
system 'git commit -v -a'
|
||||
end
|
||||
end
|
||||
|
||||
def git_add(file, config = {})
|
||||
say_status 'git add file', '', :green
|
||||
system "git add '#{file}'"
|
||||
end
|
||||
|
||||
def git_status(config = {})
|
||||
say_status 'git status', '', :green
|
||||
system 'git status'
|
||||
end
|
||||
|
||||
def git_diff(config = {})
|
||||
say_status 'git diff', '', :green
|
||||
system 'git diff'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
316
lib/homesick/cli.rb
Normal file
316
lib/homesick/cli.rb
Normal file
@@ -0,0 +1,316 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
require 'thor'
|
||||
|
||||
module Homesick
|
||||
# Homesick's command line interface
|
||||
class CLI < Thor
|
||||
include Thor::Actions
|
||||
include Homesick::Actions::FileActions
|
||||
include Homesick::Actions::GitActions
|
||||
include Homesick::Version
|
||||
include Homesick::Utils
|
||||
|
||||
add_runtime_options!
|
||||
|
||||
map '-v' => :version
|
||||
map '--version' => :version
|
||||
# Retain a mapped version of the symlink command for compatibility.
|
||||
map symlink: :link
|
||||
|
||||
def initialize(args = [], options = {}, config = {})
|
||||
super
|
||||
self.shell = Homesick::Shell.new
|
||||
end
|
||||
|
||||
desc 'clone URI', 'Clone +uri+ as a castle for homesick'
|
||||
def clone(uri)
|
||||
inside repos_dir do
|
||||
destination = nil
|
||||
if File.exist?(uri)
|
||||
uri = Pathname.new(uri).expand_path
|
||||
fail "Castle already cloned to #{uri}" if uri.to_s.start_with?(repos_dir.to_s)
|
||||
|
||||
destination = uri.basename
|
||||
|
||||
ln_s uri, destination
|
||||
elsif uri =~ GITHUB_NAME_REPO_PATTERN
|
||||
destination = Pathname.new(uri).basename
|
||||
git_clone "https://github.com/#{Regexp.last_match[1]}.git",
|
||||
destination: destination
|
||||
elsif uri =~ /%r([^%r]*?)(\.git)?\Z/ || uri =~ /[^:]+:([^:]+)(\.git)?\Z/
|
||||
destination = Pathname.new(Regexp.last_match[1])
|
||||
git_clone uri
|
||||
else
|
||||
fail "Unknown URI format: #{uri}"
|
||||
end
|
||||
|
||||
setup_castle(destination)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'rc CASTLE', 'Run the .homesickrc for the specified castle'
|
||||
def rc(name = DEFAULT_CASTLE_NAME)
|
||||
inside repos_dir do
|
||||
destination = Pathname.new(name)
|
||||
homesickrc = destination.join('.homesickrc').expand_path
|
||||
if homesickrc.exist?
|
||||
proceed = shell.yes?("#{name} has a .homesickrc. Proceed with evaling it? (This could be destructive)")
|
||||
if proceed
|
||||
say_status 'eval', homesickrc
|
||||
inside destination do
|
||||
eval homesickrc.read, binding, homesickrc.expand_path.to_s
|
||||
end
|
||||
else
|
||||
say_status 'eval skip',
|
||||
"not evaling #{homesickrc}, #{destination} may need manual configuration",
|
||||
:blue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'pull CASTLE', 'Update the specified castle'
|
||||
method_option :all,
|
||||
type: :boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
desc: 'Update all cloned castles'
|
||||
def pull(name = DEFAULT_CASTLE_NAME)
|
||||
if options[:all]
|
||||
inside_each_castle do |castle|
|
||||
say castle.to_s.gsub(repos_dir.to_s + '/', '') + ':'
|
||||
update_castle castle
|
||||
end
|
||||
else
|
||||
update_castle name
|
||||
end
|
||||
end
|
||||
|
||||
desc 'commit CASTLE MESSAGE', "Commit the specified castle's changes"
|
||||
def commit(name = DEFAULT_CASTLE_NAME, message = nil)
|
||||
commit_castle name, message
|
||||
end
|
||||
|
||||
desc 'push CASTLE', 'Push the specified castle'
|
||||
def push(name = DEFAULT_CASTLE_NAME)
|
||||
push_castle name
|
||||
end
|
||||
|
||||
desc 'unlink CASTLE', 'Unsymlinks all dotfiles from the specified castle'
|
||||
def unlink(name = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(name, 'symlink')
|
||||
|
||||
inside castle_dir(name) do
|
||||
subdirs = subdirs(name)
|
||||
|
||||
# unlink files
|
||||
unsymlink_each(name, castle_dir(name), subdirs)
|
||||
|
||||
# unlink files in subdirs
|
||||
subdirs.each do |subdir|
|
||||
unsymlink_each(name, subdir, subdirs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'link CASTLE', 'Symlinks all dotfiles from the specified castle'
|
||||
method_option :force,
|
||||
default: false,
|
||||
desc: 'Overwrite existing conflicting symlinks without prompting.'
|
||||
def link(name = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(name, 'symlink')
|
||||
|
||||
inside castle_dir(name) do
|
||||
subdirs = subdirs(name)
|
||||
|
||||
# link files
|
||||
symlink_each(name, castle_dir(name), subdirs)
|
||||
|
||||
# link files in subdirs
|
||||
subdirs.each do |subdir|
|
||||
symlink_each(name, subdir, subdirs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'track FILE CASTLE', 'add a file to a castle'
|
||||
def track(file, castle = DEFAULT_CASTLE_NAME)
|
||||
castle = Pathname.new(castle)
|
||||
file = Pathname.new(file.chomp('/'))
|
||||
check_castle_existance(castle, 'track')
|
||||
|
||||
absolute_path = file.expand_path
|
||||
relative_dir = absolute_path.relative_path_from(home_dir).dirname
|
||||
castle_path = Pathname.new(castle_dir(castle)).join(relative_dir)
|
||||
FileUtils.mkdir_p castle_path
|
||||
|
||||
# Are we already tracking this or anything inside it?
|
||||
target = Pathname.new(castle_path.join(file.basename))
|
||||
if target.exist?
|
||||
if absolute_path.directory?
|
||||
move_dir_contents(target, absolute_path)
|
||||
absolute_path.rmtree
|
||||
subdir_remove(castle, relative_dir + file.basename)
|
||||
|
||||
elsif more_recent? absolute_path, target
|
||||
target.delete
|
||||
mv absolute_path, castle_path
|
||||
else
|
||||
say_status(:track,
|
||||
"#{target} already exists, and is more recent than #{file}. Run 'homesick SYMLINK CASTLE' to create symlinks.",
|
||||
:blue)
|
||||
end
|
||||
else
|
||||
mv absolute_path, castle_path
|
||||
end
|
||||
|
||||
inside home_dir do
|
||||
absolute_path = castle_path + file.basename
|
||||
home_path = home_dir + relative_dir + file.basename
|
||||
ln_s absolute_path, home_path
|
||||
end
|
||||
|
||||
inside castle_path do
|
||||
git_add absolute_path
|
||||
end
|
||||
|
||||
# are we tracking something nested? Add the parent dir to the manifest
|
||||
subdir_add(castle, relative_dir) unless relative_dir.eql?(Pathname.new('.'))
|
||||
end
|
||||
|
||||
desc 'list', 'List cloned castles'
|
||||
def list
|
||||
inside_each_castle do |castle|
|
||||
say_status castle.relative_path_from(repos_dir).to_s,
|
||||
`git config remote.origin.url`.chomp,
|
||||
:cyan
|
||||
end
|
||||
end
|
||||
|
||||
desc 'status CASTLE', 'Shows the git status of a castle'
|
||||
def status(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(castle, 'status')
|
||||
inside repos_dir.join(castle) do
|
||||
git_status
|
||||
end
|
||||
end
|
||||
|
||||
desc 'diff CASTLE', 'Shows the git diff of uncommitted changes in a castle'
|
||||
def diff(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(castle, 'diff')
|
||||
inside repos_dir.join(castle) do
|
||||
git_diff
|
||||
end
|
||||
end
|
||||
|
||||
desc 'show_path CASTLE', 'Prints the path of a castle'
|
||||
def show_path(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance(castle, 'show_path')
|
||||
say repos_dir.join(castle)
|
||||
end
|
||||
|
||||
desc 'generate PATH', 'generate a homesick-ready git repo at PATH'
|
||||
def generate(castle)
|
||||
castle = Pathname.new(castle).expand_path
|
||||
|
||||
github_user = `git config github.user`.chomp
|
||||
github_user = nil if github_user == ''
|
||||
github_repo = castle.basename
|
||||
|
||||
empty_directory castle
|
||||
inside castle do
|
||||
git_init
|
||||
if github_user
|
||||
url = "git@github.com:#{github_user}/#{github_repo}.git"
|
||||
git_remote_add 'origin', url
|
||||
end
|
||||
|
||||
empty_directory 'home'
|
||||
end
|
||||
end
|
||||
|
||||
desc 'destroy CASTLE', 'Delete all symlinks and remove the cloned repository'
|
||||
def destroy(name)
|
||||
check_castle_existance name, 'destroy'
|
||||
|
||||
if shell.yes?('This will destroy your castle irreversible! Are you sure?')
|
||||
unlink(name)
|
||||
rm_rf repos_dir.join(name)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'cd CASTLE', 'Open a new shell in the root of the given castle'
|
||||
def cd(castle = DEFAULT_CASTLE_NAME)
|
||||
check_castle_existance castle, 'cd'
|
||||
castle_dir = repos_dir.join(castle)
|
||||
say_status "cd #{castle_dir.realpath}",
|
||||
"Opening a new shell in castle '#{castle}'. To return to the original one exit from the new shell.",
|
||||
:green
|
||||
inside castle_dir do
|
||||
system(ENV['SHELL'])
|
||||
end
|
||||
end
|
||||
|
||||
desc 'open CASTLE',
|
||||
'Open your default editor in the root of the given castle'
|
||||
def open(castle = DEFAULT_CASTLE_NAME)
|
||||
unless ENV['EDITOR']
|
||||
say_status :error,
|
||||
'The $EDITOR environment variable must be set to use this command',
|
||||
:red
|
||||
|
||||
exit(1)
|
||||
end
|
||||
check_castle_existance castle, 'open'
|
||||
castle_dir = repos_dir.join(castle)
|
||||
say_status "#{ENV['EDITOR']} #{castle_dir.realpath}",
|
||||
"Opening the root directory of castle '#{castle}' in editor '#{ENV['EDITOR']}'.",
|
||||
:green
|
||||
inside castle_dir do
|
||||
system(ENV['EDITOR'])
|
||||
end
|
||||
end
|
||||
|
||||
desc 'exec CASTLE COMMAND',
|
||||
'Execute a single shell command inside the root of a castle'
|
||||
def exec(castle, *args)
|
||||
check_castle_existance castle, 'exec'
|
||||
unless args.count > 0
|
||||
say_status :error,
|
||||
'You must pass a shell command to execute',
|
||||
:red
|
||||
exit(1)
|
||||
end
|
||||
full_command = args.join(' ')
|
||||
say_status "exec '#{full_command}'",
|
||||
"#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
|
||||
:green
|
||||
inside repos_dir.join(castle) do
|
||||
system(full_command)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'exec_all COMMAND',
|
||||
'Execute a single shell command inside the root of every cloned castle'
|
||||
def exec_all(*args)
|
||||
unless args.count > 0
|
||||
say_status :error,
|
||||
'You must pass a shell command to execute',
|
||||
:red
|
||||
exit(1)
|
||||
end
|
||||
full_command = args.join(' ')
|
||||
inside_each_castle do |castle|
|
||||
say_status "exec '#{full_command}'",
|
||||
"#{options[:pretend] ? 'Would execute' : 'Executing command'} '#{full_command}' in castle '#{castle}'",
|
||||
:green
|
||||
system(full_command)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'version', 'Display the current version of homesick'
|
||||
def version
|
||||
say Homesick::Version::STRING
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,8 @@
|
||||
class Homesick
|
||||
require 'thor'
|
||||
|
||||
module Homesick
|
||||
# Hack in support for diffing symlinks
|
||||
class Shell < Thor::Shell::Color
|
||||
|
||||
def show_diff(destination, content)
|
||||
destination = Pathname.new(destination)
|
||||
|
||||
@@ -12,6 +13,5 @@ class Homesick
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
216
lib/homesick/utils.rb
Normal file
216
lib/homesick/utils.rb
Normal file
@@ -0,0 +1,216 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
module Homesick
|
||||
# Various utility methods that are used by Homesick
|
||||
module Utils
|
||||
QUIETABLE = ['say_status']
|
||||
|
||||
PRETENDABLE = ['system']
|
||||
|
||||
QUIETABLE.each do |method_name|
|
||||
define_method(method_name) do |*args|
|
||||
super(*args) unless options[:quiet]
|
||||
end
|
||||
end
|
||||
|
||||
PRETENDABLE.each do |method_name|
|
||||
define_method(method_name) do |*args|
|
||||
super(*args) unless options[:pretend]
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def home_dir
|
||||
@home_dir ||= Pathname.new(ENV['HOME'] || '~').expand_path
|
||||
end
|
||||
|
||||
def repos_dir
|
||||
@repos_dir ||= home_dir.join('.homesick', 'repos').expand_path
|
||||
end
|
||||
|
||||
def castle_dir(name)
|
||||
repos_dir.join(name, 'home')
|
||||
end
|
||||
|
||||
def check_castle_existance(name, action)
|
||||
unless castle_dir(name).exist?
|
||||
say_status :error,
|
||||
"Could not #{action} #{name}, expected #{castle_dir(name)} exist and contain dotfiles",
|
||||
:red
|
||||
exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
def all_castles
|
||||
dirs = Pathname.glob("#{repos_dir}/**/.git", File::FNM_DOTMATCH)
|
||||
# reject paths that lie inside another castle, like git submodules
|
||||
dirs.reject do |dir|
|
||||
dirs.any? do |other|
|
||||
dir != other && dir.fnmatch(other.parent.join('*').to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def inside_each_castle(&block)
|
||||
all_castles.each do |git_dir|
|
||||
castle = git_dir.dirname
|
||||
Dir.chdir castle do # so we can call git config from the right contxt
|
||||
yield castle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_castle(castle)
|
||||
check_castle_existance(castle, 'pull')
|
||||
inside repos_dir.join(castle) do
|
||||
git_pull
|
||||
git_submodule_init
|
||||
git_submodule_update
|
||||
end
|
||||
end
|
||||
|
||||
def commit_castle(castle, message)
|
||||
check_castle_existance(castle, 'commit')
|
||||
inside repos_dir.join(castle) do
|
||||
git_commit_all message: message
|
||||
end
|
||||
end
|
||||
|
||||
def push_castle(castle)
|
||||
check_castle_existance(castle, 'push')
|
||||
inside repos_dir.join(castle) do
|
||||
git_push
|
||||
end
|
||||
end
|
||||
|
||||
def subdir_file(castle)
|
||||
repos_dir.join(castle, SUBDIR_FILENAME)
|
||||
end
|
||||
|
||||
def subdirs(castle)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
subdirs = []
|
||||
if subdir_filepath.exist?
|
||||
subdir_filepath.readlines.each do |subdir|
|
||||
subdirs.push(subdir.chomp)
|
||||
end
|
||||
end
|
||||
subdirs
|
||||
end
|
||||
|
||||
def subdir_add(castle, path)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
File.open(subdir_filepath, 'a+') do |subdir|
|
||||
subdir.puts path unless subdir.readlines.reduce(false) do |memo, line|
|
||||
line.eql?("#{path}\n") || memo
|
||||
end
|
||||
end
|
||||
|
||||
inside castle_dir(castle) do
|
||||
git_add subdir_filepath
|
||||
end
|
||||
end
|
||||
|
||||
def subdir_remove(castle, path)
|
||||
subdir_filepath = subdir_file(castle)
|
||||
if subdir_filepath.exist?
|
||||
lines = IO.readlines(subdir_filepath).delete_if do |line|
|
||||
line == "#{path}\n"
|
||||
end
|
||||
File.open(subdir_filepath, 'w') { |manfile| manfile.puts lines }
|
||||
end
|
||||
|
||||
inside castle_dir(castle) do
|
||||
git_add subdir_filepath
|
||||
end
|
||||
end
|
||||
|
||||
def move_dir_contents(target, dir_path)
|
||||
child_files = dir_path.children
|
||||
child_files.each do |child|
|
||||
|
||||
target_path = target.join(child.basename)
|
||||
if target_path.exist?
|
||||
if more_recent?(child, target_path) && target.file?
|
||||
target_path.delete
|
||||
mv child, target
|
||||
end
|
||||
next
|
||||
end
|
||||
|
||||
mv child, target
|
||||
end
|
||||
end
|
||||
|
||||
def more_recent?(first, second)
|
||||
first_p = Pathname.new(first)
|
||||
second_p = Pathname.new(second)
|
||||
first_p.mtime > second_p.mtime && !first_p.symlink?
|
||||
end
|
||||
|
||||
def collision_accepted?(destination)
|
||||
fail "Argument must be an instance of Pathname, #{destination.class.name} given" unless destination.instance_of?(Pathname)
|
||||
options[:force] || shell.file_collision(destination) { source }
|
||||
end
|
||||
|
||||
def each_file(castle, basedir, subdirs)
|
||||
absolute_basedir = Pathname.new(basedir).expand_path
|
||||
inside basedir do
|
||||
files = Pathname.glob('{.*,*}').reject do |a|
|
||||
['.', '..'].include?(a.to_s)
|
||||
end
|
||||
files.each do |path|
|
||||
absolute_path = path.expand_path
|
||||
castle_home = castle_dir(castle)
|
||||
|
||||
# make ignore dirs
|
||||
ignore_dirs = []
|
||||
subdirs.each do |subdir|
|
||||
# ignore all parent of each line in subdir file
|
||||
Pathname.new(subdir).ascend do |p|
|
||||
ignore_dirs.push(p)
|
||||
end
|
||||
end
|
||||
|
||||
# ignore dirs written in subdir file
|
||||
matched = false
|
||||
ignore_dirs.uniq.each do |ignore_dir|
|
||||
if absolute_path == castle_home.join(ignore_dir)
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
next if matched
|
||||
|
||||
relative_dir = absolute_basedir.relative_path_from(castle_home)
|
||||
home_path = home_dir.join(relative_dir).join(path)
|
||||
|
||||
yield(absolute_path, home_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unsymlink_each(castle, basedir, subdirs)
|
||||
each_file(castle, basedir, subdirs) do |absolute_path, home_path|
|
||||
rm_link home_path
|
||||
end
|
||||
end
|
||||
|
||||
def symlink_each(castle, basedir, subdirs)
|
||||
each_file(castle, basedir, subdirs) do |absolute_path, home_path|
|
||||
ln_s absolute_path, home_path
|
||||
end
|
||||
end
|
||||
|
||||
def setup_castle(path)
|
||||
if path.join('.gitmodules').exist?
|
||||
inside path do
|
||||
git_submodule_init
|
||||
git_submodule_update
|
||||
end
|
||||
end
|
||||
|
||||
rc(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,12 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
class Homesick
|
||||
module Homesick
|
||||
# A representation of Homesick's version number in constants, including a
|
||||
# String of the entire version number
|
||||
module Version
|
||||
MAJOR = 1
|
||||
MINOR = 0
|
||||
MINOR = 1
|
||||
PATCH = 0
|
||||
|
||||
STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
|
||||
STRING = [MAJOR, MINOR, PATCH].compact.join('.')
|
||||
end
|
||||
end
|
||||
|
||||
787
spec/homesick_cli_spec.rb
Normal file
787
spec/homesick_cli_spec.rb
Normal file
@@ -0,0 +1,787 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
require 'spec_helper'
|
||||
require 'capture-output'
|
||||
|
||||
describe Homesick::CLI do
|
||||
let(:home) { create_construct }
|
||||
after { home.destroy! }
|
||||
|
||||
let(:castles) { home.directory('.homesick/repos') }
|
||||
|
||||
let(:homesick) { Homesick::CLI.new }
|
||||
|
||||
before { allow(homesick).to receive(:repos_dir).and_return(castles) }
|
||||
|
||||
describe 'clone' do
|
||||
context 'has a .homesickrc' do
|
||||
it 'runs the .homesickrc' do
|
||||
somewhere = create_construct
|
||||
local_repo = somewhere.directory('some_repo')
|
||||
local_repo.file('.homesickrc') do |file|
|
||||
file << "File.open(Dir.pwd + '/testing', 'w') do |f|
|
||||
f.print 'testing'
|
||||
end"
|
||||
end
|
||||
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).with(be_a(String)).and_return(true)
|
||||
expect(homesick).to receive(:say_status).with('eval', kind_of(Pathname))
|
||||
homesick.clone local_repo
|
||||
|
||||
expect(castles.join('some_repo').join('testing')).to exist
|
||||
end
|
||||
end
|
||||
|
||||
context 'of a file' do
|
||||
it 'symlinks existing directories' do
|
||||
somewhere = create_construct
|
||||
local_repo = somewhere.directory('wtf')
|
||||
|
||||
homesick.clone local_repo
|
||||
|
||||
expect(castles.join('wtf').readlink).to eq(local_repo)
|
||||
end
|
||||
|
||||
context 'when it exists in a repo directory' do
|
||||
before do
|
||||
existing_castle = given_castle('existing_castle')
|
||||
@existing_dir = existing_castle.parent
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect(homesick).not_to receive(:git_clone)
|
||||
expect { homesick.clone @existing_dir.to_s }.to raise_error(/already cloned/i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'clones git repo like file:///path/to.git' do
|
||||
bare_repo = File.join(create_construct.to_s, 'dotfiles.git')
|
||||
system "git init --bare #{bare_repo} >/dev/null 2>&1"
|
||||
|
||||
# Capture stderr to suppress message about cloning an empty repo.
|
||||
Capture.stderr do
|
||||
homesick.clone "file://#{bare_repo}"
|
||||
end
|
||||
expect(File.directory?(File.join(home.to_s, '.homesick/repos/dotfiles')))
|
||||
.to be_true
|
||||
end
|
||||
|
||||
it 'clones git repo like git://host/path/to.git' do
|
||||
expect(homesick).to receive(:git_clone)
|
||||
.with('git://github.com/technicalpickles/pickled-vim.git')
|
||||
|
||||
homesick.clone 'git://github.com/technicalpickles/pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'clones git repo like git@host:path/to.git' do
|
||||
expect(homesick).to receive(:git_clone)
|
||||
.with('git@github.com:technicalpickles/pickled-vim.git')
|
||||
|
||||
homesick.clone 'git@github.com:technicalpickles/pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'clones git repo like http://host/path/to.git' do
|
||||
expect(homesick).to receive(:git_clone)
|
||||
.with('http://github.com/technicalpickles/pickled-vim.git')
|
||||
|
||||
homesick.clone 'http://github.com/technicalpickles/pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'clones git repo like http://host/path/to' do
|
||||
expect(homesick).to receive(:git_clone)
|
||||
.with('http://github.com/technicalpickles/pickled-vim')
|
||||
|
||||
homesick.clone 'http://github.com/technicalpickles/pickled-vim'
|
||||
end
|
||||
|
||||
it 'clones git repo like host-alias:repos.git' do
|
||||
expect(homesick).to receive(:git_clone).with('gitolite:pickled-vim.git')
|
||||
|
||||
homesick.clone 'gitolite:pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'throws an exception when trying to clone a malformed uri like malformed' do
|
||||
expect(homesick).not_to receive(:git_clone)
|
||||
expect { homesick.clone 'malformed' }.to raise_error
|
||||
end
|
||||
|
||||
it 'clones a github repo' do
|
||||
expect(homesick).to receive(:git_clone)
|
||||
.with('https://github.com/wfarr/dotfiles.git',
|
||||
destination: Pathname.new('dotfiles'))
|
||||
|
||||
homesick.clone 'wfarr/dotfiles'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'rc' do
|
||||
let(:castle) { given_castle('glencairn') }
|
||||
|
||||
context 'when told to do so' do
|
||||
before do
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).with(be_a(String)).and_return(true)
|
||||
end
|
||||
|
||||
it 'executes the .homesickrc' do
|
||||
castle.file('.homesickrc') do |file|
|
||||
file << "File.open(Dir.pwd + '/testing', 'w') do |f|
|
||||
f.print 'testing'
|
||||
end"
|
||||
end
|
||||
|
||||
expect(homesick).to receive(:say_status).with('eval', kind_of(Pathname))
|
||||
homesick.rc castle
|
||||
|
||||
expect(castle.join('testing')).to exist
|
||||
end
|
||||
end
|
||||
|
||||
context 'when told not to do so' do
|
||||
before do
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).with(be_a(String)).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not execute the .homesickrc' do
|
||||
castle.file('.homesickrc') do |file|
|
||||
file << "File.open(Dir.pwd + '/testing', 'w') do |f|
|
||||
f.print 'testing'
|
||||
end"
|
||||
end
|
||||
|
||||
expect(homesick).to receive(:say_status).with('eval skip', /not evaling.+/, :blue)
|
||||
homesick.rc castle
|
||||
|
||||
expect(castle.join('testing')).not_to exist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'link' do
|
||||
let(:castle) { given_castle('glencairn') }
|
||||
|
||||
it 'links dotfiles from a castle to the home folder' do
|
||||
dotfile = castle.file('.some_dotfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
|
||||
expect(home.join('.some_dotfile').readlink).to eq(dotfile)
|
||||
end
|
||||
|
||||
it 'links non-dotfiles from a castle to the home folder' do
|
||||
dotfile = castle.file('bin')
|
||||
|
||||
homesick.link('glencairn')
|
||||
|
||||
expect(home.join('bin').readlink).to eq(dotfile)
|
||||
end
|
||||
|
||||
context 'when forced' do
|
||||
let(:homesick) { Homesick::CLI.new [], force: true }
|
||||
|
||||
it 'can override symlinks to directories' do
|
||||
somewhere_else = create_construct
|
||||
existing_dotdir_link = home.join('.vim')
|
||||
FileUtils.ln_s somewhere_else, existing_dotdir_link
|
||||
|
||||
dotdir = castle.directory('.vim')
|
||||
|
||||
homesick.link('glencairn')
|
||||
|
||||
expect(existing_dotdir_link.readlink).to eq(dotdir)
|
||||
end
|
||||
|
||||
it 'can override existing directory' do
|
||||
existing_dotdir = home.directory('.vim')
|
||||
|
||||
dotdir = castle.directory('.vim')
|
||||
|
||||
homesick.link('glencairn')
|
||||
|
||||
expect(existing_dotdir.readlink).to eq(dotdir)
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config']) }
|
||||
it 'can symlink in sub directory' do
|
||||
dotdir = castle.directory('.config')
|
||||
dotfile = dotdir.file('.some_dotfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config')
|
||||
expect(home_dotdir.symlink?).to eq(false)
|
||||
expect(home_dotdir.join('.some_dotfile').readlink).to eq(dotfile)
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config/appA' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config/appA']) }
|
||||
it 'can symlink in nested sub directory' do
|
||||
dotdir = castle.directory('.config').directory('appA')
|
||||
dotfile = dotdir.file('.some_dotfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config').join('appA')
|
||||
expect(home_dotdir.symlink?).to eq(false)
|
||||
expect(home_dotdir.join('.some_dotfile').readlink).to eq(dotfile)
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' and '.config/someapp' in .homesick_subdir" do
|
||||
let(:castle) do
|
||||
given_castle('glencairn', ['.config', '.config/someapp'])
|
||||
end
|
||||
it 'can symlink under both of .config and .config/someapp' do
|
||||
config_dir = castle.directory('.config')
|
||||
config_dotfile = config_dir.file('.some_dotfile')
|
||||
someapp_dir = config_dir.directory('someapp')
|
||||
someapp_dotfile = someapp_dir.file('.some_appfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
|
||||
home_config_dir = home.join('.config')
|
||||
home_someapp_dir = home_config_dir.join('someapp')
|
||||
expect(home_config_dir.symlink?).to eq(false)
|
||||
expect(home_config_dir.join('.some_dotfile').readlink)
|
||||
.to eq(config_dotfile)
|
||||
expect(home_someapp_dir.symlink?).to eq(false)
|
||||
expect(home_someapp_dir.join('.some_appfile').readlink)
|
||||
.to eq(someapp_dotfile)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when call with no castle name' do
|
||||
let(:castle) { given_castle('dotfiles') }
|
||||
it 'using default castle name: "dotfiles"' do
|
||||
dotfile = castle.file('.some_dotfile')
|
||||
|
||||
homesick.link
|
||||
|
||||
expect(home.join('.some_dotfile').readlink).to eq(dotfile)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'unlink' do
|
||||
let(:castle) { given_castle('glencairn') }
|
||||
|
||||
it 'unlinks dotfiles in the home folder' do
|
||||
castle.file('.some_dotfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
expect(home.join('.some_dotfile')).not_to exist
|
||||
end
|
||||
|
||||
it 'unlinks non-dotfiles from the home folder' do
|
||||
castle.file('bin')
|
||||
|
||||
homesick.link('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
expect(home.join('bin')).not_to exist
|
||||
end
|
||||
|
||||
context "with '.config' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config']) }
|
||||
|
||||
it 'can unlink sub directories' do
|
||||
castle.directory('.config').file('.some_dotfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config')
|
||||
expect(home_dotdir).to exist
|
||||
expect(home_dotdir.join('.some_dotfile')).not_to exist
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config/appA' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config/appA']) }
|
||||
|
||||
it 'can unsymlink in nested sub directory' do
|
||||
castle.directory('.config').directory('appA').file('.some_dotfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config').join('appA')
|
||||
expect(home_dotdir).to exist
|
||||
expect(home_dotdir.join('.some_dotfile')).not_to exist
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' and '.config/someapp' in .homesick_subdir" do
|
||||
let(:castle) do
|
||||
given_castle('glencairn', ['.config', '.config/someapp'])
|
||||
end
|
||||
|
||||
it 'can unsymlink under both of .config and .config/someapp' do
|
||||
config_dir = castle.directory('.config')
|
||||
config_dir.file('.some_dotfile')
|
||||
config_dir.directory('someapp').file('.some_appfile')
|
||||
|
||||
homesick.link('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home_config_dir = home.join('.config')
|
||||
home_someapp_dir = home_config_dir.join('someapp')
|
||||
expect(home_config_dir).to exist
|
||||
expect(home_config_dir.join('.some_dotfile')).not_to exist
|
||||
expect(home_someapp_dir).to exist
|
||||
expect(home_someapp_dir.join('.some_appfile')).not_to exist
|
||||
end
|
||||
end
|
||||
|
||||
context 'when call with no castle name' do
|
||||
let(:castle) { given_castle('dotfiles') }
|
||||
|
||||
it 'using default castle name: "dotfiles"' do
|
||||
castle.file('.some_dotfile')
|
||||
|
||||
homesick.link
|
||||
homesick.unlink
|
||||
|
||||
expect(home.join('.some_dotfile')).not_to exist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'list' do
|
||||
it 'says each castle in the castle directory' do
|
||||
given_castle('zomg')
|
||||
given_castle('wtf/zomg')
|
||||
|
||||
expect(homesick).to receive(:say_status)
|
||||
.with('zomg',
|
||||
'git://github.com/technicalpickles/zomg.git',
|
||||
:cyan)
|
||||
expect(homesick).to receive(:say_status)
|
||||
.with('wtf/zomg',
|
||||
'git://github.com/technicalpickles/zomg.git',
|
||||
:cyan)
|
||||
|
||||
homesick.list
|
||||
end
|
||||
end
|
||||
|
||||
describe 'status' do
|
||||
it 'says "nothing to commit" when there are no changes' do
|
||||
given_castle('castle_repo')
|
||||
text = Capture.stdout { homesick.status('castle_repo') }
|
||||
expect(text).to match(/nothing to commit \(create\/copy files and use "git add" to track\)$/)
|
||||
end
|
||||
|
||||
it 'says "Changes to be committed" when there are changes' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
text = Capture.stdout { homesick.status('castle_repo') }
|
||||
expect(text).to match(
|
||||
/Changes to be committed:.*new file:\s*home\/.some_rc_file/m
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'diff' do
|
||||
it 'outputs an empty message when there are no changes to commit' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
Capture.stdout do
|
||||
homesick.commit 'castle_repo', 'Adding a file to the test'
|
||||
end
|
||||
text = Capture.stdout { homesick.diff('castle_repo') }
|
||||
expect(text).to eq('')
|
||||
end
|
||||
|
||||
it 'outputs a diff message when there are changes to commit' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
Capture.stdout do
|
||||
homesick.commit 'castle_repo', 'Adding a file to the test'
|
||||
end
|
||||
File.open(some_rc_file.to_s, 'w') do |file|
|
||||
file.puts 'Some test text'
|
||||
end
|
||||
text = Capture.stdout { homesick.diff('castle_repo') }
|
||||
expect(text).to match(/diff --git.+Some test text$/m)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'show_path' do
|
||||
it 'says the path of a castle' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
expect(homesick).to receive(:say).with(castle.dirname)
|
||||
|
||||
homesick.show_path('castle_repo')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'pull' do
|
||||
it 'performs a pull, submodule init and update when the given castle exists' do
|
||||
given_castle('castle_repo')
|
||||
allow(homesick).to receive(:system).once.with('git pull --quiet')
|
||||
allow(homesick).to receive(:system).once.with('git submodule --quiet init')
|
||||
allow(homesick).to receive(:system).once.with('git submodule --quiet update --init --recursive >/dev/null 2>&1')
|
||||
homesick.pull 'castle_repo'
|
||||
end
|
||||
|
||||
it 'prints an error message when trying to pull a non-existant castle' do
|
||||
expect(homesick).to receive('say_status').once
|
||||
.with(:error,
|
||||
/Could not pull castle_repo, expected .* exist and contain dotfiles/,
|
||||
:red)
|
||||
expect { homesick.pull 'castle_repo' }.to raise_error(SystemExit)
|
||||
end
|
||||
|
||||
describe '--all' do
|
||||
it 'pulls each castle when invoked with --all' do
|
||||
given_castle('castle_repo')
|
||||
given_castle('glencairn')
|
||||
allow(homesick).to receive(:system).exactly(2).times.with('git pull --quiet')
|
||||
allow(homesick).to receive(:system).exactly(2).times
|
||||
.with('git submodule --quiet init')
|
||||
allow(homesick).to receive(:system).exactly(2).times
|
||||
.with('git submodule --quiet update --init --recursive >/dev/null 2>&1')
|
||||
Capture.stdout do
|
||||
Capture.stderr { homesick.invoke 'pull', [], all: true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'push' do
|
||||
it 'performs a git push on the given castle' do
|
||||
given_castle('castle_repo')
|
||||
allow(homesick).to receive(:system).once.with('git push')
|
||||
homesick.push 'castle_repo'
|
||||
end
|
||||
|
||||
it 'prints an error message when trying to push a non-existant castle' do
|
||||
expect(homesick).to receive('say_status').once
|
||||
.with(:error,
|
||||
/Could not push castle_repo, expected .* exist and contain dotfiles/,
|
||||
:red)
|
||||
expect { homesick.push 'castle_repo' }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'track' do
|
||||
it 'moves the tracked file into the castle' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('.some_rc_file')
|
||||
expect(tracked_file).to exist
|
||||
|
||||
expect(some_rc_file.readlink).to eq(tracked_file)
|
||||
end
|
||||
|
||||
it 'handles files with parens' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_rc_file = home.file 'Default (Linux).sublime-keymap'
|
||||
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('Default (Linux).sublime-keymap')
|
||||
expect(tracked_file).to exist
|
||||
|
||||
expect(some_rc_file.readlink).to eq(tracked_file)
|
||||
end
|
||||
|
||||
it 'tracks a file in nested folder structure' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('some/nested/file.txt')
|
||||
expect(tracked_file).to exist
|
||||
expect(some_nested_file.readlink).to eq(tracked_file)
|
||||
end
|
||||
|
||||
it 'tracks a nested directory' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_dir = home.directory('some/nested/directory/')
|
||||
homesick.track(some_nested_dir.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('some/nested/directory/')
|
||||
expect(tracked_file).to exist
|
||||
expect(some_nested_dir.realpath).to eq(tracked_file.realpath)
|
||||
end
|
||||
|
||||
context 'when call with no castle name' do
|
||||
it 'using default castle name: "dotfiles"' do
|
||||
castle = given_castle('dotfiles')
|
||||
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
|
||||
homesick.track(some_rc_file.to_s)
|
||||
|
||||
tracked_file = castle.join('.some_rc_file')
|
||||
expect(tracked_file).to exist
|
||||
|
||||
expect(some_rc_file.readlink).to eq(tracked_file)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'commit' do
|
||||
it 'has a commit message when the commit succeeds' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.a_random_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
text = Capture.stdout do
|
||||
homesick.commit('castle_repo', 'Test message')
|
||||
end
|
||||
expect(text).to match(/^\[master \(root-commit\) \w+\] Test message/)
|
||||
end
|
||||
end
|
||||
|
||||
# Note that this is a test for the subdir_file related feature of track,
|
||||
# not for the subdir_file method itself.
|
||||
describe 'subdir_file' do
|
||||
|
||||
it 'adds the nested files parent to the subdir_file' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
expect(f.readline).to eq("some/nested\n")
|
||||
end
|
||||
end
|
||||
|
||||
it 'does NOT add anything if the files parent is already listed' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
other_nested_file = home.file('some/nested/other.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
homesick.track(other_nested_file.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
expect(f.readlines.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'removes the parent of a tracked file from the subdir_file if the parent itself is tracked' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
nested_parent = home.directory('some/nested/')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
homesick.track(nested_parent.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
f.each_line { |line| expect(line).not_to eq("some/nested\n") }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'destroy' do
|
||||
it 'removes the symlink files' do
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).and_return('y')
|
||||
given_castle('stronghold')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'stronghold')
|
||||
homesick.destroy('stronghold')
|
||||
|
||||
expect(some_rc_file).not_to be_exist
|
||||
end
|
||||
|
||||
it 'deletes the cloned repository' do
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).and_return('y')
|
||||
castle = given_castle('stronghold')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'stronghold')
|
||||
homesick.destroy('stronghold')
|
||||
|
||||
expect(castle).not_to be_exist
|
||||
end
|
||||
end
|
||||
|
||||
describe 'cd' do
|
||||
it "cd's to the root directory of the given castle" do
|
||||
given_castle('castle_repo')
|
||||
expect(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
||||
expect(homesick).to receive('system').once.with(ENV['SHELL'])
|
||||
Capture.stdout { homesick.cd 'castle_repo' }
|
||||
end
|
||||
|
||||
it 'returns an error message when the given castle does not exist' do
|
||||
expect(homesick).to receive('say_status').once
|
||||
.with(:error,
|
||||
/Could not cd castle_repo, expected .* exist and contain dotfiles/,
|
||||
:red)
|
||||
expect { homesick.cd 'castle_repo' }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'open' do
|
||||
it 'opens the system default editor in the root of the given castle' do
|
||||
# Make sure calls to ENV use default values for most things...
|
||||
allow(ENV).to receive(:[]).and_call_original
|
||||
# Set a default value for 'EDITOR' just in case none is set
|
||||
allow(ENV).to receive(:[]).with('EDITOR').and_return('vim')
|
||||
given_castle 'castle_repo'
|
||||
expect(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
||||
expect(homesick).to receive('system').once.with('vim')
|
||||
Capture.stdout { homesick.open 'castle_repo' }
|
||||
end
|
||||
|
||||
it 'returns an error message when the $EDITOR environment variable is not set' do
|
||||
# Set the default editor to make sure it fails.
|
||||
allow(ENV).to receive(:[]).with('EDITOR').and_return(nil)
|
||||
expect(homesick).to receive('say_status').once
|
||||
.with(:error,
|
||||
'The $EDITOR environment variable must be set to use this command',
|
||||
:red)
|
||||
expect { homesick.open 'castle_repo' }.to raise_error(SystemExit)
|
||||
end
|
||||
|
||||
it 'returns an error message when the given castle does not exist' do
|
||||
# Set a default just in case none is set
|
||||
allow(ENV).to receive(:[]).with('EDITOR').and_return('vim')
|
||||
allow(homesick).to receive('say_status').once
|
||||
.with(:error,
|
||||
/Could not open castle_repo, expected .* exist and contain dotfiles/,
|
||||
:red)
|
||||
expect { homesick.open 'castle_repo' }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'version' do
|
||||
it 'prints the current version of homesick' do
|
||||
text = Capture.stdout { homesick.version }
|
||||
expect(text.chomp).to match(/\d+\.\d+\.\d+/)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'exec' do
|
||||
before do
|
||||
given_castle 'castle_repo'
|
||||
end
|
||||
it 'executes a single command with no arguments inside a given castle' do
|
||||
allow(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
||||
allow(homesick).to receive('say_status').once
|
||||
.with(be_a(String),
|
||||
be_a(String),
|
||||
:green)
|
||||
allow(homesick).to receive('system').once.with('ls')
|
||||
Capture.stdout { homesick.exec 'castle_repo', 'ls' }
|
||||
end
|
||||
|
||||
it 'executes a single command with arguments inside a given castle' do
|
||||
allow(homesick).to receive('inside').once.with(kind_of(Pathname)).and_yield
|
||||
allow(homesick).to receive('say_status').once
|
||||
.with(be_a(String),
|
||||
be_a(String),
|
||||
:green)
|
||||
allow(homesick).to receive('system').once.with('ls -la')
|
||||
Capture.stdout { homesick.exec 'castle_repo', 'ls', '-la' }
|
||||
end
|
||||
|
||||
it 'raises an error when the method is called without a command' do
|
||||
allow(homesick).to receive('say_status').once
|
||||
.with(:error,
|
||||
be_a(String),
|
||||
:red)
|
||||
allow(homesick).to receive('exit').once.with(1)
|
||||
Capture.stdout { homesick.exec 'castle_repo' }
|
||||
end
|
||||
|
||||
context 'pretend' do
|
||||
it 'does not execute a command when the pretend option is passed' do
|
||||
allow(homesick).to receive('say_status').once
|
||||
.with(be_a(String),
|
||||
match(/.*Would execute.*/),
|
||||
:green)
|
||||
expect(homesick).to receive('system').never
|
||||
Capture.stdout { homesick.invoke 'exec', %w(castle_repo ls -la), pretend: true }
|
||||
end
|
||||
end
|
||||
|
||||
context 'quiet' do
|
||||
it 'does not print status information when quiet is passed' do
|
||||
expect(homesick).to receive('say_status').never
|
||||
allow(homesick).to receive('system').once
|
||||
.with('ls -la')
|
||||
Capture.stdout { homesick.invoke 'exec', %w(castle_repo ls -la), quiet: true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'exec_all' do
|
||||
before do
|
||||
given_castle 'castle_repo'
|
||||
given_castle 'another_castle_repo'
|
||||
end
|
||||
|
||||
it 'executes a command without arguments inside the root of each cloned castle' do
|
||||
allow(homesick).to receive('inside_each_castle').exactly(:twice).and_yield('castle_repo').and_yield('another_castle_repo')
|
||||
allow(homesick).to receive('say_status').at_least(:once)
|
||||
.with(be_a(String),
|
||||
be_a(String),
|
||||
:green)
|
||||
allow(homesick).to receive('system').at_least(:once).with('ls')
|
||||
Capture.stdout { homesick.exec_all 'ls' }
|
||||
end
|
||||
|
||||
it 'executes a command with arguments inside the root of each cloned castle' do
|
||||
allow(homesick).to receive('inside_each_castle').exactly(:twice).and_yield('castle_repo').and_yield('another_castle_repo')
|
||||
allow(homesick).to receive('say_status').at_least(:once)
|
||||
.with(be_a(String),
|
||||
be_a(String),
|
||||
:green)
|
||||
allow(homesick).to receive('system').at_least(:once).with('ls -la')
|
||||
Capture.stdout { homesick.exec_all 'ls', '-la' }
|
||||
end
|
||||
|
||||
it 'raises an error when the method is called without a command' do
|
||||
allow(homesick).to receive('say_status').once
|
||||
.with(:error,
|
||||
be_a(String),
|
||||
:red)
|
||||
allow(homesick).to receive('exit').once.with(1)
|
||||
Capture.stdout { homesick.exec_all }
|
||||
end
|
||||
|
||||
context 'pretend' do
|
||||
it 'does not execute a command when the pretend option is passed' do
|
||||
allow(homesick).to receive('say_status').at_least(:once)
|
||||
.with(be_a(String),
|
||||
match(/.*Would execute.*/),
|
||||
:green)
|
||||
expect(homesick).to receive('system').never
|
||||
Capture.stdout { homesick.invoke 'exec_all', %w(ls -la), pretend: true }
|
||||
end
|
||||
end
|
||||
|
||||
context 'quiet' do
|
||||
it 'does not print status information when quiet is passed' do
|
||||
expect(homesick).to receive('say_status').never
|
||||
allow(homesick).to receive('system').at_least(:once)
|
||||
.with('ls -la')
|
||||
Capture.stdout { homesick.invoke 'exec_all', %w(ls -la), quiet: true }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,621 +0,0 @@
|
||||
# -*- encoding : utf-8 -*-
|
||||
require 'spec_helper'
|
||||
require 'capture-output'
|
||||
|
||||
describe 'homesick' do
|
||||
let(:home) { create_construct }
|
||||
after { home.destroy! }
|
||||
|
||||
let(:castles) { home.directory('.homesick/repos') }
|
||||
|
||||
let(:homesick) { Homesick.new }
|
||||
|
||||
before { homesick.stub(:repos_dir).and_return(castles) }
|
||||
|
||||
describe 'clone' do
|
||||
context 'has a .homesickrc' do
|
||||
it 'should run the .homesickrc' do
|
||||
somewhere = create_construct
|
||||
local_repo = somewhere.directory('some_repo')
|
||||
local_repo.file('.homesickrc') do |file|
|
||||
file << "File.open(Dir.pwd + '/testing', 'w') { |f| f.print 'testing' }"
|
||||
end
|
||||
|
||||
expect($stdout).to receive(:print)
|
||||
expect($stdin).to receive(:gets).and_return('y')
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:say_status).with('eval', kind_of(Pathname))
|
||||
homesick.clone local_repo
|
||||
|
||||
castles.join('some_repo').join('testing').should exist
|
||||
end
|
||||
end
|
||||
|
||||
context 'of a file' do
|
||||
it 'should symlink existing directories' do
|
||||
somewhere = create_construct
|
||||
local_repo = somewhere.directory('wtf')
|
||||
|
||||
homesick.clone local_repo
|
||||
|
||||
castles.join('wtf').readlink.should == local_repo
|
||||
end
|
||||
|
||||
context 'when it exists in a repo directory' do
|
||||
before do
|
||||
existing_castle = given_castle('existing_castle')
|
||||
@existing_dir = existing_castle.parent
|
||||
end
|
||||
|
||||
it 'should raise an error' do
|
||||
homesick.should_not_receive(:git_clone)
|
||||
expect { homesick.clone @existing_dir.to_s }.to raise_error(/already cloned/i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should clone git repo like file:///path/to.git' do
|
||||
bare_repo = File.join(create_construct.to_s, 'dotfiles.git')
|
||||
system "git init --bare #{bare_repo} >/dev/null 2>&1"
|
||||
# Capture stderr to suppress message about cloning an empty repo.
|
||||
Capture.stderr do
|
||||
homesick.clone "file://#{bare_repo}"
|
||||
end
|
||||
File.directory?(File.join(home.to_s, '.homesick/repos/dotfiles')).should be_true
|
||||
end
|
||||
|
||||
it 'should clone git repo like git://host/path/to.git' do
|
||||
homesick.should_receive(:git_clone).with('git://github.com/technicalpickles/pickled-vim.git')
|
||||
|
||||
homesick.clone 'git://github.com/technicalpickles/pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'should clone git repo like git@host:path/to.git' do
|
||||
homesick.should_receive(:git_clone).with('git@github.com:technicalpickles/pickled-vim.git')
|
||||
|
||||
homesick.clone 'git@github.com:technicalpickles/pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'should clone git repo like http://host/path/to.git' do
|
||||
homesick.should_receive(:git_clone).with('http://github.com/technicalpickles/pickled-vim.git')
|
||||
|
||||
homesick.clone 'http://github.com/technicalpickles/pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'should clone git repo like http://host/path/to' do
|
||||
homesick.should_receive(:git_clone).with('http://github.com/technicalpickles/pickled-vim')
|
||||
|
||||
homesick.clone 'http://github.com/technicalpickles/pickled-vim'
|
||||
end
|
||||
|
||||
it 'should clone git repo like host-alias:repos.git' do
|
||||
homesick.should_receive(:git_clone).with('gitolite:pickled-vim.git')
|
||||
|
||||
homesick.clone 'gitolite:pickled-vim.git'
|
||||
end
|
||||
|
||||
it 'should throw an exception when trying to clone a malformed uri like malformed' do
|
||||
homesick.should_not_receive(:git_clone)
|
||||
expect { homesick.clone 'malformed' }.to raise_error
|
||||
end
|
||||
|
||||
it 'should clone a github repo' do
|
||||
homesick.should_receive(:git_clone).with('https://github.com/wfarr/dotfiles.git', :destination => Pathname.new('dotfiles'))
|
||||
|
||||
homesick.clone 'wfarr/dotfiles'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'rc' do
|
||||
let(:castle) { given_castle('glencairn') }
|
||||
|
||||
context 'when told to do so' do
|
||||
before do
|
||||
expect($stdout).to receive(:print)
|
||||
expect($stdin).to receive(:gets).and_return('y')
|
||||
end
|
||||
|
||||
it 'executes the .homesickrc' do
|
||||
castle.file('.homesickrc') do |file|
|
||||
file << "File.open(Dir.pwd + '/testing', 'w') { |f| f.print 'testing' }"
|
||||
end
|
||||
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:say_status).with('eval', kind_of(Pathname))
|
||||
homesick.rc castle
|
||||
|
||||
castle.join('testing').should exist
|
||||
end
|
||||
end
|
||||
|
||||
context 'when told not to do so' do
|
||||
before do
|
||||
expect($stdout).to receive(:print)
|
||||
expect($stdin).to receive(:gets).and_return('n')
|
||||
end
|
||||
|
||||
it 'does not execute the .homesickrc' do
|
||||
castle.file('.homesickrc') do |file|
|
||||
file << "File.open(Dir.pwd + '/testing', 'w') { |f| f.print 'testing' }"
|
||||
end
|
||||
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:say_status).with('eval skip', /not evaling.+/, :blue)
|
||||
homesick.rc castle
|
||||
|
||||
castle.join('testing').should_not exist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'symlink' do
|
||||
let(:castle) { given_castle('glencairn') }
|
||||
|
||||
it 'links dotfiles from a castle to the home folder' do
|
||||
dotfile = castle.file('.some_dotfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
|
||||
home.join('.some_dotfile').readlink.should == dotfile
|
||||
end
|
||||
|
||||
it 'links non-dotfiles from a castle to the home folder' do
|
||||
dotfile = castle.file('bin')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
|
||||
home.join('bin').readlink.should == dotfile
|
||||
end
|
||||
|
||||
context 'when forced' do
|
||||
let(:homesick) { Homesick.new [], :force => true }
|
||||
|
||||
it 'can override symlinks to directories' do
|
||||
somewhere_else = create_construct
|
||||
existing_dotdir_link = home.join('.vim')
|
||||
FileUtils.ln_s somewhere_else, existing_dotdir_link
|
||||
|
||||
dotdir = castle.directory('.vim')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
|
||||
existing_dotdir_link.readlink.should == dotdir
|
||||
end
|
||||
|
||||
it 'can override existing directory' do
|
||||
existing_dotdir = home.directory('.vim')
|
||||
|
||||
dotdir = castle.directory('.vim')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
|
||||
existing_dotdir.readlink.should == dotdir
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config']) }
|
||||
it 'can symlink in sub directory' do
|
||||
dotdir = castle.directory('.config')
|
||||
dotfile = dotdir.file('.some_dotfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config')
|
||||
home_dotdir.symlink?.should be == false
|
||||
home_dotdir.join('.some_dotfile').readlink.should == dotfile
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config/appA' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config/appA']) }
|
||||
it 'can symlink in nested sub directory' do
|
||||
dotdir = castle.directory('.config').directory('appA')
|
||||
dotfile = dotdir.file('.some_dotfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config').join('appA')
|
||||
home_dotdir.symlink?.should be == false
|
||||
home_dotdir.join('.some_dotfile').readlink.should == dotfile
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' and '.config/someapp' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config', '.config/someapp']) }
|
||||
it 'can symlink under both of .config and .config/someapp' do
|
||||
config_dir = castle.directory('.config')
|
||||
config_dotfile = config_dir.file('.some_dotfile')
|
||||
someapp_dir = config_dir.directory('someapp')
|
||||
someapp_dotfile = someapp_dir.file('.some_appfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
|
||||
home_config_dir = home.join('.config')
|
||||
home_someapp_dir = home_config_dir.join('someapp')
|
||||
home_config_dir.symlink?.should be == false
|
||||
home_config_dir.join('.some_dotfile').readlink.should be == config_dotfile
|
||||
home_someapp_dir.symlink?.should be == false
|
||||
home_someapp_dir.join('.some_appfile').readlink.should == someapp_dotfile
|
||||
end
|
||||
end
|
||||
|
||||
context "when call with no castle name" do
|
||||
let(:castle) { given_castle('dotfiles') }
|
||||
it 'using default castle name: "dotfiles"' do
|
||||
dotfile = castle.file('.some_dotfile')
|
||||
|
||||
homesick.symlink
|
||||
|
||||
home.join('.some_dotfile').readlink.should == dotfile
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'unlink' do
|
||||
let(:castle) { given_castle('glencairn') }
|
||||
|
||||
it 'unlinks dotfiles in the home folder' do
|
||||
castle.file('.some_dotfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home.join('.some_dotfile').should_not exist
|
||||
end
|
||||
|
||||
it 'unlinks non-dotfiles from the home folder' do
|
||||
castle.file('bin')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home.join('bin').should_not exist
|
||||
end
|
||||
|
||||
context "with '.config' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config']) }
|
||||
|
||||
it 'can unlink sub directories' do
|
||||
castle.directory('.config').file('.some_dotfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config')
|
||||
home_dotdir.should exist
|
||||
home_dotdir.join('.some_dotfile').should_not exist
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config/appA' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config/appA']) }
|
||||
|
||||
it 'can unsymlink in nested sub directory' do
|
||||
castle.directory('.config').directory('appA').file('.some_dotfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home_dotdir = home.join('.config').join('appA')
|
||||
home_dotdir.should exist
|
||||
home_dotdir.join('.some_dotfile').should_not exist
|
||||
end
|
||||
end
|
||||
|
||||
context "with '.config' and '.config/someapp' in .homesick_subdir" do
|
||||
let(:castle) { given_castle('glencairn', ['.config', '.config/someapp']) }
|
||||
|
||||
it 'can unsymlink under both of .config and .config/someapp' do
|
||||
config_dir = castle.directory('.config')
|
||||
config_dir.file('.some_dotfile')
|
||||
config_dir.directory('someapp').file('.some_appfile')
|
||||
|
||||
homesick.symlink('glencairn')
|
||||
homesick.unlink('glencairn')
|
||||
|
||||
home_config_dir = home.join('.config')
|
||||
home_someapp_dir = home_config_dir.join('someapp')
|
||||
home_config_dir.should exist
|
||||
home_config_dir.join('.some_dotfile').should_not exist
|
||||
home_someapp_dir.should exist
|
||||
home_someapp_dir.join('.some_appfile').should_not exist
|
||||
end
|
||||
end
|
||||
|
||||
context "when call with no castle name" do
|
||||
let(:castle) { given_castle('dotfiles') }
|
||||
|
||||
it 'using default castle name: "dotfiles"' do
|
||||
castle.file('.some_dotfile')
|
||||
|
||||
homesick.symlink
|
||||
homesick.unlink
|
||||
|
||||
home.join('.some_dotfile').should_not exist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'list' do
|
||||
it 'should say each castle in the castle directory' do
|
||||
given_castle('zomg')
|
||||
given_castle('wtf/zomg')
|
||||
|
||||
homesick.should_receive(:say_status).with('zomg', 'git://github.com/technicalpickles/zomg.git', :cyan)
|
||||
homesick.should_receive(:say_status).with('wtf/zomg', 'git://github.com/technicalpickles/zomg.git', :cyan)
|
||||
|
||||
homesick.list
|
||||
end
|
||||
end
|
||||
|
||||
describe 'status' do
|
||||
it 'should say "nothing to commit" when there are no changes' do
|
||||
given_castle('castle_repo')
|
||||
text = Capture.stdout { homesick.status('castle_repo') }
|
||||
text.should =~ /nothing to commit \(create\/copy files and use "git add" to track\)$/
|
||||
end
|
||||
|
||||
it 'should say "Changes to be committed" when there are changes' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
text = Capture.stdout { homesick.status('castle_repo') }
|
||||
text.should =~ /Changes to be committed:.*new file:\s*home\/.some_rc_file/m
|
||||
end
|
||||
end
|
||||
|
||||
describe 'diff' do
|
||||
it 'should output an empty message when there are no changes to commit' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
Capture.stdout { homesick.commit 'castle_repo', 'Adding a file to the test' }
|
||||
text = Capture.stdout { homesick.diff('castle_repo') }
|
||||
text.should eq('')
|
||||
end
|
||||
|
||||
it 'should output a diff message when there are changes to commit' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
Capture.stdout { homesick.commit 'castle_repo', 'Adding a file to the test' }
|
||||
File.open(some_rc_file.to_s, 'w') do |file|
|
||||
file.puts "Some test text"
|
||||
end
|
||||
text = Capture.stdout { homesick.diff('castle_repo') }
|
||||
text.should =~ /diff --git.+Some test text$/m
|
||||
end
|
||||
end
|
||||
|
||||
describe 'show_path' do
|
||||
it 'should say the path of a castle' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
homesick.should_receive(:say).with(castle.dirname)
|
||||
|
||||
homesick.show_path('castle_repo')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'pull' do
|
||||
it 'should perform a pull, submodule init and update when the given castle exists' do
|
||||
given_castle('castle_repo')
|
||||
homesick.stub(:system).once.with('git pull --quiet')
|
||||
homesick.stub(:system).once.with('git submodule --quiet init')
|
||||
homesick.stub(:system).once.with('git submodule --quiet update --init --recursive >/dev/null 2>&1')
|
||||
homesick.pull 'castle_repo'
|
||||
end
|
||||
|
||||
it 'should print an error message when trying to pull a non-existant castle' do
|
||||
homesick.should_receive("say_status").once.with(:error, /Could not pull castle_repo, expected \/tmp\/construct_container.* exist and contain dotfiles/, :red)
|
||||
expect { homesick.pull "castle_repo" }.to raise_error(SystemExit)
|
||||
end
|
||||
|
||||
describe '--all' do
|
||||
it 'should pull each castle when invoked with --all' do
|
||||
given_castle('castle_repo')
|
||||
given_castle('glencairn')
|
||||
homesick.stub(:system).exactly(2).times.with('git pull --quiet')
|
||||
homesick.stub(:system).exactly(2).times.with('git submodule --quiet init')
|
||||
homesick.stub(:system).exactly(2).times.with('git submodule --quiet update --init --recursive >/dev/null 2>&1')
|
||||
Capture.stdout { Capture.stderr { homesick.invoke 'pull', [], all: true } }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'push' do
|
||||
it 'should perform a git push on the given castle' do
|
||||
given_castle('castle_repo')
|
||||
homesick.stub(:system).once.with('git push')
|
||||
homesick.push 'castle_repo'
|
||||
end
|
||||
|
||||
it 'should print an error message when trying to push a non-existant castle' do
|
||||
homesick.should_receive("say_status").once.with(:error, /Could not push castle_repo, expected \/tmp\/construct_container.* exist and contain dotfiles/, :red)
|
||||
expect { homesick.push "castle_repo" }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'track' do
|
||||
it 'should move the tracked file into the castle' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('.some_rc_file')
|
||||
tracked_file.should exist
|
||||
|
||||
some_rc_file.readlink.should == tracked_file
|
||||
end
|
||||
|
||||
it 'should handle files with parens' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_rc_file = home.file 'Default (Linux).sublime-keymap'
|
||||
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('Default (Linux).sublime-keymap')
|
||||
tracked_file.should exist
|
||||
|
||||
some_rc_file.readlink.should == tracked_file
|
||||
end
|
||||
|
||||
it 'should track a file in nested folder structure' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('some/nested/file.txt')
|
||||
tracked_file.should exist
|
||||
some_nested_file.readlink.should == tracked_file
|
||||
end
|
||||
|
||||
it 'should track a nested directory' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_dir = home.directory('some/nested/directory/')
|
||||
homesick.track(some_nested_dir.to_s, 'castle_repo')
|
||||
|
||||
tracked_file = castle.join('some/nested/directory/')
|
||||
tracked_file.should exist
|
||||
some_nested_dir.realpath.should == tracked_file.realpath
|
||||
end
|
||||
|
||||
context "when call with no castle name" do
|
||||
it 'using default castle name: "dotfiles"' do
|
||||
castle = given_castle('dotfiles')
|
||||
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
|
||||
homesick.track(some_rc_file.to_s)
|
||||
|
||||
tracked_file = castle.join('.some_rc_file')
|
||||
tracked_file.should exist
|
||||
|
||||
some_rc_file.readlink.should == tracked_file
|
||||
end
|
||||
end
|
||||
|
||||
describe 'commit' do
|
||||
it 'should have a commit message when the commit succeeds' do
|
||||
given_castle('castle_repo')
|
||||
some_rc_file = home.file '.a_random_rc_file'
|
||||
homesick.track(some_rc_file.to_s, 'castle_repo')
|
||||
text = Capture.stdout { homesick.commit('castle_repo', 'Test message') }
|
||||
text.should =~ /^\[master \(root-commit\) \w+\] Test message/
|
||||
end
|
||||
end
|
||||
|
||||
describe 'subdir_file' do
|
||||
|
||||
it 'should add the nested files parent to the subdir_file' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
f.readline.should == "some/nested\n"
|
||||
end
|
||||
end
|
||||
|
||||
it 'should NOT add anything if the files parent is already listed' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
other_nested_file = home.file('some/nested/other.txt')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
homesick.track(other_nested_file.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
f.readlines.size.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
it 'should remove the parent of a tracked file from the subdir_file if the parent itself is tracked' do
|
||||
castle = given_castle('castle_repo')
|
||||
|
||||
some_nested_file = home.file('some/nested/file.txt')
|
||||
nested_parent = home.directory('some/nested/')
|
||||
homesick.track(some_nested_file.to_s, 'castle_repo')
|
||||
homesick.track(nested_parent.to_s, 'castle_repo')
|
||||
|
||||
subdir_file = castle.parent.join(Homesick::SUBDIR_FILENAME)
|
||||
File.open(subdir_file, 'r') do |f|
|
||||
f.each_line { |line| line.should_not == "some/nested\n" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "destroy" do
|
||||
it "removes the symlink files" do
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).and_return('y')
|
||||
given_castle("stronghold")
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, "stronghold")
|
||||
homesick.destroy('stronghold')
|
||||
|
||||
some_rc_file.should_not be_exist
|
||||
end
|
||||
|
||||
it "deletes the cloned repository" do
|
||||
expect_any_instance_of(Thor::Shell::Basic).to receive(:yes?).and_return('y')
|
||||
castle = given_castle("stronghold")
|
||||
some_rc_file = home.file '.some_rc_file'
|
||||
homesick.track(some_rc_file.to_s, "stronghold")
|
||||
homesick.destroy('stronghold')
|
||||
|
||||
castle.should_not be_exist
|
||||
end
|
||||
end
|
||||
|
||||
describe "cd" do
|
||||
it "cd's to the root directory of the given castle" do
|
||||
given_castle('castle_repo')
|
||||
homesick.should_receive("inside").once.with(kind_of(Pathname)).and_yield
|
||||
homesick.should_receive("system").once.with(ENV["SHELL"])
|
||||
Capture.stdout { homesick.cd 'castle_repo' }
|
||||
end
|
||||
|
||||
it "returns an error message when the given castle does not exist" do
|
||||
homesick.should_receive("say_status").once.with(:error, /Could not cd castle_repo, expected \/tmp\/construct_container.* exist and contain dotfiles/, :red)
|
||||
expect { homesick.cd "castle_repo" }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
describe "open" do
|
||||
it "opens the system default editor in the root of the given castle" do
|
||||
ENV.stub(:[]).and_call_original # Make sure calls to ENV use default values for most things...
|
||||
ENV.stub(:[]).with('EDITOR').and_return('vim') # Set a default value for 'EDITOR' just in case none is set
|
||||
given_castle 'castle_repo'
|
||||
homesick.should_receive("inside").once.with(kind_of(Pathname)).and_yield
|
||||
homesick.should_receive("system").once.with('vim')
|
||||
Capture.stdout { homesick.open 'castle_repo' }
|
||||
end
|
||||
|
||||
it "returns an error message when the $EDITOR environment variable is not set" do
|
||||
ENV.stub(:[]).with('EDITOR').and_return(nil) # Set the default editor to make sure it fails.
|
||||
homesick.should_receive("say_status").once.with(:error, "The $EDITOR environment variable must be set to use this command", :red)
|
||||
expect { homesick.open "castle_repo" }.to raise_error(SystemExit)
|
||||
end
|
||||
|
||||
it "returns an error message when the given castle does not exist" do
|
||||
ENV.stub(:[]).with('EDITOR').and_return('vim') # Set a default just in case none is set
|
||||
homesick.should_receive("say_status").once.with(:error, /Could not open castle_repo, expected \/tmp\/construct_container.* exist and contain dotfiles/, :red)
|
||||
expect { homesick.open "castle_repo" }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'version' do
|
||||
it 'should print the current version of homesick' do
|
||||
text = Capture.stdout { homesick.version }
|
||||
text.chomp.should =~ /\d+\.\d+\.\d+/
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,6 @@
|
||||
require 'coveralls'
|
||||
Coveralls.wear!
|
||||
|
||||
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
||||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||
require 'homesick'
|
||||
@@ -9,12 +12,14 @@ require 'tempfile'
|
||||
RSpec.configure do |config|
|
||||
config.include TestConstruct::Helpers
|
||||
|
||||
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
||||
|
||||
config.before { ENV['HOME'] = home.to_s }
|
||||
|
||||
config.before { silence! }
|
||||
|
||||
def silence!
|
||||
homesick.stub(:say_status)
|
||||
allow(homesick).to receive(:say_status)
|
||||
end
|
||||
|
||||
def given_castle(path, subdirs = [])
|
||||
@@ -28,7 +33,7 @@ RSpec.configure do |config|
|
||||
if subdirs
|
||||
subdir_file = castle.join(Homesick::SUBDIR_FILENAME)
|
||||
subdirs.each do |subdir|
|
||||
system "echo #{subdir} >> #{subdir_file}"
|
||||
File.open(subdir_file, 'a') { |file| file.write "\n#{subdir}\n" }
|
||||
end
|
||||
end
|
||||
return castle.directory('home')
|
||||
|
||||
Reference in New Issue
Block a user