webmat-git_remote_branch 0.2.4 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +17 -0
- data/COPYING +18 -0
- data/README +39 -13
- data/Rakefile +2 -41
- data/TODO +8 -12
- data/bin/grb +10 -3
- data/lib/git_remote_branch.rb +62 -24
- data/lib/param_reader.rb +54 -22
- data/lib/version.rb +14 -0
- data/tasks/gem.rake +73 -0
- data/tasks/test.rake +21 -0
- data/test/functional/grb_test.rb +154 -0
- data/test/helpers/array_extensions.rb +14 -0
- data/test/helpers/dir_stack.rb +25 -0
- data/test/helpers/extractable.rb +63 -0
- data/test/helpers/git_helper.rb +33 -0
- data/test/helpers/more_assertions.rb +16 -0
- data/test/helpers/shoulda_functional_helpers.rb +106 -0
- data/test/helpers/shoulda_unit_helpers.rb +88 -0
- data/test/helpers/temp_dir_helper.rb +38 -0
- data/test/test_helper.rb +16 -8
- data/test/unit/git_remote_branch_test.rb +39 -0
- data/test/unit/param_reader_test.rb +261 -0
- data/vendor/capture_fu.rb +58 -0
- metadata +46 -8
- data/test/git_helper.rb +0 -57
data/lib/version.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module GitRemoteBranch
|
2
|
+
module VERSION #:nodoc:
|
3
|
+
MAJOR = 0
|
4
|
+
MINOR = 2
|
5
|
+
TINY = 6
|
6
|
+
|
7
|
+
STRING = [MAJOR, MINOR, TINY].join('.').freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
NAME = 'git_remote_branch'.freeze
|
11
|
+
COMPLETE_NAME = "#{NAME} #{VERSION::STRING}".freeze
|
12
|
+
COMMAND_NAME = 'grb'.freeze
|
13
|
+
SHORT_NAME = COMMAND_NAME
|
14
|
+
end
|
data/tasks/gem.rake
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
task :clean => :clobber_package
|
6
|
+
|
7
|
+
spec = Gem::Specification.new do |s|
|
8
|
+
s.name = GitRemoteBranch::NAME
|
9
|
+
s.version = GitRemoteBranch::VERSION::STRING
|
10
|
+
s.summary = "git_remote_branch eases the interaction with remote branches"
|
11
|
+
s.description = "git_remote_branch is a learning tool to ease the interaction with " +
|
12
|
+
"remote branches in simple situations."
|
13
|
+
|
14
|
+
s.authors = ['Mathieu Martin', 'Carl Mercier']
|
15
|
+
s.email = "webmat@gmail.com"
|
16
|
+
s.homepage = "http://github.com/webmat/git_remote_branch"
|
17
|
+
s.rubyforge_project = 'grb'
|
18
|
+
|
19
|
+
s.has_rdoc = false
|
20
|
+
|
21
|
+
s.test_files = Dir['test/**/*']
|
22
|
+
s.files = Dir['**/*'].reject{|f| f =~ /\Apkg|\Acoverage|\.gemspec\Z/}
|
23
|
+
|
24
|
+
s.executable = 'grb'
|
25
|
+
s.bindir = "bin"
|
26
|
+
s.require_path = "lib"
|
27
|
+
|
28
|
+
s.add_dependency( 'colored', '>= 1.1' )
|
29
|
+
end
|
30
|
+
|
31
|
+
#Creates clobber_package, gem, package and repackage tasks
|
32
|
+
#Note on clobber_package: fortunately, this will clobber the CODE package
|
33
|
+
Rake::GemPackageTask.new(spec) do |p|
|
34
|
+
p.gem_spec = spec
|
35
|
+
end
|
36
|
+
|
37
|
+
TAG_COMMAND = "git tag -m 'Tagging version #{GitRemoteBranch::VERSION::STRING}' -a v#{GitRemoteBranch::VERSION::STRING}"
|
38
|
+
task :tag_warn do
|
39
|
+
puts "*" * 40,
|
40
|
+
"Don't forget to tag the release:",
|
41
|
+
'',
|
42
|
+
" " + TAG_COMMAND,
|
43
|
+
'',
|
44
|
+
"or run rake tag",
|
45
|
+
"*" * 40
|
46
|
+
end
|
47
|
+
task :tag do
|
48
|
+
sh TAG_COMMAND
|
49
|
+
end
|
50
|
+
task :gem => :tag_warn
|
51
|
+
|
52
|
+
namespace :gem do
|
53
|
+
desc "Update the gemspec for GitHub's gem server"
|
54
|
+
task :github do
|
55
|
+
File.open("#{GitRemoteBranch::NAME}.gemspec", 'w'){|f| f.puts YAML::dump(spec) }
|
56
|
+
puts "gemspec generated here: #{GitRemoteBranch::NAME}.gemspec"
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'Upload gem to rubyforge.org'
|
60
|
+
task :rubyforge => :gem do
|
61
|
+
sh 'rubyforge login'
|
62
|
+
sh "rubyforge add_release grb grb 'release #{GitRemoteBranch::VERSION::STRING}' pkg/#{spec.full_name}.gem"
|
63
|
+
sh "rubyforge add_file grb grb #{GitRemoteBranch::VERSION::STRING} pkg/#{spec.full_name}.gem"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
task :install => [:clean, :gem] do
|
68
|
+
sh "#{SUDO} gem install pkg/#{spec.full_name}.gem"
|
69
|
+
end
|
70
|
+
|
71
|
+
task :uninstall do
|
72
|
+
sh "#{SUDO} gem uninstall -v #{GitRemoteBranch::VERSION::STRING} -x #{GitRemoteBranch::NAME}"
|
73
|
+
end
|
data/tasks/test.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
desc "Run all tests"
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.pattern = 'test/**/*_test.rb'
|
6
|
+
t.verbose = true
|
7
|
+
end
|
8
|
+
|
9
|
+
namespace :test do
|
10
|
+
desc "Run functional tests"
|
11
|
+
Rake::TestTask.new(:functional) do |t|
|
12
|
+
t.pattern = 'test/functional/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Run unit tests"
|
17
|
+
Rake::TestTask.new(:unit) do |t|
|
18
|
+
t.pattern = 'test/unit/**/*_test.rb'
|
19
|
+
t.verbose = true
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require File.join( File.dirname(__FILE__), '..', 'test_helper')
|
2
|
+
|
3
|
+
class GRBTest < Test::Unit::TestCase
|
4
|
+
include ShouldaFunctionalHelpers
|
5
|
+
|
6
|
+
on_a_repository do
|
7
|
+
context "creating a branch in a local clone" do
|
8
|
+
setup do
|
9
|
+
in_directory_for :local1
|
10
|
+
run_grb_with 'create new_branch'
|
11
|
+
end
|
12
|
+
|
13
|
+
should_have_branch 'new_branch', :local, :remote
|
14
|
+
|
15
|
+
context "the remote repository" do
|
16
|
+
setup do
|
17
|
+
in_directory_for :remote
|
18
|
+
end
|
19
|
+
|
20
|
+
should_have_branch 'new_branch', :local
|
21
|
+
end
|
22
|
+
|
23
|
+
context "the other local clone, tracking the new branch" do
|
24
|
+
setup do
|
25
|
+
in_directory_for :local2
|
26
|
+
run_grb_with 'track new_branch'
|
27
|
+
end
|
28
|
+
|
29
|
+
should_have_branch 'new_branch', :local, :remote
|
30
|
+
end
|
31
|
+
|
32
|
+
context "then deleting the branch" do
|
33
|
+
setup do
|
34
|
+
run_grb_with 'delete new_branch'
|
35
|
+
end
|
36
|
+
|
37
|
+
should_not_have_branch 'new_branch', :local, :remote
|
38
|
+
|
39
|
+
context "the remote repository" do
|
40
|
+
setup do
|
41
|
+
in_directory_for :remote
|
42
|
+
end
|
43
|
+
|
44
|
+
should_not_have_branch 'new_branch', :local
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "renaming the branch" do
|
49
|
+
setup do
|
50
|
+
in_directory_for :local1
|
51
|
+
in_branch :new_branch
|
52
|
+
run_grb_with 'rename renamed_branch'
|
53
|
+
end
|
54
|
+
|
55
|
+
should_not_have_branch 'new_branch', :local, :remote
|
56
|
+
should_have_branch 'renamed_branch', :local, :remote
|
57
|
+
|
58
|
+
context "the remote repository" do
|
59
|
+
setup do
|
60
|
+
in_directory_for :remote
|
61
|
+
end
|
62
|
+
|
63
|
+
should_not_have_branch 'new_branch', :local
|
64
|
+
should_have_branch 'renamed_branch', :local
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "having a local only branch" do
|
70
|
+
setup do
|
71
|
+
in_directory_for :local1
|
72
|
+
execute "git branch my_branch"
|
73
|
+
end
|
74
|
+
|
75
|
+
should_have_branch 'my_branch', :local #Sanity check
|
76
|
+
|
77
|
+
context "remotizing the branch" do
|
78
|
+
setup do
|
79
|
+
run_grb_with 'publish my_branch'
|
80
|
+
end
|
81
|
+
|
82
|
+
should_have_branch 'my_branch', :remote
|
83
|
+
|
84
|
+
context "the remote repository" do
|
85
|
+
setup do
|
86
|
+
in_directory_for :remote
|
87
|
+
end
|
88
|
+
|
89
|
+
should_have_branch 'my_branch', :local
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "running grb with a detailed explain" do
|
95
|
+
setup do
|
96
|
+
in_directory_for :local1
|
97
|
+
@text = run_grb_with 'explain create teh_branch somewhere'
|
98
|
+
end
|
99
|
+
|
100
|
+
should "display the commands to run with the user-specified values, including current_branch" do
|
101
|
+
%w{master somewhere refs/heads/teh_branch}.each do |word|
|
102
|
+
assert_match(/#{word}/, @text)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
in_a_non_git_directory do
|
109
|
+
context "displaying help" do
|
110
|
+
setup do
|
111
|
+
@text = run_grb_with 'help'
|
112
|
+
end
|
113
|
+
|
114
|
+
should "work" do
|
115
|
+
words_in_help = %w{create delete explain git_remote_branch}
|
116
|
+
words_in_help.each do |word|
|
117
|
+
assert_match(/#{word}/, @text)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
should "not complain" do
|
122
|
+
assert_no_match(/not a git repository/i, @text)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "running grb with a generic explain" do
|
127
|
+
setup do
|
128
|
+
@text = run_grb_with 'explain create'
|
129
|
+
end
|
130
|
+
|
131
|
+
should "display the commands to run with dummy values filled in" do
|
132
|
+
#Not sure if this will turn out to be too precise to my liking...
|
133
|
+
generic_words_in_explain_create = %w{
|
134
|
+
origin current_branch refs/heads/branch_to_create
|
135
|
+
git push fetch checkout}
|
136
|
+
generic_words_in_explain_create.each do |word|
|
137
|
+
assert_match(/#{word}/, @text)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context "running grb with a detailed explain" do
|
143
|
+
setup do
|
144
|
+
@text = run_grb_with 'explain create teh_branch somewhere'
|
145
|
+
end
|
146
|
+
|
147
|
+
should "display the commands to run with the user-specified values (except for current_branch)" do
|
148
|
+
%w{somewhere current_branch refs/heads/teh_branch}.each do |word|
|
149
|
+
assert_match(/#{word}/, @text)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Can be included in any class that responds to #each.
|
2
|
+
# Such as Array.
|
3
|
+
module CountDistinct
|
4
|
+
def count_all(purge_smaller_than=0)
|
5
|
+
h={}
|
6
|
+
self.each {|e|
|
7
|
+
h[e] ? h[e] += 1 : h[e] = 1
|
8
|
+
}
|
9
|
+
h.extract{|k,v| v >= purge_smaller_than}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Array.send :include, CountDistinct
|
14
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class DirStack
|
2
|
+
attr_reader :dir_stack
|
3
|
+
|
4
|
+
def current_dir
|
5
|
+
dir_stack.size == 0 ? Dir.pwd : dir_stack.last
|
6
|
+
end
|
7
|
+
|
8
|
+
def pushd(dirname)
|
9
|
+
dir_stack.push(File.expand_path(dirname))
|
10
|
+
end
|
11
|
+
|
12
|
+
def popd
|
13
|
+
return [] if dir_stack.size==0
|
14
|
+
dir_stack.pop
|
15
|
+
dir_stack
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
dir_stack.inspect
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@dir_stack = []
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Extractable
|
2
|
+
# Hash#extract(*keys) => Hash
|
3
|
+
# Hash#extract([keys]) => Hash
|
4
|
+
# Hash#extract{|k,v| predicate } => Hash
|
5
|
+
#
|
6
|
+
# Returns a new Hash that contains only the k,v pairs where the k was
|
7
|
+
# specified in the keys array.
|
8
|
+
# If any k in keys is not present in the original Hash, it's simply
|
9
|
+
# not the resulting Hash.
|
10
|
+
#
|
11
|
+
# This is very useful to check that a Hash contains at least some desired keys
|
12
|
+
# or to get a sanitized Hash out of the one we currently have.
|
13
|
+
#
|
14
|
+
# Examples:
|
15
|
+
# h = {:bob=>'Marley',:mom=>'Barley'}
|
16
|
+
# h.extract(:bob) #=> {:bob=>'Marley'}
|
17
|
+
# h.extract(:bob, :mom) #=> {:bob=>'Marley',:mom=>'Barley'}
|
18
|
+
# h.extract([:bob, :mom]) #=> {:bob=>'Marley',:mom=>'Barley'}
|
19
|
+
# h.extract(:sos) #=> {}
|
20
|
+
|
21
|
+
def extract(*args, &block)
|
22
|
+
if block_given?
|
23
|
+
extract_block(&block)
|
24
|
+
elsif args[0].is_a? Proc
|
25
|
+
extract_block(&args[0])
|
26
|
+
elsif args.size == 0
|
27
|
+
raise ArgumentError, "extract requires either an array of keys, a block or a proc"
|
28
|
+
else
|
29
|
+
extract_keys(args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns two hashes. The first contains all pairs for which the block evaluated to true,
|
34
|
+
# the second contains all the others.
|
35
|
+
def split(&block)
|
36
|
+
trues, falses = self.class.new, self.class.new
|
37
|
+
each_pair do |k,v|
|
38
|
+
if yield(k,v)
|
39
|
+
trues[k] = v
|
40
|
+
else
|
41
|
+
falses[k] = v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
#each_pair{ |k,v| (yield(k,v) ? trues : falses)[k] = v }
|
45
|
+
return trues, falses
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def extract_keys(*keys)
|
50
|
+
extracted = self.class.new #Can therefore be included in any hash-like container
|
51
|
+
keys.flatten.each { |k| extracted[k] = self[k] if self.include?(k) }
|
52
|
+
extracted
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_block(&block)
|
56
|
+
extracted = self.class.new
|
57
|
+
each_pair{ |k,v| extracted[k] = v if yield(k,v) }
|
58
|
+
extracted
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
Hash.send :include, Extractable
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tmpdir'
|
3
|
+
require File.dirname(__FILE__) + '/temp_dir_helper'
|
4
|
+
|
5
|
+
# Instantiating a GitHelper object creates a temp directory containing 3 repos.
|
6
|
+
# 1 that's considered the remote repo and 2 peer local repos (local1 and local2).
|
7
|
+
# All 3 are synchronized with the same data (they contain a few dummy files).
|
8
|
+
# Once instantiated you can access the 3 full repo locations through attribute readers
|
9
|
+
# remote, local1 and local2.
|
10
|
+
class GitHelper < TempDirHelper
|
11
|
+
|
12
|
+
attr_reader :remote, :local1, :local2
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
super('grb_test')
|
16
|
+
|
17
|
+
@remote = init_repo(directory, 'remote')
|
18
|
+
@local1 = clone_repo(@remote, directory, 'local1')
|
19
|
+
@local2 = clone_repo(@remote, directory, 'local2')
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def init_repo(path, name)
|
24
|
+
repo_dir = File.join(path, name)
|
25
|
+
`mkdir #{repo_dir}; cd $_; git init; touch file.txt; git add .; git commit -a -m "dummy file"`
|
26
|
+
repo_dir
|
27
|
+
end
|
28
|
+
|
29
|
+
def clone_repo(origin_path, clone_path, name)
|
30
|
+
`cd #{clone_path}; git clone #{File.join(origin_path, '.git')} #{name}`
|
31
|
+
return File.join(clone_path, name)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#require 'test/unit/assertionfailederror'
|
2
|
+
module MoreAssertions
|
3
|
+
include Test::Unit
|
4
|
+
|
5
|
+
def assert_false(condition, message = nil)
|
6
|
+
unless condition == false
|
7
|
+
raise AssertionFailedError, message || "assert_false failed"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_array_content(expected_array, array, message = nil)
|
12
|
+
unless expected_array.count_all == array.count_all
|
13
|
+
raise AssertionFailedError, message || "arrays did not have the same content. Expected #{expected_array.inspect}, got #{array.inspect}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module ShouldaFunctionalHelpers
|
2
|
+
include CaptureFu
|
3
|
+
GRB_COMMAND = File.expand_path(File.dirname(__FILE__) + '/../../bin/grb') unless defined?(GRB_COMMAND)
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
base.class_eval do
|
8
|
+
include ::ShouldaFunctionalHelpers::InstanceMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def current_dir
|
14
|
+
@current_dir || raise("@current_dir is not set. Warning, Will Robinson!")
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_dir=(value)
|
18
|
+
@current_dir = value
|
19
|
+
end
|
20
|
+
|
21
|
+
# Switches to one of the directories created by GitHelper:
|
22
|
+
# :local1, :local2, :non_git or :remote
|
23
|
+
# This affects commands run with ``, system and so on.
|
24
|
+
def in_directory_for(dir)
|
25
|
+
# Just a reminder for my dumb head
|
26
|
+
raise "'in_directory_for' depends on @gh being set" unless @gh
|
27
|
+
|
28
|
+
@current_dir = eval("@gh.#{dir}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def in_branch(branch)
|
32
|
+
execute "git checkout #{branch}"
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def run_grb_with(params='')
|
37
|
+
execute "#{GRB_COMMAND} #{params}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def execute(command)
|
41
|
+
errno, returned_string = capture_process_output("cd #{current_dir} ; #{command}")
|
42
|
+
returned_string
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def get_branch_location(location)
|
47
|
+
case location.to_sym
|
48
|
+
when :local
|
49
|
+
args = '-l'
|
50
|
+
when :remote
|
51
|
+
args = '-r'
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Unknown branch location: #{location.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module ClassMethods
|
59
|
+
def should_have_branch(what_branch, *wheres)
|
60
|
+
wheres.flatten.each do |where|
|
61
|
+
should "have the branch '#{what_branch}' #{where == :local ? 'locally' : 'remotely'}" do
|
62
|
+
args = get_branch_location(where)
|
63
|
+
assert_match(/#{what_branch}/, execute("git branch #{args}"))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def should_not_have_branch(what_branch, *wheres)
|
69
|
+
wheres.flatten.each do |where|
|
70
|
+
should "not have the branch '#{what_branch}' #{where == :local ? 'locally' : 'remotely'}" do
|
71
|
+
args = get_branch_location(where)
|
72
|
+
assert_no_match(/#{what_branch}/, execute("git branch #{args}"))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_a_repository
|
78
|
+
context "on a new repository" do
|
79
|
+
setup do
|
80
|
+
@gh = GitHelper.new
|
81
|
+
end
|
82
|
+
|
83
|
+
teardown do
|
84
|
+
@gh.cleanup
|
85
|
+
end
|
86
|
+
|
87
|
+
yield
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def in_a_non_git_directory
|
92
|
+
context "on a non-git related directory" do
|
93
|
+
setup do
|
94
|
+
@temp_dir = TempDirHelper.new
|
95
|
+
@current_dir = @temp_dir.directory
|
96
|
+
end
|
97
|
+
|
98
|
+
teardown do
|
99
|
+
@temp_dir.cleanup
|
100
|
+
end
|
101
|
+
|
102
|
+
yield
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|