manifestly 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/lib/manifestly.rb +1 -0
- data/lib/manifestly/cli.rb +310 -79
- data/lib/manifestly/diff.rb +56 -0
- data/lib/manifestly/manifest.rb +4 -0
- data/lib/manifestly/manifest_item.rb +11 -4
- data/lib/manifestly/repository.rb +139 -65
- data/lib/manifestly/ui.rb +6 -2
- data/lib/manifestly/version.rb +1 -1
- data/manifestly.gemspec +2 -0
- metadata +31 -3
- data/lib/manifestly/manifest_repository.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27e444dcd8fa28ababac67f3b65cc111962ce5ca
|
4
|
+
data.tar.gz: 5320806e0e66db13b621fa82e929a52189184940
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38e852ab6e864f55fbb592ff32dd6051c3acc9c69c9767bb5fb654b273477b78525d4cab563d846c34ac49ca9e8ed5d6a0b29875b3b1b3e313db605e665cf06a
|
7
|
+
data.tar.gz: bf0b841d7e45b285a0719c412a8b9b896f9d6bc2fa909e0223ba6bcc8a8f12d16e52774bd7837e32c37dad4e217fde86c898cdab8480545cd9556ef3fb43831c
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/lib/manifestly.rb
CHANGED
data/lib/manifestly/cli.rb
CHANGED
@@ -3,8 +3,7 @@ require 'rainbow'
|
|
3
3
|
require 'command_line_reporter'
|
4
4
|
require 'git'
|
5
5
|
require 'securerandom'
|
6
|
-
|
7
|
-
# TODO make ruby 1.9 compatible
|
6
|
+
require 'ruby-progressbar'
|
8
7
|
|
9
8
|
module Manifestly
|
10
9
|
class CLI < Thor
|
@@ -13,96 +12,223 @@ module Manifestly
|
|
13
12
|
include CommandLineReporter
|
14
13
|
include Manifestly::Ui
|
15
14
|
|
15
|
+
#
|
16
|
+
# Common command line options
|
17
|
+
#
|
18
|
+
|
16
19
|
def self.search_paths_option
|
17
20
|
method_option :search_paths,
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
21
|
+
desc: "A list of paths where git repositories can be found",
|
22
|
+
type: :array,
|
23
|
+
required: false,
|
24
|
+
banner: '',
|
25
|
+
default: '.'
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.repo_option
|
29
|
+
method_option :repo,
|
30
|
+
desc: "The github manifest repository to use, given as 'organization/reponame'",
|
31
|
+
type: :string,
|
32
|
+
banner: '',
|
33
|
+
required: true
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.repo_file_option(description=nil)
|
37
|
+
description ||= "The name of the repository file, with path if applicable"
|
38
|
+
method_option :repo_file,
|
39
|
+
desc: description,
|
40
|
+
type: :string,
|
41
|
+
banner: '',
|
42
|
+
required: true
|
22
43
|
end
|
23
44
|
|
45
|
+
def self.file_option(description_action=nil)
|
46
|
+
description = "The local manifest file"
|
47
|
+
description += " to #{description_action}" if description_action
|
48
|
+
|
49
|
+
method_option :file,
|
50
|
+
desc: description,
|
51
|
+
type: :string,
|
52
|
+
banner: '',
|
53
|
+
required: true
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Command-line actions
|
58
|
+
#
|
59
|
+
|
24
60
|
desc "create", "Create a new manifest"
|
25
61
|
search_paths_option
|
26
62
|
method_option :based_on,
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
63
|
+
desc: "A manifest file to use as a starting point",
|
64
|
+
type: :string,
|
65
|
+
banner: '',
|
66
|
+
required: false
|
67
|
+
long_desc <<-DESC
|
68
|
+
Interactively create a manifest file, either from scratch or using
|
69
|
+
an exisitng manifest as a starting point.
|
70
|
+
|
71
|
+
When run, the current manifest will be shown and you will have the
|
72
|
+
following options:
|
73
|
+
|
74
|
+
(a)dd repository - entering 'a' will show you a list of repositories
|
75
|
+
that can be added. Select multiple by index on this chooser screen.
|
76
|
+
|
77
|
+
(r)emove repository - entering 'r' followed by a manifest index will
|
78
|
+
remove that repository from the manifest, e.g. 'r 3'
|
79
|
+
|
80
|
+
(f)etch - entering 'f' will fetch the contents of the manifest repos.
|
81
|
+
The selectable commits will only include those that have been fetched.
|
82
|
+
|
83
|
+
(c)hoose commit - entering 'c' followed by a manifest index will show
|
84
|
+
you a screen where you can choose the manifest commit for that repo.
|
85
|
+
(see details below)
|
86
|
+
|
87
|
+
(w)rite manifest - entering 'w' will prompt you for a name to use when
|
88
|
+
writing out the manifest to disk.
|
89
|
+
|
90
|
+
(q)uit - entering 'q' exits with a "are you sure?" prompt. 'q!' exits
|
91
|
+
immediately.
|
92
|
+
|
93
|
+
When choosing commits,
|
94
|
+
|
95
|
+
(n)ext page / (p)revious page - entering 'n' or 'p' will page through commits.
|
96
|
+
|
97
|
+
(c)hoose index - entering 'c' followed by a table index chooses that commit,
|
98
|
+
e.g. 'c 12'
|
99
|
+
|
100
|
+
(m)anual SHA entry - entering 'm' followed by a SHA fragment selects that
|
101
|
+
commit, e.g. 'm 8623e'
|
102
|
+
|
103
|
+
(t)oggle PRs only - entering 't' toggles filtering by PR commits only
|
104
|
+
|
105
|
+
(r)eturn - entering 'r' returns to the previous menu
|
106
|
+
|
107
|
+
Examples:
|
108
|
+
|
109
|
+
$ manifestly create\x5
|
110
|
+
Create manifest from scratch with the default search path
|
111
|
+
|
112
|
+
$ manifestly create --search_paths=..\x5
|
113
|
+
Create manifest looking for repositories one dir up
|
114
|
+
|
115
|
+
$ manifestly create --based_on=~my.manifest --search_paths=~jim/repos\x5
|
116
|
+
Create manifest starting from an existing one
|
117
|
+
DESC
|
30
118
|
def create
|
31
119
|
manifest = if options[:based_on]
|
32
|
-
|
120
|
+
read_manifest(options[:based_on]) || return
|
33
121
|
else
|
34
122
|
Manifest.new
|
35
123
|
end
|
36
124
|
|
37
|
-
|
125
|
+
present_create_menu(manifest)
|
38
126
|
end
|
39
127
|
|
40
128
|
desc "apply", "Sets the manifest's repository's current states to the commits listed in the manifest"
|
41
129
|
search_paths_option
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
130
|
+
file_option("apply")
|
131
|
+
long_desc <<-DESC
|
132
|
+
Check to make sure the repositories you are deploying from have their state committed.
|
133
|
+
DESC
|
46
134
|
def apply
|
47
|
-
|
135
|
+
begin
|
136
|
+
manifest = read_manifest(options[:file]) || return
|
137
|
+
rescue Manifestly::ManifestItem::MultipleSameNameRepositories => e
|
138
|
+
say "Multiple repositories have the same name (#{e.message}) so we " +
|
139
|
+
"can't apply the manifest. Try limiting the search_paths or " +
|
140
|
+
"separate the duplicates."
|
141
|
+
return
|
142
|
+
end
|
48
143
|
manifest.items.each(&:checkout_commit!)
|
49
144
|
end
|
50
145
|
|
51
|
-
desc "
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
:
|
58
|
-
:
|
59
|
-
:
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
146
|
+
desc "upload", "Upload a local manifest file to a manifest repository"
|
147
|
+
file_option("upload")
|
148
|
+
repo_option
|
149
|
+
repo_file_option("The name of the manifest to upload to in the repository, with path if applicable")
|
150
|
+
method_option :message,
|
151
|
+
desc: "A message to permanently record with this manifest",
|
152
|
+
type: :string,
|
153
|
+
banner: '',
|
154
|
+
required: true
|
155
|
+
long_desc <<-DESC
|
156
|
+
Upload a manifest when you want to share it with others or persist it
|
157
|
+
permanently. Since manifests are stored remotely as versions of a file
|
158
|
+
in git, it cannot be changed once uploaded.
|
159
|
+
DESC
|
160
|
+
def upload
|
161
|
+
repository = Repository.load_cached(options[:repo], update: true)
|
162
|
+
|
163
|
+
begin
|
164
|
+
repository.push_file!(options[:file], options[:repo_file], options[:message])
|
165
|
+
rescue Manifestly::Repository::ManifestUnchanged => e
|
166
|
+
say "The manifest you requested to push is already the latest and so could not be pushed."
|
167
|
+
end
|
66
168
|
end
|
67
169
|
|
68
|
-
desc "
|
170
|
+
desc "download", "Downloads a manifest file from a manifest repository"
|
69
171
|
method_option :sha,
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
73
|
-
|
74
|
-
|
75
|
-
:type => :string,
|
76
|
-
:required => true
|
172
|
+
desc: "The commit SHA of the manifest on the remote repository",
|
173
|
+
type: :string,
|
174
|
+
banner: '',
|
175
|
+
required: true
|
176
|
+
repo_option
|
77
177
|
method_option :save_as,
|
78
|
-
:
|
79
|
-
:
|
80
|
-
:
|
81
|
-
|
82
|
-
|
178
|
+
desc: "The name to use for the downloaded file (defaults to '<SHA>.manifest')",
|
179
|
+
type: :string,
|
180
|
+
banner: '',
|
181
|
+
required: false
|
182
|
+
long_desc <<-DESC
|
183
|
+
You must have a copy of a manifest locally to `apply` it to your local
|
184
|
+
repositories. A local copy is also useful when creating a new manifest
|
185
|
+
based on an existing one.
|
186
|
+
DESC
|
187
|
+
def download
|
188
|
+
repository = Repository.load_cached(options[:repo], update: true)
|
189
|
+
|
190
|
+
commit_content = begin
|
191
|
+
repository.get_commit_content(options[:sha])
|
192
|
+
rescue Manifestly::Repository::CommitNotPresent
|
193
|
+
say('That SHA is invalid')
|
194
|
+
return
|
195
|
+
end
|
196
|
+
|
197
|
+
save_as = options[:save_as]
|
198
|
+
|
199
|
+
if save_as.nil?
|
200
|
+
# Get the whole SHA so filenames are consistent
|
201
|
+
sha = repository.find_commit(options[:sha]).sha
|
202
|
+
save_as = "#{sha[0..9]}.manifest"
|
203
|
+
end
|
204
|
+
|
205
|
+
File.open(save_as, 'w') { |file| file.write(commit_content) }
|
206
|
+
say "Downloaded #{save_as}. #{commit_content.split("\n").count} line(s)."
|
83
207
|
end
|
84
208
|
|
85
|
-
desc "list", "Lists
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
209
|
+
desc "list", "Lists variants of one manifest from a manifest repository"
|
210
|
+
repo_option
|
211
|
+
repo_file_option("list variants of")
|
212
|
+
long_desc <<-DESC
|
213
|
+
Right now you can't do much other than see the versions of a manifest.
|
214
|
+
(Versions of a manifest are just revisions of the file in a git history,
|
215
|
+
which is why the versions are identified by SHA hashes.). Later we can
|
216
|
+
add the ability to download, apply, or create directly from the listing.
|
217
|
+
DESC
|
94
218
|
def list
|
95
|
-
|
219
|
+
repository = Repository.load_cached(options[:repo], update: true)
|
220
|
+
commits = repository.file_commits(options[:repo_file])
|
221
|
+
present_list_menu(commits, show_author: true)
|
96
222
|
end
|
97
223
|
|
98
224
|
protected
|
99
225
|
|
100
|
-
def
|
226
|
+
def present_create_menu(manifest)
|
101
227
|
while true
|
102
228
|
print_manifest(manifest)
|
103
229
|
|
104
230
|
action, args = ask_and_split(
|
105
|
-
'(a)dd or (r)emove repository; (c)hoose commit; (w)rite manifest; (q)uit:'
|
231
|
+
'(a)dd or (r)emove repository; (f)etch; (c)hoose commit; (w)rite manifest; (q)uit:'
|
106
232
|
) || next
|
107
233
|
|
108
234
|
case action
|
@@ -111,6 +237,12 @@ module Manifestly
|
|
111
237
|
when 'r'
|
112
238
|
indices = convert_args_to_indices(args) || next
|
113
239
|
manifest.remove_repositories_by_index(indices)
|
240
|
+
when 'f'
|
241
|
+
progress = ProgressBar.create(title: "Fetching", total: manifest.items.count)
|
242
|
+
manifest.items.each do |item|
|
243
|
+
item.fetch
|
244
|
+
progress.increment
|
245
|
+
end
|
114
246
|
when 'c'
|
115
247
|
indices = convert_args_to_indices(args, true) || next
|
116
248
|
present_commit_menu(manifest[indices.first])
|
@@ -121,15 +253,20 @@ module Manifestly
|
|
121
253
|
|
122
254
|
manifest.write(filename)
|
123
255
|
break
|
256
|
+
when 'q!'
|
257
|
+
break
|
124
258
|
when 'q'
|
125
259
|
break if yes?('Are you sure you want to quit? (y or yes):')
|
126
260
|
end
|
127
261
|
end
|
128
262
|
end
|
129
263
|
|
130
|
-
def present_commit_menu(manifest_item)
|
264
|
+
def present_commit_menu(manifest_item, options={})
|
265
|
+
page = 0
|
266
|
+
|
131
267
|
while true
|
132
|
-
|
268
|
+
options[:page] = page
|
269
|
+
print_commits(manifest_item.repository.commits, options)
|
133
270
|
|
134
271
|
action, args = ask_and_split(
|
135
272
|
'(n)ext or (p)revious page; (c)hoose index; (m)anual SHA entry; (t)oggle PRs only; (r)eturn:'
|
@@ -137,9 +274,9 @@ module Manifestly
|
|
137
274
|
|
138
275
|
case action
|
139
276
|
when 'n'
|
140
|
-
|
277
|
+
page += 1
|
141
278
|
when 'p'
|
142
|
-
|
279
|
+
page -= 1
|
143
280
|
when 'c'
|
144
281
|
indices = convert_args_to_indices(args, true) || next
|
145
282
|
manifest_item.set_commit_by_index(indices.first)
|
@@ -155,12 +292,35 @@ module Manifestly
|
|
155
292
|
end
|
156
293
|
when 't'
|
157
294
|
manifest_item.repository.toggle_prs_only
|
295
|
+
page = 0
|
158
296
|
when 'r'
|
159
297
|
break
|
160
298
|
end
|
161
299
|
end
|
162
300
|
end
|
163
301
|
|
302
|
+
def present_list_menu(commits, options={})
|
303
|
+
page = 0
|
304
|
+
|
305
|
+
while true
|
306
|
+
options[:page] = page
|
307
|
+
print_commits(commits, options)
|
308
|
+
|
309
|
+
action, args = ask_and_split(
|
310
|
+
'(n)ext or (p)revious page; (q)uit:'
|
311
|
+
) || next
|
312
|
+
|
313
|
+
case action
|
314
|
+
when 'n'
|
315
|
+
page += 1
|
316
|
+
when 'p'
|
317
|
+
page -= 1
|
318
|
+
when 'q'
|
319
|
+
break
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
164
324
|
def ask_and_split(message)
|
165
325
|
answer = ask(message).downcase.split
|
166
326
|
|
@@ -187,17 +347,35 @@ module Manifestly
|
|
187
347
|
end
|
188
348
|
end
|
189
349
|
|
350
|
+
def read_manifest(file)
|
351
|
+
begin
|
352
|
+
Manifest.read(file, available_repositories)
|
353
|
+
rescue Manifestly::ManifestItem::RepositoryNotFound
|
354
|
+
say "Couldn't find all the repositories listed in #{file}. " +
|
355
|
+
"Might need to specify --search_paths."
|
356
|
+
nil
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
190
360
|
def add_repositories(manifest)
|
191
361
|
puts "\n"
|
362
|
+
|
192
363
|
selected_repositories = select(
|
193
|
-
repository_choices,
|
364
|
+
repository_choices(manifest),
|
194
365
|
hide_shortcuts: true,
|
195
366
|
choice_name: "repository",
|
196
|
-
question: "\nChoose which repositories you want in the manifest (e.g. '0 2 5'):"
|
367
|
+
question: "\nChoose which repositories you want in the manifest (e.g. '0 2 5') or (r)eturn:",
|
368
|
+
no_selection: 'r'
|
197
369
|
)
|
198
370
|
|
371
|
+
return if selected_repositories.nil?
|
372
|
+
|
199
373
|
selected_repositories.each do |repository|
|
200
|
-
|
374
|
+
begin
|
375
|
+
manifest.add_repository(repository)
|
376
|
+
rescue Manifestly::Repository::NoCommitsError => e
|
377
|
+
say "Cannot add #{repository.display_name} because it doesn't have any commits."
|
378
|
+
end
|
201
379
|
end
|
202
380
|
end
|
203
381
|
|
@@ -205,8 +383,8 @@ module Manifestly
|
|
205
383
|
puts "\n"
|
206
384
|
puts "Current Manifest:\n"
|
207
385
|
puts "\n"
|
208
|
-
table :
|
209
|
-
row :
|
386
|
+
table border: false do
|
387
|
+
row header: true do
|
210
388
|
column "#", width: 4
|
211
389
|
column "Repository", width: 36
|
212
390
|
column "Branch", width: 30
|
@@ -232,28 +410,61 @@ module Manifestly
|
|
232
410
|
puts "\n"
|
233
411
|
end
|
234
412
|
|
235
|
-
def
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
413
|
+
def print_commits(commits, options={})
|
414
|
+
column_widths = {
|
415
|
+
number: 4,
|
416
|
+
sha: 10,
|
417
|
+
message: 54,
|
418
|
+
author: options[:show_author] ? 14 : 0,
|
419
|
+
date: 25
|
420
|
+
}
|
421
|
+
|
422
|
+
num_columns = column_widths.values.count{|v| v != 0}
|
423
|
+
total_column_widths = column_widths.values.inject{|sum,x| sum + x }
|
424
|
+
total_table_width =
|
425
|
+
total_column_widths +
|
426
|
+
num_columns + 1 + # column separators
|
427
|
+
num_columns * 2 # padding
|
428
|
+
|
429
|
+
width_overage = total_table_width - terminal_width
|
430
|
+
|
431
|
+
if width_overage > 0
|
432
|
+
column_widths[:message] -= width_overage
|
433
|
+
end
|
434
|
+
|
435
|
+
page = options[:page] || 0
|
436
|
+
per_page = options[:per_page] || 15
|
437
|
+
|
438
|
+
last_page = commits.size / per_page
|
439
|
+
last_page -= 1 if commits.size % per_page == 0 # account for full pages
|
440
|
+
|
441
|
+
page = 0 if page < 0
|
442
|
+
page = last_page if page > last_page
|
443
|
+
|
444
|
+
first_commit = page*per_page
|
445
|
+
last_commit = [page*per_page+per_page-1, commits.size-1].min
|
446
|
+
|
447
|
+
page_commits = commits[first_commit..last_commit]
|
448
|
+
|
449
|
+
table border: true do
|
450
|
+
row header: true do
|
451
|
+
column "#", width: column_widths[:number]
|
452
|
+
column "SHA", width: column_widths[:sha]
|
453
|
+
column "Message", width: column_widths[:message]
|
454
|
+
column "Author", width: column_widths[:author] if options[:show_author]
|
455
|
+
column "Date", width: column_widths[:date]
|
245
456
|
end
|
246
457
|
|
247
|
-
|
458
|
+
page_commits.each_with_index do |commit, index|
|
248
459
|
row do
|
249
460
|
column "#{index}", align: 'right'
|
250
461
|
column "#{commit.sha[0..9]}"
|
251
462
|
column "#{summarize_commit_message(commit)}"
|
463
|
+
column "#{commit.author.name[0..13]}" if options[:show_author]
|
252
464
|
column "#{commit.date}"
|
253
465
|
end
|
254
466
|
end
|
255
467
|
end
|
256
|
-
puts "\n"
|
257
468
|
end
|
258
469
|
|
259
470
|
def summarize_commit_message(commit)
|
@@ -263,8 +474,11 @@ module Manifestly
|
|
263
474
|
.gsub(/\s+/, ' ')[0..79]
|
264
475
|
end
|
265
476
|
|
266
|
-
def repository_choices
|
267
|
-
available_repositories.collect{|repo| {display: repo.
|
477
|
+
def repository_choices(except_in_manifest=nil)
|
478
|
+
choices = available_repositories.collect{|repo| {display: repo.display_name, value: repo}}
|
479
|
+
except_in_manifest.nil? ?
|
480
|
+
choices :
|
481
|
+
choices.reject{|choice| except_in_manifest.includes?(choice[:value])}
|
268
482
|
end
|
269
483
|
|
270
484
|
def available_repositories
|
@@ -285,5 +499,22 @@ module Manifestly
|
|
285
499
|
[options[:search_paths]].flatten
|
286
500
|
end
|
287
501
|
|
502
|
+
def terminal_height
|
503
|
+
# This code was derived from Thor and from Rake, the latter of which is
|
504
|
+
# available under MIT-LICENSE Copyright 2003, 2004 Jim Weirich
|
505
|
+
if ENV["THOR_ROWS"]
|
506
|
+
result = ENV["THOR_ROWS"].to_i
|
507
|
+
else
|
508
|
+
result = unix? ? dynamic_width : 40
|
509
|
+
end
|
510
|
+
result < 10 ? 40 : result
|
511
|
+
rescue
|
512
|
+
40
|
513
|
+
end
|
514
|
+
|
515
|
+
def dynamic_height
|
516
|
+
%x{stty size 2>/dev/null}.split[0].to_i
|
517
|
+
end
|
518
|
+
|
288
519
|
end
|
289
520
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Manifestly
|
2
|
+
class Diff
|
3
|
+
|
4
|
+
class File
|
5
|
+
def initialize(file_diff_string)
|
6
|
+
@lines = file_diff_string.split("\n")
|
7
|
+
|
8
|
+
@from_name = filename(true)
|
9
|
+
@to_name = filename(false)
|
10
|
+
|
11
|
+
@from_content = content_lines.grep(/^[\- ]/).collect{|l| l[1..-1]}.join("\n")
|
12
|
+
@to_content = content_lines.grep(/^[\+ ]/).collect{|l| l[1..-1]}.join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :from_name, :to_name, :from_content, :to_content
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def filename(from)
|
20
|
+
regex = from ? /^\-\-\- / : /^\+\+\+ /
|
21
|
+
filename = @lines.grep(regex).first[6..-1]
|
22
|
+
filename = nil if filename == "ev/null"
|
23
|
+
filename
|
24
|
+
end
|
25
|
+
|
26
|
+
def content_lines
|
27
|
+
return @content_lines if @content_lines
|
28
|
+
|
29
|
+
at_at_line_index = @lines.find_index{|ll| ll[0..2] == "@@ "}
|
30
|
+
@content_lines = @lines[at_at_line_index+1..-1]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(diff_string)
|
36
|
+
file_strings = diff_string.split("diff --git ")
|
37
|
+
file_strings.reject!(&:blank?)
|
38
|
+
@files = file_strings.collect{|file_string| File.new(file_string)}
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :files
|
42
|
+
|
43
|
+
def has_surviving_file?(filename)
|
44
|
+
files.any?{|file| file.to_name == filename}
|
45
|
+
end
|
46
|
+
|
47
|
+
def num_files
|
48
|
+
files.length
|
49
|
+
end
|
50
|
+
|
51
|
+
def [](index)
|
52
|
+
files[index]
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/manifestly/manifest.rb
CHANGED
@@ -3,14 +3,15 @@ module Manifestly
|
|
3
3
|
|
4
4
|
class InvalidManifestItem < StandardError; end
|
5
5
|
class RepositoryNotFound < StandardError; end
|
6
|
+
class MultipleSameNameRepositories < StandardError; end
|
6
7
|
|
7
8
|
def initialize(repository)
|
8
9
|
@repository = repository
|
9
|
-
@commit = repository.
|
10
|
+
@commit = repository.current_commit
|
10
11
|
end
|
11
12
|
|
12
13
|
def repository_name
|
13
|
-
@repository.
|
14
|
+
@repository.display_name
|
14
15
|
end
|
15
16
|
|
16
17
|
def set_commit_by_index(index)
|
@@ -21,6 +22,10 @@ module Manifestly
|
|
21
22
|
@commit = @repository.find_commit(sha)
|
22
23
|
end
|
23
24
|
|
25
|
+
def fetch
|
26
|
+
@repository.fetch
|
27
|
+
end
|
28
|
+
|
24
29
|
def checkout_commit!
|
25
30
|
@repository.checkout_commit(@commit.sha)
|
26
31
|
end
|
@@ -33,8 +38,10 @@ module Manifestly
|
|
33
38
|
repo_name, sha = string.split('@').collect(&:strip)
|
34
39
|
raise(InvalidManifestItem, string) if repo_name.blank? || sha.blank?
|
35
40
|
|
36
|
-
|
37
|
-
raise(
|
41
|
+
matching_repositories = repositories.select{|repo| repo.github_name == repo_name}
|
42
|
+
raise(MultipleSameNameRepositories, repo_name) if matching_repositories.size > 1
|
43
|
+
raise(RepositoryNotFound, repo_name) if matching_repositories.empty?
|
44
|
+
repository = matching_repositories.first
|
38
45
|
|
39
46
|
item = ManifestItem.new(repository)
|
40
47
|
item.set_commit_by_sha(sha)
|
@@ -1,87 +1,161 @@
|
|
1
|
-
|
1
|
+
require 'fileutils'
|
2
2
|
|
3
|
-
|
3
|
+
module Manifestly
|
4
|
+
class Repository
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
repository.is_git_repository? ? repository : nil
|
10
|
-
end
|
6
|
+
class CommitNotPresent < StandardError; end
|
7
|
+
class ManifestUnchanged < StandardError; end
|
8
|
+
class CommitContentError < StandardError; end
|
9
|
+
class NoCommitsError < StandardError; end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
# Returns an object if can load a git repository at the specified path,
|
12
|
+
# otherwise nil
|
13
|
+
def self.load(path)
|
14
|
+
repository = new(path)
|
15
|
+
repository.is_git_repository? ? repository : nil
|
17
16
|
end
|
18
|
-
end
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
# Loads a gem-cached copy of the specified repository, cloning it if
|
19
|
+
# necessary.
|
20
|
+
def self.load_cached(github_name, options)
|
21
|
+
options[:update] ||= false
|
23
22
|
|
24
|
-
|
23
|
+
raise(IllegalArgument, "Repository name is blank.") if github_name.blank?
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
log = log.grep("Merge pull request") if @prs_only
|
29
|
-
log.skip(@commit_page * COMMITS_PER_PAGE)
|
30
|
-
end
|
25
|
+
path = "./.manifestly/.manifest_repositories/#{github_name}"
|
26
|
+
FileUtils.mkdir_p(path)
|
31
27
|
|
32
|
-
|
33
|
-
@commit_page += 1
|
34
|
-
end
|
28
|
+
repository = load(path)
|
35
29
|
|
36
|
-
|
37
|
-
|
38
|
-
|
30
|
+
if repository.nil?
|
31
|
+
url = "git@github.com:#{github_name}.git"
|
32
|
+
Git.clone(url, path)
|
33
|
+
repository = new(path)
|
34
|
+
end
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
|
36
|
+
repository.make_like_just_cloned! if options[:update]
|
37
|
+
repository
|
38
|
+
end
|
43
39
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
40
|
+
def is_git_repository?
|
41
|
+
begin
|
42
|
+
git
|
43
|
+
rescue ArgumentError
|
44
|
+
false
|
45
|
+
end
|
50
46
|
end
|
51
|
-
end
|
52
47
|
|
53
|
-
|
54
|
-
|
55
|
-
|
48
|
+
def fetch
|
49
|
+
git.fetch
|
50
|
+
end
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
def git
|
53
|
+
Git.open(@path)
|
54
|
+
end
|
60
55
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
56
|
+
def make_like_just_cloned!
|
57
|
+
git.branch('master').checkout
|
58
|
+
git.fetch
|
59
|
+
git.reset_hard('origin/master')
|
60
|
+
end
|
65
61
|
|
66
|
-
|
67
|
-
|
68
|
-
|
62
|
+
def push_file!(local_file_path, repository_file_path, message)
|
63
|
+
full_repository_file_path = File.join(@path, repository_file_path)
|
64
|
+
FileUtils.cp(local_file_path, full_repository_file_path)
|
65
|
+
git.add(repository_file_path)
|
66
|
+
raise ManifestUnchanged if git.status.changed.empty?
|
67
|
+
git.commit(message)
|
68
|
+
git.push
|
69
|
+
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
def commits
|
72
|
+
begin
|
73
|
+
log = git.log(1000000).object('origin/master') # don't limit
|
74
|
+
log = log.grep("Merge pull request") if @prs_only
|
75
|
+
log.tap(&:first) # tap to force otherwise lazy checks
|
76
|
+
rescue Git::GitExecuteError => e
|
77
|
+
raise NoCommitsError
|
78
|
+
end
|
79
|
+
end
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
|
81
|
+
# returns the commit matching the provided sha or raises
|
82
|
+
def find_commit(sha)
|
83
|
+
begin
|
84
|
+
git.gcommit(sha).tap(&:sha)
|
85
|
+
rescue Git::GitExecuteError => e
|
86
|
+
raise CommitNotPresent, "SHA not found: #{sha}"
|
87
|
+
end
|
88
|
+
end
|
78
89
|
|
79
|
-
|
90
|
+
def get_commit_content(sha)
|
91
|
+
diff_string = find_commit("#{sha}^").diff(sha).to_s
|
92
|
+
sha_diff = Diff.new(diff_string)
|
80
93
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
94
|
+
raise(CommitContentError, "No content to retrieve for SHA #{sha}!") if sha_diff.num_files == 0
|
95
|
+
raise(CommitContentError, "More than one file in the commit for SHA #{sha}!") if sha_diff.num_files > 1
|
96
|
+
|
97
|
+
sha_diff[0].to_content
|
98
|
+
end
|
99
|
+
|
100
|
+
def file_commits(file)
|
101
|
+
commits = git.log
|
102
|
+
commits = commits.select do |commit|
|
103
|
+
diff = Diff.new(commit.diff_parent.to_s)
|
104
|
+
diff.has_surviving_file?(file)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def checkout_commit(sha)
|
109
|
+
git.checkout(sha)
|
110
|
+
end
|
111
|
+
|
112
|
+
def current_branch_name
|
113
|
+
git.lib.branch_current
|
114
|
+
end
|
115
|
+
|
116
|
+
def current_commit
|
117
|
+
sha = git.show.split("\n")[0].split(" ")[1]
|
118
|
+
find_commit(sha)
|
119
|
+
end
|
86
120
|
|
121
|
+
def toggle_prs_only
|
122
|
+
@prs_only = !@prs_only
|
123
|
+
end
|
124
|
+
|
125
|
+
def origin
|
126
|
+
@origin ||= git.remotes.select{|remote| remote.name == 'origin'}.first
|
127
|
+
end
|
128
|
+
|
129
|
+
def github_name
|
130
|
+
return nil if origin.nil?
|
131
|
+
origin.url[/github.com.(.*).git/,1]
|
132
|
+
end
|
133
|
+
|
134
|
+
def working_dir
|
135
|
+
git.dir
|
136
|
+
end
|
137
|
+
|
138
|
+
def display_name
|
139
|
+
if github_name
|
140
|
+
repo_name = github_name.split('/').last
|
141
|
+
dir_name = working_dir.to_s.split(File::SEPARATOR).last
|
142
|
+
if repo_name == dir_name
|
143
|
+
github_name
|
144
|
+
else
|
145
|
+
github_name + " (#{dir_name})"
|
146
|
+
end
|
147
|
+
else
|
148
|
+
working_dir.to_s
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
protected
|
153
|
+
|
154
|
+
def initialize(path)
|
155
|
+
@path = path
|
156
|
+
@commit_page = 0
|
157
|
+
@prs_only = false
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
87
161
|
end
|
data/lib/manifestly/ui.rb
CHANGED
@@ -29,8 +29,8 @@ module Manifestly
|
|
29
29
|
say Rainbow("The input '#{options[:inputs].join(' ')}' did not match any choices.").red if selections.empty?
|
30
30
|
else
|
31
31
|
if !options[:hide_choices]
|
32
|
-
table :
|
33
|
-
row :
|
32
|
+
table border: false do
|
33
|
+
row header: true do
|
34
34
|
column "", width: 4
|
35
35
|
column options[:choice_name].capitalize, width: 40
|
36
36
|
column "Shortcut", width:10 unless options[:hide_shortcuts]
|
@@ -47,6 +47,10 @@ module Manifestly
|
|
47
47
|
|
48
48
|
selected_indices = ask(options[:question]).split(" ")
|
49
49
|
|
50
|
+
return if ( options[:no_selection] &&
|
51
|
+
selected_indices.length == 1 &&
|
52
|
+
selected_indices[0] == options[:no_selection] )
|
53
|
+
|
50
54
|
if selected_indices.length != 1 && options[:select_one]
|
51
55
|
say Rainbow("Please choose only one #{options[:choice_name]}! (or CTRL + C to exit)").red
|
52
56
|
options[:hide_choices] = true
|
data/lib/manifestly/version.rb
CHANGED
data/manifestly.gemspec
CHANGED
@@ -22,8 +22,10 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_dependency 'rainbow'
|
23
23
|
spec.add_dependency 'command_line_reporter'
|
24
24
|
spec.add_dependency 'git'
|
25
|
+
spec.add_dependency 'ruby-progressbar'
|
25
26
|
|
26
27
|
spec.add_development_dependency 'byebug'
|
27
28
|
spec.add_development_dependency "bundler", "~> 1.9"
|
28
29
|
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "rspec"
|
29
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: manifestly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- JP Slavinsky
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ruby-progressbar
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: byebug
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,20 @@ dependencies:
|
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '10.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
111
139
|
description:
|
112
140
|
email:
|
113
141
|
- jps@kindlinglabs.com
|
@@ -130,9 +158,9 @@ files:
|
|
130
158
|
- exe/manifestly
|
131
159
|
- lib/manifestly.rb
|
132
160
|
- lib/manifestly/cli.rb
|
161
|
+
- lib/manifestly/diff.rb
|
133
162
|
- lib/manifestly/manifest.rb
|
134
163
|
- lib/manifestly/manifest_item.rb
|
135
|
-
- lib/manifestly/manifest_repository.rb
|
136
164
|
- lib/manifestly/repository.rb
|
137
165
|
- lib/manifestly/ui.rb
|
138
166
|
- lib/manifestly/utilities.rb
|