git_multicast 0.0.4.pre

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: e46e4f027fd8d347a1d09cf7024aecf2ff3f6f13
4
+ data.tar.gz: 8be7a4a65d3881666ff158e139520502af03f07c
5
+ SHA512:
6
+ metadata.gz: 711a42c85a18f09fb8765ccbf9e11756205d4e56c930ed24e55dccf55ad0b697ccbe179e053d42291c8b141e77d46307cddfcf45453d43981ca9a8eb022a1db2
7
+ data.tar.gz: be91deb5e0635645fcebeba52e9129fba81831cb5532168e759904b0cdb35b16e1cf5b9505a798e1a247746b65fc57a81f0ea26c6f2a0b1c0251d7bc9c7e1721
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_multicast (0.0.4.pre)
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_multicast!
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,23 @@
1
+ # Multicast your git actions.
2
+
3
+ Have you ever need to clone a whole bunch of repositories? Have you forgot to pull remote changes?
4
+
5
+ ### GitMassDo to the rescue!
6
+
7
+ `git_mass_do` is a ruby gem that provides a simple `cli` for issuing commands to
8
+ multiple git repositories, much like a multicast sends data to multiple
9
+ recipients.
10
+
11
+ `git_mass_do` executes actions in parallel, so cloning 30 repositories will take
12
+ just as long as cloning the biggest one, and nothing more.
13
+
14
+ Actions currently supported:
15
+
16
+ * Git clone all repositories of an user or organization (github only).
17
+ * Git pull all repositories in a directory.
18
+
19
+ Actions to be supported:
20
+
21
+ * Git clone repositories from hosts other than github.
22
+ * Pass options to git pull.
23
+ * Schedule git mass pull
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_multicast ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../lib/git_multicast/cli'
3
+
4
+ GitMulticast::Cli.start
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'git_multicast'
3
+ s.version = '0.0.4.pre'
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_multicast'
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,33 @@
1
+ module GitMulticast
2
+ class BitbucketAdapter
3
+ def initialize(repo)
4
+ @repo = repo
5
+ end
6
+
7
+ def adapt
8
+ make_struct(repo_hash)
9
+ end
10
+
11
+ protected
12
+
13
+ attr_reader :repo
14
+
15
+ def repo_hash
16
+ @repo_hash ||= make_repo_hash
17
+ end
18
+
19
+ def make_repo_hash
20
+ {
21
+ fork: !repo.parent.nil?,
22
+ ssh_url: repo.links._clone.last.href,
23
+ url: repo.links.self.href,
24
+ parent: nil,
25
+ name: repo.name
26
+ }
27
+ end
28
+
29
+ def make_struct(hash)
30
+ RecursiveOpenStruct.new(hash, recurse_over_arrays: true)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ module GitMulticast
2
+ class BitbucketFetcher
3
+ REPOS_URI = 'https://bitbucket.org/api/2.0/repositories/%{username}'
4
+
5
+ def self.get_all_repos_from_user(username)
6
+ uri_str = REPOS_URI % { username: username }
7
+ uri = URI(uri_str)
8
+
9
+ response = Net::HTTP.get_response(uri)
10
+ response_json = JSON.parse(response.body)
11
+
12
+ # Damn...
13
+ response_json['values'].each do |node|
14
+ node['links']['_clone'] = node['links']['clone']
15
+ end
16
+
17
+ bb_repos = response_json['values'].map { |hash| make_struct(hash) }
18
+ bb_repos.map { |bb_repo| BitbucketAdapter.new(bb_repo).adapt }
19
+ end
20
+
21
+ def self.get_repo_parent(url)
22
+ bb_repo = get_repo(url).parent
23
+ BitbucketAdapter.new(bb_repo).adapt
24
+ end
25
+
26
+ def self.get_repo(url)
27
+ response = Net::HTTP.get_response(URI(url))
28
+ bb_repo = make_struct(JSON.parse(response.body))
29
+ BitbucketAdapter.new(bb_repo).adapt
30
+ end
31
+
32
+ def self.make_struct(hash)
33
+ RecursiveOpenStruct.new(hash, recurse_over_arrays: true)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ require 'thor'
2
+
3
+ require_relative '../git_multicast'
4
+
5
+ module GitMulticast
6
+ class Cli < Thor
7
+ desc 'git_multicast pull', 'Git pulls all repositories contained in current directory.'
8
+ def pull
9
+ Puller.new(Dir.pwd).pull
10
+ end
11
+
12
+ desc 'git_multicast clone :username', 'Git pulls all repositories contained in current directory.'
13
+ def clone(username)
14
+ Cloner.new(username, Dir.pwd).clone!
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module GitMulticast
5
+ class Cloner
6
+ include Process
7
+
8
+ def initialize(username, dir)
9
+ @username = username
10
+ @dir = dir
11
+ end
12
+
13
+ def clone!
14
+ start_time = Time.now
15
+ repos = RepositoryFetcher.get_all_repos_from_user(username)
16
+ statuses = clone_em_all!(repos)
17
+
18
+ OutputFormatter.format(repos, statuses, start_time)
19
+ end
20
+
21
+ protected
22
+
23
+ attr_reader :username, :dir
24
+
25
+ def clone_em_all!(repos)
26
+ repos.map do |repo|
27
+ spawn(make_command(repo))
28
+ end
29
+ waitall.map { |_, status| status }
30
+ end
31
+
32
+ def make_command(repo)
33
+ if repo.fork
34
+ parent_repo = RepositoryFetcher.get_repo_parent(repo.url)
35
+ "git clone #{repo.ssh_url} #{ File.join(dir, repo.name) } && \
36
+ git -C \"#{ File.join(dir, repo.name) }\" remote add upstream \
37
+ #{parent_repo.ssh_url} --fetch"
38
+ else
39
+ "git clone #{repo.ssh_url} #{ File.join(dir, repo.name) }"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require 'recursive-open-struct'
2
+ require 'json'
3
+
4
+ module GitMulticast
5
+ class GithubFetcher
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_parent(url)
19
+ get_repo(url).parent
20
+ end
21
+
22
+ def self.get_repo(url)
23
+ response = Net::HTTP.get_response(URI(url))
24
+ make_struct(JSON.parse(response.body))
25
+ end
26
+
27
+ def self.make_struct(hash)
28
+ RecursiveOpenStruct.new(hash, recurse_over_arrays: true)
29
+ end
30
+ end
31
+ 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 GitMulticast
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,33 @@
1
+ module GitMulticast
2
+ class RepositoryFetcher
3
+ FETCHERS = [
4
+ GitMulticast::GithubFetcher,
5
+ GitMulticast::BitbucketFetcher
6
+ ]
7
+
8
+ def self.get_all_repos_from_user(username)
9
+ multicast(FETCHERS, :get_all_repos_from_user, username).flatten
10
+ end
11
+
12
+ def self.get_repo_parent(url)
13
+ fetcher_by_url(url).get_repo_parent(url)
14
+ end
15
+
16
+ def self.fetcher_by_url(url)
17
+ fetchers_names = FETCHERS.map do |fetcher|
18
+ match = fetcher.to_s.match(/::(.*)$/)
19
+ match[1].gsub('Fetcher', '').downcase if match
20
+ end
21
+
22
+ triples = ([url] * FETCHERS.count).zip(fetchers_names, FETCHERS)
23
+
24
+ triples.select { |u, name, _| u.match name }.first.last
25
+ end
26
+
27
+ def self.multicast(list, method, *args)
28
+ list.map do |e|
29
+ e.send(method, *args)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module GitMulticast
2
+ VERSION = '0.0.0'
3
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'git_multicast/version'
2
+ require_relative 'git_multicast/cloner'
3
+ require_relative 'git_multicast/puller'
4
+ require_relative 'git_multicast/output_formatter'
5
+ require_relative 'git_multicast/bitbucket_adapter'
6
+
7
+ require_relative 'git_multicast/github_fetcher'
8
+ require_relative 'git_multicast/bitbucket_fetcher'
9
+ require_relative 'git_multicast/repository_fetcher'
10
+
11
+ module GitMulticast
12
+ end
@@ -0,0 +1,25 @@
1
+ describe GitMulticast::BitbucketAdapter do
2
+ subject(:adapter) { described_class.new(repo) }
3
+
4
+ let(:repo) { double(:repo) }
5
+
6
+ before do
7
+ allow(repo).to receive_message_chain(:links, :_clone, :last, :href)
8
+ .and_return('git@bucketbit.org:foo/bar.git')
9
+ allow(repo).to receive_message_chain(:links, :self, :href)
10
+ .and_return('http://bucketbit.org/test-repo')
11
+
12
+ allow(repo).to receive(:name).and_return('test-repo')
13
+ allow(repo).to receive(:parent).and_return(nil)
14
+ end
15
+
16
+ describe '#adapt' do
17
+ subject(:adapt) { adapter.adapt }
18
+
19
+ it { expect(adapt.name).to eq('test-repo') }
20
+ it { expect(adapt.fork).to be_falsy }
21
+ it { expect(adapt.url).to eq('http://bucketbit.org/test-repo') }
22
+ it { expect(adapt.ssh_url).to eq('git@bucketbit.org:foo/bar.git') }
23
+ it { expect(adapt.parent).to be_nil }
24
+ end
25
+ end
@@ -0,0 +1,97 @@
1
+ describe GitMulticast::BitbucketFetcher do
2
+ subject(:fetcher) { described_class }
3
+
4
+ let(:uri) { URI(url) }
5
+ let(:response) { instance_double(Net::HTTPResponse) }
6
+ let(:body) do
7
+ '{ "values": [\
8
+ { "links": { "clone": "I be a body" } },\
9
+ { "links": { "clone": "I be other body" } }\
10
+ ]}'
11
+ end
12
+
13
+ let(:json) do
14
+ { 'values' =>
15
+ [
16
+ { 'links' => { 'clone' => 'I be a body' } },
17
+ { 'links' => { 'clone' => 'I be other body' } }
18
+ ]
19
+ }
20
+ end
21
+
22
+ let(:adapter) do
23
+ instance_double(GitMulticast::BitbucketAdapter, adapt: adapted_repo)
24
+ end
25
+ let(:adapted_repo) { double(:adapted_repo) }
26
+
27
+ before do
28
+ allow(JSON).to receive(:parse).and_return(json)
29
+
30
+ allow(Net::HTTP).to receive(:get_response).and_return(response)
31
+ allow(response).to receive(:body).and_return(body)
32
+
33
+ allow(GitMulticast::BitbucketAdapter).to receive(:new).and_return(adapter)
34
+ end
35
+
36
+ describe '.get_repo' do
37
+ subject(:get_repo) { fetcher.get_repo(url) }
38
+
39
+ let(:url) { 'http://example.com/foo/bar/33' }
40
+
41
+ it 'calls http get' do
42
+ expect(Net::HTTP).to receive(:get_response).with(uri)
43
+
44
+ get_repo
45
+ end
46
+
47
+ it 'parses the resulting json' do
48
+ expect(JSON).to receive(:parse).with(body)
49
+
50
+ get_repo
51
+ end
52
+
53
+ it 'makes a struct with the result body' do
54
+ expect(RecursiveOpenStruct).to receive(:new).with(
55
+ json, recurse_over_arrays: true
56
+ )
57
+
58
+ get_repo
59
+ end
60
+
61
+ it 'adapts result to the standard interface' do
62
+ expect(adapter).to receive(:adapt)
63
+ is_expected.to eq(adapted_repo)
64
+ end
65
+ end
66
+
67
+ describe '.get_all_repos_from_user' do
68
+ subject(:get_all_repos_from_user) { fetcher.get_all_repos_from_user(user) }
69
+
70
+ let(:user) { 'mrwhite' }
71
+ let(:url) { 'https://bitbucket.org/api/2.0/repositories/mrwhite' }
72
+
73
+ it 'calls http get' do
74
+ expect(Net::HTTP).to receive(:get_response).with(uri)
75
+
76
+ get_all_repos_from_user
77
+ end
78
+
79
+ it 'parses the resulting json' do
80
+ expect(JSON).to receive(:parse).with(body)
81
+
82
+ get_all_repos_from_user
83
+ end
84
+
85
+ it 'builds each repository as a RecursiveOpenStruct' do
86
+ expect(RecursiveOpenStruct).to receive(:new).twice
87
+
88
+ get_all_repos_from_user
89
+ end
90
+
91
+ it 'adapts each struct' do
92
+ expect(adapter).to receive(:adapt).twice
93
+
94
+ get_all_repos_from_user
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,70 @@
1
+ module GitMulticast
2
+ describe Cloner do
3
+ subject(:cloner) { described_class.new(username, dir) }
4
+
5
+ let(:username) { 'ironman' }
6
+ let(:dir) { '/kifita/' }
7
+
8
+ let(:repo) do
9
+ double(
10
+ :repo,
11
+ ssh_url: 'git@hubgit.com:bar/foo',
12
+ url: 'http://hubgit.com/bar/foo',
13
+ fork: false,
14
+ name: 'foo')
15
+ end
16
+ let(:repos) { [repo] * 3 }
17
+
18
+ before do
19
+ allow(cloner).to receive(:spawn).and_return(nil)
20
+ allow(cloner).to receive(:waitall).and_return([])
21
+
22
+ allow(RepositoryFetcher).to receive(:get_all_repos_from_user)
23
+ .and_return(repos)
24
+ allow(OutputFormatter).to receive(:format)
25
+ end
26
+
27
+ describe '#clone!' do
28
+ subject(:clone!) { cloner.clone! }
29
+
30
+ it 'spawns a clone job for each repo' do
31
+ expect(cloner).to receive(:spawn)
32
+ .with("git clone #{repo.ssh_url} /kifita/foo").exactly(3).times
33
+
34
+ clone!
35
+ end
36
+
37
+ context 'when repo is a fork'do
38
+ let(:parent_repo) do
39
+ double(:parent, ssh_url: 'git@hubgit.com:parent/repo')
40
+ end
41
+
42
+ before do
43
+ allow(RepositoryFetcher).to receive(
44
+ :get_repo_parent
45
+ ).and_return(parent_repo)
46
+
47
+ allow(repo).to receive(:fork).and_return(true)
48
+ end
49
+
50
+ it 'gets parent repository by url' do
51
+ expect(RepositoryFetcher).to receive(:get_repo_parent)
52
+ .with(repo.url)
53
+
54
+ clone!
55
+ end
56
+
57
+ it 'adds upstream remote' do
58
+ expect(cloner).to receive(:spawn)
59
+ .with(
60
+ "git clone #{repo.ssh_url} /kifita/foo && \
61
+ git -C \"/kifita/foo\" remote add upstream #{parent_repo.ssh_url} \
62
+ --fetch"
63
+ )
64
+
65
+ clone!
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,74 @@
1
+ describe GitMulticast::GithubFetcher do
2
+ subject(:fetcher) { described_class }
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,51 @@
1
+ describe GitMulticast::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 -r 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,33 @@
1
+ describe GitMulticast::RepositoryFetcher do
2
+ subject(:fetcher) { described_class }
3
+
4
+ let(:fetchers) { described_class::FETCHERS }
5
+
6
+ let(:username) { 'chuck norris' }
7
+
8
+ describe '.get_all_repos_from_user' do
9
+ subject(:get_all_repos_from_user) do
10
+ fetcher.get_all_repos_from_user(username)
11
+ end
12
+
13
+ it 'gets repositories from all fetchers' do
14
+ fetchers.each do |e|
15
+ expect(e).to receive(:get_all_repos_from_user)
16
+ end
17
+
18
+ get_all_repos_from_user
19
+ end
20
+ end
21
+
22
+ describe 'self.get_parent_repo' do
23
+ subject(:get_parent_repo) { fetcher.get_parent_repo(url) }
24
+
25
+ let(:url) { 'http://bitbucket.im.wrong.as.hell' }
26
+
27
+ it 'delegates to the right fetcher' do
28
+ expect(GitMulticast::BitbucketFetcher).to receive(:get_parent_repo).with(url)
29
+
30
+ get_parent_repo
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ require_relative '../lib/git_multicast'
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,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_multicast
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4.pre
5
+ platform: ruby
6
+ authors:
7
+ - Renan Ranelli
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-06 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_multicast
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_multicast
113
+ - git_multicast.gemspec
114
+ - lib/git_multicast.rb
115
+ - lib/git_multicast/bitbucket_adapter.rb
116
+ - lib/git_multicast/bitbucket_fetcher.rb
117
+ - lib/git_multicast/cli.rb
118
+ - lib/git_multicast/cloner.rb
119
+ - lib/git_multicast/github_fetcher.rb
120
+ - lib/git_multicast/output_formatter.rb
121
+ - lib/git_multicast/puller.rb
122
+ - lib/git_multicast/repository_fetcher.rb
123
+ - lib/git_multicast/version.rb
124
+ - spec/git_multicast/bitbucket_adapter_spec.rb
125
+ - spec/git_multicast/bitbucket_fetcher_spec.rb
126
+ - spec/git_multicast/cloner_spec.rb
127
+ - spec/git_multicast/github_fetcher_spec.rb
128
+ - spec/git_multicast/puller_spec.rb
129
+ - spec/git_multicast/repository_fetcher_spec.rb
130
+ - spec/spec_helper.rb
131
+ homepage: http://github.com/rranelli/git_multicast
132
+ licenses:
133
+ - MIT
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '2.0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">"
147
+ - !ruby/object:Gem::Version
148
+ version: 1.3.1
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.2.2
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: Execute mass actions on git repositories concurrently
155
+ test_files: []