git-fastclone 0.0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of git-fastclone might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 153c297d291e6ad8edcb66333438e70a4babfcfe
4
- data.tar.gz: a3df048e6e1ab5e900e341513cb20248d996896d
3
+ metadata.gz: 4f2b4822387b6fa1c847455e8c09cf2bd48b711b
4
+ data.tar.gz: c30eba26437f7f4f4160af9ce0bf8e9e662d36b6
5
5
  SHA512:
6
- metadata.gz: 1f31a2cdd9cb886b3af613a639dc5992eb5653c69b116e506e701053a037a0e25223cee0a6d7241220d9138c71b4351db54afc91215b5dce3ad3a9f364bb1acf
7
- data.tar.gz: bf0c7a716e84db621438ee3b5ef58df7f46349a92c7a1d875d8aa3b32b5f0e2239c63220d3df8bed84b8ba03d7c2b26c2882496effc71006315be0e5ae8673aa
6
+ metadata.gz: ac6a82830270df7b8d1918905883cd9c8f9845f653b3a0886c3ca701f19fd84c5773e1179db7db0c3a83ab4469fe8abd013573fe19adf6babf9be28e10b2363a
7
+ data.tar.gz: fdf2fec80e7b8e7a13aef313c0c231159a363c49f6ef6dd25f954b4e1ca8fced18512bd6cac66f624c331e98a1548caa8043e9b9addd841f5f4312bd9f1e9dd2
data/LICENSE.txt ADDED
@@ -0,0 +1,177 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ git-fastclone
2
+ =============
3
+ [![Build Status](https://travis-ci.org/square/git-fastclone.svg?branch=master)](https://travis-ci.org/square/git-fastclone)
4
+
5
+ git-fastclone is git clone --recursive on steroids.
6
+
7
+
8
+ Why fastclone?
9
+ --------------
10
+ Doing lots of repeated checkouts on a specific machine?
11
+
12
+ | Repository | 1st Fastclone | 2nd Fastclone | git clone | cp -R |
13
+ | -----------|---------------|---------------|-----------|-------|
14
+ | angular.js | 8s | 3s | 6s | 0.5s |
15
+ | bootstrap | 26s | 3s | 11s | 0.2s |
16
+ | gradle | 25s | 9s | 19s | 6.2s |
17
+ | linux | 4m 53s | 1m 6s | 3m 51s | 29s |
18
+ | react.js | 18s | 3s | 8s | 0.5s |
19
+ | tensorflow | 19s | 4s | 8s | 1.5s |
20
+
21
+ Above times captured using `time` without verbose mode.
22
+
23
+
24
+ What does it do?
25
+ ----------------
26
+ It creates a reference repo with `git clone --mirror` in `/var/tmp/git-fastclone/reference` for each
27
+ repository and git submodule linked in the main repo. You can control where it puts these by
28
+ changing the `REFERENCE_REPO_DIR` environment variable.
29
+
30
+ It aggressively updates these mirrors from origin and then clones from the mirrors into the
31
+ directory of your choosing. It always works recursively and multithreaded to get your checkout up as
32
+ fast as possible.
33
+
34
+
35
+ Usage
36
+ -----
37
+ gem install git-fastclone
38
+ git fastclone [options] <git-repo-url>
39
+
40
+ -b, --branch <branch> Clone a specific branch
41
+ -v, --verbose Shows more info
42
+
43
+ Change the default `REFERENCE_REPO_DIR` environment variable if necessary.
44
+
45
+ Cygwin users need to add `~/bin` to PATH.
46
+
47
+
48
+ How to test?
49
+ ------------
50
+ Manual testing:
51
+
52
+ ruby -Ilib bin/git-fastclone <git url>
53
+
54
+ Compatible with Travis and Kochiku.
55
+
56
+
57
+ Contributing
58
+ ------------
59
+ If you would like to contribute to git-fastclone, you can fork the repository and send us pull
60
+ requests.
61
+
62
+ When submitting code, please make every effort to follow existing conventions and style in order to
63
+ keep the code as readable as possible.
64
+
65
+ Before accepting any pull requests, we need you to sign an [Individual Contributor Agreement][1]
66
+ (Google form).
67
+
68
+
69
+ Acknowledgements
70
+ ----------------
71
+ [thoughtbot/cocaine][2] - jyurek and collaborators
72
+
73
+ [robolson][3]
74
+
75
+ [ianchesal][4]
76
+
77
+ [mtauraso][5]
78
+
79
+ [chriseckhardt][6]
80
+
81
+
82
+ License
83
+ -------
84
+ Copyright 2015 Square Inc.
85
+
86
+ Licensed under the Apache License, Version 2.0 (the "License");
87
+ you may not use this file except in compliance with the License.
88
+ You may obtain a copy of the License at
89
+
90
+ http://www.apache.org/licenses/LICENSE-2.0
91
+
92
+ Unless required by applicable law or agreed to in writing, software
93
+ distributed under the License is distributed on an "AS IS" BASIS,
94
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
95
+ See the License for the specific language governing permissions and
96
+ limitations under the License.
97
+
98
+
99
+ [1]: https://docs.google.com/a/squareup.com/forms/d/13WR8m5uZ2nAkJH41k7GdVBXAAbzDk00vxtEYjd6Imzg/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
100
+ [2]: https://github.com/thoughtbot/cocaine
101
+ [3]: https://github.com/robolson
102
+ [4]: https://github.com/ianchesal
103
+ [5]: https://github.com/mtauraso
104
+ [6]: https://github.com/chriseckhardt
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bundler/setup'
4
+
5
+ task default: %w(spec rubocop)
6
+
7
+ require 'rspec/core/rake_task'
8
+ RSpec::Core::RakeTask.new
9
+
10
+ require 'rubocop/rake_task'
11
+ RuboCop::RakeTask.new
data/bin/git-fastclone CHANGED
@@ -1,5 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Copyright 2015 Square Inc.
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
18
+
3
19
  require 'git-fastclone'
4
20
 
5
- GitFastClone.new.run
21
+ GitFastClone::Runner.new.run
data/lib/git-fastclone.rb CHANGED
@@ -1,184 +1,261 @@
1
+ # Copyright 2015 Square Inc.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
1
15
  require 'optparse'
2
16
  require 'fileutils'
3
- require_relative 'execution'
4
-
5
- class GitFastClone
6
- def initialize()
7
- # Prefetch reference repos for submodules we've seen before
8
- # Keep our own reference accounting of module dependencies.
9
- @prefetch_submodules = true
10
-
11
- # Thread-level locking for reference repos
12
- # TODO: Add flock-based locking if we want to avoid conflicting with ourselves.
13
- @reference_mutex = Hash.new { |hash, key| hash[key] = Mutex.new() }
14
-
15
- # Only update each reference repo once per run.
16
- # TODO: May want to update this so we don't duplicate work with other copies of ourself
17
- # Perhaps a last-updated-time and a timeout per reference repo.
18
- @reference_updated = Hash.new { |hash, key| hash[key] = false }
19
- end
17
+ require 'logger'
18
+ require 'cocaine'
20
19
 
21
- def run()
22
- @reference_dir = ENV['REFERENCE_REPO_DIR'] || "/var/tmp/git-fastclone/reference"
20
+ # Contains helper module UrlHelper and execution class GitFastClone::Runner
21
+ module GitFastClone
22
+ # Helper methods for fastclone url operations
23
+ module UrlHelper
24
+ def path_from_git_url(url)
25
+ File.basename(url, '.git')
26
+ end
27
+ module_function :path_from_git_url
28
+
29
+ def parse_update_info(line)
30
+ [line.strip.match(/'([^']*)'$/)[1], line.strip.match(/\(([^)]*)\)/)[1]]
31
+ end
32
+ module_function :parse_update_info
23
33
 
24
- FileUtils.mkdir_p(@reference_dir)
34
+ def reference_repo_name(url)
35
+ "#{url.gsub(%r{^.*://}, '').gsub(/^[^@]*@/, '').tr('/', '-').tr(':', '-')}"
36
+ end
37
+ module_function :reference_repo_name
25
38
 
26
- # One option --branch=<branch> We're not as brittle as clone. That branch can be a sha or tag and we're still okay.
27
- @options = {}
28
- OptionParser.new do |opts|
29
- opts.banner = "Usage: git fastclone [options] <git-url> [path]"
30
- @options[:branch] = nil
31
- opts.on("-b", "--branch BRANCH", "Checkout this branch rather than the default") do |branch|
32
- @options[:branch] = branch
39
+ def reference_repo_dir(url, reference_dir, using_local_repo)
40
+ if using_local_repo
41
+ File.join(reference_dir, 'local' + reference_repo_name(url))
42
+ else
43
+ File.join(reference_dir, reference_repo_name(url))
33
44
  end
34
- end.parse!
45
+ end
46
+ module_function :reference_repo_dir
35
47
 
36
- # Remaining two positional args are url and optional path
37
- url = ARGV[0]
38
- path = ARGV[1] || path_from_git_url(url)
48
+ def reference_repo_submodule_file(url, reference_dir, using_local_repo)
49
+ "#{reference_repo_dir(url, reference_dir, using_local_repo)}:submodules.txt"
50
+ end
51
+ module_function :reference_repo_submodule_file
52
+ end
39
53
 
40
- puts "Cloning #{url} to #{path}"
54
+ # Spawns one thread per submodule, and updates them in parallel. They will be
55
+ # cached in the reference directory (see DEFAULT_REFERENCE_REPO_DIR), and their
56
+ # index will be incrementally updated. This prevents a large amount of data
57
+ # copying.
58
+ class Runner
59
+ include GitFastClone::UrlHelper
41
60
 
42
- # Do a checkout with reference repositories for main and submodules
43
- clone(url, @options[:branch], path)
44
- end
61
+ DEFAULT_REFERENCE_REPO_DIR = '/var/tmp/git-fastclone/reference'
45
62
 
46
- def path_from_git_url(url)
47
- # Get the checkout path from tail-end of the url.
48
- url.match(/([^\/]*)\.git$/)[1]
49
- end
63
+ attr_accessor :reference_dir, :prefetch_submodules, :reference_mutex, :reference_updated,
64
+ :options, :logger, :abs_clone_path, :using_local_repo
65
+
66
+ def initialize
67
+ # Prefetch reference repos for submodules we've seen before
68
+ # Keep our own reference accounting of module dependencies.
69
+ self.prefetch_submodules = true
70
+
71
+ # Thread-level locking for reference repos
72
+ # TODO: Add flock-based locking if we want to avoid conflicting with
73
+ # ourselves.
74
+ self.reference_mutex = Hash.new { |hash, key| hash[key] = Mutex.new }
75
+
76
+ # Only update each reference repo once per run.
77
+ # TODO: May want to update this so we don't duplicate work with other copies
78
+ # of ourself. Perhaps a last-updated-time and a timeout per reference repo.
79
+ self.reference_updated = Hash.new { |hash, key| hash[key] = false }
80
+
81
+ self.options = {}
50
82
 
51
- # Checkout to SOURCE_DIR. Update all submodules recursively. Use reference repos everywhere for speed.
52
- def clone(url, rev, src_dir)
53
- initial_time = Time.now()
83
+ self.logger = nil # Only set in verbose mode
54
84
 
55
- with_git_mirror(url) do |mirror|
56
- fail_on_error("git", "clone", "--quiet", "--reference", mirror, url, src_dir)
85
+ self.abs_clone_path = Dir.pwd
86
+
87
+ self.using_local_repo = false
57
88
  end
58
89
 
59
- # Only checkout if we're changing branches to a non-default branch
60
- unless rev.nil? then
61
- fail_on_error("git", "checkout", "--quiet", rev, :chdir=>src_dir)
90
+ def run
91
+ url, path, options = parse_inputs
92
+ logger.info("Cloning #{url} to #{path}") if logger
93
+ clone(url, options[:branch], path)
62
94
  end
63
95
 
64
- update_submodules(src_dir, url)
96
+ def parse_inputs
97
+ usage = 'Usage: git fastclone [options] <git-url> [path]'
65
98
 
66
- final_time = Time.now()
67
- puts "Checkout of #{url} took #{final_time-initial_time}s"
68
- end
99
+ # One option --branch=<branch> We're not as brittle as clone. That branch
100
+ # can be a sha or tag and we're still okay.
101
+ OptionParser.new do |opts|
102
+ opts.banner = usage
103
+ options[:branch] = nil
104
+ opts.on('-b', '--branch BRANCH', 'Checkout this branch rather than the default') do |branch|
105
+ options[:branch] = branch
106
+ end
107
+ opts.on('-v', '--verbose', 'Verbose mode') do
108
+ self.logger = Logger.new(STDOUT)
109
+ Cocaine::CommandLine.logger = logger
110
+ end
111
+ end.parse!
112
+
113
+ fail usage unless ARGV[0]
114
+
115
+ if Dir.exist?(ARGV[0])
116
+ url = File.expand_path ARGV[0]
117
+ self.using_local_repo = true
118
+ else
119
+ url = ARGV[0]
120
+ end
121
+
122
+ path = ARGV[1] || path_from_git_url(url)
123
+
124
+ fail "Clone destination #{path} already exists!" if Dir.exist?(path)
125
+
126
+ self.reference_dir = ENV['REFERENCE_REPO_DIR'] || DEFAULT_REFERENCE_REPO_DIR
127
+ FileUtils.mkdir_p(reference_dir)
128
+
129
+ [url, path, options]
130
+ end
131
+
132
+ # Checkout to SOURCE_DIR. Update all submodules recursively. Use reference
133
+ # repos everywhere for speed.
134
+ def clone(url, rev, src_dir)
135
+ initial_time = Time.now
136
+
137
+ with_git_mirror(url) do |mirror|
138
+ Cocaine::CommandLine.new("git clone --quiet --reference '#{mirror}' '#{url}'" \
139
+ " '#{File.join(abs_clone_path, src_dir)}'").run
140
+ end
141
+
142
+ # Only checkout if we're changing branches to a non-default branch
143
+ Dir.chdir(src_dir) { Cocaine::CommandLine.new("git checkout --quiet '#{rev}'").run } if rev
144
+
145
+ update_submodules(src_dir, url)
69
146
 
70
- # Update all submodules in current directory recursively
71
- # Use a reference repository for speed.
72
- # Use a separate thread for each submodule.
73
- def update_submodules (pwd, url)
74
- # Skip if there's no submodules defined
75
- if File.exist?(File.join(pwd,".gitmodules")) then
147
+ final_time = Time.now
148
+ logger.info("Checkout of #{url} took #{final_time - initial_time}s") if logger
149
+ end
150
+
151
+ def update_submodules(pwd, url)
152
+ return unless File.exist?(File.join(abs_clone_path, pwd, '.gitmodules'))
153
+
154
+ logger.info('Updating submodules') if logger
76
155
 
77
- # Update each submodule on a different thread.
78
156
  threads = []
79
157
  submodule_url_list = []
80
158
 
81
- # Init outputs all the info we need to run the update commands.
82
- # Parse its output directly to save time.
83
- fail_on_error("git", "submodule", "init", :chdir=>pwd).split("\n").each do |line|
84
- # Submodule path (not name) is in between single quotes '' at the end of the line
85
- submodule_path = line.strip.match(/'([^']*)'$/)[1]
86
- # URL is in between parentheses ()
87
- submodule_url = line.strip.match(/\(([^)]*)\)/)[1]
159
+ Cocaine::CommandLine.new("cd '#{File.join(abs_clone_path, pwd)}'; git submodule init").run
160
+ .split("\n").each do |line|
161
+ submodule_path, submodule_url = parse_update_info(line)
88
162
  submodule_url_list << submodule_url
89
163
 
90
- # Each update happens on a separate thread for speed.
91
- threads << Thread.new do
92
- with_git_mirror(submodule_url) do |mirror|
93
- fail_on_error("git", "submodule", "update", "--quiet", "--reference", mirror, submodule_path, :chdir=>pwd)
94
- end
95
- # Recurse into the submodule directory
96
- update_submodules(File.join(pwd,submodule_path), submodule_url)
97
- end
164
+ thread_update_submodule(submodule_url, submodule_path, threads, pwd)
98
165
  end
166
+
99
167
  update_submodule_reference(url, submodule_url_list)
100
- threads.each {|t| t.join}
168
+ threads.each(&:join)
101
169
  end
102
- end
103
-
104
- def reference_repo_name(url)
105
- # Derive a unique directory name from the git url.
106
- url.gsub(/^.*:\/\//, "").gsub(/^[^@]*@/, "").gsub("/","-").gsub(":","-")
107
- end
108
170
 
109
- def reference_repo_dir(url)
110
- File.join(@reference_dir, reference_repo_name(url))
111
- end
171
+ def thread_update_submodule(submodule_url, submodule_path, threads, pwd)
172
+ threads << Thread.new do
173
+ with_git_mirror(submodule_url) do |mirror|
174
+ Cocaine::CommandLine
175
+ .new("cd '#{File.join(abs_clone_path, pwd)}'; git submodule update --quiet --reference"\
176
+ " '#{mirror}' '#{submodule_path}'").run
177
+ end
112
178
 
113
- def reference_repo_submodule_file(url)
114
- # ':' is never a valid char in a reference repo dir, so this
115
- # uniquely maps to a particular reference repo.
116
- "#{reference_repo_dir(url)}:submodules.txt"
117
- end
179
+ update_submodules(File.join(pwd, submodule_path), submodule_url)
180
+ end
181
+ end
118
182
 
119
- def with_reference_repo_lock(url)
120
- @reference_mutex[reference_repo_name(url)].synchronize do
121
- yield
183
+ def with_reference_repo_lock(url)
184
+ reference_mutex[reference_repo_name(url)].synchronize do
185
+ yield
186
+ end
122
187
  end
123
- end
124
188
 
125
- def update_submodule_reference(url, submodule_url_list)
126
- if submodule_url_list != [] and @prefetch_submodules then
127
- with_reference_repo_lock(url) do
189
+ def update_submodule_reference(url, submodule_url_list)
190
+ return if submodule_url_list.empty? || prefetch_submodules.nil?
128
191
 
192
+ with_reference_repo_lock(url) do
129
193
  # Write the dependency file using submodule list
130
- File.open(reference_repo_submodule_file(url), 'w') do |f|
131
- submodule_url_list.each do |submodule_url|
132
- f.write("#{submodule_url}\n")
133
- end
194
+ File.open(reference_repo_submodule_file(url, reference_dir, using_local_repo), 'w') do |f|
195
+ submodule_url_list.each { |submodule_url| f.write("#{submodule_url}\n") }
134
196
  end
135
-
136
197
  end
137
198
  end
138
- end
139
199
 
140
- def update_reference_repo(url)
141
- repo_name = reference_repo_name(url)
142
- mirror = reference_repo_dir(url)
200
+ # Fail_hard indicates whether the update is considered a failure of the
201
+ # overall checkout or not. When we pre-fetch based off of cached information,
202
+ # fail_hard is false. When we fetch based off info in a repository directly,
203
+ # fail_hard is true.
204
+ def update_reference_repo(url, fail_hard)
205
+ repo_name = reference_repo_name(url)
206
+ mirror = reference_repo_dir(url, reference_dir, using_local_repo)
143
207
 
144
- with_reference_repo_lock(url) do
145
- submodule_file = reference_repo_submodule_file(url)
146
- if File.exist?(submodule_file) and @prefetch_submodules then
147
- File.readlines(submodule_file).each do |line|
148
- # We don't join these threads explicitly
149
- Thread.new { update_reference_repo(line.strip) }
150
- end
208
+ with_reference_repo_lock(url) do
209
+ # we've created this to track submodules' history
210
+ submodule_file = reference_repo_submodule_file(url, reference_dir, using_local_repo)
211
+
212
+ # if prefetch is on, then grab children immediately to frontload network requests
213
+ prefetch(submodule_file) if File.exist?(submodule_file) && prefetch_submodules
214
+
215
+ # Store the fact that our repo has been updated if necessary
216
+ store_updated_repo(url, mirror, repo_name, fail_hard) unless reference_updated[repo_name]
151
217
  end
218
+ end
152
219
 
153
- if !@reference_updated[repo_name] then
154
- if !Dir.exist?(mirror)
155
- fail_on_error("git", "clone", "--mirror", url, mirror)
156
- end
157
- fail_on_error("git", "remote", "update", :chdir=> mirror)
158
- @reference_updated[repo_name] = true
220
+ # Grab the children in the event of a prefetch
221
+ def prefetch(submodule_file)
222
+ File.readlines(submodule_file).each do |line|
223
+ # We don't join these threads explicitly
224
+ Thread.new { update_reference_repo(line.strip, false) }
159
225
  end
160
226
  end
161
- end
162
227
 
163
- # Executes a block passing in the directory of an up-to-date local git mirror
164
- # for the given url. This will speed up most git commands that ask for data
165
- # over the network after the mirror is cloned initially.
166
- #
167
- # This command will create and bring the mirror up-to-date on-demand,
168
- # blocking any code passed in while the mirror is brought up-to-date
169
- #
170
- # In future we may need to synchronize with flock here if we run multiple builds
171
- # at once against the same reference repos. One build per slave at the moment means
172
- # we only need to synchronize our own threads in case a single submodule url is
173
- # included twice via multiple dependency paths
174
- def with_git_mirror(url)
175
- update_reference_repo(url)
176
-
177
- # Sometimes remote updates involve re-packing objects on a different thread
178
- # We grab the reference repo lock here just to make sure whatever thread
179
- # ended up doing the update is done with its housekeeping.
180
- with_reference_repo_lock(url) do
181
- yield reference_repo_dir(url)
228
+ # Stores the fact that our repo has been updated
229
+ def store_updated_repo(url, mirror, repo_name, fail_hard)
230
+ unless Dir.exist?(mirror)
231
+ Cocaine::CommandLine.new("git clone --mirror '#{url}' '#{mirror}'").run
232
+ end
233
+
234
+ Cocaine::CommandLine.new("cd '#{mirror}'; git remote update --prune").run
235
+
236
+ reference_updated[repo_name] = true
237
+
238
+ rescue Cocaine::ExitStatusError => e
239
+ raise e if fail_hard
240
+ end
241
+
242
+ # This command will create and bring the mirror up-to-date on-demand,
243
+ # blocking any code passed in while the mirror is brought up-to-date
244
+ #
245
+ # In future we may need to synchronize with flock here if we run multiple
246
+ # builds at once against the same reference repos. One build per slave at the
247
+ # moment means we only need to synchronize our own threads in case a single
248
+ # submodule url is included twice via multiple dependency paths
249
+ def with_git_mirror(url)
250
+ update_reference_repo(url, true)
251
+
252
+ # Sometimes remote updates involve re-packing objects on a different thread
253
+ # We grab the reference repo lock here just to make sure whatever thread
254
+ # ended up doing the update is done with its housekeeping.
255
+ # This makes sure we have control and unlock when the block returns:
256
+ with_reference_repo_lock(url) do
257
+ yield reference_repo_dir(url, reference_dir, using_local_repo)
258
+ end
182
259
  end
183
260
  end
184
261
  end
@@ -0,0 +1,3 @@
1
+ module GitFastCloneVersion
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,249 @@
1
+ # Copyright 2015 Square Inc.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+ require 'git-fastclone'
17
+
18
+ describe GitFastClone::Runner do
19
+ let(:test_url_valid) { 'ssh://git@git.com/git-fastclone.git' }
20
+ let(:test_url_invalid) { 'ssh://git@git.com/git-fastclone' }
21
+ let(:test_reference_dir) { 'test_reference_dir' }
22
+ let(:test_reference_repo_dir) { '/var/tmp/git-fastclone/reference/test_reference_dir' }
23
+ let(:placeholder_arg) { 'PH' }
24
+
25
+ # Modified ARGV, watch out
26
+ ARGV = ['ssh://git@git.com/git-fastclone.git', 'test_reference_dir']
27
+
28
+ let(:yielded) { [] }
29
+
30
+ describe '.initialize' do
31
+ it 'should initialize properly' do
32
+ stub_const('GitFastClone::DEFAULT_REFERENCE_REPO_DIR', 'new_dir')
33
+
34
+ expect(Hash).to respond_to(:new).with(2).arguments
35
+ expect(GitFastClone::DEFAULT_REFERENCE_REPO_DIR).to eq('new_dir')
36
+ expect(subject.prefetch_submodules).to eq(true)
37
+ expect(subject.reference_mutex).to eq({})
38
+ expect(subject.reference_updated).to eq({})
39
+ expect(subject.options).to eq({})
40
+ expect(subject.logger).to eq(nil)
41
+ end
42
+ end
43
+
44
+ describe '.run' do
45
+ let(:options) { {:branch => placeholder_arg} }
46
+
47
+ it 'should run with the correct args' do
48
+ allow(subject).to receive(:parse_inputs) { [placeholder_arg, placeholder_arg, options] }
49
+ expect(subject).to receive(:clone).with(placeholder_arg, placeholder_arg, placeholder_arg)
50
+
51
+ subject.run
52
+ end
53
+ end
54
+
55
+ describe '.parse_inputs' do
56
+ it 'should print the proper inputs' do
57
+ subject.reference_dir = test_reference_dir
58
+ subject.options = {}
59
+ allow(FileUtils).to receive(:mkdir_p) {}
60
+
61
+ expect(subject.parse_inputs).to eq([test_url_valid, test_reference_dir, {:branch=>nil}])
62
+ end
63
+ end
64
+
65
+ describe '.clone' do
66
+ it 'should clone correctly' do
67
+ cocaine_commandline_double = double('new_cocaine_commandline')
68
+ allow(subject).to receive(:with_git_mirror) {}
69
+ allow(cocaine_commandline_double).to receive(:run) {}
70
+ allow(Cocaine::CommandLine).to receive(:new) { cocaine_commandline_double }
71
+
72
+ expect(Time).to receive(:now).twice
73
+ expect(Cocaine::CommandLine).to receive(:new)
74
+ expect(cocaine_commandline_double).to receive(:run)
75
+
76
+ subject.clone(placeholder_arg, placeholder_arg, '.')
77
+ end
78
+ end
79
+
80
+ describe '.update_submodules' do
81
+ it 'should return if no submodules' do
82
+ subject.update_submodules(placeholder_arg, placeholder_arg)
83
+ allow(File).to receive(:exist?) { false }
84
+
85
+ expect(Thread).not_to receive(:new)
86
+ end
87
+
88
+ it 'should correctly update submodules' do
89
+ expect(subject).to receive(:update_submodule_reference)
90
+
91
+ allow(File).to receive(:exist?) { true }
92
+ subject.update_submodules('.', placeholder_arg)
93
+ end
94
+ end
95
+
96
+ describe '.thread_update_submodule' do
97
+ it 'should update correctly' do
98
+ pending('need to figure out how to test this')
99
+ fail
100
+ end
101
+ end
102
+
103
+ describe '.with_reference_repo_lock' do
104
+ it 'should acquire a lock' do
105
+ allow(Mutex).to receive(:synchronize)
106
+ expect(Mutex).to respond_to(:synchronize)
107
+
108
+ subject.with_reference_repo_lock(test_url_valid) do
109
+ yielded << test_url_valid
110
+ end
111
+
112
+ expect(yielded).to eq([test_url_valid])
113
+ end
114
+ end
115
+
116
+ describe '.update_submodule_reference' do
117
+ context 'when we have an empty submodule list' do
118
+ it 'should return' do
119
+ expect(subject).not_to receive(:with_reference_repo_lock)
120
+
121
+ subject.prefetch_submodules = true
122
+ subject.update_submodule_reference(placeholder_arg, [])
123
+ end
124
+ end
125
+
126
+ context 'with a populated submodule list' do
127
+ it 'should write to a file' do
128
+ allow(File).to receive(:open) {}
129
+ allow(File).to receive(:write) {}
130
+ allow(subject).to receive(:reference_repo_name) {}
131
+ allow(subject).to receive(:reference_repo_submodule_file) {}
132
+ expect(File).to receive(:open)
133
+
134
+ subject.update_submodule_reference(placeholder_arg, [placeholder_arg, placeholder_arg])
135
+ end
136
+ end
137
+ end
138
+
139
+ describe '.update_reference_repo' do
140
+ context 'when prefetch is on' do
141
+ it 'should grab the children immediately and then store' do
142
+ expect(subject).to receive(:prefetch).once
143
+ expect(subject).to receive(:store_updated_repo).once
144
+
145
+ allow(File).to receive(:exist?) { true }
146
+ subject.prefetch_submodules = true
147
+ subject.reference_dir = placeholder_arg
148
+ subject.update_reference_repo(test_url_valid, false)
149
+ end
150
+ end
151
+
152
+ context 'when prefetch is off' do
153
+ it 'should store the updated repo' do
154
+ expect(subject).not_to receive(:prefetch)
155
+ expect(subject).to receive(:store_updated_repo).once
156
+
157
+ allow(File).to receive(:exist?) { true }
158
+ subject.prefetch_submodules = false
159
+ subject.reference_dir = placeholder_arg
160
+ subject.update_reference_repo(placeholder_arg, false)
161
+ end
162
+ end
163
+
164
+ let(:placeholder_hash) { Hash.new }
165
+
166
+ context 'when already have a hash' do
167
+ it 'should not store' do
168
+ placeholder_hash[placeholder_arg] = true
169
+ expect(subject).not_to receive(:store_updated_repo)
170
+
171
+ allow(subject).to receive(:reference_repo_name) { placeholder_arg }
172
+ allow(subject).to receive(:reference_repo_dir) { placeholder_arg }
173
+ subject.reference_updated = placeholder_hash
174
+ subject.prefetch_submodules = false
175
+ subject.update_reference_repo(placeholder_arg, false)
176
+ end
177
+ end
178
+
179
+ context 'when do not have a hash' do
180
+ it 'should store' do
181
+ placeholder_hash[placeholder_arg] = false
182
+ expect(subject).to receive(:store_updated_repo)
183
+
184
+ allow(subject).to receive(:reference_repo_name) { placeholder_arg }
185
+ subject.reference_updated = placeholder_hash
186
+ subject.reference_dir = placeholder_arg
187
+ subject.prefetch_submodules = false
188
+ subject.update_reference_repo(placeholder_arg, false)
189
+ end
190
+ end
191
+ end
192
+
193
+ describe '.prefetch' do
194
+ it 'should go through the submodule file properly' do
195
+ expect(Thread).to receive(:new).exactly(3).times
196
+
197
+ allow(File).to receive(:readlines) { %w(1 2 3) }
198
+ subject.prefetch_submodules = true
199
+ subject.prefetch(placeholder_arg)
200
+ end
201
+ end
202
+
203
+ describe '.store_updated_repo' do
204
+ context 'when fail_hard is true' do
205
+ it 'should raise a Cocaine error' do
206
+ expect do
207
+ subject.store_updated_repo(placeholder_arg, placeholder_arg, placeholder_arg, true)
208
+ end.to raise_error(Cocaine::ExitStatusError)
209
+ end
210
+ end
211
+
212
+ context 'when fail_hard is false' do
213
+ it 'should not raise a cocaine error' do
214
+ cocaine_commandline_double = double('new_cocaine_commandline')
215
+ allow(cocaine_commandline_double).to receive(:run) { fail Cocaine::ExitStatusError }
216
+ allow(Cocaine::CommandLine).to receive(:new) { cocaine_commandline_double }
217
+
218
+ expect do
219
+ subject.store_updated_repo(placeholder_arg, placeholder_arg, placeholder_arg, false)
220
+ end.not_to raise_error(Cocaine::ExitStatusError)
221
+ end
222
+ end
223
+
224
+ let(:placeholder_hash) { Hash.new }
225
+
226
+ it 'should correctly update the hash' do
227
+ cocaine_commandline_double = double('new_cocaine_commandline')
228
+ allow(cocaine_commandline_double).to receive(:run) {}
229
+ allow(Cocaine::CommandLine).to receive(:new) { cocaine_commandline_double }
230
+
231
+ subject.reference_updated = placeholder_hash
232
+ subject.store_updated_repo(placeholder_arg, placeholder_arg, placeholder_arg, false)
233
+ expect(subject.reference_updated).to eq(placeholder_arg => true)
234
+ end
235
+ end
236
+
237
+ describe '.with_git_mirror' do
238
+ it 'should yield properly' do
239
+ allow(subject).to receive(:update_reference_repo) {}
240
+ expect(subject).to receive(:reference_repo_dir)
241
+
242
+ subject.with_git_mirror(test_url_valid) do
243
+ yielded << test_url_valid
244
+ end
245
+
246
+ expect(yielded).to eq([test_url_valid])
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,75 @@
1
+ # Copyright 2015 Square Inc.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+ require 'git-fastclone'
17
+
18
+ describe GitFastClone::UrlHelper do
19
+ let(:test_url_valid) { 'ssh://git@git.com/git-fastclone.git' }
20
+ let(:test_url_invalid) { 'ssh://git@git.com/git-fastclone' }
21
+ let(:test_reference_dir) { 'test_reference_dir' }
22
+ let(:submodule_str) do
23
+ "Submodule 'TestModule' (https://github.com/TestModule1/TestModule2) registered for path
24
+ 'TestModule'"
25
+ end
26
+
27
+ describe '.path_from_git_url' do
28
+ let(:tail) { 'git-fastclone' }
29
+
30
+ context 'with a valid path' do
31
+ it 'should get the tail' do
32
+ expect(subject.path_from_git_url(test_url_valid)).to eq(tail)
33
+ end
34
+ end
35
+
36
+ context 'with an invalid path' do
37
+ it 'should still get the tail' do
38
+ expect(subject.path_from_git_url(test_url_invalid)).to eq(tail)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '.parse_update_info' do
44
+ it 'should parse correctly' do
45
+ expect(subject.parse_update_info(submodule_str))
46
+ .to eq(['TestModule', 'https://github.com/TestModule1/TestModule2'])
47
+ end
48
+ end
49
+
50
+ describe '.reference_repo_name' do
51
+ let(:expected_result) { 'git.com-git-fastclone.git' }
52
+
53
+ it 'should come up with a unique repo name' do
54
+ expect(subject.reference_repo_name(test_url_valid)).to eq(expected_result)
55
+ end
56
+ end
57
+
58
+ describe '.reference_repo_dir' do
59
+ it 'should join correctly' do
60
+ allow(subject).to receive(:reference_repo_name) { test_reference_dir }
61
+
62
+ expect(subject.reference_repo_dir(test_url_valid, test_reference_dir, false))
63
+ .to eq(test_reference_dir + '/' + test_reference_dir)
64
+ end
65
+ end
66
+
67
+ describe '.reference_repo_submodule_file' do
68
+ it 'should return the right string' do
69
+ allow(subject).to receive(:reference_repo_dir) { test_reference_dir }
70
+
71
+ expect(subject.reference_repo_submodule_file(test_url_valid, test_reference_dir, false))
72
+ .to eq('test_reference_dir:submodules.txt')
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,2 @@
1
+ require 'rspec/core'
2
+ require 'rspec/mocks'
metadata CHANGED
@@ -1,29 +1,52 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-fastclone
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Tauraso
8
+ - James Chang
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-11-21 00:00:00.000000000 Z
12
- dependencies: []
12
+ date: 2015-09-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cocaine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
13
28
  description: A git command that uses reference repositories and threading to quickly
14
29
  and recursively clone repositories with many nested submodules
15
- email: mtauraso@gmail.com
30
+ email:
31
+ - mtauraso@squareup.com
32
+ - jchang@squareup.com
16
33
  executables:
17
34
  - git-fastclone
18
35
  extensions: []
19
36
  extra_rdoc_files: []
20
37
  files:
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
21
41
  - bin/git-fastclone
22
- - lib/execution.rb
23
42
  - lib/git-fastclone.rb
24
- homepage: https://github.com/mtauraso/git-fastclone
43
+ - lib/git-fastclone/version.rb
44
+ - spec/git_fastclone_runner_spec.rb
45
+ - spec/git_fastclone_url_helper_spec.rb
46
+ - spec/spec_helper.rb
47
+ homepage: https://github.com/square/git-fastclone
25
48
  licenses:
26
- - MIT
49
+ - Apache
27
50
  metadata: {}
28
51
  post_install_message:
29
52
  rdoc_options: []
@@ -41,8 +64,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
41
64
  version: '0'
42
65
  requirements: []
43
66
  rubyforge_project:
44
- rubygems_version: 2.2.2
67
+ rubygems_version: 2.4.6
45
68
  signing_key:
46
69
  specification_version: 4
47
70
  summary: git-clone --recursive on steroids!
48
- test_files: []
71
+ test_files:
72
+ - spec/git_fastclone_runner_spec.rb
73
+ - spec/git_fastclone_url_helper_spec.rb
74
+ - spec/spec_helper.rb
data/lib/execution.rb DELETED
@@ -1,71 +0,0 @@
1
- require 'open3'
2
-
3
- # Wrapper around open3.popen2e which fails on error
4
- #
5
- # We emulate open3.capture2e with the following changes in behavior:
6
- # 1) The command is printed to stdout before execution.
7
- # 2) Attempts to use the shell implicitly are blocked.
8
- # 3) Nonzero return codes result in the process exiting.
9
- #
10
- # If you're looking for more process/stream control read the spawn documentation, and pass
11
- # options directly here
12
- def fail_on_error (*cmd, **opts)
13
- # puts "Running Command: \n#{debug_print_cmd_list([cmd])}\n"
14
- shell_safe(cmd)
15
- output, status = Open3.capture2(*cmd, opts)
16
- exit_on_status(output, status)
17
- end
18
-
19
- # Look at a cmd list intended for spawn.
20
- # determine if spawn will call the shell implicitly, fail in that case.
21
- def shell_safe (cmd)
22
- # env and opts in the command spec both aren't of type string.
23
- # If you're only passing one string, spawn is going to launch a shell.
24
- if cmd.select{ |element| element.class == String }.length == 1
25
- puts "You tried to use sqiosbuild to call the shell implicitly. Please don't."
26
- puts "Think of the children."
27
- puts "Think of shellshock."
28
- puts "Please don't. Not ever."
29
- exit 1
30
- end
31
- end
32
-
33
- def debug_print_cmd_list(cmd_list)
34
- # Take a list of command argument lists like you'd sent to open3.pipeline or fail_on_error_pipe and
35
- # print out a string that would do the same thing when entered at the shell.
36
- #
37
- # This is a converter from our internal representation of commands to a subset of bash that
38
- # can be executed directly.
39
- #
40
- # Note this has problems if you specify env or opts
41
- # TODO: make this remove those command parts
42
- "\"" +
43
- cmd_list.map { |cmd|
44
- cmd.map { |arg|
45
- arg.gsub("\"", "\\\"") # Escape all double quotes in command arguments
46
- }.join("\" \"") # Fully quote all command parts. We add quotes to the beginning and end too.
47
- }.join("\" | \"") + # Pipe commands to one another.
48
- "\""
49
- end
50
-
51
- # If any of the statuses are bad, exits with the
52
- # return code of the first one.
53
- #
54
- # Otherwise returns first argument (output)
55
- def exit_on_status (output, status)
56
- # Do nothing for proper statuses
57
- if status.exited? && status.exitstatus == 0
58
- return output
59
- end
60
-
61
- # If we exited nonzero or abnormally, print debugging info
62
- # and explode.
63
- if status.exited?
64
- puts "Return code was #{status.exitstatus}"
65
- exit status.exitstatus
66
- end
67
- puts "This might be helpful:\nProcessStatus: #{status.inspect}\nRaw POSIX Status: #{status.to_i}\n"
68
- exit 1
69
- end
70
-
71
-