dotty 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|