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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
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,3 @@
1
+ #!/usr/bin/env ruby
2
+ require "gip"
3
+ Gip.start
@@ -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
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require "gip"
3
+ require "pathname"
4
+ require "fileutils"
5
+
6
+ require "test/unit/assertions"
7
+
8
+ World(Test::Unit::Assertions)
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
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class GipTest < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'gip'
8
+
9
+ class Test::Unit::TestCase
10
+ end
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