git_mass_do 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8f3511305b88a8cb8a9f710a787557d00b35650a
4
+ data.tar.gz: cb74e9a40e520c32a3bdb5404b9fa8d560d0b38d
5
+ SHA512:
6
+ metadata.gz: e6e982d7cddb3f0ce542171bfd9f5f8ce0340fa7e480c2f49a84f17c7f69d3297e8d19761c129f82c36689524b18362818c20e2025a270ad66d5f0bae3610a94
7
+ data.tar.gz: 4d5e18cdaa578232da5ddc075b7e4e437a350d16f011798d432a1aabf050d2e049e315aabe986badfc2f1d83a4bdf955a375437e1a220fb5b10b63b6c04e211b
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in git_mass_do.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ git_mass_do (0.0.0)
5
+ recursive-open-struct (~> 0.5.0)
6
+ thor (~> 0.19)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ coderay (1.1.0)
12
+ diff-lcs (1.2.5)
13
+ method_source (0.8.2)
14
+ pry (0.10.1)
15
+ coderay (~> 1.1.0)
16
+ method_source (~> 0.8.1)
17
+ slop (~> 3.4)
18
+ rake (10.3.2)
19
+ recursive-open-struct (0.5.0)
20
+ rspec (3.0.0)
21
+ rspec-core (~> 3.0.0)
22
+ rspec-expectations (~> 3.0.0)
23
+ rspec-mocks (~> 3.0.0)
24
+ rspec-core (3.0.4)
25
+ rspec-support (~> 3.0.0)
26
+ rspec-expectations (3.0.4)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.0.0)
29
+ rspec-mocks (3.0.4)
30
+ rspec-support (~> 3.0.0)
31
+ rspec-support (3.0.4)
32
+ slop (3.6.0)
33
+ thor (0.19.1)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ bundler (~> 1.7)
40
+ git_mass_do!
41
+ pry
42
+ rake (~> 10.0)
43
+ rspec (~> 3.0)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Renan Ranelli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # Execute mass actions on your github repositories.
2
+
3
+ <<<<<<< HEAD
4
+ Actions currently supported:
5
+ =======
6
+ actions currently supported:
7
+
8
+ >>>>>>> 0a4b41b8bdc18fbeb02fb2d11ccb89cca7b4562e
9
+ * Git clone all your repositories (and add upstream remote if it is a fork)
10
+ * Git pull all repositories in directory
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
data/bin/git_mass_do ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../lib/git_mass_do/cli'
3
+
4
+ GitMassDo::Cli.start
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'git_mass_do'
3
+ s.version = '0.0.1'
4
+ s.required_ruby_version = '~>2.0'
5
+
6
+ s.summary = 'Execute mass actions on git repositories concurrently'
7
+ s.authors = ['Renan Ranelli']
8
+ s.email = ['renanranelli@gmail.com']
9
+ s.homepage = 'http://github.com/rranelli/git_mass_do'
10
+ s.license = 'MIT'
11
+
12
+ s.files = `git ls-files -z`.split("\x0")
13
+ s.executables = s.files.grep(/^bin\//) { |f| File.basename(f) }
14
+ s.require_paths = ['lib']
15
+
16
+ s.add_dependency 'recursive-open-struct', '~> 0.5.0'
17
+ s.add_dependency 'thor', '~> 0.19'
18
+
19
+ s.add_development_dependency 'bundler', '~> 1.7'
20
+ s.add_development_dependency 'rspec', '~> 3.0'
21
+ s.add_development_dependency 'rake', '~> 10.0'
22
+ s.add_development_dependency 'pry'
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'thor'
2
+
3
+ require_relative '../git_mass_do'
4
+
5
+ module GitMassDo
6
+ class Cli < Thor
7
+ desc 'git_mass_do pull', 'Git pulls all repositories contained in current directory.'
8
+ def pull(dir = Dir.pwd)
9
+ Puller.new(dir).pull
10
+ end
11
+
12
+ desc 'git_mass_do clone :username', 'Git pulls all repositories contained in current directory.'
13
+ def clone(username)
14
+ Cloner.new(username).clone!
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module GitMassDo
5
+ class Cloner
6
+ include Process
7
+
8
+ def initialize(username)
9
+ @username = username
10
+ end
11
+
12
+ def clone_em_all!(repos)
13
+ repos.map do |repo|
14
+ spawn(make_command(repo))
15
+ end
16
+ waitall.map { |_, status| status }
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :username
22
+
23
+ def clone!
24
+ start_time = Time.now
25
+ repos = RepositoryFetcher.get_all_repos_from_user(username)
26
+ statuses = clone_em_all!(repos)
27
+
28
+ OutputFormatter.format(repos, statuses, start_time)
29
+ end
30
+
31
+ def make_command(repo)
32
+ if repo.fork
33
+ parent_repo = RepositoryFetcher.get_repo(repo.url).parent
34
+ "git clone #{repo.ssh_url} && \
35
+ git -C #{repo.name} remote add upstream #{parent_repo.ssh_url} --fetch"
36
+ else
37
+ "git clone #{repo.ssh_url}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ class OutputFormatter
2
+ def self.format(repos, statuses, start_time = nil)
3
+ repo_status_pairs = repos.zip(statuses)
4
+
5
+ # get successes and failures
6
+ success_pairs = repo_status_pairs.select { |_, status| status.success? }
7
+ failure_pairs = repo_status_pairs.reject { |_, status| status.success? }
8
+
9
+ success_pairs.each { |repo, _| puts "#{repo.name} cloned successfully." }
10
+ failure_pairs.each { |repo, _| puts "failure to clone #{repo.name}." }
11
+
12
+ puts '=========================================='
13
+ puts "Finished in #{Time.now - start_time} seconds." if start_time
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ module GitMassDo
2
+ class Puller
3
+ include Process
4
+
5
+ attr_reader :dir
6
+
7
+ def initialize(dir)
8
+ @dir = dir
9
+ end
10
+
11
+ def pull
12
+ dirs = Dir.entries(dir)
13
+ .select { |f| File.directory? f }
14
+ .reject { |f| f =~ /^\./ } # ., .. and .git and the like
15
+
16
+ dirs.each do |dir|
17
+ spawn "git -C #{dir} pull -r origin"
18
+ end
19
+
20
+ _, statuses = waitall.transpose
21
+ format_result(dirs, statuses)
22
+ end
23
+
24
+ def format_result(repositories, statuses)
25
+ repositories.zip(statuses).each do |repo, status|
26
+ puts "Pulled #{repo} successfully" if status && status.success?
27
+ puts "Failed to pull #{repo}" unless status && status.success?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'recursive-open-struct'
2
+ require 'json'
3
+
4
+ module GitMassDo
5
+ class RepositoryFetcher
6
+ REPOS_URI = 'https://api.github.com/users/%{username}/repos'
7
+
8
+ def self.get_all_repos_from_user(username)
9
+ uri_str = REPOS_URI % { username: username }
10
+ uri = URI(uri_str)
11
+
12
+ response = Net::HTTP.get_response(uri)
13
+ repos = JSON.parse(response.body)
14
+
15
+ repos.map { |hash| make_struct(hash) }
16
+ end
17
+
18
+ def self.get_repo(url)
19
+ response = Net::HTTP.get_response(URI(url))
20
+ make_struct(JSON.parse(response.body))
21
+ end
22
+
23
+ def self.make_struct(hash)
24
+ RecursiveOpenStruct.new(hash, recurse_over_arrays: true)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module GitMassDo
2
+ VERSION = '0.0.0'
3
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'git_mass_do/version'
2
+ require_relative 'git_mass_do/cloner'
3
+ require_relative 'git_mass_do/puller'
4
+ require_relative 'git_mass_do/output_formatter'
5
+ require_relative 'git_mass_do/repository_fetcher'
6
+
7
+ module GitMassDo
8
+ end
@@ -0,0 +1,66 @@
1
+ module GitMassDo
2
+ describe Cloner do
3
+ subject(:cloner) { described_class.new(username) }
4
+
5
+ let(:username) { 'ironman' }
6
+
7
+ let(:repo) { double(:repo, ssh_url: 'git@hubgit.com:foo/bar', fork: false) }
8
+ let(:repos) { [repo] * 3 }
9
+
10
+ before do
11
+ allow(cloner).to receive(:spawn).and_return(nil)
12
+ allow(cloner).to receive(:waitall).and_return([])
13
+ end
14
+
15
+ describe '#clone_em_all!' do
16
+ subject(:clone_em_all!) { cloner.clone_em_all!(repos) }
17
+
18
+ it 'spawns a clone job for each repo' do
19
+ expect(cloner).to receive(:spawn)
20
+ .with("git clone #{repo.ssh_url}").exactly(3).times
21
+
22
+ clone_em_all!
23
+ end
24
+
25
+ context 'when repo is a fork'do
26
+ let(:repo) do
27
+ double(
28
+ :repo,
29
+ ssh_url: 'git@hubgit.com:bar/foo',
30
+ url: 'http://hubgit.com/bar/foo',
31
+ fork: true,
32
+ name: 'bar'
33
+ )
34
+ end
35
+
36
+ let(:parent_repo) do
37
+ double(:parent, ssh_url: 'git@hubgit.com:parent/repo')
38
+ end
39
+
40
+ before do
41
+ allow(RepositoryFetcher).to receive_message_chain(
42
+ :get_repo, :parent
43
+ ).and_return(parent_repo)
44
+ end
45
+
46
+ it 'gets parent repository by url' do
47
+ expect(RepositoryFetcher).to receive(:get_repo)
48
+ .with(repo.url)
49
+
50
+ clone_em_all!
51
+ end
52
+
53
+ it 'adds upstream remote' do
54
+ expect(cloner).to receive(:spawn)
55
+ .with(
56
+ "git clone #{repo.ssh_url} && \
57
+ git -C #{repo.name} remote add upstream #{parent_repo.ssh_url} \
58
+ --fetch"
59
+ )
60
+
61
+ clone_em_all!
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,51 @@
1
+ describe GitMassDo::Puller do
2
+ subject(:puller) { described_class.new(dir) }
3
+
4
+ let(:dir) { '/home/' }
5
+ let(:entries) { %w(one two) }
6
+
7
+ let(:success) { double(:success, success?: true) }
8
+
9
+ before do
10
+ $stdout = StringIO.new
11
+
12
+ allow(File).to receive(:directory?).and_return(true)
13
+ allow(Dir).to receive(:entries).and_return(entries)
14
+
15
+ allow(puller).to receive(:spawn).and_return(nil)
16
+ allow(puller).to receive(:waitall).and_return([[1, success], [2, success]])
17
+ end
18
+
19
+ describe '#pull' do
20
+ subject(:pull) { puller.pull }
21
+
22
+ it 'issues a git pull command for each entry in dir' do
23
+ entries.each do |entry|
24
+ expect(puller).to receive(:spawn).with("git -C #{entry} pull origin")
25
+ end
26
+
27
+ pull
28
+ end
29
+
30
+ it 'formats results' do
31
+ expect { pull }.to output(
32
+ "Pulled one successfully\nPulled two successfully\n"
33
+ ).to_stdout
34
+ end
35
+
36
+ context 'with error output' do
37
+ before do
38
+ allow(puller).to receive(:waitall)
39
+ .and_return([[1, success], [2, nil]])
40
+ end
41
+
42
+ it 'formats results correctly when there is an error in a job' do
43
+ expect { pull }.to output(
44
+ "Pulled one successfully\nFailed to pull two\n"
45
+ ).to_stdout
46
+
47
+ pull
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,74 @@
1
+ describe GitMassDo::RepositoryFetcher do
2
+ subject(:fetcher) { GitMassDo::RepositoryFetcher }
3
+
4
+ let(:uri) { URI(url) }
5
+ let(:response) { instance_double(Net::HTTPResponse) }
6
+ let(:body) { '{"value": "I be a body", "b": "c"}' }
7
+
8
+ let(:json) { { 'value' => 'I be a body', 'b' => 'c' } }
9
+
10
+ before do
11
+ allow(JSON).to receive(:parse).and_return(json)
12
+
13
+ allow(Net::HTTP).to receive(:get_response).and_return(response)
14
+ allow(response).to receive(:body).and_return(body)
15
+ end
16
+
17
+ describe '.get_repo' do
18
+ subject(:get_repo) { fetcher.get_repo(url) }
19
+
20
+ let(:url) { 'http://example.com/foo/bar/33' }
21
+
22
+ it 'calls http get' do
23
+ expect(Net::HTTP).to receive(:get_response).with(uri)
24
+
25
+ get_repo
26
+ end
27
+
28
+ it 'parses the resulting json' do
29
+ expect(JSON).to receive(:parse).with(body)
30
+
31
+ get_repo
32
+ end
33
+
34
+ it 'Makes a struct with the result body' do
35
+ expect(RecursiveOpenStruct).to receive(:new).with(
36
+ json, recurse_over_arrays: true
37
+ )
38
+
39
+ get_repo
40
+ end
41
+ end
42
+
43
+ describe '.get_all_repos_from_user' do
44
+ subject(:get_all_repos_from_user) { fetcher.get_all_repos_from_user(user) }
45
+
46
+ let(:user) { 'mrwhite' }
47
+ let(:url) { 'https://api.github.com/users/mrwhite/repos' }
48
+
49
+ let(:json) do
50
+ [
51
+ { 'value' => 'I be a body' },
52
+ { 'b' => 'c' }
53
+ ]
54
+ end
55
+
56
+ it 'calls http get' do
57
+ expect(Net::HTTP).to receive(:get_response).with(uri)
58
+
59
+ get_all_repos_from_user
60
+ end
61
+
62
+ it 'parses the resulting json' do
63
+ expect(JSON).to receive(:parse).with(body)
64
+
65
+ get_all_repos_from_user
66
+ end
67
+
68
+ it 'builds each repository as an OpenStruct' do
69
+ expect(RecursiveOpenStruct).to receive(:new).twice
70
+
71
+ get_all_repos_from_user
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,6 @@
1
+ require_relative '../lib/git_mass_do'
2
+
3
+ def fixture_path(filename)
4
+ return '' if filename == ''
5
+ File.join(File.absolute_path(File.dirname(__FILE__)), 'fixtures', filename)
6
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_mass_do
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Renan Ranelli
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: recursive-open-struct
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.19'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.19'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - renanranelli@gmail.com
100
+ executables:
101
+ - git_mass_do
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - Gemfile
108
+ - Gemfile.lock
109
+ - LICENSE
110
+ - README.md
111
+ - Rakefile
112
+ - bin/git_mass_do
113
+ - git_mass_do.gemspec
114
+ - lib/git_mass_do.rb
115
+ - lib/git_mass_do/cli.rb
116
+ - lib/git_mass_do/cloner.rb
117
+ - lib/git_mass_do/output_formatter.rb
118
+ - lib/git_mass_do/puller.rb
119
+ - lib/git_mass_do/repository_fetcher.rb
120
+ - lib/git_mass_do/version.rb
121
+ - spec/git_mass_do/cloner_spec.rb
122
+ - spec/git_mass_do/puller_spec.rb
123
+ - spec/git_mass_do/repository_fetcher_spec.rb
124
+ - spec/spec_helper.rb
125
+ homepage: http://github.com/rranelli/git_mass_do
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '2.0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.2.2
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Execute mass actions on git repositories concurrently
149
+ test_files: []