webmat-git_remote_branch 0.2.4 → 0.2.6
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/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
|