honkster-giternal 0.2.1

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/.emacs-project ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ spec/test_repos
2
+ pkg
3
+ features/tmp
4
+ test_repos
5
+ .idea
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Pat Maddox
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,75 @@
1
+ = Why giternal
2
+
3
+ There are a couple tools out there that keep track of git externals
4
+ for you. Git submodules are built in, and braid is a different
5
+ project. They both have problems that prevent them from using those
6
+ externals collaboratively.
7
+
8
+ In a nutshell, git submodules keep a reference to the head of each
9
+ external project. This means that if Joe and Sarah each make
10
+ non-conflicting changes to their externals, and push the external
11
+ reference in the main project, one of them will get a conflict on
12
+ update. Braid doesn't treat the externals as being separate from the
13
+ main project, so any commits you make will go to the parent instead of
14
+ the external.
15
+
16
+ In order to demonstrate these issues more concretely, I've written a
17
+ script that will simulate the workflow of making changes to an
18
+ external, pushing it upstream, and pulling it into another project.
19
+ You'll notice in the submodule example that there's a conflict when
20
+ updating the main repo, and files are missing in the local external
21
+ after update. In the braid example, the changes never make it
22
+ upstream. This script checks to see if a tool allows you to not only
23
+ track dependencies but collaborate as well. To execute it, run
24
+
25
+ ruby test_trackers.rb giternal|submodules|braid
26
+
27
+ = Using it
28
+
29
+ Put a file in your project named .giternal.yml or config/giternal.yml,
30
+ that looks like this:
31
+
32
+ local_dir_name:
33
+ repo: git://path/to/repo.git
34
+ path: local/sub/dir
35
+
36
+ As an example, here's how you'd track rspec:
37
+
38
+ rspec:
39
+ repo: git://github.com/dchelimsky/rspec.git
40
+ path: vendor/plugins
41
+
42
+ To pull the externals into your workspace, run "giternal update". You
43
+ should add vendor/plugins/rspec to .gitignore to keep the files from
44
+ being added to your main repo.
45
+
46
+ = Deploying externals
47
+
48
+ I frequently use a cap task that changes to deploy_root and runs
49
+ "giternal update" to pull all the externals. The downside with this
50
+ approach is that you'll get the bleeding edge of your external, and
51
+ you may want to use a particular version that you've tested and know
52
+ works with your app. Enter freezing.
53
+
54
+ Make sure your working dir is clean and then run "giternal freeze".
55
+ The externals are no longer separate repos - the history was zipped up
56
+ so that the external can be unfrozen later. Each external is added to
57
+ the git index, so all you have to do is commit. You've got a
58
+ self-contained external that is frozen to a working version, suitable
59
+ for deploy.
60
+
61
+ After you've tagged your release, you can unfreeze the giternal with
62
+ "giternal unfreeze" and get back to development.
63
+
64
+ = How I want to work with externals
65
+
66
+ When tracking externals, the most important thing is knowing my
67
+ libraries work together, and second I can try to stay up to date.
68
+ When I update an external, I would run rake. If it passes, keep that
69
+ version. If not, look at what it would take to take for my code and
70
+ the library code to work. If I feel like I can do it, I do, otherwise
71
+ I unfreeze and stay on the older working version.
72
+
73
+ == Copyright
74
+
75
+ Copyright (c) 2009 Pat Maddox. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "#{ENV["GEM_PREFIX"]}giternal"
8
+ gem.summary = %Q{Non-sucky git externals}
9
+ gem.description = %Q{Giternal provides dead-simple management of external git dependencies. It only stores a small bit of metadata, letting you actively develop in any of the repos. Come deploy time, you can easily freeze freeze all the dependencies to particular versions}
10
+ gem.email = "pat.maddox@gmail.com"
11
+ gem.homepage = "http://github.com/pat-maddox/giternal"
12
+ gem.authors = ["Pat Maddox"]
13
+ gem.add_development_dependency "rspec"
14
+ gem.add_development_dependency "cucumber"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ begin
36
+ require 'cucumber/rake/task'
37
+ Cucumber::Rake::Task.new(:features)
38
+
39
+ task :features => :check_dependencies
40
+ rescue LoadError
41
+ task :features do
42
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
43
+ end
44
+ end
45
+
46
+ task :default => [:spec, :features]
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ if File.exist?('VERSION')
51
+ version = File.read('VERSION')
52
+ else
53
+ version = ""
54
+ end
55
+
56
+ rdoc.rdoc_dir = 'rdoc'
57
+ rdoc.title = "giternal #{version}"
58
+ rdoc.rdoc_files.include('README*')
59
+ rdoc.rdoc_files.include('lib/**/*.rb')
60
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 2
4
+ :patch: 1
5
+ :build:
data/bin/giternal ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ if File.exist?(File.dirname(__FILE__) + '/../lib/giternal.rb')
4
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
5
+ end
6
+ require 'giternal'
7
+
8
+ action = ARGV[0]
9
+ available_actions = %w(update freeze unfreeze)
10
+ unless available_actions.include?(action)
11
+ puts "Usage: giternal (#{available_actions.join(':')})"
12
+ exit 1
13
+ end
14
+
15
+ Giternal::Repository.verbose = true
16
+ app = Giternal::App.new(FileUtils.pwd)
17
+ app.run(*ARGV)
@@ -0,0 +1,25 @@
1
+ Feature: Checking out and updating externals
2
+ As a developer
3
+ I want to check out and update external projects via git
4
+ So that I can add functionality to my app with little effort
5
+
6
+ Scenario: Repository is not yet checked out
7
+ Given an external repository named 'first_external'
8
+ And 'first_external' is not yet checked out
9
+ When I update the externals
10
+ Then 'first_external' should be checked out
11
+
12
+ Scenario: Multiple externals
13
+ Given an external repository named 'first_external'
14
+ And an external repository named 'second_external'
15
+ When I update the externals
16
+ Then 'first_external' should be checked out
17
+ And 'second_external' should be checked out
18
+
19
+ Scenario: Repository checked out then updated
20
+ Given an external repository named 'first_external'
21
+ And the externals are up to date
22
+ And content is added to 'first_external'
23
+ Then 'first_external' should not be up to date
24
+ When I update the externals
25
+ Then 'first_external' should be up to date
@@ -0,0 +1,26 @@
1
+ Feature: Freeze externals
2
+ As a developer
3
+ I want to freeze externals
4
+ So that I can test and deploy my app with no worries
5
+
6
+ Scenario: Main project has one external
7
+ Given an external repository named 'first_external'
8
+ And the externals are up to date
9
+ When I freeze the externals
10
+ Then 'first_external' should no longer be a git repo
11
+ And 'first_external' should be added to the commit index
12
+
13
+ Scenario: External has been added to .gitignore
14
+ Given an external repository named 'first_external'
15
+ And the external 'first_external' has been added to .gitignore
16
+ And the externals are up to date
17
+ When I freeze the externals
18
+ Then 'first_external' should be added to the commit index
19
+
20
+ Scenario: Main project has two externals
21
+ Given an external repository named 'first_external'
22
+ And an external repository named 'second_external'
23
+ And the externals are up to date
24
+ When I freeze the external 'second_external'
25
+ Then 'second_external' should be added to the commit index
26
+ And 'first_external' should be removed from the commit index
@@ -0,0 +1,113 @@
1
+ require 'spec'
2
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
3
+ require 'giternal'
4
+ require 'giternal_helper'
5
+
6
+ def be_up_to_date
7
+ Spec::Matchers::SimpleMatcher.new("a giternal'd repository") do |repo_name|
8
+ File.directory?(GiternalHelper.checked_out_path(repo_name)).should == true
9
+ GiternalHelper.repo_contents(GiternalHelper.checked_out_path(repo_name)) ==
10
+ GiternalHelper.repo_contents(GiternalHelper.external_path(repo_name))
11
+ end
12
+ end
13
+
14
+ def be_a_git_repo
15
+ Spec::Matchers::SimpleMatcher.new("a giternal'd repository") do |repo_name|
16
+ File.directory?(GiternalHelper.checked_out_path(repo_name) + '/.git')
17
+ end
18
+ end
19
+
20
+ def be_added_to_commit_index
21
+ Spec::Matchers::SimpleMatcher.new("a giternal'd repository") do |repo_name|
22
+ Dir.chdir(GiternalHelper.tmp_path + '/main_repo') do
23
+ status = `git status`
24
+ flattened_status = status.split("\n").join(" ")
25
+ to_be_committed_regex = /new file:\W+dependencies\/#{repo_name}/
26
+ untracked_files_regex = /Untracked files:.*#{repo_name}/
27
+ status =~ to_be_committed_regex && !(flattened_status =~ untracked_files_regex)
28
+ end
29
+ end
30
+ end
31
+
32
+ Before do
33
+ GiternalHelper.clean!
34
+ GiternalHelper.create_main_repo
35
+ end
36
+
37
+ After do
38
+ GiternalHelper.clean!
39
+ end
40
+
41
+ Given /an external repository named '(.*)'/ do |repo_name|
42
+ GiternalHelper.create_repo repo_name
43
+ GiternalHelper.add_content repo_name
44
+ end
45
+
46
+ Given /'(.*)' is not yet checked out/ do |repo_name|
47
+ # TODO: Figure out why I can't use should be_false here
48
+ File.directory?(GiternalHelper.checked_out_path(repo_name)).should == false
49
+ end
50
+
51
+ Given "the externals are up to date" do
52
+ GiternalHelper.update_externals
53
+ end
54
+
55
+ Given "the externals are frozen" do
56
+ GiternalHelper.freeze_externals
57
+ end
58
+
59
+ Given /content is added to '(.*)'/ do |repo_name|
60
+ GiternalHelper.add_content(repo_name)
61
+ end
62
+
63
+ Given /^the external '(.*)' has been added to \.gitignore$/ do |repo_name|
64
+ GiternalHelper.add_external_to_ignore(repo_name)
65
+ end
66
+
67
+ When "I update the externals" do
68
+ GiternalHelper.update_externals
69
+ end
70
+
71
+ When "I freeze the externals" do
72
+ GiternalHelper.freeze_externals
73
+ end
74
+
75
+ When /I freeze the external '(.*)'/ do |external_name|
76
+ GiternalHelper.freeze_externals("dependencies/#{external_name}")
77
+ end
78
+
79
+ When "I unfreeze the externals" do
80
+ GiternalHelper.unfreeze_externals
81
+ end
82
+
83
+ When /I unfreeze the external '(.*)'/ do |external_name|
84
+ GiternalHelper.unfreeze_externals("dependencies/#{external_name}")
85
+ end
86
+
87
+ Then /'(.*)' should be checked out/ do |repo_name|
88
+ repo_name.should be_up_to_date
89
+ end
90
+
91
+ Then /'(.*)' should be up to date/ do |repo_name|
92
+ repo_name.should be_up_to_date
93
+ end
94
+
95
+ Then /'(.*)' should not be up to date/ do |repo_name|
96
+ repo_name.should_not be_up_to_date
97
+ end
98
+
99
+ Then /'(.*)' should no longer be a git repo/ do |repo_name|
100
+ repo_name.should_not be_a_git_repo
101
+ end
102
+
103
+ Then /'(.*)' should be a git repo/ do |repo_name|
104
+ repo_name.should be_a_git_repo
105
+ end
106
+
107
+ Then /'(.*)' should be added to the commit index/ do |repo_name|
108
+ repo_name.should be_added_to_commit_index
109
+ end
110
+
111
+ Then /'(.*)' should be removed from the commit index/ do |repo_name|
112
+ repo_name.should_not be_added_to_commit_index
113
+ end
@@ -0,0 +1,23 @@
1
+ Feature: Unfreeze externals
2
+ As a developer
3
+ I want to unfreeze externals
4
+ So that I can continue to update and develop on them
5
+
6
+ Scenario: Main project has one frozen external
7
+ Given an external repository named 'first_external'
8
+ And the externals are up to date
9
+ And the externals are frozen
10
+ When I unfreeze the externals
11
+ Then 'first_external' should be a git repo
12
+ And 'first_external' should be removed from the commit index
13
+
14
+ Scenario: Main project has two frozen externals
15
+ Given an external repository named 'first_external'
16
+ And an external repository named 'second_external'
17
+ And the externals are up to date
18
+ And the externals are frozen
19
+ When I unfreeze the external 'second_external'
20
+ Then 'second_external' should be a git repo
21
+ And 'second_external' should be removed from the commit index
22
+ And 'first_external' should no longer be a git repo
23
+ And 'first_external' should be added to the commit index
data/giternal.gemspec ADDED
@@ -0,0 +1,74 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{giternal}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Pat Maddox"]
12
+ s.date = %q{2009-10-10}
13
+ s.default_executable = %q{giternal}
14
+ s.description = %q{Giternal provides dead-simple management of external git dependencies. It only stores a small bit of metadata, letting you actively develop in any of the repos. Come deploy time, you can easily freeze freeze all the dependencies to particular versions}
15
+ s.email = %q{pat.maddox@gmail.com}
16
+ s.executables = ["giternal"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".emacs-project",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION.yml",
28
+ "bin/giternal",
29
+ "features/checking_out_externals.feature",
30
+ "features/freeze_externals.feature",
31
+ "features/steps/repository_steps.rb",
32
+ "features/unfreeze_externals.feature",
33
+ "giternal.gemspec",
34
+ "giternal_helper.rb",
35
+ "lib/giternal.rb",
36
+ "lib/giternal/app.rb",
37
+ "lib/giternal/repository.rb",
38
+ "lib/giternal/version.rb",
39
+ "lib/giternal/yaml_config.rb",
40
+ "spec/giternal/app_spec.rb",
41
+ "spec/giternal/repository_spec.rb",
42
+ "spec/giternal/yaml_config_spec.rb",
43
+ "spec/spec.opts",
44
+ "spec/spec_helper.rb",
45
+ "test_trackers.rb"
46
+ ]
47
+ s.homepage = %q{http://github.com/pat-maddox/giternal}
48
+ s.rdoc_options = ["--charset=UTF-8"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = %q{1.3.5}
51
+ s.summary = %q{Non-sucky git externals}
52
+ s.test_files = [
53
+ "spec/giternal/app_spec.rb",
54
+ "spec/giternal/repository_spec.rb",
55
+ "spec/giternal/yaml_config_spec.rb",
56
+ "spec/spec_helper.rb"
57
+ ]
58
+
59
+ if s.respond_to? :specification_version then
60
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
64
+ s.add_development_dependency(%q<rspec>, [">= 0"])
65
+ s.add_development_dependency(%q<cucumber>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<rspec>, [">= 0"])
68
+ s.add_dependency(%q<cucumber>, [">= 0"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<rspec>, [">= 0"])
72
+ s.add_dependency(%q<cucumber>, [">= 0"])
73
+ end
74
+ end
@@ -0,0 +1,105 @@
1
+ class GiternalHelper
2
+ @@giternal_base ||= File.expand_path(File.dirname(__FILE__))
3
+
4
+ def self.create_main_repo
5
+ FileUtils.mkdir_p tmp_path
6
+ Dir.chdir(tmp_path) do
7
+ FileUtils.mkdir "main_repo"
8
+ Dir.chdir('main_repo') do
9
+ `git init`
10
+ `echo 'first content' > starter_repo`
11
+ `git add starter_repo`
12
+ `git commit -m "starter repo"`
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.tmp_path
18
+ "/tmp/giternal_test"
19
+ end
20
+
21
+ def self.giternal_base
22
+ @@giternal_base
23
+ end
24
+
25
+ def self.base_project_dir
26
+ tmp_path + '/main_repo'
27
+ end
28
+
29
+ def self.run(*args)
30
+ `#{giternal_base}/bin/giternal #{args.join(' ')}`
31
+ end
32
+
33
+ def self.create_repo(repo_name)
34
+ Dir.chdir(tmp_path) do
35
+ FileUtils.mkdir_p "externals/#{repo_name}"
36
+ `cd externals/#{repo_name} && git init`
37
+ end
38
+ add_content repo_name
39
+ add_to_config_file repo_name
40
+ end
41
+
42
+ def self.add_to_config_file(repo_name)
43
+ config_dir = tmp_path + '/main_repo/config'
44
+ FileUtils.mkdir(config_dir) unless File.directory?(config_dir)
45
+ Dir.chdir(config_dir) do
46
+ `echo #{repo_name}: >> giternal.yml`
47
+ `echo ' repo: #{external_path(repo_name)}' >> giternal.yml`
48
+ `echo ' path: dependencies' >> giternal.yml`
49
+ end
50
+ end
51
+
52
+ def self.add_content(repo_name, content=repo_name)
53
+ Dir.chdir(tmp_path + "/externals/#{repo_name}") do
54
+ `echo #{content} >> #{content} && git add #{content}`
55
+ `git commit #{content} -m "added content to #{content}"`
56
+ end
57
+ end
58
+
59
+ def self.external_path(repo_name)
60
+ File.expand_path(tmp_path + "/externals/#{repo_name}")
61
+ end
62
+
63
+ def self.checked_out_path(repo_name)
64
+ File.expand_path(tmp_path + "/main_repo/dependencies/#{repo_name}")
65
+ end
66
+
67
+ def self.clean!
68
+ FileUtils.rm_rf tmp_path
69
+ %w(GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE).each {|var| ENV[var] = nil }
70
+ end
71
+
72
+ def self.update_externals
73
+ Dir.chdir(tmp_path + '/main_repo') do
74
+ GiternalHelper.run('update')
75
+ end
76
+ end
77
+
78
+ def self.freeze_externals(*args)
79
+ Dir.chdir(tmp_path + '/main_repo') do
80
+ GiternalHelper.run("freeze", *args)
81
+ end
82
+ end
83
+
84
+ def self.unfreeze_externals(*args)
85
+ Dir.chdir(tmp_path + '/main_repo') do
86
+ GiternalHelper.run("unfreeze", *args)
87
+ end
88
+ end
89
+
90
+ def self.repo_contents(path)
91
+ Dir.chdir(path) do
92
+ contents = `git cat-file -p HEAD`
93
+ unless contents.include?('tree') && contents.include?('author')
94
+ raise "something is wrong with the repo, output doesn't contain expected git elements:\n\n #{contents}"
95
+ end
96
+ contents
97
+ end
98
+ end
99
+
100
+ def self.add_external_to_ignore(repo_name)
101
+ Dir.chdir(tmp_path + '/main_repo') do
102
+ `echo 'dependencies/#{repo_name}' >> .gitignore`
103
+ end
104
+ end
105
+ end
data/lib/giternal.rb ADDED
@@ -0,0 +1,10 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module Giternal
5
+
6
+ end
7
+
8
+ require 'giternal/repository'
9
+ require 'giternal/yaml_config'
10
+ require 'giternal/app'
@@ -0,0 +1,61 @@
1
+ module Giternal
2
+ class App
3
+ def initialize(base_dir)
4
+ @base_dir = base_dir
5
+ end
6
+
7
+ def update
8
+ config.each_repo {|r| r.update }
9
+ end
10
+
11
+ def freezify(*dirs)
12
+ if dirs.empty?
13
+ config.each_repo {|r| r.freezify }
14
+ else
15
+ dirs.each do |dir|
16
+ if repo = config.find_repo(dir)
17
+ repo.freezify
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def unfreezify(*dirs)
24
+ if dirs.empty?
25
+ config.each_repo {|r| r.unfreezify }
26
+ else
27
+ dirs.each do |dir|
28
+ if repo = config.find_repo(dir)
29
+ repo.unfreezify
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def run(action, *args)
36
+ case action
37
+ when "freeze"
38
+ freezify(*args)
39
+ when "unfreeze"
40
+ unfreezify(*args)
41
+ else
42
+ send(action, *args)
43
+ end
44
+ end
45
+
46
+ def config
47
+ return @config if @config
48
+
49
+ config_file = ['config/giternal.yml', '.giternal.yml'].detect do |file|
50
+ File.file? File.expand_path(@base_dir + '/' + file)
51
+ end
52
+
53
+ if config_file.nil?
54
+ $stderr.puts "config/giternal.yml is missing"
55
+ exit 1
56
+ end
57
+
58
+ @config = YamlConfig.new(@base_dir, File.read(config_file))
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,92 @@
1
+ require 'fileutils'
2
+
3
+ module Giternal
4
+ class Repository
5
+ class << self
6
+ attr_accessor :verbose
7
+ end
8
+ attr_accessor :verbose
9
+
10
+ def initialize(base_dir, name, repo_url, rel_path)
11
+ @base_dir = base_dir
12
+ @name = name
13
+ @repo_url = repo_url
14
+ @rel_path = rel_path
15
+ @verbose = self.class.verbose
16
+ end
17
+
18
+ def update
19
+ git_ignore_self
20
+
21
+ return true if frozen?
22
+ FileUtils.mkdir_p checkout_path unless File.exist?(checkout_path)
23
+ if checked_out?
24
+ if !File.exist?(repo_path + '/.git')
25
+ raise "Directory '#{@name}' exists but is not a git repository"
26
+ else
27
+ update_output { `cd #{repo_path} && git pull 2>&1` }
28
+ end
29
+ else
30
+ update_output { `cd #{checkout_path} && git clone #{@repo_url} #{@name}` }
31
+ end
32
+ true
33
+ end
34
+
35
+ def freezify
36
+ return true if frozen? || !checked_out?
37
+
38
+ Dir.chdir(repo_path) do
39
+ `find .git | sort | xargs tar czf .git.frozen.tgz`
40
+ FileUtils.rm_r('.git')
41
+ end
42
+ `cd #{@base_dir} && git add -f #{rel_repo_path}`
43
+ true
44
+ end
45
+
46
+ def unfreezify
47
+ return true unless frozen?
48
+
49
+ Dir.chdir(repo_path) do
50
+ `tar xzf .git.frozen.tgz`
51
+ FileUtils.rm('.git.frozen.tgz')
52
+ end
53
+ `cd #{@base_dir} && git rm -r --cached #{rel_repo_path}`
54
+ true
55
+ end
56
+
57
+ def frozen?
58
+ File.exist?(repo_path + '/.git.frozen.tgz')
59
+ end
60
+
61
+ def checked_out?
62
+ File.exist?(repo_path)
63
+ end
64
+
65
+ private
66
+ def checkout_path
67
+ File.expand_path(File.join(@base_dir, @rel_path))
68
+ end
69
+
70
+ def repo_path
71
+ File.expand_path(checkout_path + '/' + @name)
72
+ end
73
+
74
+ def rel_repo_path
75
+ @rel_path + '/' + @name
76
+ end
77
+
78
+ def update_output(&block)
79
+ puts "Updating #{@name}" if verbose
80
+ block.call
81
+ puts " ..updated\n" if verbose
82
+ end
83
+
84
+ def git_ignore_self
85
+ Dir.chdir(@base_dir) do
86
+ unless File.exist?('.gitignore') && File.read('.gitignore').include?(rel_repo_path)
87
+ `echo '#{rel_repo_path}' >> .gitignore`
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,9 @@
1
+ module Giternal
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ require 'yaml'
2
+
3
+ module Giternal
4
+ class YamlConfig
5
+ def initialize(base_dir, yaml_string)
6
+ @base_dir = base_dir
7
+ @config_hash = YAML.load yaml_string
8
+ end
9
+
10
+ def each_repo
11
+ repositories.each { |r| yield(r) if block_given? }
12
+ end
13
+
14
+ def find_repo(path)
15
+ @config_hash.each do |name, attributes|
16
+ if path == File.join(attributes["path"], name)
17
+ return Repository.new(@base_dir, name, attributes["repo"], attributes["path"])
18
+ end
19
+ end
20
+ return nil
21
+ end
22
+
23
+ private
24
+ def repositories
25
+ @config_hash.map do |name, attributes|
26
+ Repository.new(@base_dir, name, attributes["repo"], attributes["path"])
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,64 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ module Giternal
4
+ describe App do
5
+ before(:each) do
6
+ @app = App.new("some_fake_dir")
7
+ @mock_config = stub("config", :null_object => true)
8
+ end
9
+
10
+ describe "loading the config file" do
11
+ before(:each) do
12
+ File.stub!(:file?).and_return true
13
+ File.stub!(:read).and_return "yaml config"
14
+ YamlConfig.stub!(:new).and_return @mock_config
15
+ end
16
+
17
+ it "should look for config/giternal.yml" do
18
+ File.should_receive(:file?).with(/some_fake_dir\/config\/giternal\.yml/)
19
+ @app.config
20
+ end
21
+
22
+ it "should look for .giternal.yml if giternal.yml does not exist" do
23
+ File.should_receive(:file?).with(/some_fake_dir\/config\/giternal\.yml/).and_return false
24
+ File.should_receive(:file?).with(/some_fake_dir\/\.giternal\.yml/).and_return true
25
+ @app.config
26
+ end
27
+
28
+ it "should exit with an error when no config file exists" do
29
+ File.stub!(:file?).and_return false
30
+ $stderr.should_receive(:puts)
31
+ @app.should_receive(:exit).with(1)
32
+ @app.config
33
+ end
34
+
35
+ it "should create a config from the config file" do
36
+ YamlConfig.should_receive(:new).with('some_fake_dir', "yaml config").and_return @mock_config
37
+ @app.config
38
+ end
39
+ end
40
+
41
+ describe "app actions" do
42
+ before(:each) do
43
+ @app.stub!(:config).and_return @mock_config
44
+ @mock_repo = mock("repo")
45
+ @mock_config.stub!(:each_repo).and_yield(@mock_repo)
46
+ end
47
+
48
+ it "should update each of the repositories" do
49
+ @mock_repo.should_receive(:update)
50
+ @app.update
51
+ end
52
+
53
+ it "should freeze each of the repositories" do
54
+ @mock_repo.should_receive(:freezify)
55
+ @app.freezify
56
+ end
57
+
58
+ it "should unfreeze each of the repositories" do
59
+ @mock_repo.should_receive(:unfreezify)
60
+ @app.unfreezify
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,133 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ module Giternal
4
+ describe Repository do
5
+ before(:each) do
6
+ GiternalHelper.create_main_repo
7
+ GiternalHelper.create_repo 'foo'
8
+ @repository = Repository.new(GiternalHelper.base_project_dir, "foo",
9
+ GiternalHelper.external_path('foo'),
10
+ 'dependencies')
11
+ end
12
+
13
+ it "should check itself out to a dir" do
14
+ @repository.update
15
+ File.file?(GiternalHelper.checked_out_path('foo/foo')).should be_true
16
+ File.read(GiternalHelper.checked_out_path('foo/foo')).strip.
17
+ should == 'foo'
18
+ end
19
+
20
+ it "should be ignored from git" do
21
+ @repository.update
22
+ Dir.chdir(GiternalHelper.base_project_dir) do
23
+ # TODO: What I really want is to say it shouldn't include 'foo'
24
+ `git status`.should_not include('dependencies')
25
+ end
26
+ end
27
+
28
+ it "should only add itself to .gitignore if it's not already there" do
29
+ 2.times { @repository.update }
30
+ Dir.chdir(GiternalHelper.base_project_dir) do
31
+ File.read('.gitignore').scan(/foo/).should have(1).item
32
+ # TODO: What I really want is to say it shouldn't include 'foo'
33
+ `git status`.should_not include('dependencies')
34
+ end
35
+ end
36
+
37
+ it "should not show any output when verbose mode is off" do
38
+ @repository.verbose = false
39
+ @repository.should_not_receive(:puts)
40
+ @repository.update
41
+ end
42
+
43
+ it "should not show output when verbose mode is on" do
44
+ @repository.verbose = true
45
+ @repository.should_receive(:puts).any_number_of_times
46
+ @repository.update
47
+ end
48
+
49
+ it "should update the repo when it's already been checked out" do
50
+ @repository.update
51
+ GiternalHelper.add_content 'foo', 'newfile'
52
+ @repository.update
53
+ File.file?(GiternalHelper.checked_out_path('foo/newfile')).should be_true
54
+ File.read(GiternalHelper.checked_out_path('foo/newfile')).strip.
55
+ should == 'newfile'
56
+ end
57
+
58
+ it "should raise an error if the directory exists but there's no .git dir" do
59
+ FileUtils.mkdir_p(GiternalHelper.checked_out_path('foo'))
60
+ lambda {
61
+ @repository.update
62
+ }.should raise_error(/Directory 'foo' exists but is not a git repository/)
63
+ end
64
+
65
+ describe "freezify" do
66
+ before(:each) do
67
+ GiternalHelper.create_repo('external')
68
+ @repository = Repository.new(GiternalHelper.base_project_dir, 'external',
69
+ GiternalHelper.external_path('external'),
70
+ 'dependencies')
71
+ @repository.update
72
+ end
73
+
74
+ it "should archive the .git dir" do
75
+ @repository.freezify
76
+ File.file?(GiternalHelper.checked_out_path('external/.git.frozen.tgz')).should be_true
77
+ end
78
+
79
+ it "should get rid of the .git dir" do
80
+ File.directory?(GiternalHelper.checked_out_path('external/.git')).should be_true
81
+ @repository.freezify
82
+ File.directory?(GiternalHelper.checked_out_path('external/.git')).should be_false
83
+ end
84
+ end
85
+
86
+ it "should simply return if updated when frozen" do
87
+ @repository.update
88
+ @repository.freezify
89
+ lambda { @repository.update }.should_not raise_error
90
+ end
91
+
92
+ it "should simply return when made to freeze when already frozen" do
93
+ @repository.update
94
+ @repository.freezify
95
+ lambda { @repository.freezify }.should_not raise_error
96
+ end
97
+
98
+ it "should simply return when made to freeze before checked out" do
99
+ lambda { @repository.freezify }.should_not raise_error
100
+ end
101
+
102
+ it "should simply return when made to unfreeze before checked out" do
103
+ lambda { @repository.unfreezify }.should_not raise_error
104
+ end
105
+
106
+ it "should simply return when made to unfreeze when already unfrozen" do
107
+ @repository.update
108
+ lambda { @repository.unfreezify }.should_not raise_error
109
+ end
110
+
111
+ describe "unfreezify" do
112
+ before(:each) do
113
+ GiternalHelper.create_repo('main')
114
+ GiternalHelper.create_repo('external')
115
+ @repository = Repository.new(GiternalHelper.base_project_dir, 'external',
116
+ GiternalHelper.external_path('external'),
117
+ 'dependencies')
118
+ @repository.update
119
+ @repository.freezify
120
+ end
121
+
122
+ it "should unarchive the .git dir" do
123
+ @repository.unfreezify
124
+ File.directory?(GiternalHelper.checked_out_path('external/.git')).should be_true
125
+ end
126
+
127
+ it "should remove the archived file" do
128
+ @repository.unfreezify
129
+ File.file?(GiternalHelper.checked_out_path('external/.git.frozen.tgz')).should be_false
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ module Giternal
4
+ describe YamlConfig do
5
+ it "should create repositories from the config" do
6
+ config = YamlConfig.new('base_dir',
7
+ "rspec:\n repo: git://rspec\n path: vendor/plugins\n" +
8
+ "foo:\n repo: git://at/foo\n path: path/to/foo\n")
9
+ Repository.should_receive(:new).with('base_dir', "rspec", "git://rspec", "vendor/plugins").and_return :a_repo
10
+ Repository.should_receive(:new).with('base_dir', "foo", "git://at/foo", "path/to/foo").and_return :a_repo
11
+ config.each_repo {|r| r.should == :a_repo}
12
+ end
13
+ end
14
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'spec'
6
+ end
7
+
8
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
9
+ require 'giternal'
10
+ require 'fileutils'
11
+ require 'giternal_helper'
12
+
13
+ Spec::Runner.configuration.before(:each) do
14
+ GiternalHelper.clean!
15
+ end
16
+
17
+ Spec::Runner.configuration.after(:each) do
18
+ GiternalHelper.clean!
19
+ end
data/test_trackers.rb ADDED
@@ -0,0 +1,157 @@
1
+ require 'fileutils'
2
+ module Repomanipulator
3
+ def repo_path(path)
4
+ File.expand_path File.join(File.dirname(__FILE__), "test_repos", path)
5
+ end
6
+
7
+ def create_repo(name)
8
+ path = repo_path(name)
9
+ FileUtils.mkdir_p(path)
10
+ Dir.chdir(path) do
11
+ `git init`
12
+ `echo first_file > first_file`
13
+ end
14
+ commit_all path, "created repo"
15
+ end
16
+
17
+ def commit_all(path, message)
18
+ Dir.chdir(path) do
19
+ `git add .`
20
+ puts `git commit -m "#{message}"`
21
+ end
22
+ end
23
+
24
+ def clone_repo(from, to, options=nil)
25
+ Dir.chdir(repo_path("")) { `git clone #{options} #{from} #{to}` }
26
+ end
27
+
28
+ def add_content_to(file, content)
29
+ path = repo_path file
30
+ FileUtils.touch path
31
+ File.open(path, 'w') {|f| f << content }
32
+ `cd #{File.dirname(path)} && git checkout master`
33
+ commit_all File.dirname(repo_path(file)), "added content to #{file}"
34
+ end
35
+
36
+ def push(repo)
37
+ Dir.chdir(repo_path(repo)) { `git push origin master` }
38
+ end
39
+
40
+ def pull(repo)
41
+ Dir.chdir(repo_path(repo)) { puts `git pull` }
42
+ end
43
+
44
+ def verify_repo_has_files(repo, *files)
45
+ Dir.chdir(repo_path(repo)) do
46
+ missing_files = files.select {|f| !File.exist?(f) }
47
+ raise "Expected files: [#{missing_files.join(', ')}] to exist in #{repo} but are missing" unless missing_files.empty?
48
+ end
49
+ end
50
+ end
51
+ include Repomanipulator
52
+
53
+ module GitSubmodules
54
+ def add_external(base, external)
55
+ external_path = repo_path(external)
56
+ Dir.chdir(repo_path(base)) { puts `git submodule add #{external_path} #{external}` }
57
+ commit_all repo_path(base), "committed external #{external}"
58
+ end
59
+
60
+ def update_externals(repo)
61
+ Dir.chdir(repo_path(repo)) do
62
+ puts `git submodule init`
63
+ puts `git submodule update`
64
+ end
65
+ commit_all repo_path(repo), "updated externals"
66
+ end
67
+ end
68
+
69
+ module Giternal
70
+ def add_external(base, external)
71
+ external_path = repo_path external
72
+ Dir.chdir(repo_path(base)) do
73
+ FileUtils.touch ".giternal.yml"
74
+ File.open(".giternal.yml", 'w') do |f|
75
+ f << external << ":\n"
76
+ f << " repo: #{external_path}\n"
77
+ f << " path: ."
78
+ end
79
+ `echo #{external} >> .gitignore`
80
+ end
81
+ commit_all repo_path(base), "added #{external}"
82
+ end
83
+
84
+ def update_externals(base)
85
+ Dir.chdir(repo_path(base)) { puts `giternal update` }
86
+ end
87
+ end
88
+
89
+ module Braid
90
+ def add_external(base, external)
91
+ external_path = repo_path external
92
+ Dir.chdir(repo_path(base)) { puts `braid add --type git #{external_path} #{external}` }
93
+ end
94
+
95
+ def update_externals(base)
96
+ Dir.chdir(repo_path(base)) { puts `braid update` }
97
+ end
98
+ end
99
+
100
+
101
+ drivers = {"submodules" => GitSubmodules, "giternal" => Giternal, "braid" => Braid}
102
+ unless drivers.keys.include?(ARGV.first)
103
+ puts "Run with: ruby test_tracking.rb #{drivers.keys.join('|')}"
104
+ exit
105
+ end
106
+ include drivers[ARGV.first]
107
+
108
+ FileUtils.rm_rf repo_path("")
109
+
110
+ def ALERT(message)
111
+ puts
112
+ puts "*** " + message
113
+ end
114
+
115
+ ALERT "the project has will_paginate as an external"
116
+ create_repo "will_paginate-full"
117
+ clone_repo "will_paginate-full", "will_paginate", "--bare"
118
+
119
+ create_repo "base-full"
120
+ add_external "base-full", "will_paginate"
121
+ clone_repo "base-full", "base", "--bare"
122
+
123
+ ALERT "Joe and Sarah clone copies of the project"
124
+ clone_repo "base", "joe"
125
+ update_externals "joe"
126
+
127
+ clone_repo "base", "sarah"
128
+ update_externals "sarah"
129
+
130
+ ALERT "Joe adds README to will_paginate and commits"
131
+ add_content_to "joe/will_paginate/README", "some content"
132
+
133
+ ALERT "Joe commits a change to the base project"
134
+ add_content_to "joe/foo", "in the base project"
135
+
136
+ ALERT "Joe pushes his code"
137
+ push "joe"
138
+ push "joe/will_paginate"
139
+
140
+ ALERT "Sarah adds LICENSE to her repo and commits"
141
+ add_content_to "sarah/will_paginate/LICENSE", "gnu baby"
142
+
143
+ ALERT "Sarah commits a change to the base project"
144
+ add_content_to "sarah/bar", "in the base project"
145
+
146
+ ALERT "Sarah pulls from base project and updates externals"
147
+ pull "sarah"
148
+ update_externals "sarah"
149
+ push "sarah/will_paginate"
150
+
151
+ ALERT "Does the cloned project have all the files we expect?"
152
+ verify_repo_has_files("sarah", "foo", "bar", "will_paginate/README", "will_paginate/LICENSE")
153
+
154
+ ALERT "Does the upstream external have all the files we expect?"
155
+ clone_repo "will_paginate", "will_paginate_clone"
156
+ verify_repo_has_files "will_paginate_clone", "README", "LICENSE"
157
+ puts "woooo! Collaboration ftw"
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: honkster-giternal
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 1
10
+ version: 0.2.1
11
+ platform: ruby
12
+ authors:
13
+ - Pat Maddox
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-07-20 00:00:00 -07:00
19
+ default_executable: giternal
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: cucumber
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ description: Giternal provides dead-simple management of external git dependencies. It only stores a small bit of metadata, letting you actively develop in any of the repos. Come deploy time, you can easily freeze freeze all the dependencies to particular versions
50
+ email: pat.maddox@gmail.com
51
+ executables:
52
+ - giternal
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - LICENSE
57
+ - README.rdoc
58
+ files:
59
+ - .emacs-project
60
+ - .gitignore
61
+ - LICENSE
62
+ - README.rdoc
63
+ - Rakefile
64
+ - VERSION.yml
65
+ - bin/giternal
66
+ - features/checking_out_externals.feature
67
+ - features/freeze_externals.feature
68
+ - features/steps/repository_steps.rb
69
+ - features/unfreeze_externals.feature
70
+ - giternal.gemspec
71
+ - giternal_helper.rb
72
+ - lib/giternal.rb
73
+ - lib/giternal/app.rb
74
+ - lib/giternal/repository.rb
75
+ - lib/giternal/version.rb
76
+ - lib/giternal/yaml_config.rb
77
+ - spec/giternal/app_spec.rb
78
+ - spec/giternal/repository_spec.rb
79
+ - spec/giternal/yaml_config_spec.rb
80
+ - spec/spec.opts
81
+ - spec/spec_helper.rb
82
+ - test_trackers.rb
83
+ has_rdoc: true
84
+ homepage: http://github.com/pat-maddox/giternal
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options:
89
+ - --charset=UTF-8
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ hash: 3
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ requirements: []
111
+
112
+ rubyforge_project:
113
+ rubygems_version: 1.3.7
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Non-sucky git externals
117
+ test_files:
118
+ - spec/spec_helper.rb
119
+ - spec/giternal/yaml_config_spec.rb
120
+ - spec/giternal/repository_spec.rb
121
+ - spec/giternal/app_spec.rb