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 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