francois-gip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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