git-smart-ruby-3 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.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +36 -0
- data/LICENSE.txt +20 -0
- data/README.md +68 -0
- data/Rakefile +71 -0
- data/VERSION +1 -0
- data/bin/git-smart-log +7 -0
- data/bin/git-smart-merge +7 -0
- data/bin/git-smart-pull +7 -0
- data/docs/images/git-smart.png +0 -0
- data/docs/images/smart-log-comparison.png +0 -0
- data/docs/images/smart-pull-screenshot.png +0 -0
- data/docs/introductory_blog_post.md +43 -0
- data/docs/smart-log.html +59 -0
- data/docs/smart-merge.html +212 -0
- data/docs/smart-pull.html +304 -0
- data/git-smart-ruby-3.gemspec +25 -0
- data/lib/commands/smart-log.rb +9 -0
- data/lib/commands/smart-merge.rb +63 -0
- data/lib/commands/smart-pull.rb +117 -0
- data/lib/core_ext/array.rb +9 -0
- data/lib/core_ext/hash.rb +21 -0
- data/lib/core_ext/object.rb +8 -0
- data/lib/git-smart/exceptions.rb +10 -0
- data/lib/git-smart/execution_context.rb +39 -0
- data/lib/git-smart/git_repo.rb +172 -0
- data/lib/git-smart/git_smart.rb +28 -0
- data/lib/git-smart/safe_shell.rb +21 -0
- data/lib/git-smart-ruby-3.rb +8 -0
- data/spec/smart-merge_failures_spec.rb +49 -0
- data/spec/smart-merge_spec.rb +104 -0
- data/spec/smart-pull_spec.rb +215 -0
- data/spec/spec_helper.rb +65 -0
- metadata +141 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
class GitRepo
|
7
|
+
def initialize(dir)
|
8
|
+
@dir = dir
|
9
|
+
unless File.directory?(git_dir)
|
10
|
+
raise GitSmart::RunFailed.new(
|
11
|
+
<<-MSG.gsub(/^\s+/, '')
|
12
|
+
You need to run this from within a Git directory.
|
13
|
+
Current working directory: #{File.expand_path(dir)}
|
14
|
+
Expected .git directory: #{git_dir}
|
15
|
+
MSG
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def git_dir
|
21
|
+
gitdir = Pathname.new(@dir).join('.git')
|
22
|
+
|
23
|
+
unless File.exist?(gitdir)
|
24
|
+
@dir = git('rev-parse', '--show-toplevel').chomp
|
25
|
+
gitdir = Pathname.new(@dir).join('.git') unless @dir.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
if File.file?(gitdir)
|
29
|
+
submodule = YAML.load_file(gitdir)
|
30
|
+
gitdir = Pathname.new(@dir).join(submodule['gitdir']).to_path
|
31
|
+
end
|
32
|
+
|
33
|
+
gitdir
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_branch
|
37
|
+
head_file = File.join(git_dir, 'HEAD')
|
38
|
+
File.read(head_file).strip.sub(%r(^.*refs/heads/), '')
|
39
|
+
end
|
40
|
+
|
41
|
+
def sha(ref)
|
42
|
+
sha = git('rev-parse', ref).chomp
|
43
|
+
sha.empty? ? nil : sha
|
44
|
+
end
|
45
|
+
|
46
|
+
def tracking_remote
|
47
|
+
config("branch.#{current_branch}.remote")
|
48
|
+
end
|
49
|
+
|
50
|
+
def tracking_branch
|
51
|
+
key = "branch.#{current_branch}.merge"
|
52
|
+
value = config(key)
|
53
|
+
if value.nil?
|
54
|
+
value
|
55
|
+
elsif value =~ /^refs\/heads\/(.*)$/
|
56
|
+
$1
|
57
|
+
else
|
58
|
+
raise GitSmart::UnexpectedOutput.new("Expected the config of '#{key}' to be /refs/heads/branchname, got '#{value}'")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def fetch!(remote)
|
63
|
+
git!('fetch', remote)
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge_base(ref_a, ref_b)
|
67
|
+
git('merge-base', ref_a, ref_b).chomp
|
68
|
+
end
|
69
|
+
|
70
|
+
def exists?(ref)
|
71
|
+
git('rev-parse', ref)
|
72
|
+
$?.success?
|
73
|
+
end
|
74
|
+
|
75
|
+
def rev_list(ref_a, ref_b)
|
76
|
+
git('rev-list', "#{ref_a}..#{ref_b}").split("\n")
|
77
|
+
end
|
78
|
+
|
79
|
+
def raw_status
|
80
|
+
git('status', '-s')
|
81
|
+
end
|
82
|
+
|
83
|
+
def status
|
84
|
+
raw_status.
|
85
|
+
split("\n").
|
86
|
+
map { |l| l.split(" ") }.
|
87
|
+
group_by(&:first).
|
88
|
+
map_values { |lines| lines.map(&:last) }.
|
89
|
+
map_keys { |status|
|
90
|
+
case status
|
91
|
+
when /^[^ ]*M/; :modified
|
92
|
+
when /^[^ ]*A/; :added
|
93
|
+
when /^[^ ]*D/; :deleted
|
94
|
+
when /^[^ ]*\?\?/; :untracked
|
95
|
+
when /^[^ ]*UU/; :conflicted
|
96
|
+
else raise GitSmart::UnexpectedOutput.new("Expected the output of git status to only have lines starting with A, M, D, UU, or ??. Got: \n#{raw_status}")
|
97
|
+
end
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def dirty?
|
102
|
+
status.any? { |k,v| k != :untracked && v.any? }
|
103
|
+
end
|
104
|
+
|
105
|
+
def fast_forward!(upstream)
|
106
|
+
git!('merge', '--ff-only', upstream)
|
107
|
+
end
|
108
|
+
|
109
|
+
def stash!
|
110
|
+
git!('stash')
|
111
|
+
end
|
112
|
+
|
113
|
+
def stash_pop!
|
114
|
+
git!('stash', 'pop')
|
115
|
+
end
|
116
|
+
|
117
|
+
def rebase_preserving_merges!(upstream)
|
118
|
+
git!('rebase', '--rebase-merges', upstream)
|
119
|
+
end
|
120
|
+
|
121
|
+
def read_log(nr)
|
122
|
+
git('log', '--oneline', '-n', nr.to_s).split("\n").map { |l| l.split(" ",2) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def last_commit_messages(nr)
|
126
|
+
read_log(nr).map(&:last)
|
127
|
+
end
|
128
|
+
|
129
|
+
def log_to_shell(*args)
|
130
|
+
git_shell('log', *args)
|
131
|
+
end
|
132
|
+
|
133
|
+
def merge_no_ff!(target)
|
134
|
+
git!('merge', '--no-ff', target)
|
135
|
+
end
|
136
|
+
|
137
|
+
# helper methods, left public in case other commands want to use them directly
|
138
|
+
|
139
|
+
def git(*args)
|
140
|
+
output = exec_git(*args)
|
141
|
+
$?.success? ? output : ''
|
142
|
+
end
|
143
|
+
|
144
|
+
def git!(*args)
|
145
|
+
puts "Executing: #{['git', *args].join(" ")}"
|
146
|
+
output = exec_git(*args)
|
147
|
+
to_display = output.split("\n").map { |l| " #{l}" }.join("\n")
|
148
|
+
$?.success? ? puts(to_display) : raise(GitSmart::UnexpectedOutput.new(to_display))
|
149
|
+
output
|
150
|
+
end
|
151
|
+
|
152
|
+
def git_shell(*args)
|
153
|
+
puts "Executing: #{['git', *args].join(" ")}"
|
154
|
+
Dir.chdir(@dir) {
|
155
|
+
system('git', *args)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
def config(name)
|
160
|
+
remote = git('config', name).chomp
|
161
|
+
remote.empty? ? nil : remote
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def exec_git(*args)
|
167
|
+
return if @dir.empty?
|
168
|
+
Dir.chdir(@dir) {
|
169
|
+
SafeShell.execute('git', *args)
|
170
|
+
}
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class GitSmart
|
2
|
+
def self.run(code, args)
|
3
|
+
lambda = commands[code]
|
4
|
+
if lambda
|
5
|
+
begin
|
6
|
+
lambda.call(args)
|
7
|
+
rescue GitSmart::Exception => e
|
8
|
+
if e.message && !e.message.empty?
|
9
|
+
puts e.message.red
|
10
|
+
end
|
11
|
+
end
|
12
|
+
else
|
13
|
+
puts "No command #{code.inspect} defined! Available commands are #{commands.keys.sort.inspect}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Used like this:
|
18
|
+
# GitSmart.register 'my-command' do |repo, args|
|
19
|
+
def self.register(code, &blk)
|
20
|
+
commands[code] = lambda { |args|
|
21
|
+
ExecutionContext.new.instance_exec(GitRepo.new("."), args, &blk)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.commands
|
26
|
+
@commands ||= {}
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SafeShell
|
2
|
+
def self.execute(command, *args)
|
3
|
+
read_end, write_end = IO.pipe
|
4
|
+
pid = fork do
|
5
|
+
read_end.close
|
6
|
+
STDOUT.reopen(write_end)
|
7
|
+
STDERR.reopen(write_end)
|
8
|
+
exec(command, *args)
|
9
|
+
end
|
10
|
+
write_end.close
|
11
|
+
output = read_end.read
|
12
|
+
Process.waitpid(pid)
|
13
|
+
read_end.close
|
14
|
+
output
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.execute?(*args)
|
18
|
+
execute(*args)
|
19
|
+
$?.success?
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
describe 'smart-merge with failures' do
|
6
|
+
def local_dir; WORKING_DIR + '/local'; end
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
%x[
|
10
|
+
cd #{WORKING_DIR}
|
11
|
+
mkdir local
|
12
|
+
cd local
|
13
|
+
git init
|
14
|
+
git config --local user.name 'Maxwell Smart'
|
15
|
+
git config --local user.email 'agent86@control.gov'
|
16
|
+
git config --local core.pager 'cat'
|
17
|
+
echo -e 'one\ntwo\nthree\nfour\n' > README
|
18
|
+
mkdir lib
|
19
|
+
echo 'puts "pro hax"' > lib/codes.rb
|
20
|
+
git add .
|
21
|
+
git commit -m 'first'
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with conflicting changes on master and newbranch" do
|
26
|
+
before :each do
|
27
|
+
%x[
|
28
|
+
cd #{local_dir}
|
29
|
+
git checkout -b newbranch 2> /dev/null
|
30
|
+
echo 'one\nnewbranch changes\nfour\n' > README
|
31
|
+
git commit -am 'newbranch_commit'
|
32
|
+
|
33
|
+
git checkout master 2> /dev/null
|
34
|
+
|
35
|
+
echo 'one\nmaster changes\nfour\n' > README
|
36
|
+
git commit -am 'master_commit'
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should report the failure and give instructions to the user" do
|
41
|
+
out = run_command(local_dir, 'smart-merge', 'newbranch')
|
42
|
+
local_dir.should have_git_status(:conflicted => ['README'])
|
43
|
+
out.should_not report("All good")
|
44
|
+
out.should report("Executing: git merge --no-ff newbranch")
|
45
|
+
out.should report("CONFLICT (content): Merge conflict in README")
|
46
|
+
out.should report("Automatic merge failed; fix conflicts and then commit the result.")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
describe 'smart-merge' do
|
6
|
+
def local_dir; WORKING_DIR + '/local'; end
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
%x[
|
10
|
+
cd #{WORKING_DIR}
|
11
|
+
mkdir local
|
12
|
+
cd local
|
13
|
+
git init
|
14
|
+
git config --local user.name 'Maxwell Smart'
|
15
|
+
git config --local user.email 'agent86@control.gov'
|
16
|
+
git config --local core.pager 'cat'
|
17
|
+
echo 'hurr durr' > README
|
18
|
+
mkdir lib
|
19
|
+
echo 'puts "pro hax"' > lib/codes.rb
|
20
|
+
git add .
|
21
|
+
git commit -m 'first'
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should require an argument" do
|
26
|
+
out = run_command(local_dir, 'smart-merge')
|
27
|
+
out.should report("Usage: git smart-merge ref")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should require a valid branch" do
|
31
|
+
out = run_command(local_dir, 'smart-merge', 'foo')
|
32
|
+
out.should report("Branch to merge 'foo' not recognised by git!")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should report nothing to do if the branch hasn't moved on" do
|
36
|
+
%x[
|
37
|
+
cd #{local_dir}
|
38
|
+
git branch unmoved
|
39
|
+
]
|
40
|
+
out = run_command(local_dir, 'smart-merge', 'unmoved')
|
41
|
+
out.should report("Branch 'unmoved' has no new commits. Nothing to merge in.")
|
42
|
+
out.should report("Already up-to-date.")
|
43
|
+
end
|
44
|
+
|
45
|
+
context "with local changes to newbranch" do
|
46
|
+
before :each do
|
47
|
+
%x[
|
48
|
+
cd #{local_dir}
|
49
|
+
git checkout -b newbranch 2> /dev/null
|
50
|
+
echo 'moar things!' >> README
|
51
|
+
echo 'puts "moar code!"' >> lib/moar.rb
|
52
|
+
git add .
|
53
|
+
git commit -m 'moar'
|
54
|
+
|
55
|
+
git checkout master 2> /dev/null
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should merge --no-ff, despite the branch being fast-forwardable" do
|
60
|
+
out = run_command(local_dir, 'smart-merge', 'newbranch')
|
61
|
+
out.should report("Branch 'newbranch' has diverged by 1 commit. Merging in.")
|
62
|
+
out.should report("* Branch 'master' has not moved on since 'newbranch' diverged. Running with --no-ff anyway, since a fast-forward is unexpected behaviour.")
|
63
|
+
out.should report("Executing: git merge --no-ff newbranch")
|
64
|
+
out.should report(/2 files changed, 2 insertions\(\+\)(, 0 deletions\(-\))?$/)
|
65
|
+
out.should report(/All good\. Created merge commit [\w]{7}\./)
|
66
|
+
end
|
67
|
+
|
68
|
+
context "and changes on master" do
|
69
|
+
before :each do
|
70
|
+
%x[
|
71
|
+
cd #{local_dir}
|
72
|
+
echo "puts 'moar codes too!'" >> lib/codes.rb
|
73
|
+
git add .
|
74
|
+
git commit -m 'changes on master'
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should merge in ok" do
|
79
|
+
out = run_command(local_dir, 'smart-merge', 'newbranch')
|
80
|
+
out.should report("Branch 'newbranch' has diverged by 1 commit. Merging in.")
|
81
|
+
out.should report("Branch 'master' has 1 new commit since 'newbranch' diverged.")
|
82
|
+
out.should report("Executing: git merge --no-ff newbranch")
|
83
|
+
out.should report(/2 files changed, 2 insertions\(\+\)(, 0 deletions\(-\))?$/)
|
84
|
+
out.should report(/All good\. Created merge commit [\w]{7}\./)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should stash then merge if working tree is dirty" do
|
88
|
+
%x[
|
89
|
+
cd #{local_dir}
|
90
|
+
echo "i am nub" > noob
|
91
|
+
echo "puts 'moar codes too!'" >> lib/codes.rb
|
92
|
+
git add noob
|
93
|
+
]
|
94
|
+
out = run_command(local_dir, 'smart-merge', 'newbranch')
|
95
|
+
out.should report("Executing: git stash")
|
96
|
+
out.should report("Executing: git merge --no-ff newbranch")
|
97
|
+
out.should report(/2 files changed, 2 insertions\(\+\)(, 0 deletions\(-\))?$/)
|
98
|
+
out.should report("Reapplying local changes...")
|
99
|
+
out.should report("Executing: git stash pop")
|
100
|
+
out.should report(/All good\. Created merge commit [\w]{7}\./)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
describe 'smart-pull' do
|
6
|
+
def local_dir; WORKING_DIR + '/local'; end
|
7
|
+
def remote_dir; WORKING_DIR + '/remote'; end
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
%x[
|
11
|
+
cd #{WORKING_DIR}
|
12
|
+
mkdir remote
|
13
|
+
cd remote
|
14
|
+
git init
|
15
|
+
git config --local user.name 'Maxwell Smart'
|
16
|
+
git config --local user.email 'agent86@control.gov'
|
17
|
+
git config --local core.pager 'cat'
|
18
|
+
echo 'hurr durr' > README
|
19
|
+
mkdir lib
|
20
|
+
echo 'puts "pro hax"' > lib/codes.rb
|
21
|
+
git add .
|
22
|
+
git commit -m 'first'
|
23
|
+
cd ..
|
24
|
+
git clone remote/.git local
|
25
|
+
cd local
|
26
|
+
git config --local user.name 'Agent 99'
|
27
|
+
git config --local user.email 'agent99@control.gov'
|
28
|
+
git config --local core.pager 'cat'
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should tell us there's nothing to do" do
|
33
|
+
out = run_command(local_dir, 'smart-pull')
|
34
|
+
out.should report("Executing: git fetch origin")
|
35
|
+
out.should report("Neither your local branch 'master', nor the remote branch 'origin/master' have moved on.")
|
36
|
+
out.should report("Already up-to-date")
|
37
|
+
end
|
38
|
+
|
39
|
+
context "with only local changes" do
|
40
|
+
before :each do
|
41
|
+
%x[
|
42
|
+
cd #{local_dir}
|
43
|
+
echo 'moar things!' >> README
|
44
|
+
echo 'puts "moar code!"' >> lib/moar.rb
|
45
|
+
git add .
|
46
|
+
git commit -m 'moar'
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should report that no remote changes were found" do
|
51
|
+
out = run_command(local_dir, 'smart-pull')
|
52
|
+
out.should report("Executing: git fetch origin")
|
53
|
+
out.should report("Remote branch 'origin/master' has not moved on.")
|
54
|
+
out.should report("You have 1 new commit on 'master'.")
|
55
|
+
out.should report("Already up-to-date")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with only remote changes" do
|
60
|
+
before :each do
|
61
|
+
%x[
|
62
|
+
cd #{remote_dir}
|
63
|
+
echo 'changed on the server!' >> README
|
64
|
+
git add .
|
65
|
+
git commit -m 'upstream changes'
|
66
|
+
]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should fast-forward" do
|
70
|
+
out = run_command(local_dir, 'smart-pull')
|
71
|
+
out.should report("Executing: git fetch origin")
|
72
|
+
out.should report(/master +-> +origin\/master/)
|
73
|
+
out.should report("There is 1 new commit on 'origin/master'.")
|
74
|
+
out.should report("Local branch 'master' has not moved on. Fast-forwarding.")
|
75
|
+
out.should report("Executing: git merge --ff-only origin/master")
|
76
|
+
out.should report(/Updating [^\.]+..[^\.]+/)
|
77
|
+
out.should report(/1 files? changed, 1 insertions?\(\+\)(, 0 deletions\(-\))?$/)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should not stash before fast-forwarding if untracked files are present" do
|
81
|
+
%x[
|
82
|
+
cd #{local_dir}
|
83
|
+
echo "i am nub" > noob
|
84
|
+
]
|
85
|
+
local_dir.should have_git_status({:untracked => ['noob']})
|
86
|
+
out = run_command(local_dir, 'smart-pull')
|
87
|
+
out.should report("Executing: git merge --ff-only origin/master")
|
88
|
+
out.should report(/1 files? changed, 1 insertions?\(\+\)(, 0 deletions\(-\))?$/)
|
89
|
+
local_dir.should have_git_status({:untracked => ['noob']})
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should stash, fast forward, pop if there are local changes" do
|
93
|
+
%x[
|
94
|
+
cd #{local_dir}
|
95
|
+
echo "i am nub" > noob
|
96
|
+
echo "puts 'moar codes too!'" >> lib/codes.rb
|
97
|
+
git add noob
|
98
|
+
]
|
99
|
+
local_dir.should have_git_status({:added => ['noob'], :modified => ['lib/codes.rb']})
|
100
|
+
out = run_command(local_dir, 'smart-pull')
|
101
|
+
out.should report("Working directory dirty. Stashing...")
|
102
|
+
out.should report("Executing: git stash")
|
103
|
+
out.should report("Executing: git merge --ff-only origin/master")
|
104
|
+
out.should report(/1 files? changed, 1 insertions?\(\+\)(, 0 deletions\(-\))?$/)
|
105
|
+
out.should report("Reapplying local changes...")
|
106
|
+
out.should report("Executing: git stash pop")
|
107
|
+
local_dir.should have_git_status({:added => ['noob'], :modified => ['lib/codes.rb']})
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "with diverged branches" do
|
112
|
+
before :each do
|
113
|
+
%x[
|
114
|
+
cd #{remote_dir}
|
115
|
+
echo 'changed on the server!' >> README
|
116
|
+
git add .
|
117
|
+
git commit -m 'upstream changes'
|
118
|
+
|
119
|
+
cd #{local_dir}
|
120
|
+
echo "puts 'moar codes too!'" >> lib/codes.rb
|
121
|
+
git add .
|
122
|
+
git commit -m 'local changes'
|
123
|
+
]
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should rebase" do
|
127
|
+
out = run_command(local_dir, 'smart-pull')
|
128
|
+
out.should report("Executing: git fetch origin")
|
129
|
+
out.should report(/master +-> +origin\/master/)
|
130
|
+
out.should report("There is 1 new commit on 'origin/master'.")
|
131
|
+
out.should report("You have 1 new commit on 'master'.")
|
132
|
+
out.should report("Both local and remote branches have moved on. Branch 'master' needs to be rebased onto 'origin/master'")
|
133
|
+
out.should report("Executing: git rebase -p origin/master")
|
134
|
+
out.should report("Successfully rebased and updated refs/heads/master.")
|
135
|
+
local_dir.should have_last_few_commits(['local changes', 'upstream changes', 'first'])
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should not stash before rebasing if untracked files are present" do
|
139
|
+
%x[
|
140
|
+
cd #{local_dir}
|
141
|
+
echo "i am nub" > noob
|
142
|
+
]
|
143
|
+
local_dir.should have_git_status({:untracked => ['noob']})
|
144
|
+
out = run_command(local_dir, 'smart-pull')
|
145
|
+
out.should report("Executing: git rebase -p origin/master")
|
146
|
+
out.should report("Successfully rebased and updated refs/heads/master.")
|
147
|
+
local_dir.should have_git_status({:untracked => ['noob']})
|
148
|
+
local_dir.should have_last_few_commits(['local changes', 'upstream changes', 'first'])
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should stash, rebase, pop if there are local uncommitted changes" do
|
152
|
+
%x[
|
153
|
+
cd #{local_dir}
|
154
|
+
echo "i am nub" > noob
|
155
|
+
echo "puts 'moar codes too!'" >> lib/codes.rb
|
156
|
+
git add noob
|
157
|
+
]
|
158
|
+
local_dir.should have_git_status({:added => ['noob'], :modified => ['lib/codes.rb']})
|
159
|
+
out = run_command(local_dir, 'smart-pull')
|
160
|
+
out.should report("Working directory dirty. Stashing...")
|
161
|
+
out.should report("Executing: git stash")
|
162
|
+
out.should report("Executing: git rebase -p origin/master")
|
163
|
+
out.should report("Successfully rebased and updated refs/heads/master.")
|
164
|
+
local_dir.should have_git_status({:added => ['noob'], :modified => ['lib/codes.rb']})
|
165
|
+
local_dir.should have_last_few_commits(['local changes', 'upstream changes', 'first'])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'with a submodule' do
|
170
|
+
before do
|
171
|
+
%x[
|
172
|
+
cd #{WORKING_DIR}
|
173
|
+
mkdir submodule
|
174
|
+
cd submodule
|
175
|
+
git init
|
176
|
+
git config --local user.name 'The Chief'
|
177
|
+
git config --local user.email 'agentq@control.gov'
|
178
|
+
git config --local core.pager 'cat'
|
179
|
+
echo 'Unusual, but effective.' > README
|
180
|
+
git add .
|
181
|
+
git commit -m 'first'
|
182
|
+
cd ..
|
183
|
+
cd local
|
184
|
+
git submodule add "${PWD}/../submodule/.git" submodule
|
185
|
+
git commit -am 'Add submodule'
|
186
|
+
]
|
187
|
+
end
|
188
|
+
let(:submodule_dir) { local_dir + '/submodule' }
|
189
|
+
|
190
|
+
it 'can smart-pull the repo containing the submodule' do
|
191
|
+
out = run_command(local_dir, 'smart-pull')
|
192
|
+
out.should report('Executing: git fetch origin')
|
193
|
+
out.should report("Remote branch 'origin/master' has not moved on.")
|
194
|
+
out.should report("You have 1 new commit on 'master'.")
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'can smart-pull the submodule' do
|
198
|
+
out = run_command(submodule_dir, 'smart-pull')
|
199
|
+
out.should report('Executing: git fetch origin')
|
200
|
+
out.should report("Neither your local branch 'master', nor the remote branch 'origin/master' have moved on.")
|
201
|
+
out.should report('Already up-to-date')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'outside of a repo' do
|
206
|
+
it 'should report a meaningful error' do
|
207
|
+
Dir.mktmpdir do |non_repo_dir|
|
208
|
+
out = run_command(non_repo_dir, 'smart-pull')
|
209
|
+
out.should report 'You need to run this from within a Git directory'
|
210
|
+
out.should report 'Current working directory: '
|
211
|
+
out.should report 'Expected .git directory: '
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/../lib/git-smart-ruby-3'
|
5
|
+
|
6
|
+
WORKING_DIR = File.dirname(__FILE__) + '/working'
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.before :each do
|
10
|
+
FileUtils.rm_rf WORKING_DIR
|
11
|
+
FileUtils.mkdir_p WORKING_DIR
|
12
|
+
end
|
13
|
+
|
14
|
+
config.after :each do
|
15
|
+
FileUtils.rm_rf WORKING_DIR
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_command(dir, command, *args)
|
20
|
+
require 'stringio'
|
21
|
+
$stdout = stdout = StringIO.new
|
22
|
+
|
23
|
+
Dir.chdir(dir) { GitSmart.run(command, args) }
|
24
|
+
|
25
|
+
$stdout = STDOUT
|
26
|
+
stdout.string.split("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
RSpec::Matchers.define :report do |expected|
|
30
|
+
failure_message_for_should do |actual|
|
31
|
+
"expected to see #{expected.inspect}, got \n\n#{actual.map { |line| " #{line}" }.join("\n")}"
|
32
|
+
end
|
33
|
+
failure_message_for_should_not do |actual|
|
34
|
+
"expected not to see #{expected.inspect} in \n\n#{actual.map { |line| " #{line}" }.join("\n")}"
|
35
|
+
end
|
36
|
+
match do |actual|
|
37
|
+
actual.any? { |line| line[expected] }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
RSpec::Matchers.define :have_git_status do |expected|
|
43
|
+
failure_message_for_should do |dir|
|
44
|
+
"expected '#{dir}' to have git status of #{expected.inspect}, got #{GitRepo.new(dir).status.inspect}"
|
45
|
+
end
|
46
|
+
failure_message_for_should_not do |actual|
|
47
|
+
"expected '#{dir}' to not have git status of #{expected.inspect}, got #{GitRepo.new(dir).status.inspect}"
|
48
|
+
end
|
49
|
+
match do |dir|
|
50
|
+
GitRepo.new(dir).status == expected
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
RSpec::Matchers.define :have_last_few_commits do |expected|
|
56
|
+
failure_message_for_should do |dir|
|
57
|
+
"expected '#{dir}' to have last few commits of #{expected.inspect}, got #{GitRepo.new(dir).last_commit_messages(expected.length).inspect}"
|
58
|
+
end
|
59
|
+
failure_message_for_should_not do |actual|
|
60
|
+
"expected '#{dir}' to not have git status of #{expected.inspect}, got #{GitRepo.new(dir).last_commit_messages(expected.length).inspect}"
|
61
|
+
end
|
62
|
+
match do |dir|
|
63
|
+
GitRepo.new(dir).last_commit_messages(expected.length) == expected
|
64
|
+
end
|
65
|
+
end
|