git-duet 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/.simplecov +1 -0
- data/.travis.yml +4 -6
- data/Gemfile +1 -0
- data/LICENSE +1 -1
- data/README.md +1 -0
- data/Rakefile +7 -1
- data/bin/git-duet +2 -1
- data/bin/git-duet-commit +2 -1
- data/bin/git-duet-install-hook +2 -1
- data/bin/git-duet-pre-commit +2 -1
- data/bin/git-solo +2 -1
- data/git-duet.gemspec +5 -7
- data/lib/git-duet.rb +1 -0
- data/lib/git/duet.rb +4 -4
- data/lib/git/duet/author_mapper.rb +14 -10
- data/lib/git/duet/cli.rb +22 -26
- data/lib/git/duet/command_methods.rb +44 -12
- data/lib/git/duet/commit_command.rb +20 -15
- data/lib/git/duet/config.rb +11 -0
- data/lib/git/duet/duet_command.rb +11 -5
- data/lib/git/duet/install_hook_command.rb +14 -10
- data/lib/git/duet/pre_commit_command.rb +11 -8
- data/lib/git/duet/script_die_error.rb +2 -0
- data/lib/git/duet/solo_command.rb +21 -7
- data/lib/git/duet/version.rb +3 -1
- data/spec/integration/end_to_end_spec.rb +74 -54
- data/spec/lib/git/duet/author_mapper_spec.rb +45 -42
- data/spec/lib/git/duet/cli_spec.rb +20 -19
- data/spec/lib/git/duet/command_methods_spec.rb +21 -11
- data/spec/lib/git/duet/duet_command_spec.rb +75 -51
- data/spec/lib/git/duet/pre_commit_command_spec.rb +19 -14
- data/spec/lib/git/duet/solo_command_spec.rb +118 -73
- data/spec/spec_helper.rb +3 -16
- data/spec/support/author_mapper_helper.rb +22 -18
- metadata +25 -41
- data/.rbenv-version +0 -1
- data/bin/git-duet-pre-commit-tk +0 -3
- data/lib/git/duet/key_error.rb +0 -3
@@ -1,3 +1,4 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
1
2
|
require 'git/duet/author_mapper'
|
2
3
|
|
3
4
|
describe Git::Duet::AuthorMapper do
|
@@ -22,83 +23,85 @@ describe Git::Duet::AuthorMapper do
|
|
22
23
|
ENV.delete('GIT_DUET_AUTHORS_FILE')
|
23
24
|
end
|
24
25
|
|
25
|
-
it '
|
26
|
-
instance = described_class
|
27
|
-
|
26
|
+
it 'uses an authors file given at initialization' do
|
27
|
+
instance = described_class
|
28
|
+
.new('/blarggie/blarggie/new/friend/.git-authors')
|
29
|
+
instance.authors_file
|
30
|
+
.should == '/blarggie/blarggie/new/friend/.git-authors'
|
28
31
|
end
|
29
32
|
|
30
|
-
it '
|
33
|
+
it 'uses the `GIT_DUET_AUTHORS_FILE` if provided' do
|
31
34
|
ENV['GIT_DUET_AUTHORS_FILE'] = '/fizzle/bizzle/.git-authors'
|
32
35
|
instance = described_class.new
|
33
36
|
instance.authors_file.should == '/fizzle/bizzle/.git-authors'
|
34
37
|
end
|
35
38
|
|
36
|
-
it '
|
39
|
+
it 'falls back to using `~/.git-authors` for the author map' do
|
37
40
|
subject.authors_file.should == File.join(ENV['HOME'], '.git-authors')
|
38
41
|
end
|
39
42
|
|
40
|
-
it '
|
43
|
+
it 'lets missing author errors bubble up' do
|
41
44
|
expect { subject.map('bzzzrt') }.to raise_error
|
42
45
|
end
|
43
46
|
|
44
|
-
it '
|
47
|
+
it 'maps initials to name -> email pairs' do
|
45
48
|
subject.map('jd').fetch('jd').should == {
|
46
|
-
:
|
47
|
-
:
|
49
|
+
name: 'Jane Doe',
|
50
|
+
email: 'jane@awesome.biz'
|
48
51
|
}
|
49
52
|
end
|
50
53
|
|
51
|
-
it '
|
54
|
+
it 'constructs default emails from first initial and last name + domain' do
|
52
55
|
subject.map('hb').should == {
|
53
56
|
'hb' => {
|
54
|
-
:
|
55
|
-
:
|
57
|
+
name: 'Hampton Bones',
|
58
|
+
email: 'h.bones@awesometown.me'
|
56
59
|
}
|
57
60
|
}
|
58
61
|
end
|
59
62
|
|
60
|
-
it '
|
63
|
+
it 'constructs emails from optional username (if given) + domain' do
|
61
64
|
subject.map('fb').should == {
|
62
65
|
'fb' => {
|
63
|
-
:
|
64
|
-
:
|
66
|
+
name: 'Frances Bar',
|
67
|
+
email: 'frances@awesometown.me'
|
65
68
|
}
|
66
69
|
}
|
67
70
|
end
|
68
71
|
|
69
|
-
it '
|
72
|
+
it 'uses an explicitly-configured email address if given' do
|
70
73
|
subject.map('jd').should == {
|
71
74
|
'jd' => {
|
72
|
-
:
|
73
|
-
:
|
75
|
+
name: 'Jane Doe',
|
76
|
+
email: 'jane@awesome.biz'
|
74
77
|
}
|
75
78
|
}
|
76
79
|
end
|
77
80
|
|
78
|
-
it '
|
81
|
+
it 'maps any number of initials to name -> email pairs' do
|
79
82
|
subject.map('jd', 'fb', 'qx', 'hb').should == {
|
80
83
|
'jd' => {
|
81
|
-
:
|
82
|
-
:
|
84
|
+
name: 'Jane Doe',
|
85
|
+
email: 'jane@awesome.biz'
|
83
86
|
},
|
84
87
|
'fb' => {
|
85
|
-
:
|
86
|
-
:
|
88
|
+
name: 'Frances Bar',
|
89
|
+
email: 'frances@awesometown.me'
|
87
90
|
},
|
88
91
|
'qx' => {
|
89
|
-
:
|
90
|
-
:
|
92
|
+
name: 'Quincy Xavier',
|
93
|
+
email: 'qx@awesometown.me'
|
91
94
|
},
|
92
95
|
'hb' => {
|
93
|
-
:
|
94
|
-
:
|
96
|
+
name: 'Hampton Bones',
|
97
|
+
email: 'h.bones@awesometown.me'
|
95
98
|
}
|
96
99
|
}
|
97
100
|
end
|
98
101
|
|
99
102
|
context 'when using a `~/.pairs` config' do
|
100
103
|
before :each do
|
101
|
-
subject.stub(:
|
104
|
+
subject.stub(cfg: {
|
102
105
|
'pairs' => {
|
103
106
|
'jd' => 'Jane Doe; jdoe',
|
104
107
|
'fb' => 'Frances Bar; frances',
|
@@ -114,30 +117,30 @@ describe Git::Duet::AuthorMapper do
|
|
114
117
|
})
|
115
118
|
end
|
116
119
|
|
117
|
-
it '
|
120
|
+
it 'maps initials to name -> email pairs' do
|
118
121
|
subject.map('jd').fetch('jd').should == {
|
119
|
-
:
|
120
|
-
:
|
122
|
+
name: 'Jane Doe',
|
123
|
+
email: 'jane@awesome.biz'
|
121
124
|
}
|
122
125
|
end
|
123
126
|
|
124
|
-
it '
|
127
|
+
it 'maps any number of initials to name -> email pairs' do
|
125
128
|
subject.map('jd', 'fb', 'qx', 'hb').should == {
|
126
129
|
'jd' => {
|
127
|
-
:
|
128
|
-
:
|
130
|
+
name: 'Jane Doe',
|
131
|
+
email: 'jane@awesome.biz'
|
129
132
|
},
|
130
133
|
'fb' => {
|
131
|
-
:
|
132
|
-
:
|
134
|
+
name: 'Frances Bar',
|
135
|
+
email: 'frances@awesometown.me'
|
133
136
|
},
|
134
137
|
'qx' => {
|
135
|
-
:
|
136
|
-
:
|
138
|
+
name: 'Quincy Xavier',
|
139
|
+
email: 'qx@awesometown.me'
|
137
140
|
},
|
138
141
|
'hb' => {
|
139
|
-
:
|
140
|
-
:
|
142
|
+
name: 'Hampton Bones',
|
143
|
+
email: 'h.bones@awesometown.me'
|
141
144
|
}
|
142
145
|
}
|
143
146
|
end
|
@@ -159,14 +162,14 @@ describe Git::Duet::AuthorMapper do
|
|
159
162
|
)
|
160
163
|
end
|
161
164
|
|
162
|
-
it '
|
165
|
+
it 'warns about missing authors file' do
|
163
166
|
$stderr.should_receive(:puts).with(
|
164
167
|
/Missing or corrupt authors file.*#{bad_path}/i
|
165
168
|
)
|
166
169
|
expect { subject.map('zzz') }.to raise_error
|
167
170
|
end
|
168
171
|
|
169
|
-
it '
|
172
|
+
it 'raises a ScriptDieError' do
|
170
173
|
$stderr.stub(:puts)
|
171
174
|
expect { subject.map('zzz') }.to raise_error(Git::Duet::ScriptDieError)
|
172
175
|
end
|
@@ -1,46 +1,47 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
1
2
|
require 'git/duet/cli'
|
2
3
|
require 'git/duet/solo_command'
|
3
4
|
require 'git/duet/duet_command'
|
4
5
|
require 'git/duet/pre_commit_command'
|
5
6
|
|
6
7
|
describe Git::Duet::Cli do
|
7
|
-
subject { described_class }
|
8
|
+
subject(:cli) { described_class }
|
8
9
|
|
9
|
-
it '
|
10
|
-
|
10
|
+
it 'responds to `.main`' do
|
11
|
+
cli.should respond_to(:run)
|
11
12
|
end
|
12
13
|
|
13
|
-
it '
|
14
|
-
expect {
|
14
|
+
it 'requires the prog name and argv array' do
|
15
|
+
expect { cli.run }.to raise_error(ArgumentError)
|
15
16
|
end
|
16
17
|
|
17
|
-
it '
|
18
|
-
expect {
|
18
|
+
it 'explodes on unknown prog names' do
|
19
|
+
expect { cli.run('bork', []) }.to raise_error(ScriptError)
|
19
20
|
end
|
20
21
|
|
21
|
-
it '
|
22
|
-
|
23
|
-
|
22
|
+
it 'returns the exit status from any script error deaths' do
|
23
|
+
cli.stub(:solo).and_raise(Git::Duet::ScriptDieError.new(99))
|
24
|
+
cli.run('git-solo', %w(ty -q)).should == 99
|
24
25
|
end
|
25
26
|
|
26
|
-
it '
|
27
|
-
Git::Duet::SoloCommand.stub(:
|
27
|
+
it 'runs `solo` when the progname matches /solo$/' do
|
28
|
+
Git::Duet::SoloCommand.stub(new: double('solo').tap do |solo|
|
28
29
|
solo.should_receive(:execute!)
|
29
30
|
end)
|
30
|
-
|
31
|
+
cli.run('git-solo', %w(jd -q))
|
31
32
|
end
|
32
33
|
|
33
|
-
it '
|
34
|
-
Git::Duet::DuetCommand.stub(:
|
34
|
+
it 'runs `duet` when progname matches /duet$/' do
|
35
|
+
Git::Duet::DuetCommand.stub(new: double('duet').tap do |duet|
|
35
36
|
duet.should_receive(:execute!)
|
36
37
|
end)
|
37
|
-
|
38
|
+
cli.run('git-duet', %w(jd fb -q))
|
38
39
|
end
|
39
40
|
|
40
|
-
it '
|
41
|
-
Git::Duet::PreCommitCommand.stub(:
|
41
|
+
it 'runs `pre_commit` when progname matches /pre-commit$/' do
|
42
|
+
Git::Duet::PreCommitCommand.stub(new: double('pre-commit').tap do |pc|
|
42
43
|
pc.should_receive(:execute!)
|
43
44
|
end)
|
44
|
-
|
45
|
+
cli.run('git-duet-pre-commit', %w(-q))
|
45
46
|
end
|
46
47
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
1
2
|
require 'git/duet/command_methods'
|
2
3
|
|
3
4
|
describe Git::Duet::CommandMethods do
|
@@ -23,17 +24,21 @@ describe Git::Duet::CommandMethods do
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
it '
|
27
|
-
subject.should_receive(:`)
|
28
|
-
|
29
|
-
subject.should_receive(:`)
|
27
|
+
it 'writes env vars to a custom git config tree' do
|
28
|
+
subject.should_receive(:`)
|
29
|
+
.with("git config #{Git::Duet::Config.namespace}.fizzle-baz 'awesome'")
|
30
|
+
subject.should_receive(:`)
|
31
|
+
.with("git config #{Git::Duet::Config.namespace}.oh-snarf 'mumra'")
|
32
|
+
subject.should_receive(:`)
|
33
|
+
.with(/^git config #{Git::Duet::Config.namespace}.mtime \d+/)
|
30
34
|
subject.send(:write_env_vars)
|
31
35
|
end
|
32
36
|
|
33
|
-
it '
|
37
|
+
it 'explodes if a subshell returns non-zero' do
|
34
38
|
subject.stub(:`)
|
35
|
-
|
36
|
-
expect { subject.send(:exec_check, 'ls hamsters') }
|
39
|
+
$CHILD_STATUS.should_receive(:exitstatus).and_return(1)
|
40
|
+
expect { subject.send(:exec_check, 'ls hamsters') }
|
41
|
+
.to raise_error(StandardError)
|
37
42
|
end
|
38
43
|
|
39
44
|
context 'when configured to operate on the global config' do
|
@@ -41,10 +46,15 @@ describe Git::Duet::CommandMethods do
|
|
41
46
|
subject.instance_variable_set(:@global, true)
|
42
47
|
end
|
43
48
|
|
44
|
-
it '
|
45
|
-
subject.should_receive(:`)
|
46
|
-
|
47
|
-
|
49
|
+
it 'writes env vars to a custom global git config tree' do
|
50
|
+
subject.should_receive(:`)
|
51
|
+
.with("git config --global #{Git::Duet::Config.namespace}" <<
|
52
|
+
".fizzle-baz 'awesome'")
|
53
|
+
subject.should_receive(:`)
|
54
|
+
.with("git config --global #{Git::Duet::Config.namespace}" <<
|
55
|
+
".oh-snarf 'mumra'")
|
56
|
+
subject.should_receive(:`)
|
57
|
+
.with(/^git config --global #{Git::Duet::Config.namespace}.mtime \d+/)
|
48
58
|
subject.send(:write_env_vars)
|
49
59
|
end
|
50
60
|
end
|
@@ -1,71 +1,70 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
1
2
|
require 'git/duet/duet_command'
|
2
3
|
require 'support/author_mapper_helper'
|
3
4
|
|
4
5
|
describe Git::Duet::DuetCommand do
|
5
6
|
include SpecSupport::AuthorMapperHelper
|
6
7
|
|
7
|
-
let
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
let :omega do
|
12
|
-
random_author
|
13
|
-
end
|
14
|
-
|
15
|
-
subject do
|
16
|
-
described_class.new(alpha, omega)
|
17
|
-
end
|
8
|
+
let(:alpha) { random_author }
|
9
|
+
let(:omega) { random_author }
|
10
|
+
subject(:cmd) { described_class.new(alpha, omega) }
|
18
11
|
|
19
12
|
before :each do
|
20
|
-
|
21
|
-
am.stub(:
|
13
|
+
cmd.stub(author_mapper: double('author mapper').tap do |am|
|
14
|
+
am.stub(map: author_mapping)
|
22
15
|
end)
|
23
|
-
|
24
|
-
|
16
|
+
cmd.stub(:` => '')
|
17
|
+
cmd.stub(:report_env_vars)
|
25
18
|
Dir.stub(:chdir) do |&block|
|
26
19
|
block.call
|
27
20
|
end
|
28
|
-
File.stub(:open) do |filename,mode
|
21
|
+
File.stub(:open) do |filename, mode, &block|
|
29
22
|
block.call(double('outfile').as_null_object)
|
30
23
|
end
|
31
24
|
end
|
32
25
|
|
33
|
-
it '
|
26
|
+
it 'requires alpha and omega sets of initials' do
|
34
27
|
expect { described_class.new }.to raise_error(ArgumentError)
|
35
28
|
end
|
36
29
|
|
37
|
-
it '
|
38
|
-
|
30
|
+
it 'responds to `execute!`' do
|
31
|
+
cmd.should respond_to(:execute!)
|
39
32
|
end
|
40
33
|
|
41
|
-
it '
|
42
|
-
|
34
|
+
it '(privately) responds to `write_env_vars`' do
|
35
|
+
cmd.private_methods.map(&:to_sym).should include(:write_env_vars)
|
43
36
|
end
|
44
37
|
|
45
|
-
it '
|
46
|
-
|
47
|
-
|
48
|
-
|
38
|
+
it 'sets the alpha name as git config user.name' do
|
39
|
+
cmd.stub(:`).with(/git config user\.email/)
|
40
|
+
cmd.should_receive(:`)
|
41
|
+
.with("git config user.name '#{author_mapping[alpha][:name]}'")
|
42
|
+
cmd.execute!
|
49
43
|
end
|
50
44
|
|
51
|
-
it '
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
it 'sets the alpha email as git config user.email' do
|
46
|
+
cmd.stub(:`).with(/git config user\.name/)
|
47
|
+
cmd.should_receive(:`)
|
48
|
+
.with("git config user.email '#{author_mapping[alpha][:email]}'")
|
49
|
+
cmd.execute!
|
55
50
|
end
|
56
51
|
|
57
|
-
it '
|
58
|
-
|
59
|
-
$stdout.should_receive(:puts)
|
60
|
-
|
61
|
-
$stdout.should_receive(:puts)
|
62
|
-
|
63
|
-
|
52
|
+
it 'reports env vars to $stdout' do
|
53
|
+
cmd.unstub(:report_env_vars)
|
54
|
+
$stdout.should_receive(:puts)
|
55
|
+
.with(/^GIT_AUTHOR_NAME='#{author_mapping[alpha][:name]}'/)
|
56
|
+
$stdout.should_receive(:puts)
|
57
|
+
.with(/^GIT_AUTHOR_EMAIL='#{author_mapping[alpha][:email]}'/)
|
58
|
+
$stdout.should_receive(:puts)
|
59
|
+
.with(/^GIT_COMMITTER_NAME='#{author_mapping[omega][:name]}'/)
|
60
|
+
$stdout.should_receive(:puts)
|
61
|
+
.with(/^GIT_COMMITTER_EMAIL='#{author_mapping[omega][:email]}'/)
|
62
|
+
cmd.execute!
|
64
63
|
end
|
65
64
|
|
66
|
-
it '
|
67
|
-
|
68
|
-
|
65
|
+
it 'sets the alpha as author and omega as committer in custom git config' do
|
66
|
+
cmd.should_receive(:write_env_vars)
|
67
|
+
cmd.execute!
|
69
68
|
end
|
70
69
|
|
71
70
|
%w(alpha omega).each do |author_type|
|
@@ -73,27 +72,52 @@ describe Git::Duet::DuetCommand do
|
|
73
72
|
let(:"#{author_type}") { 'brzzzt' }
|
74
73
|
|
75
74
|
it 'aborts' do
|
76
|
-
|
77
|
-
expect {
|
75
|
+
cmd.stub(error: nil)
|
76
|
+
expect { cmd.execute! }.to raise_error(Git::Duet::ScriptDieError)
|
78
77
|
end
|
79
78
|
end
|
80
79
|
end
|
81
80
|
|
82
81
|
context 'when configured to operate on the global config' do
|
83
|
-
subject
|
84
|
-
|
82
|
+
subject(:cmd) { described_class.new(alpha, omega, false, true) }
|
83
|
+
|
84
|
+
it 'sets the alpha name as global git config user.name' do
|
85
|
+
cmd.stub(:`).with(/git config --global user\.email/)
|
86
|
+
alpha_name = author_mapping[alpha][:name]
|
87
|
+
cmd.should_receive(:`)
|
88
|
+
.with("git config --global user.name '#{alpha_name}'")
|
89
|
+
cmd.execute!
|
85
90
|
end
|
86
91
|
|
87
|
-
it '
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
it 'sets the alpha email as global git config user.email' do
|
93
|
+
cmd.stub(:`).with(/git config --global user\.name/)
|
94
|
+
alpha_email = author_mapping[alpha][:email]
|
95
|
+
cmd.should_receive(:`)
|
96
|
+
.with("git config --global user.email '#{alpha_email}'")
|
97
|
+
cmd.execute!
|
91
98
|
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when given no arguments' do
|
102
|
+
let(:alpha) { nil }
|
103
|
+
let(:omega) { nil }
|
104
|
+
|
105
|
+
it 'shows the current duet author settings' do
|
106
|
+
git_config_output = <<-EOF.gsub(/^ {8}/, '')
|
107
|
+
#{Git::Duet::Config.namespace}.git-author-name Test Author
|
108
|
+
#{Git::Duet::Config.namespace}.git-author-email author@test.com
|
109
|
+
#{Git::Duet::Config.namespace}.git-committer-name Test Committer
|
110
|
+
#{Git::Duet::Config.namespace}.git-committer-email committer@test.com
|
111
|
+
#{Git::Duet::Config.namespace}.mtime 138039#{rand(1000..9999)}
|
112
|
+
EOF
|
113
|
+
|
114
|
+
cmd.stub(:`)
|
115
|
+
.with("git config --get-regexp #{Git::Duet::Config.namespace}") do
|
116
|
+
git_config_output
|
117
|
+
end
|
118
|
+
$stdout.should_receive(:puts).with(git_config_output)
|
92
119
|
|
93
|
-
|
94
|
-
subject.stub(:`).with(/git config --global user\.name/)
|
95
|
-
subject.should_receive(:`).with("git config --global user.email '#{author_mapping[alpha][:email]}'")
|
96
|
-
subject.execute!
|
120
|
+
cmd.execute!
|
97
121
|
end
|
98
122
|
end
|
99
123
|
end
|