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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e99d41748251cf3299fda9c0d9a5175377b9b4dd
4
- data.tar.gz: 9bd8f1e0fc81ef7f0ca0ab23d0a1a4fd3f67c4e9
3
+ metadata.gz: 27e444dcd8fa28ababac67f3b65cc111962ce5ca
4
+ data.tar.gz: 5320806e0e66db13b621fa82e929a52189184940
5
5
  SHA512:
6
- metadata.gz: cbb2d0025da752056c50b00ab2b0a3eb3c9d39d5db16f1d442bf4423ce21fffd2a403fd063e6d099e25c201101f5971b147794ce2bbe0c73f16b497952e511dc
7
- data.tar.gz: 27fe5f795c060428d1a0af2d791c35dd4d9f7fbdbd3deeb4d245a76b0fccd3c36d00307b5fca4ef3dd099470d019590aab0ebcfa2f72c4f7e7f2cfc9d78a10c9
6
+ metadata.gz: 38e852ab6e864f55fbb592ff32dd6051c3acc9c69c9767bb5fb654b273477b78525d4cab563d846c34ac49ca9e8ed5d6a0b29875b3b1b3e313db605e665cf06a
7
+ data.tar.gz: bf0b841d7e45b285a0719c412a8b9b896f9d6bc2fa909e0223ba6bcc8a8f12d16e52774bd7837e32c37dad4e217fde86c898cdab8480545cd9556ef3fb43831c
data/.gitignore CHANGED
@@ -9,4 +9,5 @@
9
9
  /tmp/
10
10
  .DS_Store
11
11
  *.manifest
12
+ .manifestly
12
13
  *.*~
@@ -1,3 +1,8 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  rvm:
3
4
  - 1.9.3
5
+ cache: bundler
6
+ bundler_args: --retry=6
7
+ script:
8
+ - bundle exec rake
@@ -1,5 +1,6 @@
1
1
  require_relative "manifestly/version"
2
2
  require_relative 'manifestly/utilities'
3
+ require_relative 'manifestly/diff'
3
4
  require_relative 'manifestly/repository'
4
5
  require_relative 'manifestly/manifest'
5
6
  require_relative 'manifestly/ui'
@@ -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
- :desc => "A list of paths where git repositories can be found",
19
- :type => :array,
20
- :required => false,
21
- :default => '.'
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
- :desc => "A manifest file to use as a starting point",
28
- :type => :string,
29
- :required => false
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
- Manifest.read(options[:based_on], available_repositories)
120
+ read_manifest(options[:based_on]) || return
33
121
  else
34
122
  Manifest.new
35
123
  end
36
124
 
37
- present_manifest_menu(manifest)
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
- method_option :file,
43
- :desc => "The manifest file to apply",
44
- :type => :string,
45
- :required => true
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
- manifest = Manifest.read(options[:file], available_repositories)
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 "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.")
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 "pull", "Downloads a manifest file from a manifest repository"
170
+ desc "download", "Downloads a manifest file from a manifest repository"
69
171
  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
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
- :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.")
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 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
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
- say("Not yet implemented.")
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 present_manifest_menu(manifest)
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
- print_commit_shas(manifest_item)
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
- manifest_item.repository.next_page_of_commits
277
+ page += 1
141
278
  when 'p'
142
- manifest_item.repository.prev_page_of_commits
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
- manifest.add_repository(repository)
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 :border => false do
209
- row :header => true do
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 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
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
- manifest_item.repository.commits.each_with_index do |commit, index|
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.github_name || repo.working_dir, value: 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
@@ -46,6 +46,10 @@ module Manifestly
46
46
  end
47
47
  end
48
48
 
49
+ def includes?(repository)
50
+ @items.any?{|item| item.repository.display_name == repository.display_name}
51
+ end
52
+
49
53
  attr_reader :items
50
54
 
51
55
  def empty?
@@ -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.commits.first
10
+ @commit = repository.current_commit
10
11
  end
11
12
 
12
13
  def repository_name
13
- @repository.github_name
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
- repository = repositories.select{|repo| repo.github_name == repo_name}.first
37
- raise(RepositoryNotFound, repo_name) if repository.nil?
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
- class Manifestly::Repository
1
+ require 'fileutils'
2
2
 
3
- class CommitNotPresent < StandardError; end
3
+ module Manifestly
4
+ class Repository
4
5
 
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
6
+ class CommitNotPresent < StandardError; end
7
+ class ManifestUnchanged < StandardError; end
8
+ class CommitContentError < StandardError; end
9
+ class NoCommitsError < StandardError; end
11
10
 
12
- def is_git_repository?
13
- begin
14
- git
15
- rescue ArgumentError
16
- false
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
- def git
21
- Git.open(@path)
22
- end
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
- COMMITS_PER_PAGE = 15
23
+ raise(IllegalArgument, "Repository name is blank.") if github_name.blank?
25
24
 
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
25
+ path = "./.manifestly/.manifest_repositories/#{github_name}"
26
+ FileUtils.mkdir_p(path)
31
27
 
32
- def next_page_of_commits
33
- @commit_page += 1
34
- end
28
+ repository = load(path)
35
29
 
36
- def prev_page_of_commits
37
- @commit_page -= 1
38
- end
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
- def reset_page_of_commits
41
- @commit_page = 0
42
- end
36
+ repository.make_like_just_cloned! if options[:update]
37
+ repository
38
+ end
43
39
 
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}"
40
+ def is_git_repository?
41
+ begin
42
+ git
43
+ rescue ArgumentError
44
+ false
45
+ end
50
46
  end
51
- end
52
47
 
53
- def checkout_commit(sha)
54
- git.checkout(sha)
55
- end
48
+ def fetch
49
+ git.fetch
50
+ end
56
51
 
57
- def current_branch_name
58
- git.lib.branch_current
59
- end
52
+ def git
53
+ Git.open(@path)
54
+ end
60
55
 
61
- def toggle_prs_only
62
- @commit_page = 0
63
- @prs_only = !@prs_only
64
- end
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
- def origin
67
- @origin ||= git.remotes.select{|remote| remote.name == 'origin'}.first
68
- end
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
- def github_name
71
- return nil if origin.nil?
72
- origin.url[/github.com.(.*).git/,1]
73
- end
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
- def working_dir
76
- git.dir
77
- end
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
- protected
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
- def initialize(path)
82
- @path = path
83
- @commit_page = 0
84
- @prs_only = false
85
- end
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
@@ -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 :border => false do
33
- row :header => true do
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
@@ -1,3 +1,3 @@
1
1
  module Manifestly
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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: 0.1.0
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-20 00:00:00.000000000 Z
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
@@ -1,16 +0,0 @@
1
- module Manifestly
2
- class ManifestRepository
3
-
4
- def self.get(url_or_name)
5
- # Checkout locally in tmp dir if not yet avail
6
- # then wrap with Repository and instantiate
7
- end
8
-
9
- protected
10
-
11
- def initialize()
12
-
13
- end
14
-
15
- end
16
- end