├── .gitignore
├── .ruby-gemset
├── .ruby-version
├── .travis.yml
├── Gemfile
├── Gemfile.lock
├── LICENSE.md
├── README.md
├── Rakefile
├── bin
└── hitch
├── hitch.gemspec
├── lib
├── hitch.rb
└── hitch
│ ├── author.rb
│ ├── hitch.sh
│ └── ui.rb
└── spec
├── acceptance
└── hitch_spec.rb
├── hitch
├── author_spec.rb
└── ui_spec.rb
├── hitch_spec.rb
└── spec_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | pkg/*
2 | doc/*
3 | tmp
4 | *gem
5 |
--------------------------------------------------------------------------------
/.ruby-gemset:
--------------------------------------------------------------------------------
1 | hitch
2 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.6.6
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.1.9
4 | - 2.2.5
5 | - 2.3.1
6 | branches:
7 | only:
8 | - master
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'http://rubygems.org'
2 |
3 | gem 'highline', '~>1.6.2'
4 | gem 'rake'
5 | gem 'rspec', '~>2.6.0'
6 | gem 'pry'
7 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | coderay (1.0.9)
5 | diff-lcs (1.1.3)
6 | highline (1.6.20)
7 | method_source (0.8.2)
8 | pry (0.9.12.2)
9 | coderay (~> 1.0.5)
10 | method_source (~> 0.8)
11 | slop (~> 3.4)
12 | rake (10.1.0)
13 | rspec (2.6.0)
14 | rspec-core (~> 2.6.0)
15 | rspec-expectations (~> 2.6.0)
16 | rspec-mocks (~> 2.6.0)
17 | rspec-core (2.6.4)
18 | rspec-expectations (2.6.0)
19 | diff-lcs (~> 1.1.2)
20 | rspec-mocks (2.6.0)
21 | slop (3.4.6)
22 |
23 | PLATFORMS
24 | ruby
25 |
26 | DEPENDENCIES
27 | highline (~> 1.6.2)
28 | pry
29 | rake
30 | rspec (~> 2.6.0)
31 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2009-2011 Rogelio J. Samour
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | hitch
2 | =====
3 | by Rogelio J. Samour (http://blog.therubymug.com)
4 |
5 | [](https://travis-ci.org/therubymug/hitch)
6 |
7 | Description:
8 | -----------
9 |
10 | Hitch allows developers to be properly credited when Pair Programming and using Git.
11 |
12 | Features:
13 | --------
14 |
15 | * Persists pair(s) between different terminal instances.
16 | * Creates a unique email address for the pair. (e.g. dev+fry+leela@hashrocket.com) This provides the ability to create a Gravatar for the pair.
17 | * Allows you to expire the pair information in N hours. e.g. hitch --expire 8 fry leela
18 |
19 | Synopsis:
20 | --------
21 |
22 | - For leela and fry to pair:
23 | - hitch leela fry
24 | - To override group email:
25 | - hitch leela fry -g dev@hashrocket.com
26 | - To clear pair info:
27 | - hitch -u
28 | - For a complete list of features:
29 | - hitch -h
30 | - Creating a Gravatar for your pair:
31 | - Once I've hitched with my pair. (e.g. hitch leela fry) I have now created a unique email: dev+fry+leela@hashrocket.com
32 | - Then, I go to gravatar.com. Add an image to that particular email address and I'm done.
33 |
34 | Install:
35 | -------
36 |
37 | * gem install hitch
38 | * chruby users run this:
39 |
for x in $(chruby | cut -c 3- | awk '{print $1}'); do chruby $x && gem install hitch; done
40 | * rvm users run this:
41 | for x in $(rvm list strings); do rvm use $x@global && gem install hitch; done
42 | * rbenv users run this:
43 | for x in $(rbenv versions | cut -c 3- | awk '{print $1}'); do rbenv shell $x && gem install hitch; done
44 | * hitch --setup >> ~/.bashrc
45 | - this prints out the necessary shell function and aliases you need to add to your ~/.bashrc or ~/.zshrc
46 | * Or copy/paste [the code](lib/hitch/hitch.sh) into your ~/.bashrc or ~/.zshrc
47 | * As another option, copy/symlink the script to a separate file (e.g. `~/.bash/hitch.sh` or `/etc/profile.d/hitch.sh`) and source it. You can get the path using `hitch --setup-path`.
48 |
49 | Development:
50 | -----------
51 |
52 | * It's easier if you use rvm.
53 | * Fork hitch
54 | * When you cd into the directory the .rvmrc will activate and create a hitch gemset
55 | * Add tests and code for your feature
56 | * Create a pull request
57 | * Double-check TravisCI to make sure all tests pass
58 |
59 | Requirements:
60 | ------------
61 |
62 | * Git, HighLine
63 |
64 | Acknowledgements:
65 | ----------------
66 |
67 | * Stephen Caudill
68 | * Les Hill
69 | * Tim Pope
70 |
71 | License:
72 | -------
73 | Released under the MIT License. See the [LICENSE][license] file for further details.
74 |
75 | [license]: https://github.com/therubymug/hitch/blob/master/LICENSE.md
76 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #############################################################################
2 | #
3 | # Helper functions
4 | #
5 | #############################################################################
6 |
7 | def name
8 | @name ||= Dir['*.gemspec'].first.split('.').first
9 | end
10 |
11 | def version
12 | line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
13 | line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
14 | end
15 |
16 | def date
17 | Date.today.to_s
18 | end
19 |
20 | def gemspec_file
21 | "#{name}.gemspec"
22 | end
23 |
24 | def gem_file
25 | "#{name}-#{version}.gem"
26 | end
27 |
28 | def replace_header(head, header_name)
29 | head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
30 | end
31 |
32 | #############################################################################
33 | #
34 | # Standard tasks
35 | #
36 | #############################################################################
37 |
38 | require 'rspec'
39 | require 'rspec/core/rake_task'
40 |
41 | desc "Run all specs"
42 | task RSpec::Core::RakeTask.new('spec')
43 |
44 | task :default => "spec"
45 |
46 | desc "Open an irb session preloaded with this library"
47 | task :console do
48 | sh "irb -rubygems -r ./lib/#{name}.rb"
49 | end
50 |
51 | #############################################################################
52 | #
53 | # Custom tasks (add your own tasks here)
54 | #
55 | #############################################################################
56 |
57 |
58 | #############################################################################
59 | #
60 | # Packaging tasks
61 | #
62 | #############################################################################
63 |
64 | desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
65 | task :release => :build do
66 | unless `git branch` =~ /^\* master$/
67 | puts "You must be on the master branch to release!"
68 | exit!
69 | end
70 | sh "git commit --allow-empty -a -m 'Release #{version}'"
71 | sh "git tag v#{version}"
72 | sh "git push origin master"
73 | sh "git push origin v#{version}"
74 | sh "gem push pkg/#{name}-#{version}.gem"
75 | end
76 |
77 | desc "Build #{gem_file} into the pkg directory"
78 | task :build => :gemspec do
79 | sh "mkdir -p pkg"
80 | sh "gem build #{gemspec_file}"
81 | sh "mv #{gem_file} pkg"
82 | end
83 |
84 | desc "Generate #{gemspec_file}"
85 | task :gemspec => :validate do
86 | # read spec file and split out manifest section
87 | spec = File.read(gemspec_file)
88 | head, manifest, tail = spec.split(" # = MANIFEST =\n")
89 |
90 | # replace name version and date
91 | replace_header(head, :name)
92 | replace_header(head, :version)
93 | replace_header(head, :date)
94 |
95 | # determine file list from git ls-files
96 | files = `git ls-files`.
97 | split("\n").
98 | sort.
99 | reject { |file| file =~ /^\./ }.
100 | reject { |file| file =~ /^(rdoc|pkg)/ }.
101 | map { |file| " #{file}" }.
102 | join("\n")
103 |
104 | # piece file back together and write
105 | manifest = " s.files = %w[\n#{files}\n ]\n"
106 | spec = [head, manifest, tail].join(" # = MANIFEST =\n")
107 | File.open(gemspec_file, 'w') { |io| io.write(spec) }
108 | puts "Updated #{gemspec_file}"
109 | end
110 |
111 | desc "Validate #{gemspec_file}"
112 | task :validate do
113 | libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
114 | unless libfiles.empty?
115 | puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
116 | exit!
117 | end
118 | unless Dir['VERSION*'].empty?
119 | puts "A `VERSION` file at root level violates Gem best practices."
120 | exit!
121 | end
122 | end
123 |
--------------------------------------------------------------------------------
/bin/hitch:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib hitch]))
4 |
5 | args = ARGV
6 | options = []
7 | opts = OptionParser.new do |opts|
8 | opts.banner = "Usage: hitch [options] first_pair_username second_pair_username"
9 | opts.separator ""
10 | opts.separator "General options:"
11 | opts.on("-s", "--setup", "Print out shell goodies") do
12 | options = [:setup]
13 | end
14 | opts.on("--setup-path", "Print out path to shell goodies") do
15 | options = [:print_setup_path]
16 | end
17 | opts.on("-e N", "--expire N", Integer, "Expire pair information in N hours.") do |n|
18 | options = [:expire, n]
19 | end
20 | opts.on("-g G", "--group G", "Set group email to G.") do |g|
21 | options = [:group_email, g]
22 | end
23 | opts.on("-u", "--unhitch", "Clear pair information") do
24 | options = [:unhitch]
25 | end
26 | opts.separator ""
27 | opts.separator "Common options:"
28 | opts.on_tail("-h", "--help", "Show this message") do
29 | puts opts
30 | exit
31 | end
32 | opts.on_tail("-v", "--version", "Show version") do
33 | abort "hitch v#{Hitch::VERSION}"
34 | end
35 | end
36 |
37 | if args.any?
38 | opts.parse!(args)
39 |
40 | if options.delete(:expire)
41 | system(Hitch.expire_command(options.pop))
42 | end
43 |
44 | if options.delete(:group_email)
45 | Hitch.group_email = options.pop
46 | end
47 |
48 | options = [:export, args] if options.empty?
49 | else
50 | options = [:print_info]
51 | end
52 |
53 | Hitch.send(*options)
54 |
--------------------------------------------------------------------------------
/hitch.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | Gem::Specification.new do |s|
3 | s.specification_version = 2 if s.respond_to? :specification_version=
4 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
5 | s.rubygems_version = '1.3.5'
6 |
7 | s.license = "MIT"
8 | s.name = %q{hitch}
9 | s.version = '1.1.0'
10 | s.date = Time.now.strftime('%F')
11 |
12 | s.description = %q{Git author attribution helper for pair programmers.}
13 | s.summary = %q{Hitch allows developers to be properly credited when Pair Programming and using Git.}
14 | s.authors = [%q{Rogelio J. Samour}]
15 | s.email = ["rogelio@therubymug.com"]
16 | s.homepage = %q{http://github.com/therubymug/hitch}
17 | s.require_paths = %w[lib]
18 | s.extra_rdoc_files = %w[README.md LICENSE.md]
19 | s.rdoc_options = [%q{--charset=UTF-8}]
20 |
21 | s.executables = [%q{hitch}]
22 | if s.respond_to? :specification_version then
23 | s.specification_version = 3
24 |
25 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26 | s.add_development_dependency(%q, [">= 2.6.0"])
27 | s.add_runtime_dependency(%q, [">= 1.6.2"])
28 | else
29 | s.add_dependency(%q, [">= 2.6.0"])
30 | s.add_dependency(%q, [">= 1.6.2"])
31 | end
32 | else
33 | s.add_dependency(%q, [">= 2.6.0"])
34 | s.add_dependency(%q, [">= 1.6.2"])
35 | end
36 |
37 | ## Leave this section as-is. It will be automatically generated from the
38 | ## contents of your Git repository via the gemspec task. DO NOT REMOVE
39 | ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
40 | # = MANIFEST =
41 | s.files = %w[
42 | Gemfile
43 | Gemfile.lock
44 | LICENSE.md
45 | README.md
46 | Rakefile
47 | bin/hitch
48 | hitch.gemspec
49 | lib/hitch.rb
50 | lib/hitch/author.rb
51 | lib/hitch/hitch.sh
52 | lib/hitch/ui.rb
53 | spec/acceptance/hitch_spec.rb
54 | spec/hitch/author_spec.rb
55 | spec/hitch/ui_spec.rb
56 | spec/hitch_spec.rb
57 | spec/spec_helper.rb
58 | ]
59 | # = MANIFEST =
60 |
61 | s.test_files = s.files.grep(%r{^spec/})
62 | end
63 |
--------------------------------------------------------------------------------
/lib/hitch.rb:
--------------------------------------------------------------------------------
1 | require 'highline'
2 | require 'yaml'
3 |
4 | require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib hitch author]))
5 | require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib hitch ui]))
6 |
7 | module Hitch
8 |
9 | VERSION = '1.1.0'
10 |
11 | def self.print_info
12 | if Hitch.pairing? && STDOUT.tty?
13 | Hitch::UI.highline.say("#{Hitch.git_author_name} <#{Hitch.git_author_email}>")
14 | end
15 | end
16 |
17 | def self.export(pairs)
18 | Hitch.current_pair = pairs
19 | write_export_file
20 | print_info
21 | end
22 |
23 | def self.expire_command(time)
24 | %Q(sleep #{to_seconds(time)} && #{Hitch.bin_path} --unhitch&)
25 | end
26 |
27 | def self.unhitch
28 | Hitch.current_pair = []
29 | write_export_file
30 | end
31 |
32 | def self.author_command
33 | if Hitch.pairing?
34 | %Q{export GIT_AUTHOR_NAME="#{Hitch.git_author_name}" GIT_AUTHOR_EMAIL="#{Hitch.git_author_email}"}
35 | else
36 | "unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL"
37 | end
38 | end
39 |
40 | def self.group_email
41 | config[:group_email] ||= Hitch::UI.prompt_for_group_email
42 | end
43 |
44 | def self.group_email=(email)
45 | config[:group_email] = email
46 | write_file
47 | end
48 |
49 | def self.current_pair
50 | config[:current_pair] ||= []
51 | end
52 |
53 | def self.current_pair=(pairs)
54 | config[:current_pair] = []
55 | pairs.each do |author|
56 | if Hitch::Author.find(author)
57 | config[:current_pair] << author
58 | else
59 | if Hitch::UI.prompt_for_pair(author)
60 | config[:current_pair] << author
61 | end
62 | end
63 | end
64 | write_file
65 | end
66 |
67 | def self.git_author_name
68 | devs = current_pair.sort.map {|pair| Hitch::Author.find(pair)}
69 | case devs.length
70 | when 1, 2
71 | devs.join(" and ")
72 | else
73 | "#{devs[0...-1].join(', ')}, and #{devs[-1]}"
74 | end
75 | end
76 |
77 | def self.git_author_email
78 | "#{group_prefix}+#{current_pair.sort.join('+')}@#{group_domain}"
79 | end
80 |
81 | def self.group_prefix
82 | group_email.split('@').first
83 | end
84 |
85 | def self.group_domain
86 | group_email.split('@').last
87 | end
88 |
89 | def self.setup_path
90 | File.join(File.dirname(__FILE__), 'hitch', 'hitch.sh')
91 | end
92 |
93 | def self.print_setup_path
94 | puts setup_path
95 | end
96 |
97 | def self.setup
98 | Hitch::UI.highline.say(File.read(setup_path))
99 | end
100 |
101 | def self.write_file
102 | File.open(hitchrc, File::CREAT|File::TRUNC|File::RDWR, 0644) do |out|
103 | YAML.dump(config, out)
104 | end
105 | end
106 |
107 | private
108 |
109 | def self.config
110 | @config ||= get_config
111 | end
112 |
113 | def self.get_config
114 | if File.exists?(hitchrc)
115 | yamlized = YAML::load_file(hitchrc)
116 | return yamlized if yamlized.kind_of?(Hash)
117 | end
118 | return {}
119 | end
120 |
121 | def self.hitchrc
122 | File.expand_path('~/.hitchrc')
123 | end
124 |
125 | def self.hitch_export_authors
126 | File.expand_path('~/.hitch_export_authors')
127 | end
128 |
129 | def self.bin_path
130 | %x[which hitch].chomp
131 | end
132 |
133 | def self.pairing?
134 | current_pair.any?
135 | end
136 |
137 | def self.to_seconds(value)
138 | value = value.to_s.gsub(/[^\d]/, '').to_i
139 | unless value.zero?
140 | value * 60 * 60
141 | else
142 | raise StandardError
143 | end
144 | end
145 |
146 | def self.write_export_file
147 | File.open(hitch_export_authors, 'w'){|f| f.write(author_command) }
148 | end
149 |
150 | end
151 |
--------------------------------------------------------------------------------
/lib/hitch/author.rb:
--------------------------------------------------------------------------------
1 | module Hitch
2 | class Author
3 |
4 | def self.add(author_github, author_email)
5 | unless find(author_github)
6 | available_pairs[author_github] = author_email
7 | end
8 | end
9 |
10 | def self.find(author_github)
11 | available_pairs[author_github]
12 | end
13 |
14 | def self.write_file
15 | File.open(hitch_pairs, File::CREAT|File::TRUNC|File::RDWR, 0644) do |out|
16 | YAML.dump(available_pairs, out)
17 | end
18 | end
19 |
20 | private
21 |
22 | def self.hitch_pairs
23 | File.expand_path("~/.hitch_pairs")
24 | end
25 |
26 | def self.available_pairs
27 | @available_pairs ||= get_available_pairs
28 | end
29 |
30 | def self.get_available_pairs
31 | if File.exists?(hitch_pairs)
32 | yamlized = YAML::load_file(hitch_pairs)
33 | return yamlized if yamlized.kind_of?(Hash)
34 | end
35 | return {}
36 | end
37 |
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/hitch/hitch.sh:
--------------------------------------------------------------------------------
1 | # Add the following to your ~/.bashrc or ~/.zshrc
2 | #
3 | # Alternatively, copy/symlink this file and source in your shell. See `hitch --setup-path`.
4 |
5 | hitch() {
6 | command hitch "$@"
7 | if [[ -s "$HOME/.hitch_export_authors" ]] ; then source "$HOME/.hitch_export_authors" ; fi
8 | }
9 | alias unhitch='hitch -u'
10 |
11 | # Uncomment to persist pair info between terminal instances
12 | # hitch
13 |
--------------------------------------------------------------------------------
/lib/hitch/ui.rb:
--------------------------------------------------------------------------------
1 | HighLine.track_eof = false
2 |
3 | module Hitch
4 | class UI
5 |
6 | def self.prompt_for_group_email
7 | Hitch.group_email = highline.ask("What is the group email? e.g. dev@hashrocket.com will become dev+therubymug+leshill@hashrocket.com") do |q|
8 | q.case = :down
9 | q.validate = /\A[a-zA-Z0-9_\.\-\+]+@[a-zA-Z1-9\-]+\.[a-zA-Z0-9\-\.]+\z/
10 | end.to_s
11 | end
12 |
13 | def self.prompt_for_pair(new_author)
14 | highline.say("I don't know who #{new_author} is.")
15 | if highline.agree("Do you want to add #{new_author} to ~/.hitch_pairs?")
16 | author_name = highline.ask("What is #{new_author}'s full name?").to_s
17 | Hitch::Author.add(new_author, author_name)
18 | Hitch::Author.write_file
19 | return new_author
20 | else
21 | highline.say("Ignoring #{new_author}.")
22 | end
23 | return nil
24 | end
25 |
26 | private
27 |
28 | def self.highline
29 | @highline ||= HighLine.new
30 | end
31 |
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/spec/acceptance/hitch_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'stringio'
3 |
4 | describe Hitch do
5 | describe 'configuration file' do
6 | let(:hitchrc){ Tempfile.new('hitchrc') }
7 |
8 | before do
9 | described_class.stub(:hitchrc => hitchrc.path)
10 | end
11 |
12 | def with_custom_input_io(io)
13 | old_stdin = $stdin
14 | $stdin = io
15 | yield
16 | ensure
17 | $stdin = old_stdin
18 | end
19 |
20 | it 'contains the group email address' do
21 | email = StringIO.new('test@example.com')
22 | with_custom_input_io(email) do
23 | described_class.group_email
24 | end
25 |
26 | hitchrc.read.should include(email.string)
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/hitch/author_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Hitch::Author do
4 |
5 | let(:base_pairs) do
6 | {
7 | 'bender' => 'Bender Bending Rodriguez',
8 | 'fry' => 'Phillip J. Fry',
9 | 'leela' => 'Turanga Leela'
10 | }
11 | end
12 |
13 | before { Hitch::Author.stub(:available_pairs).and_return(base_pairs) }
14 |
15 | describe ".write_file" do
16 | it "writes the contents of Hitch::Author.available_pairs to the hitch_pairs file" do
17 | YAML.should_receive(:dump)
18 | Hitch::Author.write_file
19 | end
20 | end
21 |
22 | describe ".add" do
23 |
24 | before { Hitch::Author.stub(:available_pairs).and_return({}) }
25 |
26 | context "when the author is not present" do
27 | it "adds the author to Hitch::Author.available_pairs" do
28 | Hitch::Author.add("therubymug", "Rogelio J. Samour")
29 | Hitch::Author.available_pairs.should == {"therubymug" => "Rogelio J. Samour"}
30 | end
31 | end
32 |
33 | end
34 |
35 | describe ".find" do
36 |
37 | context "when the author is present" do
38 |
39 | before do
40 | Hitch::Author.stub(:available_pairs).and_return({"therubymug" => "Rogelio J. Samour"})
41 | end
42 |
43 | it "and_return the full name" do
44 | Hitch::Author.find("therubymug").should == "Rogelio J. Samour"
45 | end
46 |
47 | end
48 |
49 | context "when the author is not present" do
50 | it "and_return nil" do
51 | Hitch::Author.find("nobody").should be_nil
52 | end
53 | end
54 |
55 | end
56 |
57 | end
58 |
--------------------------------------------------------------------------------
/spec/hitch/ui_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Hitch::UI do
4 |
5 | describe '.prompt_for_group_email' do
6 |
7 | let(:question_message) { "What is the group email? e.g. dev@hashrocket.com will become dev+therubymug+leshill@hashrocket.com" }
8 |
9 | it 'prompts for group email' do
10 | Hitch::UI.highline.should_receive(:ask).with(question_message)
11 | Hitch::UI.prompt_for_group_email
12 | end
13 |
14 | it 'returns the given group email' do
15 | Hitch::UI.highline.stub(:ask).with(question_message).and_return('dev@hashrocket.com')
16 | Hitch::UI.prompt_for_group_email.should == 'dev@hashrocket.com'
17 | end
18 |
19 | context 'when group email contains a `+` in it' do
20 | let(:group_email) { 'dev+hitch@hashrocket.com' }
21 | it 'sets Hitch.group_email' do
22 | question = double({:case= => nil, :validate= => nil})
23 | question.should_receive(:case=).with(:down)
24 | question.should_receive(:validate=).with(/\A[a-zA-Z0-9_\.\-\+]+@[a-zA-Z1-9\-]+\.[a-zA-Z0-9\-\.]+\z/)
25 | Hitch.should_receive('group_email=').with(group_email)
26 | Hitch::UI.highline.stub(:ask).with(question_message).and_yield(question).and_return(group_email)
27 | Hitch::UI.prompt_for_group_email
28 | end
29 | end
30 |
31 | context 'when group email does not contain a `+` in it' do
32 | let(:group_email) { 'dev@hashrocket.com' }
33 | it 'sets Hitch.group_email' do
34 | question = double({:case= => nil, :validate= => nil})
35 | question.should_receive(:case=).with(:down)
36 | question.should_receive(:validate=).with(/\A[a-zA-Z0-9_\.\-\+]+@[a-zA-Z1-9\-]+\.[a-zA-Z0-9\-\.]+\z/)
37 | Hitch.should_receive('group_email=').with(group_email)
38 | Hitch::UI.highline.stub(:ask).with(question_message).and_yield(question).and_return(group_email)
39 | Hitch::UI.prompt_for_group_email
40 | end
41 | end
42 |
43 | end
44 |
45 | describe '.prompt_for_pair' do
46 |
47 | let(:new_author) { 'leela' }
48 | let(:new_author_name) { 'Turanga Leela' }
49 |
50 | before do
51 | Hitch::UI.highline.stub(:ask).and_return(new_author_name)
52 | Hitch::UI.highline.stub(:say)
53 | end
54 |
55 | it 'states that the new pair is not in the hitch_pairs file' do
56 | Hitch::UI.highline.should_receive(:say).with("I don't know who #{new_author} is.")
57 | Hitch::UI.prompt_for_pair(new_author)
58 | end
59 |
60 | it 'prompts for pair' do
61 | Hitch::UI.highline.should_receive(:agree).with("Do you want to add #{new_author} to ~/.hitch_pairs?")
62 | Hitch::UI.prompt_for_pair(new_author)
63 | end
64 |
65 | context 'when user does not agree to add new author' do
66 | it "states it's ignoring the author" do
67 | Hitch::UI.highline.stub(:agree).and_return(false)
68 | Hitch::UI.highline.should_receive(:say).with("Ignoring #{new_author}.")
69 | Hitch::UI.prompt_for_pair(new_author)
70 | end
71 | end
72 |
73 | context 'when user agrees to add new author' do
74 |
75 | before do
76 | Hitch::UI.highline.stub(:agree).and_return(true)
77 | end
78 |
79 | it "asks for the new author's name" do
80 | Hitch::UI.highline.should_receive(:ask).with("What is #{new_author}'s full name?")
81 | Hitch::UI.prompt_for_pair(new_author)
82 | end
83 |
84 | it "adds the new author" do
85 | Hitch::Author.should_receive(:add).with(new_author, new_author_name)
86 | Hitch::UI.prompt_for_pair(new_author)
87 | end
88 |
89 | it "writes the ~/.hitch_pairs file" do
90 | Hitch::Author.should_receive(:write_file)
91 | Hitch::UI.prompt_for_pair(new_author)
92 | end
93 |
94 | end
95 |
96 | end
97 |
98 | end
99 |
--------------------------------------------------------------------------------
/spec/hitch_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Hitch do
4 |
5 | let(:hitch_pairs) do
6 | {
7 | "conan" => "Conan O'Brien",
8 | "fry" => "Philip J. Fry",
9 | "leela" => "Turanga Leela",
10 | "zoidberg" => "John A. Zoidberg"
11 | }
12 | end
13 |
14 | let(:hitch_config) do
15 | { :group_email => 'dev@hashrocket.com',
16 | :current_pair => ['leela', 'fry'] }
17 | end
18 |
19 | before do
20 | Hitch.stub(:config).and_return(hitch_config)
21 | Hitch::Author.stub(:available_pairs).and_return(hitch_pairs)
22 | end
23 |
24 | describe '.expire_command' do
25 | before do
26 | Hitch.stub(:bin_path).and_return('/usr/local/bin/hitch')
27 | end
28 |
29 | context 'with a valid string' do
30 | let(:time_string) { '8' }
31 | it 'returns the system command to call' do
32 | Hitch.expire_command(time_string).should == 'sleep 28800 && /usr/local/bin/hitch --unhitch&'
33 | end
34 | end
35 |
36 | context 'with a valid integer' do
37 | let(:time_string) { 8 }
38 | it 'returns the system command to call' do
39 | Hitch.expire_command(time_string).should == 'sleep 28800 && /usr/local/bin/hitch --unhitch&'
40 | end
41 | end
42 |
43 | context 'with an invalid string' do
44 | let(:time_string) { 'BAR' }
45 | it 'raises an error' do
46 | expect {Hitch.expire_command(time_string)}.to raise_error(StandardError)
47 | end
48 | end
49 |
50 | end
51 |
52 | describe '.author_command' do
53 |
54 | context 'when pairing' do
55 | it 'returns the export shell command for GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL' do
56 | Hitch.current_pair = ['leela', 'fry']
57 | Hitch.author_command.should == %q{export GIT_AUTHOR_NAME="Philip J. Fry and Turanga Leela" GIT_AUTHOR_EMAIL="dev+fry+leela@hashrocket.com"}
58 | end
59 | end
60 |
61 | context 'when not pairing' do
62 | it 'returns the unset shell command for GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL' do
63 | Hitch.current_pair = []
64 | Hitch.author_command.should == "unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL"
65 | end
66 | end
67 |
68 | context 'with more than 2 developers' do
69 | it "joins 3+ developers together with commas and an 'and'" do
70 | Hitch.current_pair = ['leela', 'fry', 'zoidberg']
71 | Hitch.author_command.should == %q{export GIT_AUTHOR_NAME="Philip J. Fry, Turanga Leela, and John A. Zoidberg" GIT_AUTHOR_EMAIL="dev+fry+leela+zoidberg@hashrocket.com"}
72 | end
73 | end
74 |
75 | it 'escapes special characters' do
76 | Hitch.current_pair = ["conan", "fry"]
77 | Hitch.author_command.should == %q{export GIT_AUTHOR_NAME="Conan O'Brien and Philip J. Fry" GIT_AUTHOR_EMAIL="dev+conan+fry@hashrocket.com"}
78 | end
79 |
80 | end
81 |
82 | describe '.unhitch' do
83 |
84 | let(:pairs) { ['fry', 'leela'] }
85 |
86 | it 'sets the current pair to an empty array' do
87 | Hitch.should_receive(:current_pair=).with([])
88 | Hitch.unhitch
89 | end
90 |
91 | it 'writes the export file' do
92 | Hitch.should_receive(:write_export_file)
93 | Hitch.unhitch
94 | end
95 |
96 | end
97 |
98 | describe '.print_info' do
99 |
100 | context 'when pairing' do
101 | it 'returns the pair name and email being used' do
102 | Hitch::UI.highline.should_receive(:say).with("Philip J. Fry and Turanga Leela ")
103 | Hitch.current_pair = ['leela', 'fry']
104 | Hitch.print_info
105 | end
106 | end
107 |
108 | context 'when not pairing' do
109 | it 'returns nothing' do
110 | Hitch.current_pair = []
111 | Hitch.print_info.should be_nil
112 | end
113 | end
114 |
115 | context 'when not in an interactive shell' do
116 | it 'returns nothing' do
117 | STDOUT.stub(:tty?).and_return(false)
118 | Hitch::UI.highline.should_not_receive(:say)
119 | Hitch.current_pair = ['leela', 'fry']
120 | Hitch.print_info
121 | end
122 | end
123 |
124 | end
125 |
126 | describe '.export' do
127 |
128 | let(:pairs) { ['fry', 'leela'] }
129 |
130 | before do
131 | Hitch.stub(:print_info)
132 | end
133 |
134 | it 'sets the current pair' do
135 | Hitch.should_receive(:current_pair=).with(pairs)
136 | Hitch.export(pairs)
137 | end
138 |
139 | it 'writes the export file' do
140 | Hitch.should_receive(:write_export_file)
141 | Hitch.export(pairs)
142 | end
143 |
144 | it 'prints out pair info' do
145 | Hitch.should_receive(:print_info)
146 | Hitch.export(pairs)
147 | end
148 |
149 | end
150 |
151 | describe '.current_pair=' do
152 |
153 | before { Hitch.stub(:write_file) }
154 |
155 | it 'writes the hitchrc file' do
156 | Hitch.should_receive(:write_file)
157 | Hitch.current_pair = ['leela', 'fry']
158 | end
159 |
160 | context 'when there are no new authors' do
161 | it 'sets the current pair with the authors given' do
162 | Hitch.current_pair = ['leela', 'fry']
163 | Hitch.current_pair.should == ['leela', 'fry']
164 | end
165 | end
166 |
167 | context 'when there are new authors' do
168 |
169 | let(:new_author) { 'therubymug' }
170 |
171 | it "prompts for the new author's info" do
172 | Hitch::UI.should_receive(:prompt_for_pair).with(new_author)
173 | Hitch.current_pair = [new_author, 'fry']
174 | end
175 |
176 | it 'adds the new author to current pair' do
177 | Hitch::UI.stub(:prompt_for_pair).and_return(new_author)
178 | Hitch.current_pair = [new_author, 'fry']
179 | Hitch.current_pair.should == [new_author, 'fry']
180 | end
181 |
182 | end
183 |
184 | end
185 |
186 | describe '.current_pair' do
187 | it 'returns an array of Github usernames' do
188 | Hitch.stub(:config).and_return(hitch_config)
189 | Hitch.current_pair.should == ['leela', 'fry']
190 | end
191 | end
192 |
193 | describe '.group_email' do
194 |
195 | context 'when absent from Hitch.config' do
196 | before { Hitch.stub(:config).and_return({}) }
197 | it 'prompts the user for it' do
198 | Hitch::UI.should_receive(:prompt_for_group_email)
199 | Hitch.group_email
200 | end
201 | end
202 |
203 | context 'when present in Hitch.config' do
204 | it 'returns the value' do
205 | Hitch.group_email.should == 'dev@hashrocket.com'
206 | end
207 | end
208 |
209 | end
210 |
211 | describe '.group_email=' do
212 |
213 | before { Hitch.stub(:write_file) }
214 |
215 | it 'writes the hitchrc file' do
216 | Hitch.should_receive(:write_file)
217 | Hitch.group_email = 'dev@hashrocket.com'
218 | end
219 |
220 | it 'sets the current pair with the authors given' do
221 | Hitch.group_email = 'dev@hashrocket.com'
222 | Hitch.group_email.should == 'dev@hashrocket.com'
223 | end
224 |
225 | end
226 |
227 | describe '.group_prefix' do
228 | it 'returns the user portion of the group email' do
229 | Hitch.group_prefix.should == 'dev'
230 | end
231 | end
232 |
233 | describe '.group_domain' do
234 | it 'returns the user portion of the group email' do
235 | Hitch.group_domain.should == 'hashrocket.com'
236 | end
237 | end
238 |
239 | describe '.git_author_name' do
240 | it 'returns all author names joined by an "and" sorted alphabetically' do
241 | Hitch.git_author_name.should == 'Philip J. Fry and Turanga Leela'
242 | end
243 | end
244 |
245 | describe '.git_author_email' do
246 | it 'returns all author github names coalesced into the group email' do
247 | Hitch.git_author_email.should == 'dev+fry+leela@hashrocket.com'
248 | end
249 | end
250 |
251 | describe ".write_file" do
252 | it "writes the contents of Hitch.config to the hitchrc file" do
253 | YAML.should_receive(:dump)
254 | Hitch.write_file
255 | end
256 | end
257 |
258 | end
259 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rspec'
2 | require 'fileutils'
3 | require 'tempfile'
4 |
5 | require File.join(File.dirname(__FILE__), '..', 'lib', 'hitch')
6 |
7 | RSpec.configure do |config|
8 | config.color_enabled = true
9 | config.formatter = 'progress'
10 | config.before(:each) do
11 | Hitch.stub(:hitchrc).and_return(Tempfile.new('hitchrc').path)
12 | Hitch.stub(:hitch_export_authors).and_return(Tempfile.new('hitch_export_authors').path)
13 | Hitch::Author.stub(:hitch_pairs).and_return(Tempfile.new('hitch_pairs').path)
14 | end
15 | end
16 |
--------------------------------------------------------------------------------