manifestly 0.1.0 → 1.0.0
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.
- 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
|