francois-gip 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +22 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/bin/gip +3 -0
- data/features/import.feature +12 -0
- data/features/step_definitions/gip_steps.rb +12 -0
- data/features/support/command_line.rb +11 -0
- data/features/support/env.rb +8 -0
- data/gip.gemspec +54 -0
- data/lib/gip.rb +186 -0
- data/test/gip_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +69 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 François Beausoleil
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
= Gip into place: Piston without the SVN cruft
|
2
|
+
|
3
|
+
This is an implementation of Tom Dysinger's http://dysinger.net/2008/04/29/replacing-braid-or-piston-for-git-with-40-lines-of-rake/ as a full command-line client. I'm standing on the shoulders of giants...
|
4
|
+
|
5
|
+
Gip is a thin layer above git-read-tree. If you want more information, you can look at http://assets.en.oreilly.com/1/event/24/Smacking%20Git%20Around%20-%20Advanced%20Git%20Tricks%20Presentation.pdf, pages 254-297.
|
6
|
+
|
7
|
+
== Fair Warning
|
8
|
+
|
9
|
+
Since Gip copies the upstream repositories directly in your repository, please be advised that your repository will *grow* quickly. For each remote you add, you will receive all commits from that repository. That also means the full history for that project. And when I mean all, I do mean it. If you vendor Rails, you are forewarned: you will add nearly 20 MiB to your own repository. This is a trade-off between Piston (which only imports the latest HEAD) and having subtrees available for easily propagating changes upstream.
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
$ gip import git://github.com/mislav/will_paginate.git vendor/plugins/mislav-will_paginate
|
14
|
+
$ gip update vendor/plugins/mislav-will_paginate
|
15
|
+
|
16
|
+
Gip stores it's metadata in a .gipinfo file.
|
17
|
+
|
18
|
+
Gip automatically commits whenever possible: after import, after update. If after an update a conflict occurs, the commit will be aborted and you are given the chance to resolve the conflicts. You have the full power of Git at your disposal.
|
19
|
+
|
20
|
+
== Copyright
|
21
|
+
|
22
|
+
Copyright (c) 2009 François Beausoleil. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "gip"
|
8
|
+
gem.summary = %Q{Gip into place: Piston without the SVN cruft}
|
9
|
+
gem.email = "francois@teksol.info"
|
10
|
+
gem.homepage = "http://github.com/francois/gip"
|
11
|
+
gem.authors = ["François Beausoleil"]
|
12
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
Rake::TestTask.new(:test) do |test|
|
21
|
+
test.libs << 'lib' << 'test'
|
22
|
+
test.pattern = 'test/**/*_test.rb'
|
23
|
+
test.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rcov/rcovtask'
|
28
|
+
Rcov::RcovTask.new do |test|
|
29
|
+
test.libs << 'test'
|
30
|
+
test.pattern = 'test/**/*_test.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
task :rcov do
|
35
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'cucumber/rake/task'
|
41
|
+
Cucumber::Rake::Task.new(:features)
|
42
|
+
rescue LoadError
|
43
|
+
task :features do
|
44
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :test
|
49
|
+
|
50
|
+
require 'rake/rdoctask'
|
51
|
+
Rake::RDocTask.new do |rdoc|
|
52
|
+
if File.exist?('VERSION.yml')
|
53
|
+
config = YAML.load(File.read('VERSION.yml'))
|
54
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
55
|
+
else
|
56
|
+
version = ""
|
57
|
+
end
|
58
|
+
|
59
|
+
rdoc.rdoc_dir = 'rdoc'
|
60
|
+
rdoc.title = "gip #{version}"
|
61
|
+
rdoc.rdoc_files.include('README*')
|
62
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
63
|
+
end
|
64
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/gip
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: Importing a repository
|
2
|
+
In order to have control over his deployments
|
3
|
+
A developer will import a remote repository
|
4
|
+
So that his vendor code is protected from inadvertent upstream changes
|
5
|
+
|
6
|
+
Scenario: Importing into an existing repository
|
7
|
+
Given a project
|
8
|
+
And a vendor project named 'libcalc'
|
9
|
+
When I run 'gip import __libcalc__ vendor/libcalc'
|
10
|
+
Then I should see "Imported __libcalc__ into vendor/libcalc"
|
11
|
+
And the file '.gipinfo' should contain 'vendor/libcalc,__libcalc__'
|
12
|
+
And the working copy should be clean
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Given /^a project$/ do
|
2
|
+
@project_dir = Pathname.new(Dir.tmpdir) + "gip/#{Process.pid}/project-dir"
|
3
|
+
FileUtils::Verbose.rm_rf(@project_dir)
|
4
|
+
@project_dir.mkpath
|
5
|
+
Dir.chdir(@project_dir) do
|
6
|
+
sh "touch README", :verbose => true
|
7
|
+
sh "git init", :verbose => true
|
8
|
+
sh "git add --all", :verbose => true
|
9
|
+
sh "git commit --message 'Initial commit'", :verbose => true
|
10
|
+
sh "ls -A", :verbose => true
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
def sh(command_line, options={})
|
2
|
+
verbose = $DEBUG || options[:verbose]
|
3
|
+
puts "CWD:#{Dir.pwd}" if verbose
|
4
|
+
p command_line if verbose
|
5
|
+
result = `#{command_line}`
|
6
|
+
p $? if verbose
|
7
|
+
puts "====" if verbose
|
8
|
+
puts result if verbose
|
9
|
+
puts "====" if verbose
|
10
|
+
result
|
11
|
+
end
|
data/gip.gemspec
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{gip}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Fran\303\247ois Beausoleil"]
|
9
|
+
s.date = %q{2009-06-26}
|
10
|
+
s.default_executable = %q{gip}
|
11
|
+
s.email = %q{francois@teksol.info}
|
12
|
+
s.executables = ["gip"]
|
13
|
+
s.extra_rdoc_files = [
|
14
|
+
"LICENSE",
|
15
|
+
"README.rdoc"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
".document",
|
19
|
+
".gitignore",
|
20
|
+
"LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"bin/gip",
|
25
|
+
"features/import.feature",
|
26
|
+
"features/step_definitions/gip_steps.rb",
|
27
|
+
"features/support/command_line.rb",
|
28
|
+
"features/support/env.rb",
|
29
|
+
"gip.gemspec",
|
30
|
+
"lib/gip.rb",
|
31
|
+
"test/gip_test.rb",
|
32
|
+
"test/test_helper.rb"
|
33
|
+
]
|
34
|
+
s.has_rdoc = true
|
35
|
+
s.homepage = %q{http://github.com/francois/gip}
|
36
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.3.2}
|
39
|
+
s.summary = %q{Gip into place: Piston without the SVN cruft}
|
40
|
+
s.test_files = [
|
41
|
+
"test/gip_test.rb",
|
42
|
+
"test/test_helper.rb"
|
43
|
+
]
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
50
|
+
else
|
51
|
+
end
|
52
|
+
else
|
53
|
+
end
|
54
|
+
end
|
data/lib/gip.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "uri"
|
3
|
+
require "csv"
|
4
|
+
|
5
|
+
class Gip < Thor
|
6
|
+
map %w(--version -v) => :version, %w(--help -h) => :help
|
7
|
+
|
8
|
+
def version
|
9
|
+
print "Gip v"
|
10
|
+
puts File.read(File.dirname(__FILE__) + "/../VERSION")
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "import REPOSITORY_URL [path]", <<DESC
|
14
|
+
Imports the repository at path in the current tree.
|
15
|
+
|
16
|
+
If path is absent, the repository's base name will be used.
|
17
|
+
--remote specifies the name of the remote. If unspecified, the repository's base name will be used.
|
18
|
+
--commit specifies which commit to import. If unspecified, 'REMOTE/master' will be used. You can use any <tree-ish> that Git will recognize (SHA-1, branch name, tag name). The remote's name will always be prefixed to this value.
|
19
|
+
|
20
|
+
In all cases, a .gipinfo file will be created/updated with the correct remotes specified. The .gipinfo file is a CSV file with 2 columns: remote name,repository URL.
|
21
|
+
DESC
|
22
|
+
method_options :commit => :optional, :remote => :optional, :verbose => 0
|
23
|
+
def import(repository_url, path=nil)
|
24
|
+
uri = URI.parse(repository_url)
|
25
|
+
path = File.basename(uri.path).sub(File.extname(uri.path), "") unless path
|
26
|
+
name = options[:remote]
|
27
|
+
name = File.basename(uri.path).sub(File.extname(uri.path), "") unless name
|
28
|
+
|
29
|
+
remote = Remote.new(name, repository_url, path)
|
30
|
+
commit = extract_commit(remote)
|
31
|
+
puts "Importing #{remote.url} into #{remote.path} at #{commit}"
|
32
|
+
|
33
|
+
create_remote remote.name, remote.url
|
34
|
+
git :fetch, remote.name
|
35
|
+
git :"read-tree", "--prefix=#{remote.path}/", "-u", commit
|
36
|
+
gipinfo(remote)
|
37
|
+
git :add, ".gipinfo"
|
38
|
+
git :commit, "-m", "Vendored #{repository_url} at #{commit}", :verbose => true
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Creates or updates remotes in this repository", <<DESC
|
42
|
+
Given the remotes described in a .gipinfo, creates or updates Git remotes in this repository.
|
43
|
+
DESC
|
44
|
+
method_options :verbose => 0
|
45
|
+
def remotify
|
46
|
+
read_gipinfo.each do |remote|
|
47
|
+
create_remote(remote.name, remote.url)
|
48
|
+
git :fetch, remote.name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Freshens the tree at PATH", <<DESC
|
53
|
+
Given a previously imported tree at PATH, updates it to the latest HEAD, or whatever --commit specifies.
|
54
|
+
|
55
|
+
--commit defaults to 'master', and will always be prefixed with the remote's name.
|
56
|
+
DESC
|
57
|
+
method_options :verbose => 0, :commit => :optional
|
58
|
+
def update(path=nil)
|
59
|
+
read_gipinfo.each do |remote|
|
60
|
+
next unless remote.path == path
|
61
|
+
commit = extract_commit(remote)
|
62
|
+
puts "Freshening #{remote.path} from #{remote.url} to #{commit}"
|
63
|
+
|
64
|
+
create_remote remote.name, remote.url
|
65
|
+
git :fetch, remote.name
|
66
|
+
git :merge, "-s", :subtree, "#{remote.name}/#{commit}", :verbose => true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def extract_commit(remote)
|
72
|
+
commit = options[:commit]
|
73
|
+
commit = "master" unless commit
|
74
|
+
commit = "#{remote.name}/#{commit}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_remote(remote_name, repository_url)
|
78
|
+
git :remote, :add, remote_name, repository_url
|
79
|
+
rescue CommandError => e
|
80
|
+
# 128 means remote already exists
|
81
|
+
raise unless e.exitstatus == 128
|
82
|
+
end
|
83
|
+
|
84
|
+
def gipinfo(remote)
|
85
|
+
info = read_gipinfo
|
86
|
+
info << remote
|
87
|
+
write_gipinfo(info)
|
88
|
+
end
|
89
|
+
|
90
|
+
def read_gipinfo
|
91
|
+
if File.file?(".gipinfo")
|
92
|
+
CSV.read(".gipinfo").inject(Array.new) do |memo, (name, url, path)|
|
93
|
+
next memo if name =~ /^\s*#/
|
94
|
+
memo << Remote.new(name, url, path)
|
95
|
+
end
|
96
|
+
else
|
97
|
+
Array.new
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def write_gipinfo(remotes)
|
102
|
+
CSV.open(".gipinfo", "w") do |io|
|
103
|
+
io << ["# This is the GIP gipinfo file. See http://github.com/francois/gip for details. Gip is a RubyGem: sudo gem install francois-gip."]
|
104
|
+
io << ["# This file maps a series of remote names to repository URLs. This file is here to ease the work of your team."]
|
105
|
+
io << ["# Run 'gip remotify' to generate the appropriate remotes in your repository."]
|
106
|
+
|
107
|
+
remotes.each do |remote|
|
108
|
+
io << remote.to_a
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def git(*args)
|
114
|
+
run_cmd :git, *args
|
115
|
+
end
|
116
|
+
|
117
|
+
def run_cmd(executable, *args)
|
118
|
+
opts = args.last.is_a?(Hash) ? args.pop : Hash.new
|
119
|
+
|
120
|
+
args.collect! {|arg| arg.to_s =~ /\s|\*|\?|"|\n|\r/ ? %Q('#{arg}') : arg}
|
121
|
+
args.collect! {|arg| arg ? arg : '""'}
|
122
|
+
cmd = %Q|#{executable} #{args.join(' ')}|
|
123
|
+
p cmd if options[:verbose] > 0
|
124
|
+
|
125
|
+
original_language = ENV["LANGUAGE"]
|
126
|
+
begin
|
127
|
+
ENV["LANGUAGE"] = "C"
|
128
|
+
value = run_real(cmd)
|
129
|
+
p value if options[:verbose] > 1 && !value.to_s.strip.empty?
|
130
|
+
puts value if opts[:verbose]
|
131
|
+
return value
|
132
|
+
ensure
|
133
|
+
ENV["LANGUAGE"] = original_language
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
begin
|
138
|
+
raise LoadError, "Not implemented on Win32 machines" if RUBY_PLATFORM =~ /mswin32/
|
139
|
+
require "open4"
|
140
|
+
|
141
|
+
def run_real(cmd)
|
142
|
+
begin
|
143
|
+
pid, stdin, stdout, stderr = Open4::popen4(cmd)
|
144
|
+
_, cmdstatus = Process.waitpid2(pid)
|
145
|
+
raise CommandError.new("#{cmd.inspect} exited with status: #{cmdstatus.exitstatus}\n#{stderr.read}", cmdstatus) unless cmdstatus.success? || cmdstatus.exitstatus == 1
|
146
|
+
return stdout.read
|
147
|
+
rescue Errno::ENOENT
|
148
|
+
raise BadCommand, cmd.inspect
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
rescue LoadError
|
153
|
+
# On platforms where open4 is unavailable, we fallback to running using
|
154
|
+
# the backtick method of Kernel.
|
155
|
+
def run_real(cmd)
|
156
|
+
out = `#{cmd}`
|
157
|
+
raise BadCommand, cmd.inspect if $?.exitstatus == 127
|
158
|
+
raise CommandError.new("#{cmd.inspect} exited with status: #{$?.exitstatus}", $?) unless $?.success? || $?.exitstatus == 1
|
159
|
+
out
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class BadCommand < StandardError; end
|
164
|
+
class CommandError < StandardError
|
165
|
+
def initialize(message, status)
|
166
|
+
super(message)
|
167
|
+
@status = status
|
168
|
+
end
|
169
|
+
|
170
|
+
def exitstatus
|
171
|
+
@status.exitstatus
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class Remote
|
176
|
+
attr_accessor :name, :url, :path
|
177
|
+
|
178
|
+
def initialize(name, url, path)
|
179
|
+
@name, @url, @path = name, url, path
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_a
|
183
|
+
[@name, @url, @path]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/test/gip_test.rb
ADDED
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: francois-gip
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Fran\xC3\xA7ois Beausoleil"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-26 00:00:00 -07:00
|
13
|
+
default_executable: gip
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: francois@teksol.info
|
18
|
+
executables:
|
19
|
+
- gip
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .document
|
27
|
+
- .gitignore
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- bin/gip
|
33
|
+
- features/import.feature
|
34
|
+
- features/step_definitions/gip_steps.rb
|
35
|
+
- features/support/command_line.rb
|
36
|
+
- features/support/env.rb
|
37
|
+
- gip.gemspec
|
38
|
+
- lib/gip.rb
|
39
|
+
- test/gip_test.rb
|
40
|
+
- test/test_helper.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/francois/gip
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: "Gip into place: Piston without the SVN cruft"
|
67
|
+
test_files:
|
68
|
+
- test/gip_test.rb
|
69
|
+
- test/test_helper.rb
|