cherrybase 0.0.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 born2snipe
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,23 @@
1
+ = cherrybase
2
+
3
+ Git cherry-pick a range of commits from a another branch into your current branch
4
+
5
+ == Example usages:
6
+ This will attempt to cherry-pick from the <first-commit> all the way to the very last commit of the <branch>.
7
+
8
+ cherrybase <branch> <first-commit>
9
+
10
+ This will attempt to cherry-pick from the <first-commit> all the way to <last-commit> from the <branch>.
11
+
12
+ cherrybase <branch> <first-commit>..<last-commit>
13
+
14
+
15
+ If you encounter a merge conflict you should be able to handle this similar to how you would a 'git rebase'. You make the changes as needed and stage the changes for commit. You now have two choices just like with rebasing '--continue' or '--abort'.
16
+
17
+ This will commit your changes using the authorship of the original commit. Then attempt to proceed with the cherry-picking.
18
+
19
+ cherrybase --continue
20
+
21
+ This will reset your HEAD back to the original commit just before you started cherrybasing.
22
+
23
+ cherrybase --abort
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "cherrybase"
8
+ gem.summary = %Q{Ruby gem to cherry-pick a range of commits with similar rebase options}
9
+ gem.description = %Q{Ruby gem to cherry-pick a range of commits with similar rebase options}
10
+ gem.email = "born2snipe@gmail.com"
11
+ gem.homepage = "http://github.com/born2snipe/cherrybase"
12
+ gem.authors = ["born2snipe"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: 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
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "cherrybase #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/cherrybase ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+ require 'executor'
5
+
6
+ Cherrybase::Executor.new().execute([*ARGV])
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{cherrybase}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["born2snipe"]
12
+ s.date = %q{2009-12-12}
13
+ s.default_executable = %q{cherrybase}
14
+ s.description = %q{Ruby gem to cherry-pick a range of commits with similar rebase options}
15
+ s.email = %q{born2snipe@gmail.com}
16
+ s.executables = ["cherrybase"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/cherrybase",
29
+ "cherrybase.gemspec",
30
+ "lib/args.rb",
31
+ "lib/baser.rb",
32
+ "lib/cmd.rb",
33
+ "lib/executor.rb",
34
+ "lib/file_util.rb",
35
+ "lib/git.rb",
36
+ "spec/args_spec.rb",
37
+ "spec/baser_spec.rb",
38
+ "spec/executor_spec.rb",
39
+ "spec/file_util_spec.rb",
40
+ "spec/git_spec.rb",
41
+ "spec/spec.opts",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+ s.homepage = %q{http://github.com/born2snipe/cherrybase}
45
+ s.rdoc_options = ["--charset=UTF-8"]
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.5}
48
+ s.summary = %q{Ruby gem to cherry-pick a range of commits with similar rebase options}
49
+ s.test_files = [
50
+ "spec/args_spec.rb",
51
+ "spec/baser_spec.rb",
52
+ "spec/executor_spec.rb",
53
+ "spec/file_util_spec.rb",
54
+ "spec/git_spec.rb",
55
+ "spec/spec_helper.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
64
+ else
65
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
69
+ end
70
+ end
71
+
data/lib/args.rb ADDED
@@ -0,0 +1,32 @@
1
+ module Cherrybase
2
+ class Args
3
+ def parse(args)
4
+ if args.include?('--help')
5
+ return {'help' => true}
6
+ end
7
+
8
+ result = {}
9
+
10
+ if !args.include?('--continue') && !args.include?('--abort')
11
+ raise "You must supply at least the branch name and the starting commit hash to begin cherrybasing" if args.length != 2
12
+
13
+ result['branch'] = args[0]
14
+
15
+ if args[1] =~ /[0-9a-z]+\.\.[0-9a-z]+/
16
+ parts = args[1].split('..')
17
+ result['starting-commit'] = parts[0]
18
+ result['ending-commit'] = parts[1]
19
+ else
20
+ result['starting-commit'] = args[1]
21
+ end
22
+ else
23
+ result['continue'] = args.include?('--continue')
24
+ result['abort'] = args.include?('--abort')
25
+
26
+ raise "You supplied --abort and --continue, please pick one" if result['continue'] && result['abort']
27
+ end
28
+
29
+ result
30
+ end
31
+ end
32
+ end
data/lib/baser.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'git'
2
+ require 'file_util'
3
+
4
+ module Cherrybase
5
+ class Baser
6
+
7
+ def initialize(git = Cherrybase::Git.new, file_util = Cherrybase::FileUtil.new)
8
+ @git = git
9
+ @file_util = file_util
10
+ end
11
+
12
+ def init(branch_name, starting_commit, ending_commit)
13
+ raise "Could not locate .git folder! Is this a Git repository?" if !@file_util.git_repo?
14
+ raise "Could not find branch (#{branch_name}) in the Git repository" if !@git.has_branch?(branch_name)
15
+ raise "It appears you are already in the middle of a cherrybase!?" if @file_util.temp_file?
16
+ raise "Could not locate START hash (#{starting_commit}) in the Git repository history" if !@git.has_commit?(branch_name, starting_commit)
17
+ raise "Could not locate END hash (#{ending_commit}) in the Git repository history" if ending_commit != nil && !@git.has_commit?(branch_name, ending_commit)
18
+
19
+ first_commit = @git.resolve_commit(branch_name, starting_commit)
20
+ if (ending_commit)
21
+ last_commit = @git.resolve_commit(branch_name, ending_commit)
22
+ else
23
+ last_commit = @git.last_commit(branch_name)
24
+ end
25
+
26
+ commits = @git.commits_to_cherrypick(branch_name, first_commit, last_commit)
27
+ @file_util.write_temp_file(@git.last_commit(@git.current_branch), first_commit, commits)
28
+ end
29
+
30
+ def continue(commit_previous_hash = false)
31
+ raise "It appears you are not in the middle of a cherrybase!?" if !@file_util.temp_file?
32
+
33
+ temp_data = @file_util.read_temp_file()
34
+ commits = temp_data['commits']
35
+ next_cherrypick = temp_data['next_cherrypick']
36
+
37
+ if commit_previous_hash
38
+ @git.commit(commits[commits.index(next_cherrypick) - 1])
39
+ end
40
+
41
+ conflicts_found = false
42
+ last_commit_applied = nil
43
+ i = commits.index(next_cherrypick)
44
+
45
+ while i < commits.length
46
+ puts "Applying #{i+1} of #{commits.length} cherry-picks\r"
47
+ last_commit_applied = commits[i]
48
+ @git.cherry_pick(last_commit_applied)
49
+ if @git.has_conflicts?
50
+ conflicts_found = true
51
+ break
52
+ end
53
+ i += 1
54
+ end
55
+
56
+ if conflicts_found
57
+ puts "Conflict(s) Encountered!"
58
+ @git.status
59
+ if (last_commit_applied == commits.last)
60
+ @file_util.delete_temp_file()
61
+ else
62
+ @file_util.write_temp_file(temp_data['starting_commit'], commits.last, commits)
63
+ end
64
+ else
65
+ @file_util.delete_temp_file()
66
+ end
67
+ puts "Cherrybase completed!"
68
+ end
69
+
70
+ def abort()
71
+ raise "It appears you are not in the middle of a cherrybase!?" if !@file_util.temp_file?
72
+ temp_data = @file_util.read_temp_file()
73
+ @git.reset(temp_data['starting_commit'])
74
+ @file_util.delete_temp_file()
75
+ end
76
+
77
+ end
78
+ end
data/lib/cmd.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'open3'
2
+
3
+ module Cherrybase
4
+ DEBUG = false
5
+
6
+ class Cmd
7
+ def run(command = '', show_lines = false)
8
+ if DEBUG
9
+ puts "[Cmd::run] #{command}"
10
+ end
11
+ lines = []
12
+ Open3.popen3(command) { |stdin, stdout, stderr| lines = stdout.readlines }
13
+ if DEBUG || show_lines
14
+ lines.each do |line|
15
+ puts line
16
+ end
17
+ end
18
+ lines
19
+ end
20
+ end
21
+ end
data/lib/executor.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'args'
2
+ require 'baser'
3
+
4
+ module Cherrybase
5
+ class Executor
6
+ def initialize(baser = Cherrybase::Baser.new)
7
+ @baser = baser
8
+ end
9
+
10
+ def execute(args)
11
+ begin
12
+ input = Cherrybase::Args.new().parse(args)
13
+ if (input['continue'])
14
+ @baser.continue(true)
15
+ else
16
+ if (input['abort'])
17
+ @baser.abort
18
+ else
19
+ if input['help']
20
+ showHelp()
21
+ else
22
+ @baser.init(input['branch'], input['starting-commit'], input['ending-commit'])
23
+ @baser.continue
24
+ end
25
+ end
26
+ end
27
+ rescue RuntimeError => err
28
+ puts "\n#{err}\n\n"
29
+ showUsage()
30
+ end
31
+ end
32
+
33
+ def showUsage()
34
+ puts "Usage: cherrybase [<branch> [<commit> | <commit>..<commit>]] | --continue | --abort"
35
+ end
36
+
37
+ def showHelp()
38
+ puts "NAME"
39
+ puts "\tcherrybase - cherry-pick a range of commits from one branch to the current branch"
40
+ puts "SYNOPSIS"
41
+ puts "\tcherrybase [<branch> [<commit> | <commit>..<commit>]] | --continue | --abort"
42
+ puts "DESCRIPTION"
43
+ puts "\tThe idea is to cherry-pick across multiple commits and have similar functionality as the rebase command."
44
+ end
45
+
46
+ end
47
+ end
data/lib/file_util.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'yaml'
2
+
3
+ module Cherrybase
4
+ class FileUtil
5
+ def git_repo?(directory = File.expand_path('.'))
6
+ git_root_dir(directory) != nil
7
+ end
8
+
9
+ def git_root_dir(directory = File.expand_path('.'))
10
+ current_directory = directory
11
+ while !File.exists?(File.join(current_directory, '.git'))
12
+ current_directory = File.dirname(current_directory)
13
+ end
14
+ current_directory
15
+ end
16
+
17
+ def temp_file?(directory = File.expand_path('.'))
18
+ File.exist?(temp_file(directory))
19
+ end
20
+
21
+ def temp_file(directory = File.expand_path('.'))
22
+ File.join(File.join(git_root_dir(directory), '.git'), 'cherrybase')
23
+ end
24
+
25
+ def delete_temp_file(directory = File.expand_path('.'))
26
+ File.delete(temp_file(directory))
27
+ end
28
+
29
+ def read_temp_file(directory = File.expand_path('.'))
30
+ YAML::load_file( temp_file(directory) )
31
+ end
32
+
33
+ def write_temp_file(starting_commit = nil, next_cherrypick = nil, commits = nil, directory = File.expand_path('.'))
34
+ data = {
35
+ "starting_commit" => starting_commit,
36
+ "next_cherrypick" => next_cherrypick,
37
+ "commits" => commits
38
+ }
39
+ filename = temp_file(directory)
40
+ File.open(filename, "w") do |f|
41
+ f.write(YAML::dump(data))
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/git.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'cmd'
2
+
3
+ module Cherrybase
4
+ class Git
5
+ def initialize(cmd = Cherrybase::Cmd.new)
6
+ @cmd = cmd
7
+ end
8
+
9
+ def reset(commit_hash)
10
+ @cmd.run("git reset --hard #{commit_hash}")
11
+ end
12
+
13
+ def has_commit?(branch_name, commit_hash)
14
+ resolve_commit(branch_name, commit_hash) != nil
15
+ end
16
+
17
+ def resolve_commit(branch_name, commit_hash)
18
+ if commit_hash
19
+ raise "Please supply at least 5 characters for a commit hash" if commit_hash.length < 5
20
+ @cmd.run("git log #{branch_name} --pretty=oneline").each do |line|
21
+ if line == commit_hash || line.include?(commit_hash)
22
+ return line.split(' ')[0]
23
+ end
24
+ end
25
+ end
26
+ nil
27
+ end
28
+
29
+ def last_commit(branch_name)
30
+ lines = @cmd.run("git log #{branch_name} --pretty=oneline")
31
+ if (lines.length > 0)
32
+ lines[0].split(' ')[0]
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def cherry_pick(commit_hash)
39
+ @cmd.run("git cherry-pick #{commit_hash}")
40
+ end
41
+
42
+ def has_conflicts?()
43
+ @cmd.run("git ls-files -tu").length > 0
44
+ end
45
+
46
+ def commit(commit_hash)
47
+ @cmd.run("git commit -C #{commit_hash}")
48
+ end
49
+
50
+ def status()
51
+ @cmd.run("git status", true)
52
+ end
53
+
54
+ def current_branch()
55
+ @cmd.run("git branch").each do |line|
56
+ if line.index('*')
57
+ return line.gsub(/\*(.+)/, '\1').strip
58
+ end
59
+ end
60
+ nil
61
+ end
62
+
63
+ def has_branch?(branch_name)
64
+ @cmd.run("git branch").each do |line|
65
+ if line.strip == branch_name
66
+ return true
67
+ end
68
+ end
69
+ false
70
+ end
71
+
72
+ def commits_to_cherrypick(branch_name, first_commit = nil, last_commit = nil)
73
+ commits = []
74
+ @cmd.run("git log #{branch_name} --pretty=oneline").each do |line|
75
+ commit_hash = line.split(' ')[0]
76
+ if commit_hash == first_commit
77
+ commits << commit_hash
78
+ break
79
+ else
80
+ commits << commit_hash
81
+ end
82
+ end
83
+
84
+ if last_commit
85
+ remove_commits = []
86
+ commits.each do |commit|
87
+ if commit == last_commit
88
+ break
89
+ else
90
+ remove_commits << commit
91
+ end
92
+ end
93
+ commits = commits - remove_commits
94
+ end
95
+
96
+ commits.reverse
97
+ end
98
+
99
+ def last_svn_commit()
100
+ last_commit_hash = nil
101
+ svn_commit_found = false
102
+ @cmd.run("git log").each do |line|
103
+ if line.include?("git-svn-id")
104
+ svn_commit_found = true
105
+ break
106
+ else
107
+ if line =~ /commit [a-z0-9]+$/
108
+ last_commit_hash = line[7,line.length]
109
+ end
110
+ end
111
+ end
112
+ if (!svn_commit_found)
113
+ last_commit_hash = nil
114
+ end
115
+ last_commit_hash
116
+ end
117
+ end
118
+ end
data/spec/args_spec.rb ADDED
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Cherrybase::Args do
4
+
5
+ before(:each) do
6
+ @args = Cherrybase::Args.new
7
+ end
8
+
9
+ it "should show help" do
10
+ @args.parse(['--help']).should == {'help' => true}
11
+ end
12
+
13
+ it "should break up a range of commits and set the starting and the last commit" do
14
+ @args.parse(['branch', 'start..end']).should == {'branch'=>'branch', 'starting-commit' => 'start', 'ending-commit' => 'end'}
15
+ end
16
+
17
+ it "should raise an error if a branch name is given without a commit hash" do
18
+ lambda {
19
+ @args.parse(['branch-name'])
20
+ }.should raise_error(RuntimeError, 'You must supply at least the branch name and the starting commit hash to begin cherrybasing')
21
+ end
22
+
23
+ it "should set the branch name and the commit hash given" do
24
+ @args.parse(['branch-name', 'commit-hash']).should == {'branch' => 'branch-name', 'starting-commit' => 'commit-hash'}
25
+ end
26
+
27
+ it "should raise an error if a commit hash is not given" do
28
+ lambda {
29
+ @args.parse([])
30
+ }.should raise_error(RuntimeError, 'You must supply at least the branch name and the starting commit hash to begin cherrybasing')
31
+ end
32
+
33
+ it "should raise an error if both --abort and --continue are given" do
34
+ lambda {
35
+ @args.parse(['--abort', '--continue'])
36
+ }.should raise_error(RuntimeError, "You supplied --abort and --continue, please pick one")
37
+ end
38
+
39
+ it "should set abort to true if --abort is given" do
40
+ @args.parse(['--abort']).should == {'abort' => true, 'continue' => false}
41
+ end
42
+
43
+ it "should set continue to true if --continue is given" do
44
+ @args.parse(['--continue']).should == {'continue' => true, 'abort' => false}
45
+ end
46
+
47
+ end
@@ -0,0 +1,201 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Cherrybase::Baser do
4
+ BRANCH = 'branch-name'
5
+
6
+ before(:each) do
7
+ @git = mock("git")
8
+ @file_util = mock("file_util")
9
+ @baser = Cherrybase::Baser.new(@git, @file_util)
10
+ end
11
+
12
+ it "should reset HEAD back to the last original commit before any cherry-picks" do
13
+ @file_util.stub!(:temp_file?).and_return(true)
14
+ @file_util.stub!(:read_temp_file).and_return({"starting_commit" => "start"})
15
+ @file_util.should_receive(:delete_temp_file)
16
+ @git.should_receive(:reset).with("start")
17
+
18
+ @baser.abort
19
+ end
20
+
21
+ it "should raise an error if you try to abort while not in a cherrybase" do
22
+ @file_util.should_receive(:temp_file?).and_return(false)
23
+
24
+ lambda {
25
+ @baser.abort
26
+ }.should raise_error(RuntimeError, "It appears you are not in the middle of a cherrybase!?")
27
+ end
28
+
29
+ it "should use the end commit if given" do
30
+ @file_util.should_receive(:git_repo?).and_return(true)
31
+ @file_util.should_receive(:temp_file?).and_return(false)
32
+ @git.should_receive(:has_branch?).with(BRANCH).and_return(true)
33
+ @git.should_receive(:has_commit?).with(BRANCH, 'starting-commit').and_return(true)
34
+ @git.should_receive(:has_commit?).with(BRANCH, 'end-commit').and_return(true)
35
+ @git.should_receive(:commits_to_cherrypick).with(BRANCH, 'first_commit', 'last_commit').and_return(['commits-to-cherrypick'])
36
+ @git.stub!(:resolve_commit).with(BRANCH, "starting-commit").and_return("first_commit")
37
+ @git.stub!(:resolve_commit).with(BRANCH, "end-commit").and_return("last_commit")
38
+ @file_util.should_receive(:write_temp_file).with('last_original_commit', 'first_commit', ['commits-to-cherrypick'])
39
+ @git.stub!(:current_branch).and_return("current")
40
+ @git.stub!(:last_commit).with("current").and_return("last_original_commit")
41
+
42
+ @baser.init(BRANCH, 'starting-commit', "end-commit")
43
+ end
44
+
45
+ it "should raise an error if the end commit could not be located in the history" do
46
+ @file_util.should_receive(:git_repo?).and_return(true)
47
+ @file_util.should_receive(:temp_file?).and_return(false)
48
+ @git.should_receive(:has_branch?).with(BRANCH).and_return(true)
49
+ @git.should_receive(:has_commit?).with(BRANCH, "start").and_return(true)
50
+ @git.should_receive(:has_commit?).with(BRANCH, "end").and_return(false)
51
+ lambda {
52
+ @baser.init(BRANCH, "start", "end")
53
+ }.should raise_error(RuntimeError, "Could not locate END hash (end) in the Git repository history")
54
+ end
55
+
56
+ it "should raise an error if the start commit could not be located in the history" do
57
+ @file_util.should_receive(:git_repo?).and_return(true)
58
+ @file_util.should_receive(:temp_file?).and_return(false)
59
+ @git.should_receive(:has_branch?).with(BRANCH).and_return(true)
60
+ lambda {
61
+ @git.should_receive(:has_commit?).with(BRANCH, "doesNotExist").and_return(false)
62
+ @baser.init(BRANCH, "doesNotExist", nil)
63
+ }.should raise_error(RuntimeError, "Could not locate START hash (doesNotExist) in the Git repository history")
64
+ end
65
+
66
+ it "should commit staged merge resolution" do
67
+ @file_util.should_receive(:temp_file?).and_return(true)
68
+ @file_util.should_receive(:read_temp_file).and_return({
69
+ "starting_commit" => "start",
70
+ "next_cherrypick" => "commit1",
71
+ "commits" => ["start", "commit1"]
72
+ })
73
+ @git.should_receive(:commit).with("start")
74
+ @git.should_receive(:cherry_pick).with("commit1")
75
+ @git.should_receive(:has_conflicts?).and_return(false)
76
+ @file_util.should_receive(:delete_temp_file)
77
+
78
+ @baser.continue(true)
79
+ end
80
+
81
+ it "should start apply commits based on the next_cherrypick" do
82
+ @file_util.should_receive(:temp_file?).and_return(true)
83
+ @file_util.should_receive(:read_temp_file).and_return({
84
+ "starting_commit" => "start",
85
+ "next_cherrypick" => "commit1",
86
+ "commits" => ["start", "commit1"]
87
+ })
88
+ @git.should_receive(:cherry_pick).with("commit1")
89
+ @git.should_receive(:has_conflicts?).and_return(false)
90
+ @file_util.should_receive(:delete_temp_file)
91
+
92
+ @baser.continue
93
+ end
94
+
95
+ it "should cleanup the temp file if a conflict is encountered on the last commit" do
96
+ @file_util.should_receive(:temp_file?).and_return(true)
97
+ @file_util.should_receive(:read_temp_file).and_return({
98
+ "starting_commit" => "start",
99
+ "next_cherrypick" => "start",
100
+ "commits" => ["start"]
101
+ })
102
+ @git.should_receive(:cherry_pick).with("start")
103
+ @git.should_receive(:has_conflicts?).and_return(true)
104
+ @git.should_receive(:status)
105
+ @file_util.should_receive(:delete_temp_file)
106
+
107
+ @baser.continue
108
+ end
109
+
110
+ it "should stop cherrypicking if a conflict is found" do
111
+ @file_util.should_receive(:temp_file?).and_return(true)
112
+ @file_util.should_receive(:read_temp_file).and_return({
113
+ "starting_commit" => "start",
114
+ "next_cherrypick" => "start",
115
+ "commits" => ["start", "end"]
116
+ })
117
+ @git.should_receive(:cherry_pick).with("start")
118
+ @git.should_receive(:has_conflicts?).and_return(true)
119
+ @git.should_receive(:status)
120
+ @file_util.should_receive(:write_temp_file).with("start", "end", ["start", "end"])
121
+
122
+ @baser.continue
123
+ end
124
+
125
+
126
+ it "should attempt to cherry-pick all the commits left (two commits)" do
127
+ @file_util.should_receive(:temp_file?).and_return(true)
128
+ @file_util.should_receive(:read_temp_file).and_return({
129
+ "starting_commit" => "start",
130
+ "next_cherrypick" => "start",
131
+ "commits" => ["start", "end"]
132
+ })
133
+ @git.should_receive(:cherry_pick).with("start")
134
+ @git.should_receive(:cherry_pick).with("end")
135
+ @git.should_receive(:has_conflicts?).and_return(false)
136
+ @git.should_receive(:has_conflicts?).and_return(false)
137
+ @file_util.should_receive(:delete_temp_file)
138
+
139
+ @baser.continue
140
+ end
141
+
142
+ it "should attempt to cherry-pick all the commits left (one commit)" do
143
+ @file_util.should_receive(:temp_file?).and_return(true)
144
+ @file_util.should_receive(:read_temp_file).and_return({
145
+ "starting_commit" => "start",
146
+ "next_cherrypick" => "start",
147
+ "commits" => ["start"]
148
+ })
149
+ @git.should_receive(:cherry_pick).with("start")
150
+ @git.should_receive(:has_conflicts?).and_return(false)
151
+ @file_util.should_receive(:delete_temp_file)
152
+
153
+ @baser.continue
154
+ end
155
+
156
+ it "should throw an error if you are not in the middle of a cherrybase" do
157
+ @file_util.should_receive(:temp_file?).and_return(false)
158
+ lambda {
159
+ @baser.continue
160
+ }.should raise_error(RuntimeError, "It appears you are not in the middle of a cherrybase!?")
161
+ end
162
+
163
+ it "should create the cherrybase temp file with the given branch's last commit" do
164
+ @file_util.should_receive(:git_repo?).and_return(true)
165
+ @file_util.should_receive(:temp_file?).and_return(false)
166
+ @git.should_receive(:has_branch?).with(BRANCH).and_return(true)
167
+ @git.should_receive(:last_commit).with(BRANCH).and_return('last-commit')
168
+ @git.should_receive(:has_commit?).with(BRANCH, 'starting-commit').and_return(true)
169
+ @git.should_receive(:commits_to_cherrypick).with(BRANCH, 'first_commit', 'last-commit').and_return(['commits-to-cherrypick'])
170
+ @git.stub!(:resolve_commit).with(BRANCH, "starting-commit").and_return("first_commit")
171
+ @git.stub!(:current_branch).and_return("current")
172
+ @git.stub!(:last_commit).with("current").and_return("last_original_commit")
173
+ @file_util.should_receive(:write_temp_file).with('last_original_commit', 'first_commit', ['commits-to-cherrypick'])
174
+ @baser.init(BRANCH, 'starting-commit', nil)
175
+ end
176
+
177
+ it "should throw an error if the given branch name does not exist in the repository" do
178
+ lambda {
179
+ @file_util.should_receive(:git_repo?).and_return(true)
180
+ @git.should_receive(:has_branch?).with(BRANCH).and_return(false)
181
+ @baser.init(BRANCH, nil, nil)
182
+ }.should raise_error(RuntimeError, "Could not find branch (branch-name) in the Git repository")
183
+ end
184
+
185
+ it "should throw an exception if the git repo folder could not be discovered" do
186
+ lambda {
187
+ @file_util.should_receive(:git_repo?).and_return(false)
188
+ @baser.init(nil, nil, nil)
189
+ }.should raise_error(RuntimeError, "Could not locate .git folder! Is this a Git repository?")
190
+ end
191
+
192
+ it "should throw an error if you already in the middle of a cherrybase" do
193
+ @file_util.should_receive(:git_repo?).and_return(true)
194
+ @git.should_receive(:has_branch?).with(BRANCH).and_return(true)
195
+ lambda {
196
+ @file_util.should_receive(:temp_file?).and_return(true)
197
+ @baser.init("branch-name", nil, nil)
198
+ }.should raise_error(RuntimeError, "It appears you are already in the middle of a cherrybase!?")
199
+ end
200
+
201
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Cherrybase::Executor do
4
+
5
+ before(:each) do
6
+ @baser = mock('baser')
7
+ @executor = Cherrybase::Executor.new(@baser)
8
+ end
9
+
10
+ it "should tell the baser to init with the appropriate branch name and range of commits" do
11
+ @baser.should_receive(:init).with('branch-name', 'starting-commit', 'ending-commit')
12
+ @baser.should_receive(:continue)
13
+ @executor.execute(['branch-name', 'starting-commit..ending-commit'])
14
+ end
15
+
16
+ it "should tell the baser to init with the appropriate branch and starting commit" do
17
+ @baser.should_receive(:init).with('branch-name', 'starting-commit', nil)
18
+ @baser.should_receive(:continue)
19
+ @executor.execute(['branch-name', 'starting-commit'])
20
+ end
21
+
22
+ it "should tell the baser to abort" do
23
+ @baser.should_receive(:abort)
24
+ @executor.execute(['--abort'])
25
+ end
26
+
27
+ it "should tell the baser to continue to the next commit" do
28
+ @baser.should_receive(:continue).with(true)
29
+ @executor.execute(['--continue'])
30
+ end
31
+
32
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Cherrybase::FileUtil do
4
+
5
+ before(:each) do
6
+ @fixtures_dir = File.expand_path(File.join(File.join(__FILE__, ".."), ".."), "fixtures")
7
+ @project = File.join(File.join(@fixtures_dir, "project"))
8
+ @file_util = Cherrybase::FileUtil.new
9
+ end
10
+
11
+ it "should delete the temp file" do
12
+ test_project = File.join(@fixtures_dir, 'cherrybase-inprogress')
13
+ @file_util.write_temp_file("starting-commit", "next-commit", ["commit1", "commit2"], test_project)
14
+
15
+ @file_util.delete_temp_file(test_project)
16
+ @file_util.temp_file?(test_project).should == false
17
+ end
18
+
19
+ it "should read the temp file" do
20
+ test_project = File.join(@fixtures_dir, 'cherrybase-inprogress')
21
+ @file_util.write_temp_file("starting-commit", "next-commit", ["commit1", "commit2"], test_project)
22
+ @file_util.read_temp_file(test_project).should == {
23
+ "starting_commit" => "starting-commit",
24
+ "next_cherrypick" => "next-commit",
25
+ "commits" => ["commit1", "commit2"]
26
+ }
27
+ end
28
+
29
+ it "should return nil if the temp file does not exist in the .git folder" do
30
+ expected_tempfile = File.join(File.join(@project, '.git'), 'cherrybase')
31
+ @file_util.temp_file?(@project).should == false
32
+ end
33
+
34
+ it "should find the temp file if it exists in the .git folder" do
35
+ test_project = File.join(@fixtures_dir, 'cherrybase-inprogress')
36
+ @file_util.write_temp_file("starting-commit", "next-commit", ["commit1", "commit2"], test_project)
37
+ expected_tempfile = File.join(File.join(test_project, '.git'), 'cherrybase')
38
+ @file_util.temp_file?(test_project).should == true
39
+ end
40
+
41
+ it "should recursively look up the directory tree and return the project directory" do
42
+ @file_util.git_root_dir(File.join(@project, "module")).should == @project
43
+ end
44
+
45
+ it "should return the current directory if the current directory contains the .git folder" do
46
+ @file_util.git_root_dir(@project).should == @project
47
+ end
48
+
49
+ it "should recursively look up directory tree for a git repository" do
50
+ @file_util.git_repo?(File.join(@project, "module")).should == true
51
+ end
52
+
53
+ it "should find git repo if in the git main dir" do
54
+ @file_util.git_repo?(@project).should == true
55
+ end
56
+
57
+ end
data/spec/git_spec.rb ADDED
@@ -0,0 +1,156 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Cherrybase::Git do
4
+
5
+ before(:each) do
6
+ @cmd = mock("cmd")
7
+ @git = Cherrybase::Git.new(@cmd)
8
+ end
9
+
10
+ it "should reset the HEAD to the given commit" do
11
+ @cmd.should_receive(:run).with("git reset --hard commit")
12
+
13
+ @git.reset("commit")
14
+ end
15
+
16
+ it "should raise an error if the commit hash given is not at least 5 characters" do
17
+ lambda {
18
+ @git.has_commit?("branch", "1234")
19
+ }.should raise_error(RuntimeError, "Please supply at least 5 characters for a commit hash")
20
+ end
21
+
22
+ it "should return true if a partial match is found for the given hash" do
23
+ @cmd.should_receive(:run).with("git log branch --pretty=oneline").and_return(["commit1", "commit2-hash", "commit3"])
24
+ @git.has_commit?("branch", "-hash").should == true
25
+ end
26
+
27
+ it "should return true if an exact match is found for the given hash" do
28
+ @cmd.should_receive(:run).with("git log branch --pretty=oneline").and_return(["commit"])
29
+ @git.has_commit?("branch", "commit").should == true
30
+ end
31
+
32
+ it "should return false if a hash could not be found in the history of the given branch" do
33
+ @cmd.should_receive(:run).with("git log branch --pretty=oneline").and_return([])
34
+ @git.has_commit?("branch", "commit").should == false
35
+ end
36
+
37
+ it "should return nil if there is no commit history" do
38
+ @cmd.should_receive(:run).with("git log branch --pretty=oneline").and_return([])
39
+ @git.last_commit('branch').should == nil
40
+ end
41
+
42
+ it "should return the last commit of the given branch" do
43
+ pretty_log_lines = ["hash1 comment-1", "hash2 comment-2", "hash3 comment-3"]
44
+ @cmd.should_receive(:run).with("git log branch --pretty=oneline").and_return(pretty_log_lines)
45
+ @git.last_commit('branch').should == 'hash1'
46
+ end
47
+
48
+ it "should return the current branch name" do
49
+ @cmd.should_receive(:run).with("git branch").and_return(["branch1", "* branch2"])
50
+ @git.current_branch().should == "branch2"
51
+ end
52
+
53
+ it "should return false if the branch does not exist" do
54
+ @cmd.should_receive(:run).with("git branch").and_return(["branch1", "branch2"])
55
+ @git.has_branch?("doesNotExist").should == false
56
+ end
57
+
58
+ it "should return true if the branch exists" do
59
+ @cmd.should_receive(:run).with("git branch").and_return([" branch1", " branch2"])
60
+ @git.has_branch?("branch2").should == true
61
+ end
62
+
63
+ it "should cherry-pick the given commit hash" do
64
+ commit_hash = "commit hash"
65
+ @cmd.should_receive(:run).with("git cherry-pick #{commit_hash}")
66
+ @git.cherry_pick(commit_hash)
67
+ end
68
+
69
+ it "should return false if no files are marked as unmerged" do
70
+ @cmd.should_receive(:run).with("git ls-files -tu").and_return([])
71
+
72
+ @git.has_conflicts?().should == false
73
+ end
74
+
75
+ it "should return true if files are marked as unmerged" do
76
+ log_lines = [
77
+ "M 100644 e01079f2c38b76cf43780a2899c3f5bd2f50b3a7 1 readme.txt",
78
+ "M 100644 f7b5ff223f06fb323463b81b08759cf13678fd27 2 readme.txt",
79
+ "M 100644 0ebe257230ddb66e12610ad9b304c7605b61dfeb 3 readme.txt"
80
+ ]
81
+
82
+ @cmd.should_receive(:run).with("git ls-files -tu").and_return(log_lines)
83
+
84
+ @git.has_conflicts?().should == true
85
+ end
86
+
87
+ it "should shell out a commit" do
88
+ commit_hash = "commit hash"
89
+
90
+ @cmd.should_receive(:run).with("git commit -C #{commit_hash}")
91
+
92
+ @git.commit(commit_hash)
93
+ end
94
+
95
+ it "should shell out a git status" do
96
+ @cmd.should_receive(:run).with("git status", true)
97
+
98
+ @git.status
99
+ end
100
+
101
+ it "should grab all commit hashes after the commit and the last commit given" do
102
+ pretty_log_lines = ["hash1 comment-1", "hash2 comment-2", "hash3 comment-3", "hash4 commit-4"]
103
+
104
+ @cmd.stub!(:run).with("git log branch --pretty=oneline").and_return(pretty_log_lines)
105
+
106
+ @git.commits_to_cherrypick("branch", "hash3", "hash2").should == ["hash3", "hash2"]
107
+ end
108
+
109
+ it "should grab all commit hashes after the commit and the commit given" do
110
+ pretty_log_lines = ["hash1 comment-1", "hash2 comment-2", "hash3 comment-3"]
111
+
112
+ @cmd.stub!(:run).with("git log branch --pretty=oneline").and_return(pretty_log_lines)
113
+
114
+ @git.commits_to_cherrypick("branch", "hash2").should == ["hash2", "hash1"]
115
+ end
116
+
117
+
118
+ it "should return nil if no svn commit was found" do
119
+ log_lines = [
120
+ "commit hash1",
121
+ "Author: author-1",
122
+ "Date: date-1",
123
+ "",
124
+ " comment-1",
125
+ ""
126
+ ]
127
+
128
+ @cmd.stub!(:run).and_return(log_lines)
129
+
130
+ @git.last_svn_commit.should == nil
131
+ end
132
+
133
+ it "should find the last svn commit hash" do
134
+ log_lines = [
135
+ "commit hash1",
136
+ "Author: author-1",
137
+ "Date: date-1",
138
+ "",
139
+ " comment-1",
140
+ "",
141
+ "commit hash2",
142
+ "Author: author-2",
143
+ "Date: date-2",
144
+ "",
145
+ "comment-2",
146
+ "",
147
+ " git-svn-id: url",
148
+ ""
149
+ ]
150
+
151
+ @cmd.stub!(:run).and_return(log_lines)
152
+
153
+ @git.last_svn_commit.should == "hash2"
154
+ end
155
+
156
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,24 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'git'
4
+ require 'baser'
5
+ require 'file_util'
6
+ require 'args'
7
+ require 'executor'
8
+
9
+ require 'spec'
10
+ require 'spec/autorun'
11
+
12
+ Spec::Runner.configure do |config|
13
+
14
+ end
15
+
16
+ module Kernel
17
+ if ENV.keys.find {|env_var| env_var.match(/^TM_/)}
18
+ def rputs(*args)
19
+ puts( *["<pre>", args.collect {|a| CGI.escapeHTML(a.to_s)}, "</pre>"])
20
+ end
21
+ else
22
+ alias_method :rputs, :puts
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cherrybase
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - born2snipe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-12 00:00:00 -06:00
13
+ default_executable: cherrybase
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.9
24
+ version:
25
+ description: Ruby gem to cherry-pick a range of commits with similar rebase options
26
+ email: born2snipe@gmail.com
27
+ executables:
28
+ - cherrybase
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - bin/cherrybase
42
+ - cherrybase.gemspec
43
+ - lib/args.rb
44
+ - lib/baser.rb
45
+ - lib/cmd.rb
46
+ - lib/executor.rb
47
+ - lib/file_util.rb
48
+ - lib/git.rb
49
+ - spec/args_spec.rb
50
+ - spec/baser_spec.rb
51
+ - spec/executor_spec.rb
52
+ - spec/file_util_spec.rb
53
+ - spec/git_spec.rb
54
+ - spec/spec.opts
55
+ - spec/spec_helper.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/born2snipe/cherrybase
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.5
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Ruby gem to cherry-pick a range of commits with similar rebase options
84
+ test_files:
85
+ - spec/args_spec.rb
86
+ - spec/baser_spec.rb
87
+ - spec/executor_spec.rb
88
+ - spec/file_util_spec.rb
89
+ - spec/git_spec.rb
90
+ - spec/spec_helper.rb