gitsu 1.0.0 → 2.0.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.
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