git-duet 0.3.0 → 0.4.0
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/.gitignore +1 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -1
- data/Gemfile +3 -1
- data/README.md +3 -2
- data/bin/git-dci +4 -0
- data/git-duet.gemspec +1 -0
- data/lib/git/duet/author_mapper.rb +67 -61
- data/lib/git/duet/cli.rb +100 -94
- data/lib/git/duet/command_methods.rb +112 -106
- data/lib/git/duet/commit_command.rb +61 -57
- data/lib/git/duet/duet_command.rb +52 -48
- data/lib/git/duet/install_hook_command.rb +29 -25
- data/lib/git/duet/pre_commit_command.rb +43 -38
- data/lib/git/duet/solo_command.rb +52 -48
- data/lib/git/duet/version.rb +1 -1
- data/spec/integration/end_to_end_spec.rb +77 -59
- data/spec/lib/git/duet/author_mapper_spec.rb +15 -13
- data/spec/lib/git/duet/command_methods_spec.rb +2 -2
- data/spec/lib/git/duet/solo_command_spec.rb +2 -2
- metadata +40 -39
@@ -2,76 +2,80 @@
|
|
2
2
|
require 'git/duet'
|
3
3
|
require 'git/duet/command_methods'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
module Git
|
6
|
+
module Duet
|
7
|
+
class CommitCommand
|
8
|
+
include Git::Duet::CommandMethods
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def initialize(passthrough_argv, quiet = false)
|
11
|
+
@passthrough_argv = passthrough_argv
|
12
|
+
@quiet = quiet
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def execute!
|
16
|
+
in_repo_root do
|
17
|
+
add_env_vars_to_env
|
18
|
+
exec_git_commit
|
19
|
+
end
|
20
|
+
end
|
19
21
|
|
20
|
-
|
22
|
+
private
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def add_env_vars_to_env
|
25
|
+
extract_env_vars_from_git_config.each do |k, v|
|
26
|
+
ENV[k] = v
|
27
|
+
end
|
28
|
+
end
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
def env_vars
|
31
|
+
@env_vars ||= Hash[env_var_pairs]
|
32
|
+
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
def env_var_pairs
|
35
|
+
env_var_names.map do |env_var|
|
36
|
+
[env_var, env_var.downcase.gsub(/_/, '-')]
|
37
|
+
end
|
38
|
+
end
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
def quoted_passthrough_args
|
41
|
+
@passthrough_argv.map do |arg|
|
42
|
+
"'#{arg}'"
|
43
|
+
end.join(' ')
|
44
|
+
end
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
46
|
+
def signoff_arg
|
47
|
+
soloing? ? '' : '--signoff '
|
48
|
+
end
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
SOLO_ENV_VARS = %w(
|
51
|
+
GIT_AUTHOR_NAME
|
52
|
+
GIT_AUTHOR_EMAIL
|
53
|
+
)
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
DUET_ENV_VARS = %w(
|
56
|
+
GIT_AUTHOR_NAME
|
57
|
+
GIT_AUTHOR_EMAIL
|
58
|
+
GIT_COMMITTER_NAME
|
59
|
+
GIT_COMMITTER_EMAIL
|
60
|
+
)
|
59
61
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
def env_var_names
|
63
|
+
return SOLO_ENV_VARS if soloing?
|
64
|
+
DUET_ENV_VARS
|
65
|
+
end
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
def soloing?
|
68
|
+
@soloing ||=
|
69
|
+
begin
|
70
|
+
with_output_quieted do
|
71
|
+
exec_check(
|
72
|
+
"git config #{Git::Duet::Config.namespace}.git-committer-name"
|
73
|
+
).chomp
|
74
|
+
end && false
|
75
|
+
rescue StandardError
|
76
|
+
true
|
77
|
+
end
|
71
78
|
end
|
72
|
-
false
|
73
|
-
rescue StandardError
|
74
|
-
true
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
@@ -3,63 +3,67 @@ require 'git/duet'
|
|
3
3
|
require 'git/duet/author_mapper'
|
4
4
|
require 'git/duet/command_methods'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
module Git
|
7
|
+
module Duet
|
8
|
+
class DuetCommand
|
9
|
+
include Git::Duet::CommandMethods
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def initialize(alpha, omega, quiet = false, global = false)
|
12
|
+
@alpha, @omega = alpha, omega
|
13
|
+
@quiet = !!quiet
|
14
|
+
@global = !!global
|
15
|
+
@author_mapper = Git::Duet::AuthorMapper.new
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
def execute!
|
19
|
+
if alpha && omega
|
20
|
+
set_alpha_as_git_config_user
|
21
|
+
report_env_vars
|
22
|
+
write_env_vars
|
23
|
+
else
|
24
|
+
show_current_config
|
25
|
+
end
|
26
|
+
end
|
25
27
|
|
26
|
-
|
28
|
+
private
|
27
29
|
|
28
|
-
|
30
|
+
attr_accessor :alpha, :omega, :author_mapper
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
def set_alpha_as_git_config_user
|
33
|
+
%w(name email).each do |setting|
|
34
|
+
exec_check(
|
35
|
+
"#{git_config} user.#{setting} '#{alpha_info[setting.to_sym]}'"
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
def var_map
|
41
|
+
{
|
42
|
+
'GIT_AUTHOR_NAME' => alpha_info[:name],
|
43
|
+
'GIT_AUTHOR_EMAIL' => alpha_info[:email],
|
44
|
+
'GIT_COMMITTER_NAME' => omega_info[:name],
|
45
|
+
'GIT_COMMITTER_EMAIL' => omega_info[:email]
|
46
|
+
}
|
47
|
+
end
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
def alpha_info
|
50
|
+
fetch_info(alpha, 'author')
|
51
|
+
end
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
-
|
53
|
+
def omega_info
|
54
|
+
fetch_info(omega, 'committer')
|
55
|
+
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
57
|
+
def fetch_info(which, desc)
|
58
|
+
alpha_omega_info.fetch(which)
|
59
|
+
rescue KeyError, IndexError => e
|
60
|
+
error("git-duet: Failed to find #{desc}: #{e}")
|
61
|
+
raise Git::Duet::ScriptDieError, 86
|
62
|
+
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
+
def alpha_omega_info
|
65
|
+
@alpha_omega_info ||= author_mapper.map(alpha, omega)
|
66
|
+
end
|
67
|
+
end
|
64
68
|
end
|
65
69
|
end
|
@@ -3,35 +3,39 @@ require 'git/duet'
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'git/duet/command_methods'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
module Git
|
7
|
+
module Duet
|
8
|
+
class InstallHookCommand
|
9
|
+
include Git::Duet::CommandMethods
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
HOOK = <<-EOF.gsub(/^ {8}/, '')
|
12
|
+
#!/bin/bash
|
13
|
+
exec git duet-pre-commit "$@"
|
14
|
+
EOF
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
def initialize(quiet = false)
|
17
|
+
@quiet = quiet
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
def execute!
|
21
|
+
Dir.chdir(`git rev-parse --show-toplevel`.chomp) do
|
22
|
+
dest = File.join(Dir.pwd, '.git', 'hooks', 'pre-commit')
|
23
|
+
return error_hook_exists(dest) if File.exist?(dest)
|
24
|
+
File.open(dest, 'w') { |f| f.puts HOOK }
|
25
|
+
FileUtils.chmod(0755, dest)
|
26
|
+
info("git-duet-install-hook: Installed hook to #{dest}")
|
27
|
+
end
|
28
|
+
0
|
29
|
+
end
|
28
30
|
|
29
|
-
|
31
|
+
private
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def error_hook_exists(dest)
|
34
|
+
error('git-duet-install-hook: ' \
|
35
|
+
"A pre-commit hook already exists at #{dest}!")
|
36
|
+
error('git-duet-install-hook: Move it out of the way first, mkay?')
|
37
|
+
1
|
38
|
+
end
|
39
|
+
end
|
36
40
|
end
|
37
41
|
end
|
@@ -3,44 +3,49 @@ require 'git/duet'
|
|
3
3
|
require 'git/duet/command_methods'
|
4
4
|
require 'git/duet/script_die_error'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
module Git
|
7
|
+
module Duet
|
8
|
+
class PreCommitCommand
|
9
|
+
include Git::Duet::CommandMethods
|
10
|
+
|
11
|
+
def initialize(quiet = false)
|
12
|
+
@quiet = !!quiet
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute!
|
16
|
+
in_repo_root do
|
17
|
+
explode! if !env_cache_exists? || env_cache_stale?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def explode!
|
24
|
+
error('Your git duet settings are stale, human!')
|
25
|
+
error('Update them with `git duet` or `git solo`.')
|
26
|
+
fail Git::Duet::ScriptDieError, 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def env_cache_exists?
|
30
|
+
with_output_quieted do
|
31
|
+
exec_check("git config #{Git::Duet::Config.namespace}.mtime")
|
32
|
+
end
|
33
|
+
true
|
34
|
+
rescue
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def env_cache_stale?
|
39
|
+
Integer(
|
40
|
+
exec_check("git config #{Git::Duet::Config.namespace}.mtime")
|
41
|
+
) < stale_cutoff
|
42
|
+
end
|
43
|
+
|
44
|
+
def stale_cutoff
|
45
|
+
Integer(
|
46
|
+
Time.now - Integer(ENV.fetch('GIT_DUET_SECONDS_AGO_STALE', '1200'))
|
47
|
+
)
|
48
|
+
end
|
16
49
|
end
|
17
50
|
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def explode!
|
22
|
-
error('Your git duet settings are stale, human!')
|
23
|
-
error('Update them with `git duet` or `git solo`.')
|
24
|
-
fail Git::Duet::ScriptDieError, 1
|
25
|
-
end
|
26
|
-
|
27
|
-
def env_cache_exists?
|
28
|
-
with_output_quieted do
|
29
|
-
exec_check("git config #{Git::Duet::Config.namespace}.mtime")
|
30
|
-
end
|
31
|
-
true
|
32
|
-
rescue
|
33
|
-
false
|
34
|
-
end
|
35
|
-
|
36
|
-
def env_cache_stale?
|
37
|
-
Integer(exec_check("git config #{Git::Duet::Config.namespace}.mtime")) <
|
38
|
-
stale_cutoff
|
39
|
-
end
|
40
|
-
|
41
|
-
def stale_cutoff
|
42
|
-
Integer(
|
43
|
-
Time.now - Integer(ENV.fetch('GIT_DUET_SECONDS_AGO_STALE', '1200'))
|
44
|
-
)
|
45
|
-
end
|
46
51
|
end
|
@@ -3,60 +3,64 @@ require 'git/duet'
|
|
3
3
|
require 'git/duet/author_mapper'
|
4
4
|
require 'git/duet/command_methods'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@soloist = soloist
|
11
|
-
@quiet = !!quiet
|
12
|
-
@global = !!global
|
13
|
-
@author_mapper = Git::Duet::AuthorMapper.new
|
14
|
-
end
|
6
|
+
module Git
|
7
|
+
module Duet
|
8
|
+
class SoloCommand
|
9
|
+
include Git::Duet::CommandMethods
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
else
|
23
|
-
show_current_config
|
24
|
-
end
|
25
|
-
end
|
11
|
+
def initialize(soloist, quiet = false, global = false)
|
12
|
+
@soloist = soloist
|
13
|
+
@quiet = !!quiet
|
14
|
+
@global = !!global
|
15
|
+
@author_mapper = Git::Duet::AuthorMapper.new
|
16
|
+
end
|
26
17
|
|
27
|
-
|
18
|
+
def execute!
|
19
|
+
unset_committer_vars
|
20
|
+
if soloist
|
21
|
+
set_soloist_as_git_config_user
|
22
|
+
report_env_vars
|
23
|
+
write_env_vars
|
24
|
+
else
|
25
|
+
show_current_config
|
26
|
+
end
|
27
|
+
end
|
28
28
|
|
29
|
-
|
29
|
+
private
|
30
30
|
|
31
|
-
|
32
|
-
exec_check("#{git_config} user.name '#{soloist_info[:name]}'")
|
33
|
-
exec_check("#{git_config} user.email '#{soloist_info[:email]}'")
|
34
|
-
end
|
31
|
+
attr_accessor :soloist, :author_mapper
|
35
32
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
[0, 5]
|
41
|
-
)
|
42
|
-
exec_check(
|
43
|
-
"#{git_config} --unset-all #{Git::Duet::Config.namespace}." <<
|
44
|
-
'git-committer-email',
|
45
|
-
[0, 5]
|
46
|
-
)
|
47
|
-
end
|
33
|
+
def set_soloist_as_git_config_user
|
34
|
+
exec_check("#{git_config} user.name '#{soloist_info[:name]}'")
|
35
|
+
exec_check("#{git_config} user.email '#{soloist_info[:email]}'")
|
36
|
+
end
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
38
|
+
def unset_committer_vars
|
39
|
+
exec_check(
|
40
|
+
"#{git_config} --unset-all #{Git::Duet::Config.namespace}." \
|
41
|
+
'git-committer-name',
|
42
|
+
[0, 5]
|
43
|
+
)
|
44
|
+
exec_check(
|
45
|
+
"#{git_config} --unset-all #{Git::Duet::Config.namespace}." \
|
46
|
+
'git-committer-email',
|
47
|
+
[0, 5]
|
48
|
+
)
|
49
|
+
end
|
55
50
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
51
|
+
def var_map
|
52
|
+
{
|
53
|
+
'GIT_AUTHOR_NAME' => soloist_info[:name],
|
54
|
+
'GIT_AUTHOR_EMAIL' => soloist_info[:email]
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def soloist_info
|
59
|
+
@soloist_info ||= author_mapper.map(@soloist).fetch(@soloist)
|
60
|
+
rescue KeyError, IndexError => e
|
61
|
+
error("git-solo: Failed to find author: #{e}")
|
62
|
+
raise Git::Duet::ScriptDieError, 86
|
63
|
+
end
|
64
|
+
end
|
61
65
|
end
|
62
66
|
end
|