├── .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 | [![Build Status](https://travis-ci.org/therubymug/hitch.png?branch=master)](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 | --------------------------------------------------------------------------------