manifestly 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +77 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/manifestly +14 -0
- data/lib/manifestly.rb +8 -0
- data/lib/manifestly/cli.rb +289 -0
- data/lib/manifestly/manifest.rb +56 -0
- data/lib/manifestly/manifest_item.rb +48 -0
- data/lib/manifestly/manifest_repository.rb +16 -0
- data/lib/manifestly/repository.rb +87 -0
- data/lib/manifestly/ui.rb +73 -0
- data/lib/manifestly/utilities.rb +50 -0
- data/lib/manifestly/version.rb +3 -0
- data/manifestly.gemspec +29 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e99d41748251cf3299fda9c0d9a5175377b9b4dd
|
4
|
+
data.tar.gz: 9bd8f1e0fc81ef7f0ca0ab23d0a1a4fd3f67c4e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cbb2d0025da752056c50b00ab2b0a3eb3c9d39d5db16f1d442bf4423ce21fffd2a403fd063e6d099e25c201101f5971b147794ce2bbe0c73f16b497952e511dc
|
7
|
+
data.tar.gz: 27fe5f795c060428d1a0af2d791c35dd4d9f7fbdbd3deeb4d245a76b0fccd3c36d00307b5fca4ef3dd099470d019590aab0ebcfa2f72c4f7e7f2cfc9d78a10c9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
manifestly
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 JP Slavinsky
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Manifestly
|
2
|
+
|
3
|
+
Manifestly helps you manage complicated deployments involving multiple sites and git repositories.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Manifestly is run as a standalone executable. Install with:
|
8
|
+
|
9
|
+
$ gem install manifestly
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install manifestly
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Manifestly has built in help:
|
22
|
+
|
23
|
+
$ manifestly help
|
24
|
+
|
25
|
+
### create
|
26
|
+
|
27
|
+
To create a new manifest, run
|
28
|
+
|
29
|
+
$ manifestly create
|
30
|
+
|
31
|
+
Both `create` and `apply` take a `--search-paths` option to specify where the repositories of interest can be found. The default search path is the current directory.
|
32
|
+
|
33
|
+
`create` will then show you a blank manifest and give you options to add or remove repositories, or to choose the manifest commit for an existing repository. When repositories are added, their latest commit is listed. All commits seen during manifest creation are local commits only -- this tool does not look up remote commits.
|
34
|
+
|
35
|
+
### push
|
36
|
+
|
37
|
+
To push a manifest file you have created, call:
|
38
|
+
|
39
|
+
$ manifestly push --local=my_file.manifest --mfrepo=myorg/myrepo --remote=foo
|
40
|
+
|
41
|
+
This will take your local file and push it as the latest commit on top of the `foo` file at github.com/myorg/myrepo.
|
42
|
+
|
43
|
+
### pull
|
44
|
+
|
45
|
+
To pull a manifest file... instructions TBD, see built-in help.
|
46
|
+
|
47
|
+
### apply
|
48
|
+
|
49
|
+
To apply a manifest file to your local repositories... instructions TBD, see built-in help.
|
50
|
+
|
51
|
+
$ manifestly apply --file=20150919-210854-2d47.manifest
|
52
|
+
|
53
|
+
### list
|
54
|
+
|
55
|
+
To list available manifest files... instructions TBD, see built-in help
|
56
|
+
|
57
|
+
## Development
|
58
|
+
|
59
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
60
|
+
|
61
|
+
To run manifestly like an end user eventually will, run:
|
62
|
+
|
63
|
+
```
|
64
|
+
exe/manifestly <OPTIONS AND ARGS HERE>
|
65
|
+
```
|
66
|
+
|
67
|
+
Remember to pass the `--search-paths` option to tell manifestly where to find your repositories relative to where you are running the gem from.
|
68
|
+
|
69
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
1. Fork it ( https://github.com/[my-github-username]/manifestly/fork )
|
74
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
75
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
77
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "manifestly"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/manifestly
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'manifestly'
|
9
|
+
rescue LoadError => e
|
10
|
+
warn "Could not load 'manifestly'; #{e.message}"
|
11
|
+
exit -1
|
12
|
+
end
|
13
|
+
|
14
|
+
Manifestly::CLI.start
|
data/lib/manifestly.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
require "thor"
|
2
|
+
require 'rainbow'
|
3
|
+
require 'command_line_reporter'
|
4
|
+
require 'git'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
# TODO make ruby 1.9 compatible
|
8
|
+
|
9
|
+
module Manifestly
|
10
|
+
class CLI < Thor
|
11
|
+
package_name "manifestly"
|
12
|
+
|
13
|
+
include CommandLineReporter
|
14
|
+
include Manifestly::Ui
|
15
|
+
|
16
|
+
def self.search_paths_option
|
17
|
+
method_option :search_paths,
|
18
|
+
:desc => "A list of paths where git repositories can be found",
|
19
|
+
:type => :array,
|
20
|
+
:required => false,
|
21
|
+
:default => '.'
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "create", "Create a new manifest"
|
25
|
+
search_paths_option
|
26
|
+
method_option :based_on,
|
27
|
+
:desc => "A manifest file to use as a starting point",
|
28
|
+
:type => :string,
|
29
|
+
:required => false
|
30
|
+
def create
|
31
|
+
manifest = if options[:based_on]
|
32
|
+
Manifest.read(options[:based_on], available_repositories)
|
33
|
+
else
|
34
|
+
Manifest.new
|
35
|
+
end
|
36
|
+
|
37
|
+
present_manifest_menu(manifest)
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "apply", "Sets the manifest's repository's current states to the commits listed in the manifest"
|
41
|
+
search_paths_option
|
42
|
+
method_option :file,
|
43
|
+
:desc => "The manifest file to apply",
|
44
|
+
:type => :string,
|
45
|
+
:required => true
|
46
|
+
def apply
|
47
|
+
manifest = Manifest.read(options[:file], available_repositories)
|
48
|
+
manifest.items.each(&:checkout_commit!)
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "push", "Pushes a local manifest file to a manifest repository"
|
52
|
+
method_option :local,
|
53
|
+
:desc => 'The local manifest file to push',
|
54
|
+
:type => :string,
|
55
|
+
:required => true
|
56
|
+
method_option :mfrepo,
|
57
|
+
:desc => "The repository to push to (full URL or 'organization/reponame')",
|
58
|
+
:type => :string,
|
59
|
+
:required => true
|
60
|
+
method_option :remote,
|
61
|
+
:desc => "The name of the remote file",
|
62
|
+
:type => :string,
|
63
|
+
:required => true
|
64
|
+
def push
|
65
|
+
say("Not yet implemented.")
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "pull", "Downloads a manifest file from a manifest repository"
|
69
|
+
method_option :sha,
|
70
|
+
:desc => "The commit SHA of the manifest on the remote repository",
|
71
|
+
:type => :string,
|
72
|
+
:required => true
|
73
|
+
method_option :mfrepo,
|
74
|
+
:desc => "The manifest repository to pull from (full URL or 'organization/reponame')",
|
75
|
+
:type => :string,
|
76
|
+
:required => true
|
77
|
+
method_option :save_as,
|
78
|
+
:desc => "The name to use for the downloaded file (defaults to '<SHA>.manifest')",
|
79
|
+
:type => :string,
|
80
|
+
:required => false
|
81
|
+
def pull
|
82
|
+
say("Not yet implemented.")
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "list", "Lists manifests from a manifest repository"
|
86
|
+
method_option :mfrepo,
|
87
|
+
:desc => "The manifest repository to read from (full URL or 'organization/reponame')",
|
88
|
+
:type => :string,
|
89
|
+
:required => true
|
90
|
+
method_option :remote,
|
91
|
+
:desc => "The name of the manifest to read from",
|
92
|
+
:type => :string,
|
93
|
+
:required => true
|
94
|
+
def list
|
95
|
+
say("Not yet implemented.")
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
def present_manifest_menu(manifest)
|
101
|
+
while true
|
102
|
+
print_manifest(manifest)
|
103
|
+
|
104
|
+
action, args = ask_and_split(
|
105
|
+
'(a)dd or (r)emove repository; (c)hoose commit; (w)rite manifest; (q)uit:'
|
106
|
+
) || next
|
107
|
+
|
108
|
+
case action
|
109
|
+
when 'a'
|
110
|
+
add_repositories(manifest)
|
111
|
+
when 'r'
|
112
|
+
indices = convert_args_to_indices(args) || next
|
113
|
+
manifest.remove_repositories_by_index(indices)
|
114
|
+
when 'c'
|
115
|
+
indices = convert_args_to_indices(args, true) || next
|
116
|
+
present_commit_menu(manifest[indices.first])
|
117
|
+
when 'w'
|
118
|
+
default_filename = Time.now.strftime("%Y%m%d-%H%M%S") + "-#{::SecureRandom.hex(2)}.manifest"
|
119
|
+
filename = ask("Enter desired manifest filename (ENTER for '#{default_filename}'):")
|
120
|
+
filename = default_filename if filename.blank?
|
121
|
+
|
122
|
+
manifest.write(filename)
|
123
|
+
break
|
124
|
+
when 'q'
|
125
|
+
break if yes?('Are you sure you want to quit? (y or yes):')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def present_commit_menu(manifest_item)
|
131
|
+
while true
|
132
|
+
print_commit_shas(manifest_item)
|
133
|
+
|
134
|
+
action, args = ask_and_split(
|
135
|
+
'(n)ext or (p)revious page; (c)hoose index; (m)anual SHA entry; (t)oggle PRs only; (r)eturn:'
|
136
|
+
) || next
|
137
|
+
|
138
|
+
case action
|
139
|
+
when 'n'
|
140
|
+
manifest_item.repository.next_page_of_commits
|
141
|
+
when 'p'
|
142
|
+
manifest_item.repository.prev_page_of_commits
|
143
|
+
when 'c'
|
144
|
+
indices = convert_args_to_indices(args, true) || next
|
145
|
+
manifest_item.set_commit_by_index(indices.first)
|
146
|
+
break
|
147
|
+
when 'm'
|
148
|
+
sha = args.first
|
149
|
+
begin
|
150
|
+
manifest_item.set_commit_by_sha(sha)
|
151
|
+
break
|
152
|
+
rescue CommitNotPresent
|
153
|
+
say('That SHA is invalid')
|
154
|
+
next
|
155
|
+
end
|
156
|
+
when 't'
|
157
|
+
manifest_item.repository.toggle_prs_only
|
158
|
+
when 'r'
|
159
|
+
break
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def ask_and_split(message)
|
165
|
+
answer = ask(message).downcase.split
|
166
|
+
|
167
|
+
if answer.empty?
|
168
|
+
say('No response provided, please try again.')
|
169
|
+
return false
|
170
|
+
end
|
171
|
+
|
172
|
+
[answer.shift, answer]
|
173
|
+
end
|
174
|
+
|
175
|
+
def convert_args_to_indices(args, select_one=false)
|
176
|
+
if args.empty?
|
177
|
+
say('You must specify index(es) of manifest items after the action, e.g. "r 2 7".')
|
178
|
+
false
|
179
|
+
elsif select_one && args.size > 1
|
180
|
+
say('You can only specify one manifest index for this action.')
|
181
|
+
false
|
182
|
+
elsif args.any? {|si| !si.is_i?}
|
183
|
+
say('All specified indices must be integers.')
|
184
|
+
false
|
185
|
+
else
|
186
|
+
args.collect{|index| index.to_i}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def add_repositories(manifest)
|
191
|
+
puts "\n"
|
192
|
+
selected_repositories = select(
|
193
|
+
repository_choices,
|
194
|
+
hide_shortcuts: true,
|
195
|
+
choice_name: "repository",
|
196
|
+
question: "\nChoose which repositories you want in the manifest (e.g. '0 2 5'):"
|
197
|
+
)
|
198
|
+
|
199
|
+
selected_repositories.each do |repository|
|
200
|
+
manifest.add_repository(repository)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def print_manifest(manifest)
|
205
|
+
puts "\n"
|
206
|
+
puts "Current Manifest:\n"
|
207
|
+
puts "\n"
|
208
|
+
table :border => false do
|
209
|
+
row :header => true do
|
210
|
+
column "#", width: 4
|
211
|
+
column "Repository", width: 36
|
212
|
+
column "Branch", width: 30
|
213
|
+
column "SHA", width: 10
|
214
|
+
end
|
215
|
+
|
216
|
+
if manifest.empty?
|
217
|
+
row do
|
218
|
+
column ""
|
219
|
+
column "----- EMPTY -----"
|
220
|
+
end
|
221
|
+
else
|
222
|
+
manifest.items.each_with_index do |item, index|
|
223
|
+
row do
|
224
|
+
column "(#{index})", align: 'right', width: 4
|
225
|
+
column "#{item.repository_name}", width: 40
|
226
|
+
column "#{item.repository.current_branch_name}"
|
227
|
+
column "#{item.commit.sha[0..9]}", width: 10
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
puts "\n"
|
233
|
+
end
|
234
|
+
|
235
|
+
def print_commit_shas(manifest_item)
|
236
|
+
puts "\n"
|
237
|
+
puts "Commit SHAs for #{manifest_item.repository_name}:\n"
|
238
|
+
puts "\n"
|
239
|
+
table :border => true do
|
240
|
+
row :header => true do
|
241
|
+
column "#", width: 4
|
242
|
+
column "SHA", width: 10
|
243
|
+
column "Message", width: 54
|
244
|
+
column "Date", width: 25
|
245
|
+
end
|
246
|
+
|
247
|
+
manifest_item.repository.commits.each_with_index do |commit, index|
|
248
|
+
row do
|
249
|
+
column "#{index}", align: 'right'
|
250
|
+
column "#{commit.sha[0..9]}"
|
251
|
+
column "#{summarize_commit_message(commit)}"
|
252
|
+
column "#{commit.date}"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
puts "\n"
|
257
|
+
end
|
258
|
+
|
259
|
+
def summarize_commit_message(commit)
|
260
|
+
commit.message
|
261
|
+
.gsub(/Merge pull request (#\w+)( from [\w-]+\/[\w-]+)/, 'PR \1')
|
262
|
+
.gsub("\n",' ')
|
263
|
+
.gsub(/\s+/, ' ')[0..79]
|
264
|
+
end
|
265
|
+
|
266
|
+
def repository_choices
|
267
|
+
available_repositories.collect{|repo| {display: repo.github_name || repo.working_dir, value: repo}}
|
268
|
+
end
|
269
|
+
|
270
|
+
def available_repositories
|
271
|
+
@available_repositories ||= (repository_search_paths.flat_map do |path|
|
272
|
+
directories_under(path).collect{|dir| Manifestly::Repository.load(dir)}
|
273
|
+
end).compact
|
274
|
+
end
|
275
|
+
|
276
|
+
def directories_under(path)
|
277
|
+
entries = Dir.entries(path)
|
278
|
+
entries.reject!{ |dir| dir =='.' || dir == '..' }
|
279
|
+
|
280
|
+
full_entry_paths = entries.collect{|entry| File.join(path, entry)}
|
281
|
+
full_entry_paths.reject{ |path| !File.directory?(path) }
|
282
|
+
end
|
283
|
+
|
284
|
+
def repository_search_paths
|
285
|
+
[options[:search_paths]].flatten
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require_relative './manifest_item'
|
2
|
+
|
3
|
+
module Manifestly
|
4
|
+
class Manifest
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
@items = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_repository(repository)
|
11
|
+
@items.push(Manifestly::ManifestItem.new(repository))
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_item(manifest_item)
|
15
|
+
@items.push(manifest_item)
|
16
|
+
end
|
17
|
+
|
18
|
+
def remove_repositories_by_index(indices)
|
19
|
+
indices.each{|index| @items[index] = nil}
|
20
|
+
@items.compact!
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](index)
|
25
|
+
@items[index]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.read(filename, repositories)
|
29
|
+
manifest = Manifest.new
|
30
|
+
|
31
|
+
File.open(filename, 'r') do |file|
|
32
|
+
file.each_line do |line|
|
33
|
+
item = ManifestItem.from_file_string(line, repositories)
|
34
|
+
manifest.add_item(item)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
manifest
|
39
|
+
end
|
40
|
+
|
41
|
+
def write(filename)
|
42
|
+
File.open(filename, 'w') do |file|
|
43
|
+
@items.each do |item|
|
44
|
+
file.write(item.to_file_string + "\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :items
|
50
|
+
|
51
|
+
def empty?
|
52
|
+
items.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Manifestly
|
2
|
+
class ManifestItem
|
3
|
+
|
4
|
+
class InvalidManifestItem < StandardError; end
|
5
|
+
class RepositoryNotFound < StandardError; end
|
6
|
+
|
7
|
+
def initialize(repository)
|
8
|
+
@repository = repository
|
9
|
+
@commit = repository.commits.first
|
10
|
+
end
|
11
|
+
|
12
|
+
def repository_name
|
13
|
+
@repository.github_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_commit_by_index(index)
|
17
|
+
@commit = @repository.commits[index]
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_commit_by_sha(sha)
|
21
|
+
@commit = @repository.find_commit(sha)
|
22
|
+
end
|
23
|
+
|
24
|
+
def checkout_commit!
|
25
|
+
@repository.checkout_commit(@commit.sha)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_file_string
|
29
|
+
"#{repository_name} @ #{commit.sha}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.from_file_string(string, repositories)
|
33
|
+
repo_name, sha = string.split('@').collect(&:strip)
|
34
|
+
raise(InvalidManifestItem, string) if repo_name.blank? || sha.blank?
|
35
|
+
|
36
|
+
repository = repositories.select{|repo| repo.github_name == repo_name}.first
|
37
|
+
raise(RepositoryNotFound, repo_name) if repository.nil?
|
38
|
+
|
39
|
+
item = ManifestItem.new(repository)
|
40
|
+
item.set_commit_by_sha(sha)
|
41
|
+
item
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :commit
|
45
|
+
attr_reader :repository
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class Manifestly::Repository
|
2
|
+
|
3
|
+
class CommitNotPresent < StandardError; end
|
4
|
+
|
5
|
+
# Returns an object if can load a git repository at the specified path,
|
6
|
+
# otherwise nil
|
7
|
+
def self.load(path)
|
8
|
+
repository = new(path)
|
9
|
+
repository.is_git_repository? ? repository : nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def is_git_repository?
|
13
|
+
begin
|
14
|
+
git
|
15
|
+
rescue ArgumentError
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def git
|
21
|
+
Git.open(@path)
|
22
|
+
end
|
23
|
+
|
24
|
+
COMMITS_PER_PAGE = 15
|
25
|
+
|
26
|
+
def commits
|
27
|
+
log = git.log(COMMITS_PER_PAGE)
|
28
|
+
log = log.grep("Merge pull request") if @prs_only
|
29
|
+
log.skip(@commit_page * COMMITS_PER_PAGE)
|
30
|
+
end
|
31
|
+
|
32
|
+
def next_page_of_commits
|
33
|
+
@commit_page += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def prev_page_of_commits
|
37
|
+
@commit_page -= 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset_page_of_commits
|
41
|
+
@commit_page = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
# returns the commit matching the provided sha or raises
|
45
|
+
def find_commit(sha)
|
46
|
+
begin
|
47
|
+
git.gcommit(sha).tap(&:sha)
|
48
|
+
rescue Git::GitExecuteError => e
|
49
|
+
raise CommitNotPresent, "SHA not found: #{sha}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def checkout_commit(sha)
|
54
|
+
git.checkout(sha)
|
55
|
+
end
|
56
|
+
|
57
|
+
def current_branch_name
|
58
|
+
git.lib.branch_current
|
59
|
+
end
|
60
|
+
|
61
|
+
def toggle_prs_only
|
62
|
+
@commit_page = 0
|
63
|
+
@prs_only = !@prs_only
|
64
|
+
end
|
65
|
+
|
66
|
+
def origin
|
67
|
+
@origin ||= git.remotes.select{|remote| remote.name == 'origin'}.first
|
68
|
+
end
|
69
|
+
|
70
|
+
def github_name
|
71
|
+
return nil if origin.nil?
|
72
|
+
origin.url[/github.com.(.*).git/,1]
|
73
|
+
end
|
74
|
+
|
75
|
+
def working_dir
|
76
|
+
git.dir
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def initialize(path)
|
82
|
+
@path = path
|
83
|
+
@commit_page = 0
|
84
|
+
@prs_only = false
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Manifestly
|
2
|
+
module Ui
|
3
|
+
|
4
|
+
def Ui.included(base)
|
5
|
+
|
6
|
+
def select(choices, options={})
|
7
|
+
|
8
|
+
options[:hide_choices] ||= false
|
9
|
+
options[:hide_shortcuts] ||= false
|
10
|
+
options[:select_one] ||= false
|
11
|
+
options[:choice_name] ||= 'item'
|
12
|
+
options[:inputs] ||= []
|
13
|
+
options[:question] ||= "Please choose an item:" # make this dependent on choice_name and select_one
|
14
|
+
options[:hide_all_choice] ||= false
|
15
|
+
|
16
|
+
if !options[:hide_all_choice]
|
17
|
+
choices.push(display: 'All', shortcut: 'all', value: choices.collect{|c| c[:value]})
|
18
|
+
end
|
19
|
+
|
20
|
+
selections = []
|
21
|
+
|
22
|
+
if !options[:inputs].empty?
|
23
|
+
selections = (choices.select do |choice|
|
24
|
+
options[:inputs].any? do |input|
|
25
|
+
input.downcase.starts_with?(choice[:shortcut].downcase)
|
26
|
+
end
|
27
|
+
end).collect{|choice| choice[:value]}
|
28
|
+
|
29
|
+
say Rainbow("The input '#{options[:inputs].join(' ')}' did not match any choices.").red if selections.empty?
|
30
|
+
else
|
31
|
+
if !options[:hide_choices]
|
32
|
+
table :border => false do
|
33
|
+
row :header => true do
|
34
|
+
column "", width: 4
|
35
|
+
column options[:choice_name].capitalize, width: 40
|
36
|
+
column "Shortcut", width:10 unless options[:hide_shortcuts]
|
37
|
+
end
|
38
|
+
choices.each_with_index do |choice, index|
|
39
|
+
row do
|
40
|
+
column "(#{index})", align: 'right', width: 4
|
41
|
+
column "#{choice[:display]}", width: 40
|
42
|
+
column "#{choice[:shortcut] || 'n/a'}", width: 10 unless options[:hide_shortcuts]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
selected_indices = ask(options[:question]).split(" ")
|
49
|
+
|
50
|
+
if selected_indices.length != 1 && options[:select_one]
|
51
|
+
say Rainbow("Please choose only one #{options[:choice_name]}! (or CTRL + C to exit)").red
|
52
|
+
options[:hide_choices] = true
|
53
|
+
selections = select(choices, question, options)
|
54
|
+
elsif selected_indices.any? {|si| !si.is_i?}
|
55
|
+
say Rainbow("Please enter a number or numbers separated by spaces! (or CTRL + C to exit)").red
|
56
|
+
options[:hide_choices] = true
|
57
|
+
selections = select(choices, question, options)
|
58
|
+
elsif selected_indices.empty?
|
59
|
+
say Rainbow("Please choose at least one #{options[:choice_name]}! (or CTRL + C to exit)").red
|
60
|
+
selections = select(choices, question, options)
|
61
|
+
else
|
62
|
+
selections = selected_indices.collect{|si| choices[si.to_i][:value]}.flatten
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
return selections.flatten
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Hash
|
2
|
+
def get_deep(*fields)
|
3
|
+
fields.inject(self) {|acc,e| acc[e] if acc}
|
4
|
+
end
|
5
|
+
|
6
|
+
def except!(*keys)
|
7
|
+
keys.each { |key| delete(key) }
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def except(*keys)
|
12
|
+
dup.except!(*keys)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class String
|
17
|
+
def is_i?
|
18
|
+
!!(self =~ /\A[-+]?[0-9]+\z/)
|
19
|
+
end
|
20
|
+
|
21
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
22
|
+
|
23
|
+
def blank?
|
24
|
+
BLANK_RE === self
|
25
|
+
end
|
26
|
+
|
27
|
+
def starts_with?(prefix)
|
28
|
+
prefix.respond_to?(:to_str) && self[0, prefix.length] == prefix
|
29
|
+
end
|
30
|
+
|
31
|
+
def wrap(width=78)
|
32
|
+
gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class NilClass
|
37
|
+
def blank?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Display
|
43
|
+
def self.tut(message)
|
44
|
+
Rainbow(message).bright.blue
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.prompt(message)
|
48
|
+
" #{tut('> ' + message)}"
|
49
|
+
end
|
50
|
+
end
|
data/manifestly.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'manifestly/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "manifestly"
|
8
|
+
spec.version = Manifestly::VERSION
|
9
|
+
spec.authors = ["JP Slavinsky"]
|
10
|
+
spec.email = ["jps@kindlinglabs.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Manage multi-site deploy manifests}
|
13
|
+
spec.homepage = "https://github.com/openstax/manifestly"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'thor'
|
22
|
+
spec.add_dependency 'rainbow'
|
23
|
+
spec.add_dependency 'command_line_reporter'
|
24
|
+
spec.add_dependency 'git'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'byebug'
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: manifestly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JP Slavinsky
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rainbow
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: command_line_reporter
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: git
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.9'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '10.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '10.0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- jps@kindlinglabs.com
|
114
|
+
executables:
|
115
|
+
- manifestly
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- ".rspec"
|
121
|
+
- ".ruby-gemset"
|
122
|
+
- ".ruby-version"
|
123
|
+
- ".travis.yml"
|
124
|
+
- Gemfile
|
125
|
+
- LICENSE.txt
|
126
|
+
- README.md
|
127
|
+
- Rakefile
|
128
|
+
- bin/console
|
129
|
+
- bin/setup
|
130
|
+
- exe/manifestly
|
131
|
+
- lib/manifestly.rb
|
132
|
+
- lib/manifestly/cli.rb
|
133
|
+
- lib/manifestly/manifest.rb
|
134
|
+
- lib/manifestly/manifest_item.rb
|
135
|
+
- lib/manifestly/manifest_repository.rb
|
136
|
+
- lib/manifestly/repository.rb
|
137
|
+
- lib/manifestly/ui.rb
|
138
|
+
- lib/manifestly/utilities.rb
|
139
|
+
- lib/manifestly/version.rb
|
140
|
+
- manifestly.gemspec
|
141
|
+
homepage: https://github.com/openstax/manifestly
|
142
|
+
licenses:
|
143
|
+
- MIT
|
144
|
+
metadata: {}
|
145
|
+
post_install_message:
|
146
|
+
rdoc_options: []
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 2.4.8
|
162
|
+
signing_key:
|
163
|
+
specification_version: 4
|
164
|
+
summary: Manage multi-site deploy manifests
|
165
|
+
test_files: []
|