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 +0 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +75 -0
- data/Rakefile +60 -0
- data/VERSION.yml +5 -0
- data/bin/giternal +17 -0
- data/features/checking_out_externals.feature +25 -0
- data/features/freeze_externals.feature +26 -0
- data/features/steps/repository_steps.rb +113 -0
- data/features/unfreeze_externals.feature +23 -0
- data/giternal.gemspec +74 -0
- data/giternal_helper.rb +105 -0
- data/lib/giternal.rb +10 -0
- data/lib/giternal/app.rb +61 -0
- data/lib/giternal/repository.rb +92 -0
- data/lib/giternal/version.rb +9 -0
- data/lib/giternal/yaml_config.rb +30 -0
- data/spec/giternal/app_spec.rb +64 -0
- data/spec/giternal/repository_spec.rb +133 -0
- data/spec/giternal/yaml_config_spec.rb +14 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +19 -0
- data/test_trackers.rb +157 -0
- metadata +121 -0
data/.emacs-project
ADDED
File without changes
|
data/.gitignore
ADDED
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
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
|
data/giternal_helper.rb
ADDED
@@ -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
data/lib/giternal/app.rb
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|