giternal 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ spec/test_repos
2
+ pkg
3
+ features/tmp
4
+ test_repos
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Pat Maddox
1
+ Copyright (c) 2009 Pat Maddox
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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 CHANGED
@@ -1,4 +1,60 @@
1
- require 'config/requirements'
2
- require 'config/hoe' # setup Hoe + all gem configuration
3
-
4
- Dir['tasks/**/*.rake'].each { |rake| load rake }
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "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,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 0
4
+ :major: 0
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 CHANGED
@@ -1,6 +1,6 @@
1
1
  class GiternalHelper
2
- @@giternal_base = File.expand_path(File.dirname(__FILE__))
3
-
2
+ @@giternal_base ||= File.expand_path(File.dirname(__FILE__))
3
+
4
4
  def self.create_main_repo
5
5
  FileUtils.mkdir_p tmp_path
6
6
  Dir.chdir(tmp_path) do
@@ -14,10 +14,6 @@ class GiternalHelper
14
14
  end
15
15
  end
16
16
 
17
- def self.wipe_repos
18
- FileUtils.rm_r(tmp_path) if File.directory?(tmp_path)
19
- end
20
-
21
17
  def self.tmp_path
22
18
  "/tmp/giternal_test"
23
19
  end
@@ -27,7 +23,7 @@ class GiternalHelper
27
23
  end
28
24
 
29
25
  def self.base_project_dir
30
- tmp_path + '/main_repo'
26
+ tmp_path + '/main_repo'
31
27
  end
32
28
 
33
29
  def self.run(*args)
@@ -70,6 +66,7 @@ class GiternalHelper
70
66
 
71
67
  def self.clean!
72
68
  FileUtils.rm_rf tmp_path
69
+ %w(GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE).each {|var| ENV[var] = nil }
73
70
  end
74
71
 
75
72
  def self.update_externals
@@ -89,7 +86,7 @@ class GiternalHelper
89
86
  GiternalHelper.run('unfreeze')
90
87
  end
91
88
  end
92
-
89
+
93
90
  def self.repo_contents(path)
94
91
  Dir.chdir(path) do
95
92
  contents = `git cat-file -p HEAD`
data/lib/giternal/app.rb CHANGED
@@ -30,8 +30,11 @@ module Giternal
30
30
  def config
31
31
  return @config if @config
32
32
 
33
- config_file = File.expand_path(@base_dir + '/config/giternal.yml')
34
- unless File.file?(config_file)
33
+ config_file = ['config/giternal.yml', '.giternal.yml'].detect do |file|
34
+ File.file? File.expand_path(@base_dir + '/' + file)
35
+ end
36
+
37
+ if config_file.nil?
35
38
  $stderr.puts "config/giternal.yml is missing"
36
39
  exit 1
37
40
  end
@@ -16,6 +16,8 @@ module Giternal
16
16
  end
17
17
 
18
18
  def update
19
+ git_ignore_self
20
+
19
21
  return true if frozen?
20
22
  FileUtils.mkdir_p checkout_path unless File.exist?(checkout_path)
21
23
  if checked_out?
@@ -78,5 +80,13 @@ module Giternal
78
80
  block.call
79
81
  puts " ..updated\n" if verbose
80
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
81
91
  end
82
92
  end
@@ -14,8 +14,19 @@ module Giternal
14
14
  YamlConfig.stub!(:new).and_return @mock_config
15
15
  end
16
16
 
17
- it "should exit with an error when no config file exists" do
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
18
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
19
30
  $stderr.should_receive(:puts)
20
31
  @app.should_receive(:exit).with(1)
21
32
  @app.config
@@ -17,6 +17,23 @@ module Giternal
17
17
  should == 'foo'
18
18
  end
19
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
+
20
37
  it "should not show any output when verbose mode is off" do
21
38
  @repository.verbose = false
22
39
  @repository.should_not_receive(:puts)
data/spec/spec_helper.rb CHANGED
@@ -11,9 +11,9 @@ require 'fileutils'
11
11
  require 'giternal_helper'
12
12
 
13
13
  Spec::Runner.configuration.before(:each) do
14
- GiternalHelper.wipe_repos
14
+ GiternalHelper.clean!
15
15
  end
16
16
 
17
17
  Spec::Runner.configuration.after(:each) do
18
- GiternalHelper.wipe_repos
18
+ GiternalHelper.clean!
19
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"