├── .gitignore ├── CHANGELOG ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.rdoc ├── Rakefile ├── bin ├── git-remote-branch └── grb ├── etc └── grb-completion.bash ├── git_remote_branch.gemspec ├── lib ├── constants.rb ├── git_remote_branch.rb ├── monkey_patches.rb ├── param_reader.rb ├── state.rb └── version.rb ├── tasks ├── gem.rake ├── rdoc.rake └── test.rake ├── test ├── functional │ └── grb_test.rb ├── helpers │ ├── array_extensions.rb │ ├── constants.rb │ ├── extractable.rb │ ├── git_helper.rb │ ├── in_dir.rb │ ├── more_assertions.rb │ ├── shoulda_functional_helpers.rb │ ├── shoulda_unit_helpers.rb │ └── temp_dir_helper.rb ├── test_helper.rb └── unit │ ├── git_helper_test.rb │ ├── git_remote_branch_test.rb │ ├── param_reader_test.rb │ └── state_test.rb └── vendor └── capture_fu.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .ruby-* 2 | .DS_Store 3 | pkg 4 | coverage 5 | test_runs 6 | test_runs/**/* 7 | rdoc 8 | *~ 9 | .bundle 10 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | * Release 0.3.0 * 2 | 3 | Housekeeping 4 | - Support for a bunch of platforms has been added (Windows, Ubuntu, Ruby 1.9) 5 | - git_remote_branch now has actual online documentation: grb.rubyforge.org 6 | - give useful error message if grb can't find the git executable 7 | 8 | Features 9 | - track now works even if you already have a local branch of the same name 10 | - it uses git config instead of branch --track in that case 11 | - the difference can be seen by running 12 | - grb explain track master 13 | - grb explain track non_existent_branch 14 | - Let you force the usage of a specific git executable by setting the environment variable GRB_GIT to point to it. It's pretty much undocumented for now, except for this mention :-) The tests for the useful error message use this feature. So it works. 15 | 16 | * Release 0.2.6 * 17 | Three new actual features 18 | - grb rename, contributed by Caio Chassot (caiochassot.com) 19 | - grb publish, to make available and track a local branch 20 | - the --silent option, if you want to use grb in a script 21 | 22 | And other stuff 23 | - the grb bin file now works when symlinked (also thanks to Caio Chassot) 24 | - Lots and lots of unit and functional tests 25 | - bug fixes 26 | - more flexibility for running grb outside of a git repository (e.g. for 'explain' or 'help') 27 | - Now officially under the MIT license 28 | - refactored a bunch of rake tasks 29 | 30 | 31 | Versions lost in time: 32 | 0.2.1, 0.2.2, 0.2.3, 0.2.4 33 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | git_remote_branch (0.3.8) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | activesupport (3.2.12) 10 | i18n (~> 0.6) 11 | multi_json (~> 1.0) 12 | bourne (1.1.2) 13 | mocha (= 0.10.5) 14 | coderay (1.0.9) 15 | i18n (0.6.1) 16 | json (1.7.7) 17 | metaclass (0.0.1) 18 | method_source (0.8.1) 19 | mocha (0.10.5) 20 | metaclass (~> 0.0.1) 21 | multi_json (1.6.1) 22 | pry (0.9.12) 23 | coderay (~> 1.0.5) 24 | method_source (~> 0.8) 25 | slop (~> 3.4) 26 | pry-nav (0.2.3) 27 | pry (~> 0.9.10) 28 | rake (10.0.3) 29 | rdoc (3.12.1) 30 | json (~> 1.4) 31 | shoulda (3.3.2) 32 | shoulda-context (~> 1.0.1) 33 | shoulda-matchers (~> 1.4.1) 34 | shoulda-context (1.0.2) 35 | shoulda-matchers (1.4.2) 36 | activesupport (>= 3.0.0) 37 | bourne (~> 1.1.2) 38 | slop (3.4.3) 39 | test-unit (2.5.4) 40 | 41 | PLATFORMS 42 | ruby 43 | 44 | DEPENDENCIES 45 | git_remote_branch! 46 | mocha (~> 0.10.5) 47 | pry (~> 0.9.12) 48 | pry-nav (~> 0.2.3) 49 | rake (~> 10.0.3) 50 | rdoc (~> 3.12.1) 51 | shoulda (~> 3.3.2) 52 | test-unit (~> 2.5.4) 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2008 Mathieu Martin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to 7 | deal in the Software without restriction, including without limitation the 8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Archived 2 | 3 | This repository is not maintained and this tool is no longer really necessary. It has therefore been archived. 4 | 5 | == Why not git_remote_branch? 6 | 7 | Because this gem is barely being maintained anymore. 8 | Also, the code is so old that I want to cry every time I look at it :-( 9 | 10 | But it still works, I guess... 11 | 12 | == Why git_remote_branch? 13 | 14 | git_remote_branch is a simple command-line tool that makes it very easy to manipulate 15 | branches published in shared repositories. 16 | 17 | It achieves this goal by sticking to a few principles: 18 | - keep grb's commands extremely regular (they all look alike) 19 | - support aliases for commands 20 | - print all commands it runs on your behalf in red, so you eventually learn them 21 | 22 | Another nice thing about git_remote_branch is that it can simply explain a command 23 | (print out all the corresponding git commands) instead of running them on your behalf. 24 | 25 | Note: git_remote_branch assumes that the local and remote branches have the 26 | same name. Multiple remote repositories (or origins) are supported. 27 | 28 | 29 | == Installation 30 | 31 | gem install git_remote_branch 32 | 33 | If you use RVM, you may want to automatically install it on all rubies 34 | you'll install in the future: 35 | 36 | echo git_remote_branch >> ~/.rvm/gemsets/global.gems 37 | 38 | If you're on Windows, you can optionally install the following gems, to get 39 | color output: 40 | 41 | gem install windows-pr win32console 42 | 43 | === Auto-completion 44 | 45 | If you use zsh with {oh-my-zsh}[https://github.com/robbyrussell/oh-my-zsh], 46 | you can have grb auto-completion by using the {git-remote-branch plugin}[https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins/git-remote-branch]. 47 | 48 | # ~/.zshrc 49 | plugins=(git git-remote-branch ...) 50 | 51 | If you use bash, you can have grb auto-completion by souring `etc/grb-completion.bash` 52 | 53 | # ~/.bash_profile (after your Ruby has been set, e.g. after the RVM snippet) 54 | 55 | grbc=`gem contents git_remote_branch | grep grb-completion.bash` && source $grbc 56 | 57 | or copy it to your desired destination and source it. 58 | 59 | == Usage 60 | 61 | Notes: 62 | - parts between brackets are optional 63 | - When 'origin_server' is not specified, the name 'origin' is assumed. 64 | 65 | Available commands (with aliases): 66 | 67 | 68 | === Help 69 | 70 | $ grb [-h|help] #=> Displays help 71 | 72 | ==== create (alias: new) 73 | 74 | Create a new local branch as well as a corresponding remote branch based on the 75 | branch you currently have checked out. 76 | Track the new remote branch. Checkout the new branch. 77 | 78 | $ grb create branch_name [origin_server] 79 | 80 | 81 | ==== publish (aliases: remotize, share) 82 | 83 | Publish an existing local branch to the remote server. 84 | Set up the local branch to track the new remote branch. 85 | 86 | $ grb publish branch_name [origin_server] 87 | 88 | 89 | ==== delete (aliases: destroy, kill, remove, rm) 90 | 91 | Delete the remote branch then delete the local branch. 92 | The local branch is not deleted if there are pending changes. 93 | 94 | $ grb delete branch_name [origin_server] 95 | 96 | 97 | ==== track (aliases: follow grab fetch) 98 | 99 | Track an existing remote branch locally and checkout the branch. 100 | 101 | $ grb track branch_name [origin_server] 102 | 103 | 104 | ==== rename (aliases: rn mv move) 105 | 106 | Rename a remote branch and its local tracking branch. 107 | The branch you want to rename must be checked out. 108 | 109 | # On branch to be renamed 110 | $ grb rename new_branch_name [origin_server] 111 | 112 | 113 | ==== explain 114 | 115 | All commands can be prepended by the word 'explain'. Instead of executing the 116 | command, git_remote_branch will simply output the list of commands you need to 117 | run to accomplish that goal. 118 | Examples: 119 | 120 | $ grb explain create 121 | git_remote_branch version 0.3.8 122 | 123 | List of operations to do to create a new remote branch and track it locally: 124 | 125 | git push origin master:refs/heads/branch_to_create 126 | git fetch origin 127 | git branch --track branch_to_create origin/branch_to_create 128 | git checkout branch_to_create 129 | 130 | Explain your specific case: 131 | 132 | $ grb explain create my_branch github 133 | git_remote_branch version 0.3.8 134 | 135 | List of operations to do to create a new remote branch and track it locally: 136 | 137 | git push github master:refs/heads/my_branch 138 | git fetch github 139 | git branch --track my_branch github/my_branch 140 | git checkout my_branch 141 | 142 | This, of course, works for each of the grb commands. 143 | 144 | == More on git_remote_branch 145 | 146 | - {Documentation}[http://rubydoc.info/gems/git_remote_branch/0.3.3/frames] 147 | - {Code}[https://github.com/webmat/git_remote_branch] 148 | - {Bugs}[https://github.com/webmat/git_remote_branch/issues] 149 | - News at {Mathieu Martin's blog}[http://programblings.com] 150 | 151 | 152 | === History 153 | 154 | git_remote_branch in its current form was inspired by a script created by Carl Mercier and made public on his blog: {No nonsense GIT, part 1: git-remote-branch}[http://blog.carlmercier.com/2008/01/25/no-nonsense-git-part-1-git-remote-branch/] 155 | 156 | 157 | === Contributors 158 | 159 | - {Mathieu Martin}[http://www.programblings.com] 160 | - {Ivan}[https://github.com/toy] 161 | - {Caio Chassot}[http://caiochassot.com] 162 | - {Axelson}[https://github.com/axelson] 163 | - {Carl Mercier}[https://github.com/cmer] 164 | 165 | 166 | === Legalese 167 | 168 | git_remote_branch is licensed under the MIT License. See the file LICENSE for details. 169 | 170 | ==== Supported platforms 171 | 172 | This version of git_remote_branch has been tested with 173 | 174 | - OS X Mountain Lion 175 | - past versions were known to work on Linux and Windows 176 | - Ruby 1.9.3 177 | - git 1.7+ 178 | 179 | Let me know if you encounter problems running git_remote_branch with your platform. 180 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | begin 3 | require 'bundler/setup' 4 | rescue Bundler::GemNotFound => e 5 | STDERR.puts e.message 6 | STDERR.puts "Try running `bundle install`." 7 | exit! 8 | end 9 | 10 | require 'rake' 11 | 12 | GRB_ROOT = File.dirname(__FILE__) 13 | 14 | #So we can use GitRemoteBranch::NAME, VERSION and so on. 15 | require "#{GRB_ROOT}/lib/git_remote_branch" 16 | 17 | SUDO = WINDOWS ? "" : "sudo" 18 | 19 | Dir['tasks/**/*.rake'].each { |rake| load rake } 20 | 21 | desc 'Default: run all tests.' 22 | task :default => :test 23 | 24 | task :clean => [:clobber_package, :clobber_rdoc] 25 | -------------------------------------------------------------------------------- /bin/git-remote-branch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | load File.join(File.dirname(__FILE__), 'grb') 4 | -------------------------------------------------------------------------------- /bin/grb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Check out the README (or try 'grb help') before you screw up your repos ;-) 4 | 5 | require 'rubygems' 6 | 7 | THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ 8 | require File.expand_path('../lib/git_remote_branch', File.dirname(THIS_FILE)) 9 | 10 | include GitRemoteBranch 11 | 12 | unless git_found? 13 | abort <<-MSG 14 | The git executable (#{GIT}) could not be found by git_remote_branch. 15 | Make sure your PATH variable contains the path for git's binary. 16 | Your PATH: 17 | #{ENV['PATH'].split(/[:;]/) * "\n"} 18 | MSG 19 | end 20 | 21 | begin 22 | p = read_params(ARGV) 23 | rescue InvalidBranchError => ex 24 | abort ex.message 25 | end 26 | 27 | $WHISPER = p[:silent] 28 | 29 | whisper get_welcome 30 | 31 | if p[:action] == :help 32 | whisper get_usage 33 | exit 0 34 | end 35 | 36 | if p[:explain] 37 | explain_action(p[:action], p[:branch], p[:origin], p[:current_branch]) 38 | else 39 | execute_action(p[:action], p[:branch], p[:origin], p[:current_branch]) 40 | end 41 | 42 | -------------------------------------------------------------------------------- /etc/grb-completion.bash: -------------------------------------------------------------------------------- 1 | _grb() 2 | { 3 | local cur=${COMP_WORDS[COMP_CWORD]} 4 | local verb=${COMP_WORDS[1]} 5 | 6 | local position=${COMP_CWORD} 7 | 8 | if [[ "$verb" == "explain" ]]; then 9 | let "position = $position - 1" 10 | fi 11 | 12 | case "$position" in 13 | 1) 14 | COMPREPLY=( $(compgen -W "help explain create new delete destroy kill remove rm rename rn mv move track follow grab fetch" -- $cur) ) 15 | ;; 16 | 2) 17 | 18 | COMPREPLY=( $(compgen -W "$(_grb_branch)" -- $cur)) 19 | ;; 20 | 3) 21 | 22 | COMPREPLY=( $(compgen -W "$(_grb_remotes)" -- $cur)) 23 | ;; 24 | esac 25 | } 26 | 27 | _grb_branch() 28 | { 29 | { 30 | git for-each-ref refs/remotes --format="%(refname:short)" | 31 | grep -v HEAD | 32 | sed 's/^.*\///g'; 33 | git for-each-ref refs/heads --format="%(refname:short)"; 34 | } | sort | uniq 35 | } 36 | 37 | _grb_remotes() 38 | { 39 | local i IFS=$'\n' 40 | for i in $(git config --get-regexp 'remote\..*\.url' 2>/dev/null); do 41 | i="${i#remote.}" 42 | echo "${i/.url*/}" 43 | done 44 | } 45 | 46 | complete -F _grb grb 47 | 48 | -------------------------------------------------------------------------------- /git_remote_branch.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require 'version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = GitRemoteBranch::NAME 7 | s.version = GitRemoteBranch::VERSION::STRING 8 | s.license = 'MIT' 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ["Mathieu Martin", "Carl Mercier"] 11 | s.email = "webmat@gmail.com" 12 | s.homepage = "http://github.com/webmat/git_remote_branch" 13 | s.summary = %Q{git_remote_branch eases the interaction with remote branches} 14 | s.description = %Q{git_remote_branch is a learning tool to ease the interaction with remote branches in simple situations.} 15 | 16 | s.files = `git ls-files`.split("\n") 17 | s.test_files = `git ls-files -- test/*`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = ["lib"] 20 | 21 | s.add_development_dependency "rake", "~> 10.0.3" 22 | s.add_development_dependency "rdoc", "~> 3.12.1" 23 | s.add_development_dependency 'test-unit', "~> 2.5.4" 24 | s.add_development_dependency 'shoulda', "~> 3.3.2" 25 | s.add_development_dependency 'mocha', "~> 0.10.5" 26 | s.add_development_dependency 'pry', "~> 0.9.12" 27 | s.add_development_dependency 'pry-nav', "~> 0.2.3" 28 | end 29 | -------------------------------------------------------------------------------- /lib/constants.rb: -------------------------------------------------------------------------------- 1 | module GitRemoteBranch 2 | GIT = (ENV['GRB_GIT'] || 'git') 3 | 4 | LOCAL_BRANCH_LISTING_COMMAND = "#{GIT} branch -l" 5 | end 6 | -------------------------------------------------------------------------------- /lib/git_remote_branch.rb: -------------------------------------------------------------------------------- 1 | begin 2 | WINDOWS = !!(RUBY_PLATFORM =~ /win32|cygwin/) 3 | rescue Exception 4 | end 5 | 6 | grb_app_root = File.expand_path( File.dirname(__FILE__) + '/..' ) 7 | 8 | $LOAD_PATH.unshift( grb_app_root + '/vendor' ) 9 | require 'capture_fu' 10 | 11 | $LOAD_PATH.unshift( grb_app_root + '/lib' ) 12 | %w(monkey_patches constants state param_reader version).each do |f| 13 | require f 14 | end 15 | 16 | module GitRemoteBranch 17 | class InvalidBranchError < RuntimeError; end 18 | class NotOnGitRepositoryError < RuntimeError; end 19 | 20 | COMMANDS = { 21 | :create => { 22 | :description => 'create a new remote branch and track it locally', 23 | :aliases => %w{create new}, 24 | :commands => [ 25 | '"#{GIT} push #{origin} #{current_branch}:refs/heads/#{branch_name}"', 26 | '"#{GIT} fetch #{origin}"', 27 | '"#{GIT} branch --track #{branch_name} #{origin}/#{branch_name}"', 28 | '"#{GIT} checkout #{branch_name}"' 29 | ] 30 | }, 31 | 32 | :publish => { 33 | :description => 'publish an existing local branch', 34 | :aliases => %w{publish remotize share}, 35 | :commands => [ 36 | '"#{GIT} push #{origin} #{branch_name}:refs/heads/#{branch_name}"', 37 | '"#{GIT} fetch #{origin}"', 38 | '"#{GIT} branch -u #{origin}/#{branch_name} #{branch_name}"', 39 | '"#{GIT} checkout #{branch_name}"' 40 | ] 41 | }, 42 | 43 | :rename => { 44 | :description => 'rename a remote branch and its local tracking branch', 45 | :aliases => %w{rename rn mv move}, 46 | :commands => [ 47 | '"#{GIT} push #{origin} #{current_branch}:refs/heads/#{branch_name}"', 48 | '"#{GIT} fetch #{origin}"', 49 | '"#{GIT} branch --track #{branch_name} #{origin}/#{branch_name}"', 50 | '"#{GIT} checkout #{branch_name}"', 51 | '"#{GIT} push #{origin} :refs/heads/#{current_branch}"', 52 | '"#{GIT} branch -d #{current_branch}"', 53 | ] 54 | }, 55 | 56 | :delete => { 57 | :description => 'delete a local and a remote branch', 58 | :aliases => %w{delete destroy kill remove rm}, 59 | :commands => [ 60 | '"#{GIT} push #{origin} :refs/heads/#{branch_name}"', 61 | '"#{GIT} checkout master" if current_branch == branch_name', 62 | '"#{GIT} branch -d #{branch_name}"' 63 | ] 64 | }, 65 | 66 | :track => { 67 | :description => 'track an existing remote branch', 68 | :aliases => %w{track follow grab fetch}, 69 | :commands => [ 70 | '"#{GIT} fetch #{origin}"', 71 | '"#{GIT} branch --track #{branch_name} #{origin}/#{branch_name}"', 72 | ] 73 | } 74 | } unless defined?(COMMANDS) 75 | 76 | def self.get_reverse_map(commands) 77 | h={} 78 | commands.each_pair do |cmd, params| 79 | params[:aliases].each do |alias_| 80 | unless h[alias_] 81 | h[alias_] = cmd 82 | else 83 | raise "Duplicate aliases: #{alias_.inspect} already defined for command #{h[alias_].inspect}" 84 | end 85 | end 86 | end 87 | h 88 | end 89 | ALIAS_REVERSE_MAP = get_reverse_map(COMMANDS) unless defined?(ALIAS_REVERSE_MAP) 90 | 91 | def get_welcome 92 | "git_remote_branch version #{VERSION::STRING}\n\n" 93 | end 94 | 95 | def get_usage 96 | return <<-HELP 97 | Usage: 98 | 99 | #{[:create, :publish, :rename, :delete, :track].map{|action| 100 | " grb #{action} branch_name [origin_server]" 101 | } * "\n" 102 | } 103 | 104 | Notes: 105 | - If origin_server is not specified, the name 'origin' is assumed (git's default) 106 | - The rename functionality renames the current branch 107 | 108 | The explain meta-command: you can also prepend any command with the keyword 'explain'. Instead of executing the command, git_remote_branch will simply output the list of commands you need to run to accomplish that goal. 109 | Example: 110 | grb explain create 111 | grb explain create my_branch github 112 | 113 | All commands also have aliases: 114 | #{ COMMANDS.keys.map{|k| k.to_s}.sort.map {|cmd| 115 | "#{cmd}: #{COMMANDS[cmd.to_sym][:aliases].join(', ')}" }.join("\n ") } 116 | HELP 117 | end 118 | 119 | def execute_action(action, branch_name, origin, current_branch) 120 | cmds = COMMANDS[action][:commands].map{ |c| eval(c) }.compact 121 | execute_cmds(cmds) 122 | end 123 | 124 | def explain_action(action, branch_name, origin, current_branch) 125 | cmds = COMMANDS[action][:commands].map{ |c| eval(c) }.compact 126 | 127 | whisper "List of operations to do to #{COMMANDS[action][:description]}:", '' 128 | puts_cmd cmds 129 | whisper '' 130 | end 131 | 132 | def execute_cmds(*cmds) 133 | silencer = $WHISPER ? ' 2>&1' : '' 134 | cmds.flatten.each do |c| 135 | puts_cmd c 136 | `#{c}#{silencer}` 137 | whisper '' 138 | end 139 | end 140 | 141 | def puts_cmd(*cmds) 142 | cmds.flatten.each do |c| 143 | whisper "#{c}".red 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /lib/monkey_patches.rb: -------------------------------------------------------------------------------- 1 | # One nice thing about command-line tools is that nobody cares how 2 | # much you monkey-patch :-) 3 | 4 | module Kernel 5 | def whisper(*msgs) 6 | unless $WHISPER 7 | puts *msgs 8 | end 9 | end 10 | end 11 | 12 | class String 13 | def red 14 | return self if WINDOWS 15 | "\e[31m#{self}\e[0m" 16 | end 17 | 18 | def path_for_os 19 | WINDOWS ? self.gsub('/', '\\') : self 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/param_reader.rb: -------------------------------------------------------------------------------- 1 | module GitRemoteBranch 2 | 3 | private 4 | HELP_PARAMS = {:action => :help} 5 | 6 | public 7 | def read_params(argv) 8 | #TODO Some validation on the params 9 | 10 | p={} 11 | p[:silent] = silent!(argv) 12 | p[:explain] = explain_mode!(argv) 13 | 14 | p[:action] = get_action(argv[0]) or return HELP_PARAMS 15 | 16 | return HELP_PARAMS if p[:action] == :help 17 | 18 | p[:branch] = get_branch(argv[1]) 19 | p[:origin] = get_origin(argv[2]) 20 | 21 | # If in explain mode, the user doesn't have to specify a branch or be on in 22 | # actual repo to get the explanation. 23 | # Of course if he is, the explanation will be made better by using contextual info. 24 | if p[:explain] 25 | p[:branch] ||= "branch_to_#{p[:action]}" 26 | p[:current_branch] = begin 27 | get_current_branch 28 | rescue NotOnGitRepositoryError, InvalidBranchError 29 | 'current_branch' 30 | end 31 | 32 | else 33 | return HELP_PARAMS unless p[:branch] 34 | p[:current_branch] = get_current_branch 35 | end 36 | return p 37 | end 38 | 39 | def explain_mode!(argv) 40 | if argv[0].to_s.downcase == 'explain' 41 | argv.shift 42 | true 43 | else 44 | false 45 | end 46 | end 47 | 48 | def silent!(argv) 49 | !!argv.delete('--silent') 50 | end 51 | 52 | def get_action(action) 53 | ALIAS_REVERSE_MAP[action.to_s.downcase] 54 | end 55 | 56 | def get_branch(branch) 57 | branch 58 | end 59 | 60 | def get_origin(origin) 61 | return origin || 'origin' 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/state.rb: -------------------------------------------------------------------------------- 1 | module GitRemoteBranch 2 | include ::CaptureFu 3 | 4 | public 5 | def get_current_branch 6 | local_branch_information[0] 7 | end 8 | 9 | def local_branches 10 | local_branch_information[1] 11 | end 12 | 13 | def git_found? 14 | ret, msg = capture_process_output "#{GIT} --version" 15 | user_version = msg.chomp.split[2] 16 | warn "Some grb commands will not work with git version < 1.7 (you have #{user_version})" unless user_version >= '1.7' 17 | ret == 0 18 | end 19 | 20 | private 21 | # Returns an array of 2 elements: [current_branch, [all local branches]] 22 | def local_branch_information 23 | #This is sensitive to checkouts of branches specified with wrong case 24 | 25 | listing = capture_process_output("#{LOCAL_BRANCH_LISTING_COMMAND}")[1] 26 | 27 | raise(NotOnGitRepositoryError, listing.chomp) if listing =~ /Not a git repository/i 28 | if listing =~ /\(no branch\)/ 29 | raise InvalidBranchError, ["Couldn't identify the current local branch. The branch listing was:", 30 | LOCAL_BRANCH_LISTING_COMMAND.red, listing].join("\n") 31 | end 32 | 33 | current_branch = nil 34 | branches = listing.split("\n").map do |line| 35 | current = line.include? '*' 36 | clean_line = line.gsub('*','').strip 37 | current_branch = clean_line if current 38 | clean_line 39 | end 40 | 41 | return current_branch, branches 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/version.rb: -------------------------------------------------------------------------------- 1 | module GitRemoteBranch 2 | module VERSION #:nodoc: 3 | MAJOR = 0 4 | MINOR = 3 5 | TINY = 8 6 | 7 | STRING = [MAJOR, MINOR, TINY].join('.') 8 | end 9 | 10 | NAME = 'git_remote_branch' 11 | COMPLETE_NAME = "#{NAME} #{VERSION::STRING}" 12 | COMMAND_NAME = 'grb' 13 | SHORT_NAME = COMMAND_NAME 14 | end 15 | -------------------------------------------------------------------------------- /tasks/gem.rake: -------------------------------------------------------------------------------- 1 | # Reminder when shipping a version 2 | # - Bump the version in lib/version.rb 3 | # - Commit & push everything 4 | # - rake gem gem:install # then sanity check that it works 5 | # - rake gem:feeling_lucky # uploads the gem, then tags the commit 6 | 7 | require 'yaml' 8 | 9 | gem_name = GitRemoteBranch::NAME 10 | version = GitRemoteBranch::VERSION::STRING 11 | gem_file = "#{gem_name}-#{version}.gem" 12 | 13 | tag_command = "git tag -m 'Tagging version #{version}' -a v#{version}" 14 | push_tags_command = 'git push --tags' 15 | 16 | task :tag_warn do 17 | puts <<-TAG 18 | #{"*" * 40} 19 | Don't forget to tag the release: 20 | 21 | #{tag_command} 22 | #{push_tags_command} 23 | or 24 | run rake tag tag:push 25 | 26 | #{"*" * 40} 27 | TAG 28 | end 29 | 30 | task :tag do 31 | sh tag_command 32 | puts "Upload tags to repo with '#{push_tags_command}'" 33 | end 34 | 35 | namespace :tag do 36 | task :push do 37 | sh push_tags_command 38 | end 39 | end 40 | 41 | desc "Build gem and put it in pkg/" 42 | task :gem => [:test, :tag_warn] do 43 | sh "gem build #{gem_name}.gemspec && mv #{gem_file} pkg/" 44 | end 45 | 46 | namespace :gem do 47 | desc 'Upload gem to rubygems.org' 48 | task :publish => :gem do 49 | sh "gem push pkg/#{gem_file}" 50 | end 51 | 52 | desc 'Install the last gem built locally' 53 | task :install do 54 | sh "gem install pkg/#{gem_file}" 55 | end 56 | 57 | desc "Uninstall version #{version} of the gem" 58 | task :uninstall do 59 | sh "gem uninstall -v #{version} -x #{gem_name}" 60 | end 61 | 62 | desc "Build and publish the gem, tag the commit and push the tags in one command" 63 | task :feeling_lucky => [:gem, :publish, :tag, 'tag:push'] 64 | end 65 | -------------------------------------------------------------------------------- /tasks/rdoc.rake: -------------------------------------------------------------------------------- 1 | require 'rdoc/task' 2 | 3 | desc 'Generate rdoc documentation' 4 | Rake::RDocTask.new(:rdoc) do |rdoc| 5 | rdoc.rdoc_dir = 'rdoc' 6 | rdoc.title = GitRemoteBranch::NAME 7 | rdoc.rdoc_files.include('README.rdoc') 8 | end 9 | 10 | namespace :rdoc do 11 | desc 'Upload documentation to rubyforge' 12 | task :upload => :rdoc do 13 | sh "scp -r #{GRB_ROOT}/rdoc/* webmat@rubyforge.org:/var/www/gforge-projects/grb/" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /tasks/test.rake: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | desc "Run all tests" 4 | task :test => ["test:unit", "test:functional"] 5 | 6 | namespace :test do 7 | desc "Run functional tests" 8 | Rake::TestTask.new(:functional) do |t| 9 | t.pattern = 'test/functional/**/*_test.rb' 10 | t.verbose = true 11 | end 12 | 13 | desc "Run unit tests" 14 | Rake::TestTask.new(:unit) do |t| 15 | t.pattern = 'test/unit/**/*_test.rb' 16 | t.verbose = true 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/functional/grb_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class GRBTest < Test::Unit::TestCase 4 | include ShouldaFunctionalHelpers 5 | 6 | on_a_repository do 7 | context "creating a branch in a local clone" do 8 | setup do 9 | in_directory_for :local1 10 | run_grb_with 'create new_branch' 11 | end 12 | 13 | should_have_branch 'new_branch', :local, :remote 14 | 15 | context "the remote repository" do 16 | setup do 17 | in_directory_for :remote 18 | end 19 | 20 | should_have_branch 'new_branch', :local 21 | end 22 | 23 | context "the other local clone" do 24 | setup do 25 | in_directory_for :local2 26 | end 27 | 28 | context "not already having a branch of the same name" do 29 | setup do 30 | @output = run_grb_with 'track new_branch' 31 | end 32 | 33 | should_have_branch 'new_branch', :local, :remote 34 | 35 | should "use the branch --track command" do 36 | assert_match %r{branch --track}, @output 37 | end 38 | end 39 | 40 | context "having a branch of the same name" do 41 | setup do 42 | execute "git branch new_branch" 43 | @output = run_grb_with 'track new_branch' 44 | end 45 | 46 | should_have_branch 'new_branch', :local, :remote 47 | 48 | should "use the branch --track command" do 49 | assert_match %r{branch --track}, @output 50 | end 51 | end 52 | end 53 | 54 | context "then deleting the branch" do 55 | setup do 56 | run_grb_with 'delete new_branch' 57 | end 58 | 59 | should_not_have_branch 'new_branch', :local, :remote 60 | 61 | context "the remote repository" do 62 | setup do 63 | in_directory_for :remote 64 | end 65 | 66 | should_not_have_branch 'new_branch', :local 67 | end 68 | end 69 | 70 | context "renaming the branch" do 71 | setup do 72 | in_directory_for :local1 73 | in_branch :new_branch 74 | run_grb_with 'rename renamed_branch' 75 | end 76 | 77 | should_not_have_branch 'new_branch', :local, :remote 78 | should_have_branch 'renamed_branch', :local, :remote 79 | 80 | context "the remote repository" do 81 | setup do 82 | in_directory_for :remote 83 | end 84 | 85 | should_not_have_branch 'new_branch', :local 86 | should_have_branch 'renamed_branch', :local 87 | end 88 | end 89 | end 90 | 91 | context "having a local only branch" do 92 | setup do 93 | in_directory_for :local1 94 | execute "git branch my_branch" 95 | end 96 | 97 | should_have_branch 'my_branch', :local #Sanity check 98 | 99 | context "remotizing the branch" do 100 | setup do 101 | run_grb_with 'publish my_branch' 102 | end 103 | 104 | should_have_branch 'my_branch', :remote 105 | 106 | context "the remote repository" do 107 | setup do 108 | in_directory_for :remote 109 | end 110 | 111 | should_have_branch 'my_branch', :local 112 | end 113 | end 114 | end 115 | 116 | context "running grb with a detailed explain" do 117 | setup do 118 | in_directory_for :local1 119 | @text = run_grb_with 'explain create teh_branch somewhere' 120 | end 121 | 122 | should "display the commands to run with the user-specified values, including current_branch" do 123 | %w{master somewhere refs/heads/teh_branch}.each do |word| 124 | assert_match(/#{word}/, @text) 125 | end 126 | end 127 | end 128 | end 129 | 130 | in_a_non_git_directory do 131 | with_env_var :GRB_GIT, 'unknown_git_executable_name' do 132 | context "when git is not in the path" do 133 | setup do 134 | @text = run_grb_with '' 135 | end 136 | should "complain about git not being in the path" do 137 | assert_match %r{unknown_git_executable_name}, @text 138 | assert_match %r{PATH}, @text 139 | end 140 | end 141 | end 142 | 143 | context "displaying help" do 144 | setup do 145 | @text = run_grb_with 'help' 146 | end 147 | 148 | should "work" do 149 | words_in_help = %w{create delete explain git_remote_branch} 150 | words_in_help.each do |word| 151 | assert_match(/#{word}/, @text) 152 | end 153 | end 154 | 155 | should "not complain" do 156 | assert_no_match(/not a git repository/i, @text) 157 | end 158 | end 159 | 160 | context "running grb with a generic explain" do 161 | setup do 162 | @text = run_grb_with 'explain create' 163 | end 164 | 165 | should "display the commands to run with dummy values filled in" do 166 | #Not sure if this will turn out to be too precise to my liking... 167 | generic_words_in_explain_create = %w{ 168 | origin current_branch refs/heads/branch_to_create 169 | git push fetch checkout} 170 | generic_words_in_explain_create.each do |word| 171 | assert_match(/#{word}/, @text) 172 | end 173 | end 174 | end 175 | 176 | context "running grb with a detailed explain" do 177 | setup do 178 | @text = run_grb_with 'explain create teh_branch somewhere' 179 | end 180 | 181 | should "display the commands to run with the user-specified values (except for current_branch)" do 182 | %w{somewhere current_branch refs/heads/teh_branch}.each do |word| 183 | assert_match(/#{word}/, @text) 184 | end 185 | end 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /test/helpers/array_extensions.rb: -------------------------------------------------------------------------------- 1 | # Can be included in any class that responds to #each. 2 | # Such as Array. 3 | module CountDistinct 4 | def count_all(purge_smaller_than=0) 5 | h={} 6 | self.each {|e| 7 | h[e] ? h[e] += 1 : h[e] = 1 8 | } 9 | h.extract{|k,v| v >= purge_smaller_than} 10 | end 11 | end 12 | 13 | Array.send :include, CountDistinct 14 | 15 | -------------------------------------------------------------------------------- /test/helpers/constants.rb: -------------------------------------------------------------------------------- 1 | REGULAR_BRANCH_LISTING = <<-STR 2 | other_user/master 3 | * stubbed_current_branch 4 | rubyforge 5 | STR 6 | 7 | BRANCH_LISTING_WHEN_NOT_ON_BRANCH = <<-STR 8 | * (no branch) 9 | other_user/master 10 | master 11 | rubyforge 12 | STR 13 | 14 | WHEN_NOT_ON_GIT_REPOSITORY = "fatal: Not a git repository\n" 15 | 16 | -------------------------------------------------------------------------------- /test/helpers/extractable.rb: -------------------------------------------------------------------------------- 1 | module Extractable 2 | # Hash#extract(*keys) => Hash 3 | # Hash#extract([keys]) => Hash 4 | # Hash#extract{|k,v| predicate } => Hash 5 | # 6 | # Returns a new Hash that contains only the k,v pairs where the k was 7 | # specified in the keys array. 8 | # If any k in keys is not present in the original Hash, it's simply 9 | # not the resulting Hash. 10 | # 11 | # This is very useful to check that a Hash contains at least some desired keys 12 | # or to get a sanitized Hash out of the one we currently have. 13 | # 14 | # Examples: 15 | # h = {:bob=>'Marley',:mom=>'Barley'} 16 | # h.extract(:bob) #=> {:bob=>'Marley'} 17 | # h.extract(:bob, :mom) #=> {:bob=>'Marley',:mom=>'Barley'} 18 | # h.extract([:bob, :mom]) #=> {:bob=>'Marley',:mom=>'Barley'} 19 | # h.extract(:sos) #=> {} 20 | 21 | def extract(*args, &block) 22 | if block_given? 23 | extract_block(&block) 24 | elsif args[0].is_a? Proc 25 | extract_block(&args[0]) 26 | elsif args.size == 0 27 | raise ArgumentError, "extract requires either an array of keys, a block or a proc" 28 | else 29 | extract_keys(args) 30 | end 31 | end 32 | 33 | # Returns two hashes. The first contains all pairs for which the block evaluated to true, 34 | # the second contains all the others. 35 | def split(&block) 36 | trues, falses = self.class.new, self.class.new 37 | each_pair do |k,v| 38 | if yield(k,v) 39 | trues[k] = v 40 | else 41 | falses[k] = v 42 | end 43 | end 44 | #each_pair{ |k,v| (yield(k,v) ? trues : falses)[k] = v } 45 | return trues, falses 46 | end 47 | 48 | private 49 | def extract_keys(*keys) 50 | extracted = self.class.new #Can therefore be included in any hash-like container 51 | keys.flatten.each { |k| extracted[k] = self[k] if self.include?(k) } 52 | extracted 53 | end 54 | 55 | def extract_block(&block) 56 | extracted = self.class.new 57 | each_pair{ |k,v| extracted[k] = v if yield(k,v) } 58 | extracted 59 | end 60 | end 61 | 62 | 63 | Hash.send :include, Extractable 64 | -------------------------------------------------------------------------------- /test/helpers/git_helper.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/in_dir' 2 | require File.dirname(__FILE__) + '/temp_dir_helper' 3 | 4 | # Instantiating a GitHelper object creates a temp directory containing 3 repos. 5 | # 1 that's considered the remote repo and 2 peer local repos (local1 and local2). 6 | # All 3 are synchronized with the same data (they contain a few dummy files). 7 | # Once instantiated you can access the 3 full repo locations through attribute readers 8 | # remote, local1 and local2. 9 | class GitHelper < TempDirHelper 10 | include InDir 11 | GIT = GitRemoteBranch::GIT 12 | 13 | attr_reader :remote, :local1, :local2 14 | 15 | def initialize 16 | super("#{TEST_DIR}/test_runs") 17 | @remote = init_repo(directory, 'remote') 18 | @local1 = clone_repo(@remote, directory, 'local1') 19 | @local2 = clone_repo(@remote, directory, 'local2') 20 | end 21 | 22 | protected 23 | def init_repo(path, name) 24 | repo_dir = File.join(path, name) 25 | mkdir_p repo_dir 26 | 27 | in_dir repo_dir do 28 | `#{GIT} init && echo "foo" > file.txt && #{GIT} add . && #{GIT} commit -a -m "dummy file"` 29 | end 30 | raise "Error setting up repository #{name}" unless $?.exitstatus == 0 31 | repo_dir 32 | end 33 | 34 | def clone_repo(origin_path, clone_path, name) 35 | in_dir clone_path do 36 | `#{GIT} clone #{File.join(origin_path, '.git').path_for_os} #{name}` 37 | end 38 | return File.join(clone_path, name) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/helpers/in_dir.rb: -------------------------------------------------------------------------------- 1 | module InDir 2 | def in_dir(dir, &block) 3 | prev_dir = Dir.pwd 4 | Dir.chdir dir 5 | 6 | yield 7 | ensure 8 | Dir.chdir prev_dir 9 | end 10 | end -------------------------------------------------------------------------------- /test/helpers/more_assertions.rb: -------------------------------------------------------------------------------- 1 | #require 'test/unit/assertionfailederror' 2 | module MoreAssertions 3 | include Test::Unit 4 | 5 | def assert_false(condition, message = nil) 6 | unless condition == false 7 | raise AssertionFailedError, message || "assert_false failed" 8 | end 9 | end 10 | 11 | def assert_array_content(expected_array, array, message = nil) 12 | unless expected_array.count_all == array.count_all 13 | raise AssertionFailedError, message || "arrays did not have the same content. Expected #{expected_array.inspect}, got #{array.inspect}" 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/helpers/shoulda_functional_helpers.rb: -------------------------------------------------------------------------------- 1 | module ShouldaFunctionalHelpers 2 | include CaptureFu 3 | include InDir 4 | 5 | GIT = GitRemoteBranch::GIT 6 | 7 | def self.ruby_prefix 8 | if ENV['RUBY'] 9 | warn " Forcing execution of grb with ruby interpreter #{ENV['RUBY']}" 10 | ENV['RUBY'] + ' ' 11 | elsif WINDOWS 12 | 'ruby ' 13 | else 14 | '' 15 | end 16 | end 17 | 18 | # Here we're only prepending with 'ruby'. 19 | # When run as a gem, RubyGems takes care of generating a batch file that does this stuff. 20 | GRB_COMMAND = ruby_prefix + File.expand_path(File.dirname(__FILE__) + '/../../bin/grb') unless defined?(GRB_COMMAND) 21 | 22 | def self.included(base) 23 | base.extend ClassMethods 24 | base.class_eval do 25 | include ::ShouldaFunctionalHelpers::InstanceMethods 26 | end 27 | end 28 | 29 | module InstanceMethods 30 | def current_dir 31 | @current_dir || raise("@current_dir is not set. Warning, Will Robinson!") 32 | end 33 | 34 | def current_dir=(value) 35 | @current_dir = value 36 | end 37 | 38 | # Switches to one of the directories created by GitHelper: 39 | # :local1, :local2, :non_git or :remote 40 | # This affects commands run with ``, system and so on. 41 | def in_directory_for(dir) 42 | # Just a reminder for my dumb head 43 | raise "'in_directory_for' depends on @gh being set" unless @gh 44 | 45 | @current_dir = eval("@gh.#{dir}") 46 | end 47 | 48 | def in_branch(branch) 49 | execute "#{GIT} checkout #{branch}" 50 | end 51 | 52 | 53 | def run_grb_with(params='') 54 | execute "#{GRB_COMMAND} #{params}" 55 | end 56 | 57 | def execute(command) 58 | in_dir current_dir do 59 | errno, returned_string = capture_process_output(command) 60 | returned_string 61 | end 62 | end 63 | 64 | private 65 | def get_branch_location(location) 66 | case location.to_sym 67 | when :local 68 | args = '-l' 69 | when :remote 70 | args = '-r' 71 | else 72 | raise ArgumentError, "Unknown branch location: #{location.inspect}" 73 | end 74 | end 75 | end 76 | 77 | module ClassMethods 78 | def should_have_branch(what_branch, *wheres) 79 | wheres.flatten.each do |where| 80 | should "have the branch '#{what_branch}' #{where == :local ? 'locally' : 'remotely'}" do 81 | args = get_branch_location(where) 82 | assert_match(/#{what_branch}/, execute("#{GIT} branch #{args}")) 83 | end 84 | end 85 | end 86 | 87 | def should_not_have_branch(what_branch, *wheres) 88 | wheres.flatten.each do |where| 89 | should "not have the branch '#{what_branch}' #{where == :local ? 'locally' : 'remotely'}" do 90 | args = get_branch_location(where) 91 | assert_no_match(/#{what_branch}/, execute("#{GIT} branch #{args}")) 92 | end 93 | end 94 | end 95 | 96 | def on_a_repository 97 | context "on a new repository" do 98 | setup do 99 | @gh = GitHelper.new 100 | end 101 | 102 | teardown do 103 | @gh.cleanup 104 | end 105 | 106 | context '' do 107 | yield 108 | end 109 | end 110 | end 111 | 112 | def in_a_non_git_directory 113 | context "on a non-git related directory" do 114 | setup do 115 | @temp_dir = TempDirHelper.new 116 | @current_dir = @temp_dir.directory 117 | end 118 | 119 | teardown do 120 | @temp_dir.cleanup 121 | end 122 | 123 | context '' do 124 | yield 125 | end 126 | end 127 | end 128 | 129 | def with_env_var(name, value) 130 | name = name.to_s 131 | 132 | context "with environment variable '#{name}' set to '#{value}'" do 133 | setup do 134 | @env_previous_value = ENV[name] if ENV.keys.include?(name) 135 | ENV[name] = value 136 | end 137 | 138 | teardown do 139 | if @env_previous_value 140 | ENV[name] = @env_previous_value 141 | else 142 | ENV.delete(name) 143 | end 144 | end 145 | 146 | context '' do 147 | yield 148 | end 149 | end 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /test/helpers/shoulda_unit_helpers.rb: -------------------------------------------------------------------------------- 1 | module ShouldaUnitHelpers 2 | def self.included(base) 3 | base.extend ClassMethods 4 | add_param_checkers(base) 5 | end 6 | 7 | def self.add_param_checkers(base) 8 | # Excuse my french but: 9 | %w(action branch origin current_branch silent explain).each do |param| 10 | base.instance_eval(%Q! 11 | def self.should_set_#{param}_to(#{param}_value) 12 | should "set #{param} to #{ '#{' }#{ param }_value}" do 13 | assert_equal #{param}_value, @p[:#{param}] 14 | end 15 | end 16 | !) 17 | end 18 | # In other words, create a bunch of helpers like: 19 | # 20 | # def self.should_set_explain_to(explain_value) 21 | # should "set explain to #{explain_value}" do 22 | # assert_equal explain_value, @p[:explain] 23 | # end 24 | # end 25 | 26 | end 27 | 28 | module ClassMethods 29 | def should_return_help_for_parameters(params, context_explanation) 30 | context context_explanation do 31 | setup do 32 | @p = grb.read_params params 33 | end 34 | 35 | should "not even get to checking the current_branch" do 36 | grb.expects(:get_current_branch).never 37 | grb.read_params ['help'] 38 | end 39 | 40 | should "only return a hash specifying the action" do 41 | assert_array_content [:action], @p.keys 42 | end 43 | 44 | should_set_action_to :help 45 | end 46 | end 47 | 48 | def should_explain_with_current_branch(current_branch_value, current_branch_explanation) 49 | context "on an 'explain' command" do 50 | context "with no information provided other than the action" do 51 | setup do 52 | @p = grb.read_params %w{explain create} 53 | end 54 | 55 | should_set_explain_to true 56 | should_set_action_to :create 57 | should_set_origin_to 'origin' 58 | 59 | context current_branch_explanation do 60 | should_set_current_branch_to current_branch_value 61 | end 62 | 63 | should "set a dummy new branch name" do 64 | assert @p[:branch] 65 | end 66 | end 67 | 68 | context "with all information provided" do 69 | setup do 70 | @p = grb.read_params %w{explain create specific_branch specific_origin} 71 | end 72 | 73 | should_set_explain_to true 74 | should_set_action_to :create 75 | should_set_current_branch_to current_branch_value 76 | 77 | should "set the origin to 'specific_origin'" do 78 | assert_equal 'specific_origin', @p[:origin] 79 | end 80 | 81 | should "set the specified branch name" do 82 | assert_equal 'specific_branch', @p[:branch] 83 | end 84 | end 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/helpers/temp_dir_helper.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'tmpdir' 3 | 4 | class TempDirHelper 5 | include FileUtils 6 | 7 | attr_reader :directory 8 | 9 | def initialize(force_temp_dir=nil) 10 | @directory = get_temp_dir!(force_temp_dir) 11 | end 12 | 13 | def cleanup 14 | rm_rf @directory 15 | end 16 | 17 | def to_s 18 | directory 19 | end 20 | 21 | private 22 | def get_temp_dir!(parent_dir=nil) 23 | temp_root = File.expand_path( File.join( parent_dir || Dir::tmpdir) ) 24 | mkdir_p temp_root 25 | 26 | #Create new subdir with a random name 27 | new_dir='' 28 | begin 29 | new_dir = File.join( temp_root, "#{rand(10000)}" ) 30 | mkdir new_dir 31 | 32 | rescue 33 | retry 34 | end 35 | 36 | new_dir 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | begin 3 | require 'bundler/setup' 4 | rescue Bundler::GemNotFound => e 5 | STDERR.puts e.message 6 | STDERR.puts "Try running `bundle install`." 7 | exit! 8 | end 9 | require 'test/unit' 10 | 11 | require 'shoulda' 12 | require 'mocha' 13 | require 'pry-nav' 14 | 15 | TEST_DIR = File.dirname(__FILE__) 16 | 17 | require File.join( [TEST_DIR] + %w{ .. lib git_remote_branch} ) 18 | 19 | require "#{TEST_DIR}/helpers/in_dir" 20 | Dir[TEST_DIR+'/helpers/**/*.rb'].each{|f| require f} 21 | 22 | class Test::Unit::TestCase 23 | include MoreAssertions 24 | 25 | attr_reader :grb 26 | def setup 27 | @grb = Object.new 28 | @grb.send :extend, GitRemoteBranch 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/unit/git_helper_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class GitHelperTest < Test::Unit::TestCase 4 | 5 | def setup 6 | super 7 | @g = GitHelper.new 8 | @directories = [@g.remote, @g.local1, @g.local2] 9 | end 10 | 11 | def teardown 12 | @g.cleanup 13 | end 14 | 15 | def test_init 16 | @directories.each do |d| 17 | assert File.exists?(@g.remote), 'Directory for repo must be created' 18 | assert File.exists?( File.join(@g.remote, '.git') ), 'Repo must be created' 19 | end 20 | end 21 | 22 | def test_cleanup 23 | @g.cleanup 24 | 25 | @directories.each do |d| 26 | assert_false File.exists?(@g.remote), 'Each repo directory must be destroyed after cleanup' 27 | end 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /test/unit/git_remote_branch_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class GitRemoteBranchTest < Test::Unit::TestCase 4 | context 'help' do 5 | should 'contain examples for all basic commands' do 6 | GitRemoteBranch::COMMANDS.keys.each do |k| 7 | assert_match "grb #{k} branch_name", grb.get_usage 8 | end 9 | end 10 | 11 | should 'contain an example for explain' do 12 | assert_match 'grb explain', grb.get_usage 13 | end 14 | 15 | should 'contain an enumeration of all aliases' do 16 | GitRemoteBranch::COMMANDS.each_pair do |k,v| 17 | assert_match "#{k}: #{v[:aliases].join(', ')}", grb.get_usage 18 | end 19 | end 20 | end 21 | 22 | context "the reverse mapping for aliases" do 23 | GitRemoteBranch::COMMANDS.each_pair do |cmd, params| 24 | params[:aliases].each do |alias_| 25 | should "contain the alias #{alias_}" do 26 | assert GitRemoteBranch::ALIAS_REVERSE_MAP[alias_] 27 | end 28 | end 29 | end 30 | 31 | context "upon creation" do 32 | should "raise an exception when there are duplicates" do 33 | assert_raise(RuntimeError) do 34 | GitRemoteBranch.get_reverse_map( GitRemoteBranch::COMMANDS.merge(:new_command => {:aliases => ['create']}) ) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/unit/param_reader_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require "#{TEST_DIR}/helpers/constants" 3 | 4 | class ParamReaderTest < Test::Unit::TestCase 5 | include ShouldaUnitHelpers 6 | 7 | context 'read_params' do 8 | context "when on a valid branch" do 9 | setup do 10 | grb.stubs(:capture_process_output).returns([0, REGULAR_BRANCH_LISTING]) 11 | end 12 | 13 | context "on a normal valid command without an origin" do 14 | setup do 15 | @p = grb.read_params %w{create the_branch} 16 | end 17 | 18 | should_set_action_to :create 19 | should_set_branch_to 'the_branch' 20 | should_set_origin_to 'origin' 21 | should_set_current_branch_to 'stubbed_current_branch' 22 | should_set_explain_to false 23 | should_set_silent_to false 24 | end 25 | 26 | context "on a normal valid command" do 27 | setup do 28 | @p = grb.read_params %w{create the_branch the_origin} 29 | end 30 | 31 | should_set_action_to :create 32 | should_set_branch_to 'the_branch' 33 | should_set_origin_to 'the_origin' 34 | should_set_current_branch_to 'stubbed_current_branch' 35 | should_set_explain_to false 36 | should_set_silent_to false 37 | end 38 | 39 | should_explain_with_current_branch 'stubbed_current_branch', "use real current branch" 40 | 41 | should_return_help_for_parameters %w(help), "on a 'help' command" 42 | should_return_help_for_parameters %w(create), "on an incomplete command" 43 | should_return_help_for_parameters %w(decombobulate something), "on an invalid command" 44 | 45 | context "understands the --silent parameter" do 46 | context "at the beginning" do 47 | setup do 48 | @p = grb.read_params %w{--silent create some_branch some_origin} 49 | end 50 | should_set_silent_to true 51 | should_set_action_to :create 52 | should_set_branch_to 'some_branch' 53 | should_set_origin_to 'some_origin' 54 | end 55 | 56 | context "at the end" do 57 | setup do 58 | @p = grb.read_params %w{create some_branch some_origin --silent} 59 | end 60 | should_set_silent_to true 61 | should_set_action_to :create 62 | should_set_branch_to 'some_branch' 63 | should_set_origin_to 'some_origin' 64 | end 65 | 66 | context "in the freakin' middle" do 67 | setup do 68 | @p = grb.read_params %w{create --silent some_branch some_origin} 69 | end 70 | should_set_silent_to true 71 | should_set_action_to :create 72 | should_set_branch_to 'some_branch' 73 | should_set_origin_to 'some_origin' 74 | end 75 | end 76 | end 77 | 78 | context "when on an invalid branch" do 79 | setup do 80 | grb.stubs(:capture_process_output).returns([0, BRANCH_LISTING_WHEN_NOT_ON_BRANCH]) 81 | end 82 | 83 | GitRemoteBranch::COMMANDS.each_key do |action| 84 | context "running the '#{action}' command" do 85 | setup do 86 | @command = [action.to_s, 'branch_name'] 87 | end 88 | 89 | context "raising an exception" do 90 | setup do 91 | begin 92 | grb.read_params(@command) 93 | rescue => @ex 94 | end 95 | end 96 | 97 | should "raise an InvalidBranchError" do 98 | assert_kind_of GitRemoteBranch::InvalidBranchError, @ex 99 | end 100 | 101 | should "give a clear error message" do 102 | assert_match(/identify.*branch/, @ex.message) 103 | end 104 | 105 | should "display git's branch listing" do 106 | assert_match(/\(no branch\)/, @ex.message) 107 | end 108 | end 109 | end 110 | end 111 | 112 | should_explain_with_current_branch 'current_branch', "use a dummy value for the current branch" 113 | 114 | should_return_help_for_parameters %w(help), "on a 'help' command" 115 | should_return_help_for_parameters %w(create), "on an incomplete command" 116 | should_return_help_for_parameters %w(decombobulate something), "on an invalid command" 117 | end 118 | 119 | context "when not on a git repository" do 120 | setup do 121 | grb.stubs(:capture_process_output).returns([128, WHEN_NOT_ON_GIT_REPOSITORY]) 122 | end 123 | 124 | should_explain_with_current_branch 'current_branch', "use a dummy value for the current branch" 125 | 126 | should_return_help_for_parameters %w(help), "on a 'help' command" 127 | should_return_help_for_parameters %w(create), "on an incomplete command" 128 | should_return_help_for_parameters %w(decombobulate something), "on an invalid command" 129 | 130 | GitRemoteBranch::COMMANDS.each_key do |action| 131 | context "running the '#{action}' command" do 132 | setup do 133 | @command = [action.to_s, 'branch_name'] 134 | end 135 | 136 | context "raising an exception" do 137 | setup do 138 | begin 139 | grb.read_params(@command) 140 | rescue => @ex 141 | end 142 | end 143 | 144 | should "raise an NotOnGitRepositoryError" do 145 | assert_kind_of GitRemoteBranch::NotOnGitRepositoryError, @ex 146 | end 147 | 148 | should "give a clear error message" do 149 | assert_match(/fatal/, @ex.message) 150 | end 151 | end 152 | end 153 | end 154 | end 155 | end 156 | 157 | context 'explain_mode!' do 158 | context "when it receives an array beginning with 'explain'" do 159 | setup do 160 | @array = ['explain', 'create', 'some_branch'] 161 | end 162 | 163 | should "return true" do 164 | assert grb.explain_mode!(@array) 165 | end 166 | 167 | should 'accept symbol arrays as well' do 168 | assert grb.explain_mode!( @array.map{|e| e.to_sym} ) 169 | end 170 | 171 | should "remove 'explain' from the argument array" do 172 | grb.explain_mode!(@array) 173 | assert_equal ['create', 'some_branch'], @array 174 | end 175 | end 176 | 177 | context "when it receives an array that doesn't begin with 'explain'" do 178 | setup do 179 | @array = ['create', 'some_branch'] 180 | end 181 | 182 | should "return false" do 183 | assert_false grb.explain_mode!(@array) 184 | end 185 | 186 | should "not modify the argument array" do 187 | grb.explain_mode!(@array) 188 | assert_equal ['create', 'some_branch'], @array 189 | end 190 | end 191 | end 192 | 193 | context 'get_action' do 194 | GitRemoteBranch::COMMANDS.each_pair do |cmd, params| 195 | should "recognize all #{cmd} aliases" do 196 | params[:aliases].each do |alias_| 197 | assert cmd, grb.get_action(alias_).to_s 198 | end 199 | end 200 | end 201 | should 'return nil on unknown aliases' do 202 | assert_nil grb.get_action('please_dont_create_an_alias_named_like_this') 203 | end 204 | end 205 | 206 | context 'get_origin' do 207 | should "default to 'origin' when the param is nil" do 208 | assert_equal 'origin', grb.get_origin(nil) 209 | end 210 | should "return the unchanged param if it's not nil" do 211 | assert_equal 'someword', grb.get_origin('someword') 212 | end 213 | end 214 | 215 | end 216 | -------------------------------------------------------------------------------- /test/unit/state_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | require "#{TEST_DIR}/helpers/constants" 3 | 4 | class ParamReaderTest < Test::Unit::TestCase 5 | include ShouldaUnitHelpers 6 | 7 | def self.craps_out_in_invalid_situations 8 | context "when not on a git repository" do 9 | setup do 10 | grb.stubs(:capture_process_output).returns([128, WHEN_NOT_ON_GIT_REPOSITORY]) 11 | end 12 | 13 | should "raise an exception" do 14 | assert_raise(GitRemoteBranch::NotOnGitRepositoryError) { grb.get_current_branch } 15 | end 16 | end 17 | 18 | context "when on an invalid branch" do 19 | setup do 20 | grb.stubs(:capture_process_output).returns([0, BRANCH_LISTING_WHEN_NOT_ON_BRANCH]) 21 | end 22 | 23 | should "raise an exception" do 24 | assert_raise(GitRemoteBranch::InvalidBranchError) { grb.get_current_branch } 25 | end 26 | end 27 | end 28 | 29 | context 'get_current_branch' do 30 | craps_out_in_invalid_situations 31 | 32 | context "when on a valid branch" do 33 | setup do 34 | grb.stubs(:capture_process_output).returns([0, REGULAR_BRANCH_LISTING]) 35 | end 36 | 37 | should "return the current branch name" do 38 | assert_equal 'stubbed_current_branch', grb.get_current_branch 39 | end 40 | end 41 | end 42 | 43 | context 'local_branches' do 44 | craps_out_in_invalid_situations 45 | 46 | context "when on a valid branch" do 47 | setup do 48 | grb.stubs(:capture_process_output).returns([0, REGULAR_BRANCH_LISTING]) 49 | end 50 | 51 | should "return all the local branch names" do 52 | assert_array_content %w{stubbed_current_branch other_user/master rubyforge}, grb.local_branches 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /vendor/capture_fu.rb: -------------------------------------------------------------------------------- 1 | module CaptureFu 2 | VERSION = '0.0.1' 3 | 4 | def capture_output(&block) 5 | real_out, real_err = $stdout, $stderr 6 | result = fake_out = fake_err = nil 7 | begin 8 | fake_out, fake_err = Helpers::PipeStealer.new, Helpers::PipeStealer.new 9 | $stdout, $stderr = fake_out, fake_err 10 | result = yield 11 | ensure 12 | $stdout, $stderr = real_out, real_err 13 | end 14 | return result, fake_out.captured, fake_err.captured 15 | end 16 | 17 | # This first implementation is only intended for batch executions. 18 | # You can't pipe stuff programmatically to the child process. 19 | def capture_process_output(command) 20 | 21 | #capture stderr in the same stream 22 | command << ' 2>&1' unless Helpers.stderr_already_redirected(command) 23 | 24 | out = `#{command}` 25 | return $?.exitstatus, out 26 | end 27 | 28 | 29 | private 30 | 31 | module Helpers 32 | 33 | def self.stderr_already_redirected(command) 34 | #Already redirected to stdout (valid for Windows) 35 | return true if command =~ /2>&1\s*\Z/ 36 | 37 | #Redirected to /dev/null (this is clearly POSIX-dependent) 38 | return true if command =~ /2>\/dev\/null\s*\Z/ 39 | 40 | return false 41 | end 42 | 43 | class PipeStealer < File 44 | attr_reader :captured 45 | def initialize 46 | @captured = '' 47 | end 48 | def write(s) 49 | @captured << s 50 | end 51 | def captured 52 | return nil if @captured.empty? 53 | @captured.dup 54 | end 55 | end 56 | 57 | end #Helper module 58 | end 59 | --------------------------------------------------------------------------------