gitsu 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.licenseignore +2 -0
  3. data/.simplecov +16 -0
  4. data/.travis.yml +19 -2
  5. data/Gemfile +17 -1
  6. data/README.md +36 -7
  7. data/Rakefile +37 -3
  8. data/TODO +4 -2
  9. data/bin/git-su +15 -0
  10. data/bin/git-whoami +15 -0
  11. data/features/add_user.feature +16 -0
  12. data/features/change_user_in_different_scopes.feature +16 -0
  13. data/features/clear_user.feature +16 -0
  14. data/features/configure_default_scope.feature +22 -2
  15. data/features/edit_config.feature +16 -0
  16. data/features/list_users.feature +16 -0
  17. data/features/print_current_user.feature +16 -0
  18. data/features/print_options.feature +16 -0
  19. data/features/step_definitions/gitsu_steps.rb +33 -12
  20. data/features/support/env.rb +16 -0
  21. data/features/switch_to_fully_qualified_user.feature +21 -0
  22. data/features/switch_to_multiple_users.feature +64 -0
  23. data/features/switch_to_stored_user.feature +16 -0
  24. data/gitsu.gemspec +20 -3
  25. data/lib/gitsu.rb +17 -0
  26. data/lib/gitsu/array.rb +31 -0
  27. data/lib/gitsu/config_repository.rb +49 -0
  28. data/lib/gitsu/factory.rb +26 -3
  29. data/lib/gitsu/git.rb +44 -35
  30. data/lib/gitsu/gitsu.rb +27 -3
  31. data/lib/gitsu/runner.rb +16 -0
  32. data/lib/gitsu/shell.rb +17 -2
  33. data/lib/gitsu/switcher.rb +68 -30
  34. data/lib/gitsu/user.rb +80 -11
  35. data/lib/gitsu/user_file.rb +18 -2
  36. data/lib/gitsu/user_list.rb +68 -12
  37. data/lib/gitsu/version.rb +17 -1
  38. data/man/git-su.1.ronn +36 -4
  39. data/spec/gitsu/array_spec.rb +71 -0
  40. data/spec/gitsu/bin_spec.rb +16 -0
  41. data/spec/gitsu/config_repository_spec.rb +67 -0
  42. data/spec/gitsu/git_spec.rb +36 -15
  43. data/spec/gitsu/gitsu_spec.rb +41 -18
  44. data/spec/gitsu/runner_spec.rb +16 -0
  45. data/spec/gitsu/shell_spec.rb +55 -0
  46. data/spec/gitsu/switcher_spec.rb +53 -13
  47. data/spec/gitsu/user_file_spec.rb +91 -0
  48. data/spec/gitsu/user_list_spec.rb +112 -41
  49. data/spec/gitsu/user_spec.rb +97 -0
  50. data/spec/gitsu/version_spec.rb +16 -0
  51. data/spec/spec_helper.rb +16 -0
  52. metadata +45 -28
@@ -1,3 +1,19 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  require 'optparse'
2
18
 
3
19
  module GitSu
@@ -10,7 +26,14 @@ module GitSu
10
26
  def go(args)
11
27
  options = {}
12
28
  optparse = OptionParser.new do |opts|
13
- opts.banner = "Usage: git-su [options] user"
29
+ opts.banner = <<-BANNER
30
+ Gitsu Copyright (C) 2013 drrb
31
+ This program comes with ABSOLUTELY NO WARRANTY.
32
+ This is free software, and you are welcome to redistribute it under
33
+ certain conditions; see <http://www.gnu.org/licenses/> for details.
34
+
35
+ Usage: git-su [options] user
36
+ BANNER
14
37
 
15
38
  opts.on('-t', '--list', 'List the configured users') do
16
39
  options[:list] = true
@@ -20,7 +43,7 @@ module GitSu
20
43
  options[:clear] = true
21
44
  end
22
45
 
23
- opts.on('-a', '--add USER <EMAIL>', 'Add a user in email format (e.g. John Citizen <jcitizen@example.com>)') do |user|
46
+ opts.on('-a', '--add USER', 'Add a user in email format (e.g. John Citizen <jcitizen@example.com>)') do |user|
24
47
  options[:add] = user
25
48
  end
26
49
 
@@ -51,6 +74,7 @@ module GitSu
51
74
  run(options, args)
52
75
  end
53
76
 
77
+ private
54
78
  def run(options, args)
55
79
  if options[:help]
56
80
  return
@@ -85,7 +109,7 @@ module GitSu
85
109
  else
86
110
  select_scopes = scopes.empty? ? [:default] : scopes
87
111
  select_scopes.each do |scope|
88
- @switcher.request(args.join(" "), scope)
112
+ @switcher.request(scope, *args)
89
113
  end
90
114
  end
91
115
  end
@@ -1,3 +1,19 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  module GitSu
2
18
  class Runner
3
19
  def initialize(output)
@@ -1,12 +1,27 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  module GitSu
2
18
  class Shell
3
19
  def capture(command)
4
20
  output = `#{command}`.strip
5
21
  if block_given?
6
22
  yield(output, $?)
7
- else
8
- output
9
23
  end
24
+ output
10
25
  end
11
26
 
12
27
  def execute(command)
@@ -1,39 +1,36 @@
1
- class Array
2
- def list
3
- if empty?
4
- ""
5
- elsif size == 1
6
- last.to_s
7
- else
8
- "#{self[0..-2].map{|e| e.to_s}.join(", ")} and #{last.to_s}"
9
- end
10
- end
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
11
16
 
12
- def pluralize(word)
13
- size > 1 ? word + "s" : word
14
- end
15
- end
17
+ require 'gitsu/array'
16
18
 
17
19
  module GitSu
18
-
19
20
  class Switcher
20
- def initialize(git, user_list, output)
21
- @git, @user_list, @output = git, user_list, output
21
+ def initialize(config_repository, git, user_list, output)
22
+ @config_repository, @git, @user_list, @output = config_repository, git, user_list, output
22
23
  end
23
24
 
24
- def request(user, scope)
25
+ def request(scope, *user_strings)
25
26
  begin
26
- matching_user = User.parse(user)
27
- rescue User::ParseError => parse_error
28
- matching_user = @user_list.find(user)
29
- end
30
-
31
- if matching_user.none?
32
- @output.puts "No user found matching '#{user}'"
33
- else
34
- scope = scope == :default ? @git.default_select_scope : scope
35
- @git.select_user(matching_user, scope)
36
- @output.puts "Switched #{scope} user to #{@git.render matching_user}"
27
+ parsed, not_parsed = try_to_parse_all user_strings
28
+ found = find_all not_parsed
29
+ combined_user = combine_all(parsed + found)
30
+ select_user combined_user, scope
31
+ parsed.each {|user| add_parsed_user(user) }
32
+ rescue RuntimeError => error
33
+ @output.puts error.message
37
34
  end
38
35
  end
39
36
 
@@ -58,7 +55,7 @@ module GitSu
58
55
  end
59
56
 
60
57
  def clear(*scopes)
61
- scope_list = scopes.list
58
+ scope_list = scopes.to_sentence
62
59
 
63
60
  if scopes.include? :all
64
61
  scopes = [:local, :global, :system]
@@ -83,6 +80,24 @@ module GitSu
83
80
  @output.puts parse_error.message
84
81
  return
85
82
  end
83
+ add_parsed_user user
84
+ end
85
+
86
+ private
87
+ def try_to_parse_all(user_strings)
88
+ parsed = []
89
+ not_parsed = []
90
+ user_strings.each do |user_string|
91
+ begin
92
+ parsed << User.parse(user_string)
93
+ rescue User::ParseError => parse_error
94
+ not_parsed << user_string
95
+ end
96
+ end
97
+ return [parsed, not_parsed]
98
+ end
99
+
100
+ def add_parsed_user(user)
86
101
  if @user_list.list.include? user
87
102
  @output.puts "User '#{user}' already in user list"
88
103
  else
@@ -90,5 +105,28 @@ module GitSu
90
105
  @output.puts "User '#{user}' added to users"
91
106
  end
92
107
  end
108
+
109
+ def find_all(user_strings)
110
+ if user_strings.empty?
111
+ []
112
+ else
113
+ @user_list.find *user_strings
114
+ end
115
+ end
116
+
117
+ def combine_all(users)
118
+ group_email = @config_repository.group_email_address
119
+ users.inject(User::NONE) do |combined_user, user|
120
+ combined_user.combine user, group_email
121
+ end
122
+ end
123
+
124
+ def select_user(user, scope)
125
+ if scope == :default
126
+ scope = @config_repository.default_select_scope
127
+ end
128
+ @git.select_user(user, scope)
129
+ @output.puts "Switched #{scope} user to #{@git.render user}"
130
+ end
93
131
  end
94
132
  end
@@ -1,18 +1,41 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'gitsu/array'
18
+
1
19
  module GitSu
2
20
  class User
3
- NONE = User.new
21
+ class ParseError < RuntimeError
22
+ end
23
+
24
+ attr_accessor :names, :emails
25
+ protected :names, :emails
26
+
27
+ def initialize(name, email)
28
+ @names, @emails = [name], [email]
29
+ end
30
+
31
+ NONE = User.new(nil, nil)
4
32
  def NONE.to_s
5
33
  "(none)"
6
34
  end
7
35
  def NONE.to_ansi_s(name_color, email_color, reset_color)
8
- "#{name_color}(none)#{reset_color}"
36
+ name_color + to_s + reset_color
9
37
  end
10
38
 
11
- class ParseError < RuntimeError
12
- end
13
-
14
- attr_accessor :name, :email
15
-
16
39
  def User.parse(string)
17
40
  fully_qualified_user_regex = /^[^<]+<[^>]+>$/
18
41
  if string =~ fully_qualified_user_regex
@@ -24,8 +47,39 @@ module GitSu
24
47
  end
25
48
  end
26
49
 
27
- def initialize(name, email)
28
- @name, @email = name, email
50
+ def combine(other, group_email)
51
+ if none?
52
+ other
53
+ elsif other.none?
54
+ self
55
+ else
56
+ clone.combine! other, group_email
57
+ end
58
+ end
59
+
60
+ def clone
61
+ deep_clone = super
62
+ deep_clone.names = names.clone
63
+ deep_clone.emails = emails.clone
64
+ deep_clone
65
+ end
66
+
67
+ def name
68
+ names = emails_and_names.map {|email,name| name}
69
+ names.to_sentence
70
+ end
71
+
72
+ def email
73
+ emails = emails_and_names.map {|email,name| email}
74
+ if emails.size == 1
75
+ emails.first
76
+ else
77
+ email_prefixes = emails.map { |email| email.sub /@.*/, '' }
78
+ email_domain = emails.first.sub /^.*@/, ''
79
+ group_email_prefix = @group_email.sub /@.*/, ''
80
+ group_email_domain = @group_email.sub /^.*@/, ''
81
+ group_email_prefix + '+' + email_prefixes.join('+') + '@' + group_email_domain
82
+ end
29
83
  end
30
84
 
31
85
  def none?
@@ -37,7 +91,7 @@ module GitSu
37
91
  end
38
92
 
39
93
  def eql?(other)
40
- @name == other.name && @email == other.email
94
+ name == other.name && email == other.email
41
95
  end
42
96
 
43
97
  def hash
@@ -45,11 +99,26 @@ module GitSu
45
99
  end
46
100
 
47
101
  def to_ansi_s(name_color, email_color, reset_color)
48
- "#{name_color}#{@name}#{reset_color} #{email_color}<#{@email}>#{reset_color}"
102
+ "#{name_color}#{name}#{reset_color} #{email_color}<#{email}>#{reset_color}"
49
103
  end
50
104
 
51
105
  def to_s
52
106
  to_ansi_s("", "", "")
53
107
  end
108
+
109
+ protected
110
+ def combine!(other, group_email)
111
+ @names += other.names
112
+ @emails += other.emails
113
+ @group_email = group_email
114
+ self
115
+ end
116
+
117
+ private
118
+ # Array of emails and names, unique and ordered
119
+ def emails_and_names
120
+ combined = @emails.zip @names
121
+ Hash[combined].sort
122
+ end
54
123
  end
55
124
  end
@@ -1,3 +1,19 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  require 'fileutils'
2
18
  require 'yaml'
3
19
 
@@ -22,8 +38,8 @@ module GitSu
22
38
  end
23
39
 
24
40
  def read
25
- yaml_list = YAML.load_file(@file) or return []
26
- yaml_list.map do |email, name|
41
+ user_map = YAML.load_file(@file) or return []
42
+ user_map.map do |email, name|
27
43
  User.new(name, email)
28
44
  end
29
45
  end
@@ -1,7 +1,23 @@
1
+ # Gitsu
2
+ # Copyright (C) 2013 drrb
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
1
17
  module GitSu
2
18
  class UserList
3
- def initialize(file_name)
4
- @user_file = UserFile.new(file_name)
19
+ def initialize(user_file)
20
+ @user_file = user_file
5
21
  end
6
22
 
7
23
  def add(user)
@@ -12,13 +28,54 @@ module GitSu
12
28
  @user_file.read
13
29
  end
14
30
 
15
- def find(search_term)
16
- users = @user_file.read
17
- matching_users = []
18
- match_strategies.each do |strategy|
19
- matching_users += users.select { |user| strategy.call(search_term, user) }
31
+ def find(*search_terms)
32
+ all_users = list
33
+ searches = all_matching_users(all_users, search_terms)
34
+ searches.each do |search|
35
+ if search.matches.empty?
36
+ raise "No user found matching '#{search.term}'"
37
+ end
38
+ end
39
+
40
+ find_unique_combination(searches) or raise "Couldn't find a combination of unique users matching #{search_terms.map {|t| "'#{t}'" }.to_sentence}"
41
+ end
42
+
43
+ private
44
+ class Search
45
+ attr_accessor :term, :matches
46
+ def initialize(term)
47
+ @term, @matches = term, []
48
+ end
49
+ end
50
+
51
+ def all_matching_users(all_users, search_terms)
52
+ searches = search_terms.map {|search_term| Search.new(search_term)}
53
+ searches.each do |search|
54
+ match_strategies.each do |strategy|
55
+ search.matches += all_users.select { |user| strategy.call(search.term, user) }
56
+ end
57
+ search.matches.uniq!
58
+ end
59
+ end
60
+
61
+ def find_unique_combination(searches)
62
+ searches = searches.clone
63
+ # Generate all combinations
64
+ combinations = [[]]
65
+ searches.each do |search|
66
+ previous_combos = combinations.clone
67
+ combinations = []
68
+ previous_combos.each do |combo|
69
+ search.matches.each do |match|
70
+ combo_extension = combo.clone
71
+ combo_extension << match
72
+ combinations << combo_extension
73
+ end
74
+ end
75
+ end
76
+ combinations.find do |combo|
77
+ combo.uniq.size == combo.size
20
78
  end
21
- matching_users.first || User::NONE
22
79
  end
23
80
 
24
81
  def match_strategies
@@ -31,14 +88,13 @@ module GitSu
31
88
 
32
89
  # Initials
33
90
  lambda do |search_term, user|
34
- initials = user.name.downcase.split(" ").map { |word| word.chars.first }.join
35
- initials.include? search_term.downcase
91
+ initials = user.name.split(" ").map { |word| word.chars.first }.join
92
+ initials =~ /#{search_term}/i
36
93
  end,
37
94
 
38
95
  # Segment anywhere in name or email
39
96
  lambda do |search_term, user|
40
- name_and_email = "#{user.name} #{user.email}".downcase
41
- name_and_email.include? search_term.downcase
97
+ "#{user.name} #{user.email}" =~ /#{search_term}/i
42
98
  end
43
99
  ]
44
100
  end