dotty 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +110 -0
- data/Rakefile +2 -0
- data/bin/dotty +5 -0
- data/dotty.gemspec +27 -0
- data/lib/dotty/app.rb +150 -0
- data/lib/dotty/helpers.rb +19 -0
- data/lib/dotty/profile.rb +57 -0
- data/lib/dotty/repository.rb +141 -0
- data/lib/dotty/repository_actions.rb +110 -0
- data/lib/dotty/version.rb +3 -0
- data/lib/dotty.rb +18 -0
- data/spec/app_spec.rb +328 -0
- data/spec/profile_spec.rb +188 -0
- data/spec/repository_actions_spec.rb +204 -0
- data/spec/repository_spec.rb +303 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/support/shared_contexts.rb +61 -0
- data/templates/README.md +41 -0
- metadata +124 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Dotty
|
2
|
+
|
3
|
+
Dotty is a command line tool for managing your dotfiles (written in Ruby, using [Thor](https://github.com/wycats/thor/))
|
4
|
+
|
5
|
+
## Features
|
6
|
+
* Store dotfiles in multiple git repositories
|
7
|
+
* Automate symlinking and other bootstrapping of dotfile repositories
|
8
|
+
* Support profiles so that it's easy to switch between sets of dotfiles
|
9
|
+
* Shows uncommited changes / unpushed commits for your dotfile repos
|
10
|
+
* Easily update submodules in your dotfile repos
|
11
|
+
|
12
|
+
## How dotty interacts with dotty repositories
|
13
|
+
|
14
|
+
By using one or more of the approaches below, you instruct dotty on how it can boostrap and implode (opposite of bootstrap) your dotty repository.
|
15
|
+
|
16
|
+
### dotfiles/
|
17
|
+
|
18
|
+
Dotty will symlink files and directories in the root your repos dotfiles/ directory, relative to ~.
|
19
|
+
You can symlink stuff to sub directories of ~ by using the in+subdir directory naming convention.
|
20
|
+
|
21
|
+
#### Example
|
22
|
+
|
23
|
+
dotfiles/.vim => ~/.vim
|
24
|
+
dotfiles/in+.ssh/config => ~/.ssh/config
|
25
|
+
dotfiles/in+a/in+b/c => ~/a/b/c
|
26
|
+
|
27
|
+
|
28
|
+
### dotty-symlinks.yml
|
29
|
+
|
30
|
+
If you want more control over the symlinking, you can create a dotty-symlink.yml in the repo root.
|
31
|
+
|
32
|
+
#### Example
|
33
|
+
|
34
|
+
file_in_repo:filename_in_home_dir
|
35
|
+
|
36
|
+
### dotty-repository.thor
|
37
|
+
|
38
|
+
If you want to do more than symlinking, you can create a dotty-repository.thor that implements the 'bootstrap' and 'implode' thor tasks.
|
39
|
+
The class must be named "DottyRepository".
|
40
|
+
|
41
|
+
#### Example
|
42
|
+
|
43
|
+
class DottyRepository < Thor
|
44
|
+
include Thor::Actions
|
45
|
+
|
46
|
+
desc "bootstrap", "Bootstrap this repo"
|
47
|
+
def bootstrap
|
48
|
+
# Do stuff here
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "implode", "Implode this repo"
|
52
|
+
def implode
|
53
|
+
# Do stuff here
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
## Installation / usage
|
58
|
+
|
59
|
+
gem install dotty
|
60
|
+
|
61
|
+
### Requirements
|
62
|
+
* ruby (tested on 1.9.2) and rubygems
|
63
|
+
* git (the git executable must be in your $PATH)
|
64
|
+
|
65
|
+
Note: I had graphviz installed via brew, which has a 'dotty' executable. I fixed this by doing "mv /usr/local/bin/dotty{,.graphviz}"
|
66
|
+
|
67
|
+
$ dotty
|
68
|
+
|
69
|
+
Tasks:
|
70
|
+
dotty add <name> <git repo url> # Add existing dotty git repository
|
71
|
+
dotty bootstrap [name] # Bootstrap specified or all dotty repositories. Usually involves making symlinks in your home dir.
|
72
|
+
dotty create <name> <git repo url> # Create a new git repository with the specified git repo url as origin
|
73
|
+
dotty create_profile <profile name> # Create a new profile
|
74
|
+
dotty execute [repo name] <command to run> # For specified or all repositories, run given command
|
75
|
+
dotty help [TASK] # Describe available tasks or one specific task
|
76
|
+
dotty implode [name] # Opposite of bootstrap
|
77
|
+
dotty import_repos <yaml_file_location> # Imports dotty repositories from the specified yaml file location (http works)
|
78
|
+
dotty list # List installed dotty repositories
|
79
|
+
dotty profile [profile name] # Switch to given profile or show current profile if no profile name is given
|
80
|
+
dotty profiles # List profiles
|
81
|
+
dotty remove <name> # Remove dotty repository
|
82
|
+
dotty remove_profile <profile name> # Remove given profile
|
83
|
+
dotty update [name] # Update specified or all dotty repositories
|
84
|
+
dotty update_submodules [name] # For specified or all repositories, for submodules and pull
|
85
|
+
|
86
|
+
### Creating an example dotty repository
|
87
|
+
|
88
|
+
$ dotty create dotty-test git@github.com:trym/dotty-test
|
89
|
+
|
90
|
+
create repo dotty-test [git@github.com:trym/dotty-test]
|
91
|
+
run git init /Users/trym/.dotty/default/dotty-test from "."
|
92
|
+
Initialized empty Git repository in /Users/trym/.dotty/default/dotty-test/.git/
|
93
|
+
run git remote add origin git@github.com:trym/dotty-test from "./.dotty/default/dotty-test"
|
94
|
+
create .dotty/default/dotty-test/dotfiles
|
95
|
+
create .dotty/default/dotty-test/README.md
|
96
|
+
|
97
|
+
$ cd ~/.dotty/default/dotty-test
|
98
|
+
$ touch dotfiles/testfile
|
99
|
+
$ dotty bootstrap dotty-test
|
100
|
+
bootstrap dotty-test
|
101
|
+
create sers/trym/testfile
|
102
|
+
|
103
|
+
$ ls -al ~/testfile
|
104
|
+
lrwxr-xr-x 1 trym staff 55 May 3 01:05 /Users/trym/testfile -> /Users/trym/.dotty/default/dotty-test/dotfiles/testfile
|
105
|
+
|
106
|
+
$ dotty implode dotty-test
|
107
|
+
implode dotty-test
|
108
|
+
remove /Users/trym/testfile
|
109
|
+
|
110
|
+
|
data/Rakefile
ADDED
data/bin/dotty
ADDED
data/dotty.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'dotty/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'dotty'
|
7
|
+
s.version = Dotty::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Trym Skaar']
|
10
|
+
s.email = ['trym@tryms.no']
|
11
|
+
s.homepage = 'http://github.com/trym/dotty'
|
12
|
+
s.summary = %q{Dotfile manager using git repositories}
|
13
|
+
s.description = %q{Command line tool for easily managing your dotfile git repositories}
|
14
|
+
|
15
|
+
s.rubyforge_project = "dotty"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_dependency 'thor'
|
23
|
+
s.add_dependency 'hashie'
|
24
|
+
|
25
|
+
s.add_development_dependency 'rspec', '>= 2.6.0.rc4'
|
26
|
+
s.add_development_dependency 'simplecov', '>= 0.4.0'
|
27
|
+
end
|
data/lib/dotty/app.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
module Dotty
|
2
|
+
class App < Thor
|
3
|
+
include Thor::Actions
|
4
|
+
include Dotty::Helpers
|
5
|
+
|
6
|
+
ROOT_PATH = File.expand_path(File.join Thor::Util.user_home, '.dotty')
|
7
|
+
source_root File.join(File.dirname(__FILE__), '../../templates')
|
8
|
+
|
9
|
+
desc "list", "List installed dotty repositories"
|
10
|
+
def list
|
11
|
+
say "Installed dotty repositories for profile '#{Profile::current_profile}'", :blue
|
12
|
+
say "\n"
|
13
|
+
if Repository.list.empty?
|
14
|
+
say "No repositories here. Use 'create', 'add' or 'import_repos' to get going.", :yellow
|
15
|
+
else
|
16
|
+
table = Repository.list.collect do |repo|
|
17
|
+
git_changes = repo.git_status.size > 0 ? shell.set_color(" [#{repo.git_status.size} uncomitted changes]", :red) : ""
|
18
|
+
git_changes += shell.set_color(" [unpushed commits]", :red) if repo.unpushed_changes?
|
19
|
+
[repo.name, repo.url + git_changes]
|
20
|
+
end
|
21
|
+
print_table table, :ident => 2, :colwidth => 20
|
22
|
+
end
|
23
|
+
say "\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "add <name> <git repo url>", "Add existing dotty git repository"
|
27
|
+
def add(repo_name, url)
|
28
|
+
Repository.add_existing_repository repo_name, url
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "create <name> <git repo url>", "Create a new git repository with the specified git repo url as origin"
|
32
|
+
def create(repo_name, repo_url)
|
33
|
+
repo = Dotty::Repository.create_repository(repo_name, repo_url)
|
34
|
+
empty_directory File.join(repo.local_path, 'dotfiles')
|
35
|
+
copy_file "README.md", File.join(repo.local_path, 'README.md')
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "remove <name>", "Remove dotty repository"
|
39
|
+
def remove(repo_name)
|
40
|
+
find_repo!(repo_name).destroy
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "update [name]", "Update specified or all dotty repositories"
|
44
|
+
def update(repo_name=nil)
|
45
|
+
for_specified_or_all_repos(repo_name) do |repo|
|
46
|
+
actions.update repo
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "bootstrap [name]", "Bootstrap specified or all dotty repositories. Usually involves making symlinks in your home dir."
|
51
|
+
def bootstrap(repo_name=nil)
|
52
|
+
for_specified_or_all_repos(repo_name) do |repo|
|
53
|
+
actions.bootstrap repo
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "implode [name]", "Opposite of bootstrap"
|
58
|
+
def implode(repo_name=nil)
|
59
|
+
for_specified_or_all_repos(repo_name) do |repo|
|
60
|
+
actions.implode repo
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "update_submodules [name]", "For specified or all repositories, for submodules and pull"
|
65
|
+
method_options :push => false
|
66
|
+
method_options :commit => true
|
67
|
+
method_options :ignoredirty => false
|
68
|
+
def update_submodules(repo_name=nil)
|
69
|
+
for_specified_or_all_repos(repo_name) do |repo|
|
70
|
+
actions.invoke :update_submodules, [repo], options
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "execute [repo name] <command to run>", "For specified or all repositories, run given command"
|
75
|
+
def execute(repo_name=nil, command)
|
76
|
+
for_specified_or_all_repos(repo_name) do |repo|
|
77
|
+
inside repo.local_path do
|
78
|
+
run command
|
79
|
+
say "\n"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "import_repos <yaml_file_location>", "Imports dotty repositories from the specified yaml file location (http works)"
|
85
|
+
def import_repos(location)
|
86
|
+
say_status "import yaml", "Importing dotty repositories from '#{location}'", :blue
|
87
|
+
Repository.import location
|
88
|
+
end
|
89
|
+
|
90
|
+
desc "profile [profile name]", "Switch to given profile or show current profile if no profile name is given"
|
91
|
+
def profile(name=nil)
|
92
|
+
if name.nil?
|
93
|
+
say "Current dotty profile: " + shell.set_color(Profile.current_profile, :blue)
|
94
|
+
else
|
95
|
+
name = name.downcase
|
96
|
+
Profile.find!(name)
|
97
|
+
say_status "profile", "Changing to profile '#{name}'"
|
98
|
+
implode # Implode all repos for current profile
|
99
|
+
Repository.repositories = nil # Force reload of repositories
|
100
|
+
Profile.current_profile = name
|
101
|
+
Profile.write_yaml
|
102
|
+
bootstrap # Bootstrap repos for the profile we changed to
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
desc "profiles", "List profiles"
|
107
|
+
def profiles
|
108
|
+
say "DOTTY PROFILES\n", :blue
|
109
|
+
profile_names = Profile.profiles
|
110
|
+
profile_names << 'default' if profile_names.empty?
|
111
|
+
profile_names.each do |profile_name|
|
112
|
+
with_padding do
|
113
|
+
say Profile.current_profile == profile_name ? shell.set_color('* ' + profile_name, :green) : profile_name
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "create_profile <profile name>", "Create a new profile"
|
119
|
+
def create_profile(name)
|
120
|
+
say_status "create", "Create profile '#{name}'"
|
121
|
+
Profile.create name
|
122
|
+
end
|
123
|
+
|
124
|
+
desc "remove_profile <profile name>", "Remove given profile"
|
125
|
+
def remove_profile(name)
|
126
|
+
say_status "remove", "Removing profile '#{name}'"
|
127
|
+
Profile.remove name
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
def find_repo!(name)
|
133
|
+
Repository.find(name.downcase) || raise(RepositoryNotFoundError, "The specified repository does not exist")
|
134
|
+
end
|
135
|
+
|
136
|
+
def actions
|
137
|
+
@actions ||= RepositoryActions.new
|
138
|
+
end
|
139
|
+
|
140
|
+
def for_specified_or_all_repos(repo_name=nil)
|
141
|
+
if repo_name
|
142
|
+
yield find_repo!(repo_name.downcase)
|
143
|
+
else
|
144
|
+
Repository.list.each { |repo| yield repo }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dotty
|
2
|
+
module Helpers
|
3
|
+
def error(message, halt=true)
|
4
|
+
exit if halt
|
5
|
+
end
|
6
|
+
|
7
|
+
def remove_symlink(path)
|
8
|
+
if File.exist? path
|
9
|
+
say_status "remove", path
|
10
|
+
if File.symlink? path
|
11
|
+
File.delete path
|
12
|
+
else
|
13
|
+
say_status "error", "#{path} is not a symlink - not removing"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Dotty
|
2
|
+
class Profile
|
3
|
+
YAML_PATH = File.join(App::ROOT_PATH, '.profiles.yml')
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :current_profile, :profile_data
|
7
|
+
|
8
|
+
def create(name)
|
9
|
+
profile_data['profiles'].merge!(name => {})
|
10
|
+
write_yaml
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove(name)
|
14
|
+
find!(name)
|
15
|
+
@current_profile = profile_data['current_profile'] = nil
|
16
|
+
profile_data['profiles'].delete name
|
17
|
+
write_yaml
|
18
|
+
end
|
19
|
+
|
20
|
+
def profile_data
|
21
|
+
@profile_data ||= read_yaml || { 'profiles' => {} }
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_profile
|
25
|
+
@current_profile ||= profile_data['current_profile'] || profiles.first || 'default'
|
26
|
+
end
|
27
|
+
|
28
|
+
def profiles
|
29
|
+
profile_data['profiles'] && profile_data['profiles'].keys or []
|
30
|
+
end
|
31
|
+
|
32
|
+
def current_profile_data
|
33
|
+
profile_data['profiles'][current_profile] or {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_yaml
|
37
|
+
File.exist?(YAML_PATH) && YAML::load(File.open YAML_PATH)
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_yaml
|
41
|
+
profile_data['current_profile'] = current_profile
|
42
|
+
(profile_data['profiles'][current_profile] ||= {}).merge!(
|
43
|
+
'repositories' => Repository.list.inject({}) { |hsh, repo| hsh.merge(repo.name => { 'url' => repo.url }) }
|
44
|
+
)
|
45
|
+
FileUtils.mkdir_p App::ROOT_PATH unless File.directory?(App::ROOT_PATH)
|
46
|
+
File.open(YAML_PATH, 'w') do |f|
|
47
|
+
f.write profile_data.to_yaml
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def find!(name)
|
52
|
+
profile_data['profiles'] && profile_data['profiles'][name] or raise Dotty::Error, "Profile '#{name}' does not exist"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Dotty
|
2
|
+
class Repository
|
3
|
+
YAML_PATH = File.join(App::ROOT_PATH, '.repos.yml')
|
4
|
+
|
5
|
+
attr_accessor :name, :url
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :repositories
|
9
|
+
|
10
|
+
def add_existing_repository(name, url)
|
11
|
+
repo = add_repository(name, url)
|
12
|
+
actions.checkout(repo)
|
13
|
+
actions.bootstrap(repo)
|
14
|
+
return repo
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_repository(name, url)
|
18
|
+
unless Repository.find(name).nil?
|
19
|
+
raise Dotty::Error, "There is already a repository with that name"
|
20
|
+
end
|
21
|
+
repo = self.new(name, url)
|
22
|
+
list << repo
|
23
|
+
Profile::write_yaml
|
24
|
+
return repo
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_repository(name, url)
|
28
|
+
repo = add_repository(name, url)
|
29
|
+
actions.create(repo)
|
30
|
+
return repo
|
31
|
+
end
|
32
|
+
|
33
|
+
def list
|
34
|
+
@repositories ||= (Profile.current_profile_data['repositories'] || {}).collect{ |name, options| Repository.new(name, options['url']) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def find(name)
|
38
|
+
list.detect{ |repo| repo.name == name }
|
39
|
+
end
|
40
|
+
|
41
|
+
def actions
|
42
|
+
@actions ||= Dotty::RepositoryActions.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def import(location)
|
46
|
+
repositories = YAML::load(open(location).read) || {}
|
47
|
+
if repositories.empty?
|
48
|
+
error "Didn't find any repositories at '#{location}'"
|
49
|
+
end
|
50
|
+
repositories.keys.each do |name|
|
51
|
+
if find(name)
|
52
|
+
puts "Not adding '#{name}', a repository with that name already exists"
|
53
|
+
next
|
54
|
+
end
|
55
|
+
add_existing_repository(name, repositories[name]['url'])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(name, url)
|
62
|
+
unless name =~ /^[a-zA-Z0-9\.\-\_]+$/
|
63
|
+
raise InvalidRepositoryNameError, "Repository name can only contain letters, numbers and the following characters: .-_"
|
64
|
+
end
|
65
|
+
@name = name
|
66
|
+
@url = url
|
67
|
+
end
|
68
|
+
|
69
|
+
def local_path
|
70
|
+
File.join(Dotty::App::ROOT_PATH, Profile::current_profile, @name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def symlinks
|
74
|
+
symlinks_from_dotfiles_directories.merge symlinks_from_yaml
|
75
|
+
end
|
76
|
+
|
77
|
+
def symlinks_from_yaml
|
78
|
+
symlink_file_path = File.join(local_path, 'dotty-symlinks.yml')
|
79
|
+
File.exist?(symlink_file_path) && YAML::load(File.open symlink_file_path) or {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def symlinks_from_dotfiles_directories
|
83
|
+
hsh = {}
|
84
|
+
Dir.glob(File.join local_path, '*dotfiles/') do |dotfiles_directory|
|
85
|
+
dotfiles_directory = Pathname.new(dotfiles_directory)
|
86
|
+
|
87
|
+
# 'root' files
|
88
|
+
dotfiles_directory.children.each do |path|
|
89
|
+
next if path.basename.to_s =~ /^in\+/
|
90
|
+
source = path.relative_path_from(Pathname.new local_path).to_s
|
91
|
+
target = path.relative_path_from(dotfiles_directory).to_s
|
92
|
+
hsh.merge!(source => target)
|
93
|
+
end
|
94
|
+
|
95
|
+
# stuff in in+XXX directories
|
96
|
+
Dir.glob(File.join(dotfiles_directory.to_s , 'in+*/**/*'), File::FNM_DOTMATCH).each do |path|
|
97
|
+
next if path =~ %r([\.]{2}) or path =~ %r(/\.$) # Remove BS entries like dotfiles/../otherstuff TODO: do it in a better way
|
98
|
+
path = Pathname.new(path)
|
99
|
+
next if path.basename.to_s =~ /^in\+/ # We dont want to do anything with the 'placeholder' directories
|
100
|
+
source = path.relative_path_from(Pathname.new local_path).to_s
|
101
|
+
target = path.relative_path_from(Pathname.new dotfiles_directory).to_s.gsub('in+', '')
|
102
|
+
hsh.merge!(source => target)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
return hsh
|
106
|
+
end
|
107
|
+
|
108
|
+
def git_status
|
109
|
+
# TODO use --json when more people have a git that supports it
|
110
|
+
git_status_output.split("\n").inject({}) do |hsh, line|
|
111
|
+
change_type, file = line[0..1], line[3..-1]
|
112
|
+
hsh.merge(file => change_type)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def unpushed_changes?
|
117
|
+
txt = 'Your branch is ahead of'
|
118
|
+
`cd #{local_path} && git status | grep '#{txt}' 2>&1`.include?(txt)
|
119
|
+
end
|
120
|
+
|
121
|
+
def git_status_output
|
122
|
+
`cd #{local_path} && git status --porcelain`
|
123
|
+
end
|
124
|
+
|
125
|
+
def has_thor_task?
|
126
|
+
File.exist? File.join(local_path, 'dotty-repository.thor')
|
127
|
+
end
|
128
|
+
|
129
|
+
def invoke_action(action, *args)
|
130
|
+
self.class.actions.invoke action, args
|
131
|
+
end
|
132
|
+
|
133
|
+
def destroy
|
134
|
+
Repository.repositories.reject! { |repo| repo.name == name }
|
135
|
+
Dotty::Profile.write_yaml
|
136
|
+
Repository.actions.destroy self
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Dotty
|
2
|
+
class RepositoryActions < Thor
|
3
|
+
USER_HOME = Thor::Util.user_home
|
4
|
+
|
5
|
+
include Thor::Actions
|
6
|
+
include Dotty::Helpers
|
7
|
+
|
8
|
+
desc "create <repo instance>", "create repository"
|
9
|
+
def create(repo)
|
10
|
+
say_status "create repo", "#{repo.name} [#{repo.url}]", :blue
|
11
|
+
FileUtils.mkdir_p repo.local_path
|
12
|
+
run "git init #{repo.local_path}"
|
13
|
+
inside repo.local_path do
|
14
|
+
run "git remote add origin #{repo.url}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "destroy <repo instance>", "remove repository"
|
19
|
+
def destroy(repo)
|
20
|
+
say_status "remove repo", repo.name, :blue
|
21
|
+
implode repo
|
22
|
+
say_status "remove", repo.local_path
|
23
|
+
FileUtils.rm_rf repo.local_path
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "checkout <repo instance>", "add repository"
|
27
|
+
def checkout(repo)
|
28
|
+
say_status "add repo", "#{repo.name} => #{repo.url}", :blue
|
29
|
+
FileUtils.mkdir_p repo.local_path
|
30
|
+
run "git clone #{repo.url} #{repo.local_path}"
|
31
|
+
inside repo.local_path do
|
32
|
+
run "git submodule update --init"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "bootstrap <repo instance>", "bootstrap repository"
|
37
|
+
def bootstrap(repo)
|
38
|
+
say_status "bootstrap", repo.name, :blue
|
39
|
+
with_padding do
|
40
|
+
# Create symlinks defined in dotty-symlinks.yml
|
41
|
+
repo.symlinks.each do |source, destination_filename|
|
42
|
+
create_link File.join(USER_HOME, destination_filename), File.join(repo.local_path, source)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Execute repository's bootstrap task
|
46
|
+
thor_file_path = File.join(repo.local_path, 'dotty-repository.thor')
|
47
|
+
if File.exist? thor_file_path
|
48
|
+
inside repo.local_path do
|
49
|
+
run "thor dotty_repository:bootstrap"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "implode <repo instance>", "implode repository"
|
56
|
+
def implode(repo)
|
57
|
+
say_status "implode", repo.name, :blue
|
58
|
+
|
59
|
+
# Remove symlinks defined in dotty-symlinks.yml
|
60
|
+
repo.symlinks.each do |source, destination_filename|
|
61
|
+
destination_path = File.join(USER_HOME, destination_filename)
|
62
|
+
remove_symlink destination_path
|
63
|
+
end
|
64
|
+
|
65
|
+
# Execute repository's implode task
|
66
|
+
thor_file_path = File.join(repo.local_path, 'dotty-repository.thor')
|
67
|
+
if File.exist? thor_file_path
|
68
|
+
inside repo.local_path do
|
69
|
+
run "thor dotty_repository:implode"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "update <repo instance>", "update repository"
|
75
|
+
def update(repo)
|
76
|
+
say_status "update", repo.name, :blue
|
77
|
+
|
78
|
+
inside repo.local_path do
|
79
|
+
run "git fetch && git pull && git submodule update --init"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "update_submodules", "update repository's submodules"
|
84
|
+
method_options :ignoredirty => false
|
85
|
+
method_options %w(commit_message -m) => "Updated submodules"
|
86
|
+
method_options :commit => true
|
87
|
+
method_options :push => false
|
88
|
+
def update_submodules(repo)
|
89
|
+
say "update submodules", repo.name, :blue
|
90
|
+
|
91
|
+
inside repo.local_path do
|
92
|
+
cmd = []
|
93
|
+
cmd << "git submodule update --init"
|
94
|
+
cmd << "git submodule foreach git pull origin master"
|
95
|
+
if options[:commit]
|
96
|
+
changes = run('git status -s', :capture => true)
|
97
|
+
if options[:ignoredirty] or [nil, ""].include?(changes)
|
98
|
+
cmd << "git commit -am '#{options[:commit_message]}'"
|
99
|
+
else
|
100
|
+
raise Dotty::Error, "Repository '#{repo.name}' is not in a clean state - cannot commit updated submodules"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
options[:push] && cmd << "git push"
|
104
|
+
run cmd.join(" && ")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
data/lib/dotty.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'hashie'
|
5
|
+
require 'thor'
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
require 'dotty/helpers'
|
9
|
+
require 'dotty/app'
|
10
|
+
require 'dotty/profile'
|
11
|
+
require 'dotty/repository'
|
12
|
+
require 'dotty/repository_actions'
|
13
|
+
|
14
|
+
module Dotty
|
15
|
+
class Error < StandardError; end
|
16
|
+
class RepositoryNotFoundError < Error; end
|
17
|
+
class InvalidRepositoryNameError < Error; end
|
18
|
+
end
|