Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a2df591c0 | ||
|
|
4923265dea | ||
|
|
79421580e9 | ||
|
|
cabde9e5f1 | ||
|
|
0d60ae9d1a | ||
|
|
d5317b8e17 | ||
|
|
3b8a5b4be4 | ||
|
|
6590a1eeff | ||
|
|
693ae5f05e | ||
|
|
da3002f199 | ||
|
|
feaaab2fa4 | ||
|
|
59f75711a4 | ||
|
|
f24030b51f | ||
|
|
71bb120a12 | ||
|
|
85f46e01b1 | ||
|
|
c5b24b9b38 | ||
|
|
68460af45e | ||
|
|
5614b6b8b3 | ||
|
|
570b063632 | ||
|
|
1d398587d0 | ||
|
|
085853faaa | ||
|
|
21b4e344a9 | ||
|
|
a6194dfe8b | ||
|
|
5692194fa2 | ||
|
|
11745098c2 | ||
|
|
b1bb0c996c | ||
|
|
a62039da50 | ||
|
|
4bfd1c60c2 | ||
|
|
f0e11abb5b | ||
|
|
ed397bdaf8 | ||
|
|
2f5e20d963 | ||
|
|
cc83a4e1fa | ||
|
|
dcc5cb0bc1 | ||
|
|
978416d1e4 | ||
|
|
1c12c73e4b | ||
|
|
1016002638 | ||
|
|
6431a864ad | ||
|
|
42f661cfbf | ||
|
|
7632591681 | ||
|
|
a9a5b81dc5 | ||
|
|
721c10cffd | ||
|
|
332aad8ad0 | ||
|
|
171b4c1fb8 | ||
|
|
60d4458bbc | ||
|
|
9ad171ab78 | ||
|
|
5918746059 | ||
|
|
4641843ffd | ||
|
|
1a181b907c | ||
|
|
fb7595d254 | ||
|
|
c8f0999035 | ||
|
|
46faec7857 | ||
|
|
e35d3fe6ba | ||
|
|
ba620e0f7f | ||
|
|
5700f55dc3 | ||
|
|
2c92010093 | ||
|
|
03490531d8 | ||
|
|
7bd9759e81 | ||
|
|
a808f56caf | ||
|
|
b7e2b45e69 | ||
|
|
63c45d7c3a | ||
|
|
096067ac62 | ||
|
|
8d6bf4c0c5 | ||
|
|
882b862780 | ||
|
|
e06a5d6300 | ||
|
|
7451e8c739 | ||
|
|
f034f773c5 | ||
|
|
681fd98dc3 | ||
|
|
e57b139e32 | ||
|
|
b64bfe2bb6 | ||
|
|
ee04b5788a | ||
|
|
2e8d431ab5 | ||
|
|
3465c37c0e | ||
|
|
bf6894e313 | ||
|
|
77e3f7f479 | ||
|
|
753f5027b0 | ||
|
|
23c012a527 | ||
|
|
895543641b | ||
|
|
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 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -46,3 +46,8 @@ pkg
|
|||||||
|
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
|
homesick*.gem
|
||||||
|
|
||||||
|
# rbenv configuration
|
||||||
|
.ruby-version
|
||||||
|
|||||||
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,6 @@
|
|||||||
language: ruby
|
language: ruby
|
||||||
rvm:
|
rvm:
|
||||||
- 2.0.0
|
- 2.4.0
|
||||||
- 1.9.3
|
- 2.3.3
|
||||||
|
- 2.2.6
|
||||||
|
sudo: false
|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
|
#1.1.5
|
||||||
|
* Fixed problem with version number being incorrect.
|
||||||
|
|
||||||
|
#1.1.4
|
||||||
|
* Make sure symlink conflicts are explicitly communicated to a user and symlinks are not silently overwritten
|
||||||
|
* Use real paths of symlinks when linking a castle into home
|
||||||
|
* Fix a problem when in a diff when asking a user to resolve a conflict
|
||||||
|
* Some code refactoring and fixes
|
||||||
|
|
||||||
|
#1.1.3
|
||||||
|
* Allow a destination to be passed when cloning a castle
|
||||||
|
* Make sure `homesick edit` opens default editor in the root of the given castle
|
||||||
|
* Fixed bug when diffing edited files
|
||||||
|
* Fixed crashing bug when attempting to diff directories
|
||||||
|
* Ensure that messages are escaped correctly on `git commit all`
|
||||||
|
|
||||||
|
#1.1.2
|
||||||
|
* Added '--force' option to the rc command to bypass confirmation checks when running a .homesickrc file
|
||||||
|
* Added a check to make sure that a minimum of Git 1.8.0 is installed. This stops Homesick failing silently if Git is not installed.
|
||||||
|
* Code refactoring and fixes.
|
||||||
|
|
||||||
|
#1.1.0
|
||||||
|
* Added exec and exec_all commands to run commands inside one or all clones castles.
|
||||||
|
* Code refactoring.
|
||||||
|
|
||||||
#1.0.0
|
#1.0.0
|
||||||
* Removed support for Ruby 1.8.7
|
* Removed support for Ruby 1.8.7
|
||||||
* Added a version command
|
* Added a version command
|
||||||
@@ -30,7 +55,7 @@
|
|||||||
* Introduce .homesick_subdir #39
|
* Introduce .homesick_subdir #39
|
||||||
|
|
||||||
# 0.8.1
|
# 0.8.1
|
||||||
*Fixed `homesick list` bug on ruby 2.0 #37
|
* Fixed `homesick list` bug on ruby 2.0 #37
|
||||||
|
|
||||||
# 0.8.0
|
# 0.8.0
|
||||||
* Introduce commit & push command
|
* Introduce commit & push command
|
||||||
|
|||||||
40
Gemfile
40
Gemfile
@@ -1,26 +1,36 @@
|
|||||||
require 'rbconfig'
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
this_ruby = Gem::Version.new(RUBY_VERSION)
|
||||||
|
ruby_230 = Gem::Version.new('2.3.0')
|
||||||
|
|
||||||
# Add dependencies required to use your gem here.
|
# Add dependencies required to use your gem here.
|
||||||
gem "thor", ">= 0.14.0"
|
gem 'thor', '>= 0.14.0'
|
||||||
|
|
||||||
# Add dependencies to develop your gem here.
|
# Add dependencies to develop your gem here.
|
||||||
# Include everything needed to run rake, tests, features, etc.
|
# Include everything needed to run rake, tests, features, etc.
|
||||||
group :development do
|
group :development do
|
||||||
gem "rake", ">= 0.8.7"
|
gem 'capture-output', '~> 1.0.0'
|
||||||
gem "rspec", "~> 2.10"
|
gem 'coveralls', require: false
|
||||||
gem "guard"
|
gem 'guard'
|
||||||
gem "guard-rspec"
|
gem 'guard-rspec'
|
||||||
gem "rb-readline", "~> 0.5.0"
|
gem 'jeweler', '>= 1.6.2', '< 2.2' if this_ruby < ruby_230
|
||||||
gem "jeweler", ">= 1.6.2"
|
gem 'jeweler', '>= 1.6.2' if this_ruby >= ruby_230
|
||||||
gem "rcov", :platforms => :mri_18
|
gem 'rake', '>= 0.8.7'
|
||||||
gem "simplecov", :platforms => :mri_19
|
gem 'rb-readline', '~> 0.5.0'
|
||||||
gem "test_construct"
|
gem 'rspec', '~> 3.5.0'
|
||||||
gem "capture-output", "~> 1.0.0"
|
gem 'rubocop'
|
||||||
if RbConfig::CONFIG['host_os'] =~ /linux|freebsd|openbsd|sunos|solaris/
|
gem 'test_construct'
|
||||||
|
|
||||||
|
install_if -> { RUBY_PLATFORM =~ /linux|freebsd|openbsd|sunos|solaris/ } do
|
||||||
gem 'libnotify'
|
gem 'libnotify'
|
||||||
end
|
end
|
||||||
if RUBY_VERSION >= '1.9.2'
|
|
||||||
gem "rubocop"
|
install_if -> { RUBY_PLATFORM =~ /darwin/ } do
|
||||||
|
gem 'terminal-notifier-guard', '~> 1.7.0'
|
||||||
|
end
|
||||||
|
|
||||||
|
install_if -> { this_ruby < ruby_230 } do
|
||||||
|
gem 'listen', '< 3'
|
||||||
|
gem 'rack', '< 2'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# homesick
|
# homesick
|
||||||
|
|
||||||
[](http://badge.fury.io/rb/homesick)
|
[](http://badge.fury.io/rb/homesick)
|
||||||
[](https://travis-ci.org/technicalpickles/homesick)
|
[](https://travis-ci.org/technicalpickles/homesick)
|
||||||
[](https://gemnasium.com/technicalpickles/homesick)
|
[](https://gemnasium.com/technicalpickles/homesick)
|
||||||
[](https://codeclimate.com/github/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.
|
Your home directory is your castle. Don't leave your dotfiles behind.
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ If you need to add further configuration steps you can add these in a file calle
|
|||||||
|
|
||||||
homesick rc CASTLE
|
homesick rc CASTLE
|
||||||
|
|
||||||
The contents of the .homesickrc file must be valid Ruby code as the file will be executed with Ruby's eval construct. The .homesickrc is also passed the current homesick object during its execution and this is available within the .homesickrc file as the 'self' variable.
|
The contents of the .homesickrc file must be valid Ruby code as the file will be executed with Ruby's eval construct. The .homesickrc is also passed the current homesick object during its execution and this is available within the .homesickrc file as the 'self' variable. As the rc operation can be destructive the command normally asks for confirmation before proceeding. You can bypass this by passing the '--force' option, for example `homesick rc --force CASTLE`.
|
||||||
|
|
||||||
If you're not sure what castles you have around, you can easily list them:
|
If you're not sure what castles you have around, you can easily list them:
|
||||||
|
|
||||||
@@ -64,6 +66,14 @@ To open your default editor in the root of a castle (the $EDITOR environment var
|
|||||||
|
|
||||||
homesick open CASTLE
|
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:
|
Not sure what else homesick has up its sleeve? There's always the built in help:
|
||||||
|
|
||||||
homesick help
|
homesick help
|
||||||
@@ -154,8 +164,9 @@ and castle
|
|||||||
|
|
||||||
Homesick is tested on the following Ruby versions:
|
Homesick is tested on the following Ruby versions:
|
||||||
|
|
||||||
* 1.9.3
|
* 2.2.6
|
||||||
* 2.0.0
|
* 2.3.3
|
||||||
|
* 2.4.0
|
||||||
|
|
||||||
## Note on Patches/Pull Requests
|
## Note on Patches/Pull Requests
|
||||||
|
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ $LOAD_PATH.unshift lib.to_s
|
|||||||
|
|
||||||
require 'homesick'
|
require 'homesick'
|
||||||
|
|
||||||
Homesick.start
|
Homesick::CLI.start
|
||||||
|
|||||||
121
homesick.gemspec
121
homesick.gemspec
@@ -2,18 +2,19 @@
|
|||||||
# DO NOT EDIT THIS FILE DIRECTLY
|
# DO NOT EDIT THIS FILE DIRECTLY
|
||||||
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# stub: homesick 1.0.0 ruby lib
|
# stub: homesick 1.1.5 ruby lib
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "homesick"
|
s.name = "homesick".freeze
|
||||||
s.version = "1.0.0"
|
s.version = "1.1.5"
|
||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
||||||
s.authors = ["Joshua Nichols", "Yusuke Murata"]
|
s.require_paths = ["lib".freeze]
|
||||||
s.date = "2014-01-16"
|
s.authors = ["Joshua Nichols".freeze, "Yusuke Murata".freeze]
|
||||||
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.date = "2017-03-23"
|
||||||
s.email = ["josh@technicalpickles.com", "info@muratayusuke.com"]
|
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 ".freeze
|
||||||
s.executables = ["homesick"]
|
s.email = ["josh@technicalpickles.com".freeze, "info@muratayusuke.com".freeze]
|
||||||
|
s.executables = ["homesick".freeze]
|
||||||
s.extra_rdoc_files = [
|
s.extra_rdoc_files = [
|
||||||
"ChangeLog.markdown",
|
"ChangeLog.markdown",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
@@ -22,6 +23,7 @@ Gem::Specification.new do |s|
|
|||||||
s.files = [
|
s.files = [
|
||||||
".document",
|
".document",
|
||||||
".rspec",
|
".rspec",
|
||||||
|
".rubocop.yml",
|
||||||
".travis.yml",
|
".travis.yml",
|
||||||
"ChangeLog.markdown",
|
"ChangeLog.markdown",
|
||||||
"Gemfile",
|
"Gemfile",
|
||||||
@@ -32,65 +34,72 @@ Gem::Specification.new do |s|
|
|||||||
"bin/homesick",
|
"bin/homesick",
|
||||||
"homesick.gemspec",
|
"homesick.gemspec",
|
||||||
"lib/homesick.rb",
|
"lib/homesick.rb",
|
||||||
"lib/homesick/actions.rb",
|
"lib/homesick/actions/file_actions.rb",
|
||||||
"lib/homesick/shell.rb",
|
"lib/homesick/actions/git_actions.rb",
|
||||||
|
"lib/homesick/cli.rb",
|
||||||
|
"lib/homesick/utils.rb",
|
||||||
"lib/homesick/version.rb",
|
"lib/homesick/version.rb",
|
||||||
"spec/homesick_spec.rb",
|
"spec/homesick_cli_spec.rb",
|
||||||
"spec/spec.opts",
|
"spec/spec.opts",
|
||||||
"spec/spec_helper.rb"
|
"spec/spec_helper.rb"
|
||||||
]
|
]
|
||||||
s.homepage = "http://github.com/technicalpickles/homesick"
|
s.homepage = "http://github.com/technicalpickles/homesick".freeze
|
||||||
s.licenses = ["MIT"]
|
s.licenses = ["MIT".freeze]
|
||||||
s.require_paths = ["lib"]
|
s.rubygems_version = "2.6.11".freeze
|
||||||
s.rubygems_version = "2.1.11"
|
s.summary = "Your home directory is your castle. Don't leave your dotfiles behind.".freeze
|
||||||
s.summary = "Your home directory is your castle. Don't leave your dotfiles behind."
|
|
||||||
|
|
||||||
if s.respond_to? :specification_version then
|
if s.respond_to? :specification_version then
|
||||||
s.specification_version = 4
|
s.specification_version = 4
|
||||||
|
|
||||||
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
||||||
s.add_runtime_dependency(%q<thor>, [">= 0.14.0"])
|
s.add_runtime_dependency(%q<thor>.freeze, [">= 0.14.0"])
|
||||||
s.add_development_dependency(%q<rake>, [">= 0.8.7"])
|
s.add_development_dependency(%q<capture-output>.freeze, ["~> 1.0.0"])
|
||||||
s.add_development_dependency(%q<rspec>, ["~> 2.10"])
|
s.add_development_dependency(%q<coveralls>.freeze, [">= 0"])
|
||||||
s.add_development_dependency(%q<guard>, [">= 0"])
|
s.add_development_dependency(%q<guard>.freeze, [">= 0"])
|
||||||
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
s.add_development_dependency(%q<guard-rspec>.freeze, [">= 0"])
|
||||||
s.add_development_dependency(%q<rb-readline>, ["~> 0.5.0"])
|
s.add_development_dependency(%q<jeweler>.freeze, [">= 1.6.2"])
|
||||||
s.add_development_dependency(%q<jeweler>, [">= 1.6.2"])
|
s.add_development_dependency(%q<rake>.freeze, [">= 0.8.7"])
|
||||||
s.add_development_dependency(%q<rcov>, [">= 0"])
|
s.add_development_dependency(%q<rb-readline>.freeze, ["~> 0.5.0"])
|
||||||
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
s.add_development_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
|
||||||
s.add_development_dependency(%q<test_construct>, [">= 0"])
|
s.add_development_dependency(%q<rubocop>.freeze, [">= 0"])
|
||||||
s.add_development_dependency(%q<capture-output>, ["~> 1.0.0"])
|
s.add_development_dependency(%q<test_construct>.freeze, [">= 0"])
|
||||||
s.add_development_dependency(%q<libnotify>, [">= 0"])
|
s.add_development_dependency(%q<libnotify>.freeze, [">= 0"])
|
||||||
s.add_development_dependency(%q<rubocop>, [">= 0"])
|
s.add_development_dependency(%q<terminal-notifier-guard>.freeze, ["~> 1.7.0"])
|
||||||
|
s.add_development_dependency(%q<listen>.freeze, ["< 3"])
|
||||||
|
s.add_development_dependency(%q<rack>.freeze, ["< 2"])
|
||||||
else
|
else
|
||||||
s.add_dependency(%q<thor>, [">= 0.14.0"])
|
s.add_dependency(%q<thor>.freeze, [">= 0.14.0"])
|
||||||
s.add_dependency(%q<rake>, [">= 0.8.7"])
|
s.add_dependency(%q<capture-output>.freeze, ["~> 1.0.0"])
|
||||||
s.add_dependency(%q<rspec>, ["~> 2.10"])
|
s.add_dependency(%q<coveralls>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<guard>, [">= 0"])
|
s.add_dependency(%q<guard>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
s.add_dependency(%q<guard-rspec>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<rb-readline>, ["~> 0.5.0"])
|
s.add_dependency(%q<jeweler>.freeze, [">= 1.6.2"])
|
||||||
s.add_dependency(%q<jeweler>, [">= 1.6.2"])
|
s.add_dependency(%q<rake>.freeze, [">= 0.8.7"])
|
||||||
s.add_dependency(%q<rcov>, [">= 0"])
|
s.add_dependency(%q<rb-readline>.freeze, ["~> 0.5.0"])
|
||||||
s.add_dependency(%q<simplecov>, [">= 0"])
|
s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
|
||||||
s.add_dependency(%q<test_construct>, [">= 0"])
|
s.add_dependency(%q<rubocop>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<capture-output>, ["~> 1.0.0"])
|
s.add_dependency(%q<test_construct>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<libnotify>, [">= 0"])
|
s.add_dependency(%q<libnotify>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<rubocop>, [">= 0"])
|
s.add_dependency(%q<terminal-notifier-guard>.freeze, ["~> 1.7.0"])
|
||||||
|
s.add_dependency(%q<listen>.freeze, ["< 3"])
|
||||||
|
s.add_dependency(%q<rack>.freeze, ["< 2"])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
s.add_dependency(%q<thor>, [">= 0.14.0"])
|
s.add_dependency(%q<thor>.freeze, [">= 0.14.0"])
|
||||||
s.add_dependency(%q<rake>, [">= 0.8.7"])
|
s.add_dependency(%q<capture-output>.freeze, ["~> 1.0.0"])
|
||||||
s.add_dependency(%q<rspec>, ["~> 2.10"])
|
s.add_dependency(%q<coveralls>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<guard>, [">= 0"])
|
s.add_dependency(%q<guard>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
s.add_dependency(%q<guard-rspec>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<rb-readline>, ["~> 0.5.0"])
|
s.add_dependency(%q<jeweler>.freeze, [">= 1.6.2"])
|
||||||
s.add_dependency(%q<jeweler>, [">= 1.6.2"])
|
s.add_dependency(%q<rake>.freeze, [">= 0.8.7"])
|
||||||
s.add_dependency(%q<rcov>, [">= 0"])
|
s.add_dependency(%q<rb-readline>.freeze, ["~> 0.5.0"])
|
||||||
s.add_dependency(%q<simplecov>, [">= 0"])
|
s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
|
||||||
s.add_dependency(%q<test_construct>, [">= 0"])
|
s.add_dependency(%q<rubocop>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<capture-output>, ["~> 1.0.0"])
|
s.add_dependency(%q<test_construct>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<libnotify>, [">= 0"])
|
s.add_dependency(%q<libnotify>.freeze, [">= 0"])
|
||||||
s.add_dependency(%q<rubocop>, [">= 0"])
|
s.add_dependency(%q<terminal-notifier-guard>.freeze, ["~> 1.7.0"])
|
||||||
|
s.add_dependency(%q<listen>.freeze, ["< 3"])
|
||||||
|
s.add_dependency(%q<rack>.freeze, ["< 2"])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
452
lib/homesick.rb
452
lib/homesick.rb
@@ -1,450 +1,14 @@
|
|||||||
# -*- encoding : utf-8 -*-
|
# -*- encoding : utf-8 -*-
|
||||||
require 'thor'
|
require 'homesick/actions/file_actions'
|
||||||
|
require 'homesick/actions/git_actions'
|
||||||
|
require 'homesick/version'
|
||||||
|
require 'homesick/utils'
|
||||||
|
require 'homesick/cli'
|
||||||
|
|
||||||
class Homesick < Thor
|
# Homesick's top-level module
|
||||||
autoload :Shell, 'homesick/shell'
|
module Homesick
|
||||||
autoload :Actions, 'homesick/actions'
|
GITHUB_NAME_REPO_PATTERN = %r{\A([A-Za-z0-9_-]+/[A-Za-z0-9_-]+)\Z}
|
||||||
autoload :Version, 'homesick/version'
|
|
||||||
|
|
||||||
include Thor::Actions
|
|
||||||
include Homesick::Actions
|
|
||||||
include Homesick::Version
|
|
||||||
|
|
||||||
add_runtime_options!
|
|
||||||
|
|
||||||
GITHUB_NAME_REPO_PATTERN = /\A([A-Za-z0-9_-]+\/[A-Za-z0-9_-]+)\Z/
|
|
||||||
SUBDIR_FILENAME = '.homesick_subdir'
|
SUBDIR_FILENAME = '.homesick_subdir'
|
||||||
|
|
||||||
DEFAULT_CASTLE_NAME = 'dotfiles'
|
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
|
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
|
|
||||||
87
lib/homesick/actions/file_actions.rb
Normal file
87
lib/homesick/actions/file_actions.rb
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# -*- encoding : utf-8 -*-
|
||||||
|
module Homesick
|
||||||
|
module Actions
|
||||||
|
# File-related helper methods for Homesick
|
||||||
|
module FileActions
|
||||||
|
def mv(source, destination)
|
||||||
|
source = Pathname.new(source)
|
||||||
|
destination = Pathname.new(destination + source.basename)
|
||||||
|
case
|
||||||
|
when destination.exist? && (options[:force] || shell.file_collision(destination) { source })
|
||||||
|
say_status :conflict, "#{destination} exists", :red
|
||||||
|
FileUtils.mv source, destination unless options[:pretend]
|
||||||
|
else
|
||||||
|
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)
|
||||||
|
source = Pathname.new(source).realpath
|
||||||
|
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, :conflict
|
||||||
|
if action == :conflict
|
||||||
|
say_status :conflict, "#{destination} exists", :red
|
||||||
|
else
|
||||||
|
say_status :conflict,
|
||||||
|
"#{destination} exists and points to #{destination.readlink}",
|
||||||
|
:red
|
||||||
|
end
|
||||||
|
if collision_accepted?(destination, source)
|
||||||
|
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
|
||||||
113
lib/homesick/actions/git_actions.rb
Normal file
113
lib/homesick/actions/git_actions.rb
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# -*- encoding : utf-8 -*-
|
||||||
|
module Homesick
|
||||||
|
module Actions
|
||||||
|
# Git-related helper methods for Homesick
|
||||||
|
module GitActions
|
||||||
|
# Information on the minimum git version required for Homesick
|
||||||
|
MIN_VERSION = {
|
||||||
|
major: 1,
|
||||||
|
minor: 8,
|
||||||
|
patch: 0
|
||||||
|
}
|
||||||
|
STRING = MIN_VERSION.values.join('.')
|
||||||
|
|
||||||
|
def git_version_correct?
|
||||||
|
info = `git --version`.scan(/(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
|
||||||
|
return false unless info.count == 3
|
||||||
|
current_version = Hash[[:major, :minor, :patch].zip(info)]
|
||||||
|
return true if current_version.eql?(MIN_VERSION)
|
||||||
|
return true if current_version[:major] > MIN_VERSION[:major]
|
||||||
|
return true if current_version[:major] == MIN_VERSION[:major] && current_version[:minor] > MIN_VERSION[:minor]
|
||||||
|
return true if current_version[:major] == MIN_VERSION[:major] && current_version[:minor] == MIN_VERSION[:minor] && current_version[:patch] >= MIN_VERSION[:patch]
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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.is_a?(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
|
||||||
|
say_status 'git submodule', 'init', :green
|
||||||
|
system 'git submodule --quiet init'
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_submodule_update
|
||||||
|
say_status 'git submodule', 'update', :green
|
||||||
|
system 'git submodule --quiet update --init --recursive >/dev/null 2>&1'
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_pull
|
||||||
|
say_status 'git pull', '', :green
|
||||||
|
system 'git pull --quiet'
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_push
|
||||||
|
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)
|
||||||
|
say_status 'git add file', '', :green
|
||||||
|
system "git add '#{file}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_status
|
||||||
|
say_status 'git status', '', :green
|
||||||
|
system 'git status'
|
||||||
|
end
|
||||||
|
|
||||||
|
def git_diff
|
||||||
|
say_status 'git diff', '', :green
|
||||||
|
system 'git diff'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
330
lib/homesick/cli.rb
Normal file
330
lib/homesick/cli.rb
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
# -*- 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
|
||||||
|
# Check if git is installed
|
||||||
|
unless git_version_correct?
|
||||||
|
say_status :error, "Git version >= #{Homesick::Actions::GitActions::STRING} must be installed to use Homesick", :red
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
# Hack in support for diffing symlinks
|
||||||
|
# Also adds support for checking if destination or content is a directory
|
||||||
|
shell_metaclass = class << shell; self; end
|
||||||
|
shell_metaclass.send(:define_method, :show_diff) do |destination, content|
|
||||||
|
destination = Pathname.new(destination)
|
||||||
|
content = Pathname.new(content)
|
||||||
|
return 'Unable to create diff: destination or content is a directory' if destination.directory? || content.directory?
|
||||||
|
return super(destination, content) unless destination.symlink?
|
||||||
|
say "- #{destination.readlink}", :red, true
|
||||||
|
say "+ #{content.expand_path}", :green, true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'clone URI CASTLE_NAME', 'Clone +uri+ as a castle with name CASTLE_NAME for homesick'
|
||||||
|
def clone(uri, destination=nil)
|
||||||
|
destination = Pathname.new(destination) unless destination.nil?
|
||||||
|
|
||||||
|
inside repos_dir do
|
||||||
|
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 if destination.nil?
|
||||||
|
|
||||||
|
ln_s uri, destination
|
||||||
|
elsif uri =~ GITHUB_NAME_REPO_PATTERN
|
||||||
|
destination = Pathname.new(uri).basename if destination.nil?
|
||||||
|
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].gsub(/\.git$/, '')).basename if destination.nil?
|
||||||
|
git_clone uri, destination: destination
|
||||||
|
else
|
||||||
|
fail "Unknown URI format: #{uri}"
|
||||||
|
end
|
||||||
|
|
||||||
|
setup_castle(destination)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'rc CASTLE', 'Run the .homesickrc for the specified castle'
|
||||||
|
method_option :force,
|
||||||
|
type: :boolean,
|
||||||
|
default: false,
|
||||||
|
desc: 'Evaluate .homesickrc without prompting.'
|
||||||
|
def rc(name = DEFAULT_CASTLE_NAME)
|
||||||
|
inside repos_dir do
|
||||||
|
destination = Pathname.new(name)
|
||||||
|
homesickrc = destination.join('.homesickrc').expand_path
|
||||||
|
return unless homesickrc.exist?
|
||||||
|
proceed = options[:force] || shell.yes?("#{name} has a .homesickrc. Proceed with evaling it? (This could be destructive)")
|
||||||
|
return say_status 'eval skip', "not evaling #{homesickrc}, #{destination} may need manual configuration", :blue unless proceed
|
||||||
|
say_status 'eval', homesickrc
|
||||||
|
inside destination do
|
||||||
|
eval homesickrc.read, binding, homesickrc.expand_path.to_s
|
||||||
|
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,
|
||||||
|
type: :boolean,
|
||||||
|
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'
|
||||||
|
return unless shell.yes?('This will destroy your castle irreversible! Are you sure?')
|
||||||
|
|
||||||
|
unlink(name)
|
||||||
|
rm_rf repos_dir.join(name)
|
||||||
|
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 "#{castle_dir.realpath}: #{ENV['EDITOR']} .",
|
||||||
|
"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,17 +0,0 @@
|
|||||||
class Homesick
|
|
||||||
# Hack in support for diffing symlinks
|
|
||||||
class Shell < Thor::Shell::Color
|
|
||||||
|
|
||||||
def show_diff(destination, content)
|
|
||||||
destination = Pathname.new(destination)
|
|
||||||
|
|
||||||
if destination.symlink?
|
|
||||||
say "- #{destination.readlink}", :red, true
|
|
||||||
say "+ #{content.expand_path}", :green, true
|
|
||||||
else
|
|
||||||
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 -*-
|
||||||
|
require 'pathname'
|
||||||
|
|
||||||
|
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'] || '~').realpath
|
||||||
|
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)
|
||||||
|
return if castle_dir(name).exist?
|
||||||
|
say_status :error,
|
||||||
|
"Could not #{action} #{name}, expected #{castle_dir(name)} exist and contain dotfiles",
|
||||||
|
:red
|
||||||
|
exit(1)
|
||||||
|
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
|
||||||
|
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, source)
|
||||||
|
fail "Arguments must be instances of Pathname, #{destination.class.name} and #{source.class.name} given" unless destination.instance_of?(Pathname) && source.instance_of?(Pathname)
|
||||||
|
options[:force] || shell.file_collision(destination) { File.binread(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 -*-
|
# -*- 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
|
module Version
|
||||||
MAJOR = 1
|
MAJOR = 1
|
||||||
MINOR = 0
|
MINOR = 1
|
||||||
PATCH = 0
|
PATCH = 5
|
||||||
|
|
||||||
STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
|
STRING = [MAJOR, MINOR, PATCH].compact.join('.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
824
spec/homesick_cli_spec.rb
Normal file
824
spec/homesick_cli_spec.rb
Normal file
@@ -0,0 +1,824 @@
|
|||||||
|
# -*- encoding : utf-8 -*-
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'capture-output'
|
||||||
|
require 'pathname'
|
||||||
|
|
||||||
|
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 'smoke tests' do
|
||||||
|
context 'when running bin/homesick' do
|
||||||
|
before do
|
||||||
|
bin_path = Pathname.new(__FILE__).parent.parent
|
||||||
|
@output = `#{bin_path.expand_path}/bin/homesick`
|
||||||
|
end
|
||||||
|
it 'should output some text when bin/homesick is called' do
|
||||||
|
expect(@output.length).to be > 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a git version that doesn\'t meet the minimum required is installed' do
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Homesick::Actions::GitActions).to receive(:`).and_return('git version 1.7.6')
|
||||||
|
end
|
||||||
|
it 'should raise an exception' do
|
||||||
|
output = Capture.stdout { expect { Homesick::CLI.new }.to raise_error SystemExit }
|
||||||
|
expect(output.chomp).to include(Homesick::Actions::GitActions::STRING)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a git version that is the same as the minimum required is installed' do
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Homesick::Actions::GitActions).to receive(:`).at_least(:once).and_return("git version #{Homesick::Actions::GitActions::STRING}")
|
||||||
|
end
|
||||||
|
it 'should not raise an exception' do
|
||||||
|
output = Capture.stdout { expect { Homesick::CLI.new }.not_to raise_error }
|
||||||
|
expect(output.chomp).not_to include(Homesick::Actions::GitActions::STRING)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a git version that is greater than the minimum required is installed' do
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Homesick::Actions::GitActions).to receive(:`).at_least(:once).and_return('git version 3.9.8')
|
||||||
|
end
|
||||||
|
it 'should not raise an exception' do
|
||||||
|
output = Capture.stdout { expect { Homesick::CLI.new }.not_to raise_error }
|
||||||
|
expect(output.chomp).not_to include(Homesick::Actions::GitActions::STRING)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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_truthy
|
||||||
|
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', destination: Pathname.new('pickled-vim'))
|
||||||
|
|
||||||
|
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', destination: Pathname.new('pickled-vim'))
|
||||||
|
|
||||||
|
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', destination: Pathname.new('pickled-vim'))
|
||||||
|
|
||||||
|
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', destination: Pathname.new('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',
|
||||||
|
destination: Pathname.new('pickled-vim'))
|
||||||
|
|
||||||
|
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(RuntimeError)
|
||||||
|
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
|
||||||
|
|
||||||
|
it 'accepts a destination', :focus do
|
||||||
|
expect(homesick).to receive(:git_clone)
|
||||||
|
.with('https://github.com/wfarr/dotfiles.git',
|
||||||
|
destination: Pathname.new('other-name'))
|
||||||
|
|
||||||
|
homesick.clone 'wfarr/dotfiles', 'other-name'
|
||||||
|
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 options[:force] == true' do
|
||||||
|
let(:homesick) { Homesick::CLI.new [], force: true }
|
||||||
|
before do
|
||||||
|
expect_any_instance_of(Thor::Shell::Basic).to_not receive(:yes?)
|
||||||
|
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_castle' 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(%r{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(%r{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(/#{Regexp.escape(Homesick::Version::STRING)}/)
|
||||||
|
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,20 +1,24 @@
|
|||||||
|
require 'coveralls'
|
||||||
|
Coveralls.wear!
|
||||||
|
|
||||||
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
||||||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||||
require 'homesick'
|
require 'homesick'
|
||||||
require 'rspec'
|
require 'rspec'
|
||||||
require 'rspec/autorun'
|
|
||||||
require 'test_construct'
|
require 'test_construct'
|
||||||
require 'tempfile'
|
require 'tempfile'
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.include TestConstruct::Helpers
|
config.include TestConstruct::Helpers
|
||||||
|
|
||||||
|
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
||||||
|
|
||||||
config.before { ENV['HOME'] = home.to_s }
|
config.before { ENV['HOME'] = home.to_s }
|
||||||
|
|
||||||
config.before { silence! }
|
config.before { silence! }
|
||||||
|
|
||||||
def silence!
|
def silence!
|
||||||
homesick.stub(:say_status)
|
allow(homesick).to receive(:say_status)
|
||||||
end
|
end
|
||||||
|
|
||||||
def given_castle(path, subdirs = [])
|
def given_castle(path, subdirs = [])
|
||||||
@@ -28,7 +32,7 @@ RSpec.configure do |config|
|
|||||||
if subdirs
|
if subdirs
|
||||||
subdir_file = castle.join(Homesick::SUBDIR_FILENAME)
|
subdir_file = castle.join(Homesick::SUBDIR_FILENAME)
|
||||||
subdirs.each do |subdir|
|
subdirs.each do |subdir|
|
||||||
system "echo #{subdir} >> #{subdir_file}"
|
File.open(subdir_file, 'a') { |file| file.write "\n#{subdir}\n" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return castle.directory('home')
|
return castle.directory('home')
|
||||||
|
|||||||
Reference in New Issue
Block a user