├── Gemfile ├── lib ├── git-up │ └── version.rb └── git-up.rb ├── docker-compose.yml ├── .document ├── bin └── git-up ├── .gitignore ├── Dockerfile ├── Rakefile ├── git-up.gemspec ├── LICENSE ├── RVM.md ├── README.md └── man └── git-up.1 /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /lib/git-up/version.rb: -------------------------------------------------------------------------------- 1 | class GitUp 2 | VERSION = "0.5.12" 3 | end 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | rake: 2 | build: . 3 | volumes: 4 | - .:/code 5 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /bin/git-up: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'git-up' 4 | 5 | GitUp.new.run(ARGV) 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | Gemfile.lock 21 | 22 | ## PROJECT::SPECIFIC 23 | /html 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.1.3 2 | 3 | RUN mkdir /code 4 | WORKDIR /code 5 | 6 | COPY lib/git-up/version.rb /code/lib/git-up/version.rb 7 | COPY git-up.gemspec /code/git-up.gemspec 8 | COPY Gemfile /code/Gemfile 9 | COPY Gemfile.lock /code/Gemfile.lock 10 | RUN bundle install 11 | 12 | COPY . /code 13 | ENTRYPOINT ["rake"] 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => :test 2 | 3 | task :gem do 4 | sh "rm -r pkg/*" 5 | sh "gem build git-up.gemspec" 6 | sh "mv git-up-*.gem pkg" 7 | puts "Built " + Dir.glob("pkg/*.gem")[0] 8 | end 9 | 10 | require 'rdoc/task' 11 | Rake::RDocTask.new do |rdoc| 12 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 13 | 14 | rdoc.rdoc_dir = 'rdoc' 15 | rdoc.title = "git-up #{version}" 16 | rdoc.rdoc_files.include('README*') 17 | rdoc.rdoc_files.include('lib/**/*.rb') 18 | end 19 | 20 | task :man do 21 | require 'tempfile' 22 | 23 | markdown = File.read("README.md") 24 | markdown.gsub!(/^!\[(.+)\]\(.*\)/, ' \1') 25 | 26 | Tempfile.open('README') do |f| 27 | f.write(markdown) 28 | f.flush 29 | sh "ronn --pipe --roff #{f.path} > man/git-up.1" 30 | end 31 | end 32 | 33 | task :html do 34 | sh "rm -rf html" 35 | sh "mkdir html" 36 | sh "ronn --pipe --html --style=toc README.md > html/index.html" 37 | end 38 | -------------------------------------------------------------------------------- /git-up.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib/', __FILE__) 3 | $:.unshift lib unless $:.include?(lib) 4 | 5 | require 'git-up/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "git-up" 9 | s.version = GitUp::VERSION 10 | s.platform = Gem::Platform::RUBY 11 | s.authors = ["Aanand Prasad", "Elliot Crosby-McCullough", "Adrian Irving-Beer", "Joshua Wehner"] 12 | s.email = ["aanand.prasad@gmail.com", "elliot.cm@gmail.com"] 13 | s.homepage = "http://github.com/aanand/git-up" 14 | s.summary = "git command to fetch and rebase all branches" 15 | s.license = "MIT" 16 | 17 | s.add_dependency "colored", ">= 1.2" 18 | s.add_dependency "grit" 19 | s.add_development_dependency "ronn" 20 | 21 | s.files = Dir.glob("{bin,lib,man}/**/*") + %w(LICENSE README.md) 22 | s.require_path = 'lib' 23 | s.executables = Dir.glob("bin/*").map(&File.method(:basename)) 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Aanand Prasad 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /RVM.md: -------------------------------------------------------------------------------- 1 | Known RVM issues 2 | ================ 3 | 4 | Under RVM some users have [reported](https://github.com/aanand/git-up/issues/32) that running `git-up` works fine, but 5 | running `git up` results in one of many issues, the most common being: 6 | 7 | spawn.rb:187:in `_pspawn': Invalid command name (ArgumentError) 8 | 9 | cause 10 | ----- 11 | 12 | The underlying cause is a combination of the way RVM sets the correct 13 | ruby interpreter in the environment, and the way git interprets and 14 | executes `git up`. 15 | 16 | When you run `git [some command]` git will first look internally to see 17 | if it can fulfil the command, then it will attempt to "shell out" and run 18 | whatever `git-[some command]` executable it can find in your $PATH. 19 | 20 | When git "shells out" all of the environmental variables will be 21 | exposed, and it will be able to find our `git-up` executable in the $PATH. 22 | However because of the way RVM magically sets your ruby interpreter, 23 | when `git-up` is ran `ruby` still points to the non-RVM system default. 24 | 25 | workaround 26 | ---------- 27 | 28 | To fix this we need to make sure that RVM gets to do it's business 29 | before `git-up` is ran. 30 | 31 | RVM provides a handy way of doing this called "wrappers", so let's 32 | generate a wrapper for git-up like so: 33 | 34 | rvm wrapper [ruby-version]@[gemset-name] --no-prefix git-up 35 | 36 | Next we need to make sure that git finds our wrapper first, to do this 37 | we can make use of the `/usr/libexec/git-core` directory that git uses 38 | for some of it's own commands. 39 | 40 | sudo ln -s $HOME/.rvm/bin/git-up /usr/libexec/git-core/git-up 41 | 42 | Finally we need to modify our wrapper to prevent bundler issues when we 43 | are working on ruby projects, so open that file in your favourite 44 | editor, find this line: 45 | 46 | exec git-up "$@" 47 | 48 | and change it to: 49 | 50 | ruby `which git-up` "$@" 51 | 52 | This may seem strange, but it bypasses `ruby_noexec_wrapper` which 53 | may cause issues if you are working in a project with a Gemfile. 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | git-up(1) -- fetch and rebase all locally-tracked remote branches 2 | ================================================================= 3 | 4 | [![Code Climate](https://codeclimate.com/github/aanand/git-up.png)](https://codeclimate.com/github/aanand/git-up) 5 | 6 | WARNING 7 | ------- 8 | 9 | This project is no longer maintained, for several reasons: 10 | 11 | - I've stopped using the workflow that made it relevant to me. 12 | - Git 2.0 updated the default behaviour to remove the main problem it was solving (by changing the default behaviour of `git push` so it acts only on the current branch, instead of all branches). 13 | - As of Git 2.9, `git pull --rebase --autostash` does basically the same thing. 14 | 15 | Accordingly, if you update to Git 2.9 or later, you can use this alias instead of installing `git-up`: 16 | 17 | git config --global alias.up 'pull --rebase --autostash' 18 | 19 | If you'd rather this happened on every `git pull`, then you can do this: 20 | 21 | git config --global pull.rebase true 22 | git config --global rebase.autoStash true 23 | 24 | SYNOPSIS 25 | -------- 26 | 27 | `git pull` has two problems: 28 | 29 | * It merges upstream changes by default, when it's really more polite to [rebase over them](http://gitready.com/advanced/2009/02/11/pull-with-rebase.html), unless your collaborators enjoy a commit graph that looks like bedhead. 30 | * It only updates the branch you're currently on, which means `git push` will shout at you for being behind on branches you don't particularly care about right now. 31 | 32 | Solve them once and for all. 33 | 34 | INSTALL 35 | ------- 36 | 37 | $ gem install git-up 38 | 39 | Windows support is predictably absent. Try the [Python port](https://github.com/msiemens/PyGitUp), which was started for that reason. 40 | 41 | USE 42 | --- 43 | 44 | ![$ git up](http://dl.dropbox.com/u/166030/git-up/screenshot.png) 45 | 46 | ALTHOUGH 47 | -------- 48 | 49 | `git-up` is working well for a lot of people, but a rigorous proof has yet to be formulated that it will definitely not mess with your git setup, delete data or post inane drivel to Hacker News on your behalf. Best practice is to delete your Hacker News account before installing. 50 | 51 | DIFFICULTIES 52 | ------------ 53 | 54 | ### Windows 55 | Windows support is an ongoing pain. Have a look at [this ticket](https://github.com/aanand/git-up/issues/34) if you really need it, or if you're bored. 56 | 57 | ### spawn.rb:187:in `_pspawn': Invalid command name (ArgumentError) 58 | 59 | If you're using RVM and you get this error, [read this](https://github.com/aanand/git-up/blob/master/RVM.md). 60 | 61 | CONFIGURATION 62 | ------------- 63 | 64 | `git-up` has a few configuration options, which use git's configuration system. Each can be set either globally or per-project. To set an option globally, append the `--global` flag to `git config`, which you can run anywhere: 65 | 66 | git config --global git-up.bundler.check true 67 | 68 | To set it within a project, run the command inside that project's directory and omit the `--global` flag: 69 | 70 | cd myproject 71 | git config git-up.bundler.check true 72 | 73 | ### git-up.bundler.check [true|false] 74 | 75 | Default: **false**. If **true**, git-up will check your app for any new bundled gems and suggest a `bundle install` if necessary. 76 | 77 | ### git-up.bundler.autoinstall [true|false] 78 | 79 | Default: **false**. If **true**, and if `git-up.bundler.check` is also set to **true**, git-up will run `bundle install` for you if it finds missing gems. 80 | 81 | ### git-up.fetch.prune [true|false] 82 | 83 | Default: **true**. Append the `--prune` flag when running `git fetch`, if your git version supports it (1.6.6 or greater), telling it to [remove any remote tracking branches which no longer exist on the remote](http://linux.die.net/man/1/git-fetch). 84 | 85 | ### git-up.fetch.all [true|false] 86 | 87 | Default: **false**. Normally, git-up will only fetch remotes for which there is at least one local tracking branch. Setting this option to **true** will make git-up always fetch from all remotes, which is useful if e.g. you use a remote to push to your CI system but never check those branches out. 88 | 89 | ### git-up.rebase.arguments [string] 90 | 91 | Default: **unset**. Additional arguments to pass to `git rebase`. For example, setting this to `--preserve-merges` will recreate your merge commits in the rebased branch. 92 | 93 | ### git-up.rebase.auto [true|false] 94 | 95 | Default: **true**. If this option is set to **false**, git-up will not rebase branches for you. Instead, it will print a message saying they are diverged and let you handle rebasing them later. This can be useful if you have a lot of in-progress work that you don't want to deal with at once, but still want to update other branches. 96 | 97 | ### git-up.rebase.log-hook "COMMAND" 98 | 99 | Default: **unset**. Runs **COMMAND** every time a branch is rebased or fast-forwarded, with the old head as **$1** and the new head as **$2**. This can be used to view logs or diffs of incoming changes. For example: `'echo "changes on $1:"; git log --oneline --decorate $1..$2'` 100 | -------------------------------------------------------------------------------- /man/git-up.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "GIT\-UP" "1" "April 2016" "" "" 5 | . 6 | .SH "NAME" 7 | \fBgit\-up\fR \- fetch and rebase all locally\-tracked remote branches 8 | . 9 | .P 10 | \fIhttps://codeclimate\.com/github/aanand/git\-up\fR 11 | . 12 | .SH "WARNING" 13 | This project is no longer maintained, for several reasons: 14 | . 15 | .IP "\(bu" 4 16 | I\'ve stopped using the workflow that made it relevant to me\. 17 | . 18 | .IP "\(bu" 4 19 | Git 2\.0 updated the default behaviour to remove the main problem it was solving (by changing the default behaviour of \fBgit push\fR so it acts only on the current branch, instead of all branches)\. 20 | . 21 | .IP "\(bu" 4 22 | Auto\-stashing is now supported natively with \fBgit rebase \-\-autostash\fR\. 23 | . 24 | .IP "" 0 25 | . 26 | .P 27 | Accordingly, this line will get you a \fBgit up\fR that works just as well and doesn\'t involve installing any Ruby: 28 | . 29 | .IP "" 4 30 | . 31 | .nf 32 | 33 | git config \-\-global alias\.up \e 34 | \'!git fetch && git rebase \-\-autostash FETCH_HEAD\' 35 | . 36 | .fi 37 | . 38 | .IP "" 0 39 | . 40 | .SH "SYNOPSIS" 41 | \fBgit pull\fR has two problems: 42 | . 43 | .IP "\(bu" 4 44 | It merges upstream changes by default, when it\'s really more polite to rebase over them \fIhttp://gitready\.com/advanced/2009/02/11/pull\-with\-rebase\.html\fR, unless your collaborators enjoy a commit graph that looks like bedhead\. 45 | . 46 | .IP "\(bu" 4 47 | It only updates the branch you\'re currently on, which means \fBgit push\fR will shout at you for being behind on branches you don\'t particularly care about right now\. 48 | . 49 | .IP "" 0 50 | . 51 | .P 52 | Solve them once and for all\. 53 | . 54 | .SH "INSTALL" 55 | . 56 | .nf 57 | 58 | $ gem install git\-up 59 | . 60 | .fi 61 | . 62 | .P 63 | Windows support is predictably absent\. Try the Python port \fIhttps://github\.com/msiemens/PyGitUp\fR, which was started for that reason\. 64 | . 65 | .SH "USE" 66 | . 67 | .nf 68 | 69 | $ git up 70 | . 71 | .fi 72 | . 73 | .SH "ALTHOUGH" 74 | \fBgit\-up\fR is working well for a lot of people, but a rigorous proof has yet to be formulated that it will definitely not mess with your git setup, delete data or post inane drivel to Hacker News on your behalf\. Best practice is to delete your Hacker News account before installing\. 75 | . 76 | .SH "DIFFICULTIES" 77 | . 78 | .SS "Windows" 79 | Windows support is an ongoing pain\. Have a look at this ticket \fIhttps://github\.com/aanand/git\-up/issues/34\fR if you really need it, or if you\'re bored\. 80 | . 81 | .SS "spawn\.rb:187:in `_pspawn\': Invalid command name (ArgumentError)" 82 | If you\'re using RVM and you get this error, read this \fIhttps://github\.com/aanand/git\-up/blob/master/RVM\.md\fR\. 83 | . 84 | .SH "CONFIGURATION" 85 | \fBgit\-up\fR has a few configuration options, which use git\'s configuration system\. Each can be set either globally or per\-project\. To set an option globally, append the \fB\-\-global\fR flag to \fBgit config\fR, which you can run anywhere: 86 | . 87 | .IP "" 4 88 | . 89 | .nf 90 | 91 | git config \-\-global git\-up\.bundler\.check true 92 | . 93 | .fi 94 | . 95 | .IP "" 0 96 | . 97 | .P 98 | To set it within a project, run the command inside that project\'s directory and omit the \fB\-\-global\fR flag: 99 | . 100 | .IP "" 4 101 | . 102 | .nf 103 | 104 | cd myproject 105 | git config git\-up\.bundler\.check true 106 | . 107 | .fi 108 | . 109 | .IP "" 0 110 | . 111 | .SS "git\-up\.bundler\.check [true|false]" 112 | Default: \fBfalse\fR\. If \fBtrue\fR, git\-up will check your app for any new bundled gems and suggest a \fBbundle install\fR if necessary\. 113 | . 114 | .SS "git\-up\.bundler\.autoinstall [true|false]" 115 | Default: \fBfalse\fR\. If \fBtrue\fR, and if \fBgit\-up\.bundler\.check\fR is also set to \fBtrue\fR, git\-up will run \fBbundle install\fR for you if it finds missing gems\. 116 | . 117 | .SS "git\-up\.fetch\.prune [true|false]" 118 | Default: \fBtrue\fR\. Append the \fB\-\-prune\fR flag when running \fBgit fetch\fR, if your git version supports it (1\.6\.6 or greater), telling it to remove any remote tracking branches which no longer exist on the remote \fIhttp://linux\.die\.net/man/1/git\-fetch\fR\. 119 | . 120 | .SS "git\-up\.fetch\.all [true|false]" 121 | Default: \fBfalse\fR\. Normally, git\-up will only fetch remotes for which there is at least one local tracking branch\. Setting this option to \fBtrue\fR will make git\-up always fetch from all remotes, which is useful if e\.g\. you use a remote to push to your CI system but never check those branches out\. 122 | . 123 | .SS "git\-up\.rebase\.arguments [string]" 124 | Default: \fBunset\fR\. Additional arguments to pass to \fBgit rebase\fR\. For example, setting this to \fB\-\-preserve\-merges\fR will recreate your merge commits in the rebased branch\. 125 | . 126 | .SS "git\-up\.rebase\.auto [true|false]" 127 | Default: \fBtrue\fR\. If this option is set to \fBfalse\fR, git\-up will not rebase branches for you\. Instead, it will print a message saying they are diverged and let you handle rebasing them later\. This can be useful if you have a lot of in\-progress work that you don\'t want to deal with at once, but still want to update other branches\. 128 | . 129 | .SS "git\-up\.rebase\.log\-hook \"COMMAND\"" 130 | Default: \fBunset\fR\. Runs \fBCOMMAND\fR every time a branch is rebased or fast\-forwarded, with the old head as \fB$1\fR and the new head as \fB$2\fR\. This can be used to view logs or diffs of incoming changes\. For example: \fB\'echo "changes on $1:"; git log \-\-oneline \-\-decorate $1\.\.$2\'\fR 131 | -------------------------------------------------------------------------------- /lib/git-up.rb: -------------------------------------------------------------------------------- 1 | require 'colored' 2 | require 'grit' 3 | 4 | require 'git-up/version' 5 | 6 | class GitUp 7 | def run(argv) 8 | @fetch = true 9 | 10 | process_args(argv) 11 | 12 | if @fetch 13 | command = ['git', 'fetch', '--multiple'] 14 | command << '--prune' if prune? 15 | command += config("fetch.all") ? ['--all'] : remotes 16 | 17 | # puts command.join(" ") # TODO: implement a 'debug' config option 18 | system(*command) 19 | raise GitError, "`git fetch` failed" unless $? == 0 20 | end 21 | 22 | @remote_map = nil # flush cache after fetch 23 | 24 | Grit::Git.with_timeout(0) do 25 | with_stash do 26 | returning_to_current_branch do 27 | rebase_all_branches 28 | end 29 | end 30 | end 31 | 32 | check_bundler 33 | rescue GitError => e 34 | puts e.message 35 | exit 1 36 | end 37 | 38 | def process_args(argv) 39 | banner = < 0 81 | 82 | dest_dir = File.join(destination, "man1") 83 | dest_path = File.join(dest_dir, File.basename(man_path)) 84 | 85 | exit(1) unless system "mkdir", "-p", dest_dir 86 | exit(1) unless system "cp", man_path, dest_path 87 | 88 | puts "Installed to #{dest_path}" 89 | 90 | exit 91 | when ["-h"], ["--help"] 92 | $stderr.puts(banner) 93 | exit 94 | when ["-no-f"], ["--no-fetch"] 95 | @fetch = false 96 | else 97 | $stderr.puts(banner) 98 | exit 1 99 | end 100 | end 101 | 102 | def rebase_all_branches 103 | col_width = branches.map { |b| b.name.length }.max + 1 104 | 105 | branches.each do |branch| 106 | remote = remote_map[branch.name] 107 | 108 | curbranch = branch.name.ljust(col_width) 109 | if branch.name == repo.head.name 110 | print curbranch.bold 111 | else 112 | print curbranch 113 | end 114 | 115 | if remote.commit.sha == branch.commit.sha 116 | puts "up to date".green 117 | next 118 | end 119 | 120 | base = merge_base(branch.name, remote.name) 121 | 122 | if base == remote.commit.sha 123 | puts "ahead of upstream".cyan 124 | next 125 | end 126 | 127 | if base == branch.commit.sha 128 | puts "fast-forwarding...".yellow 129 | elsif config("rebase.auto") == 'false' 130 | puts "diverged".red 131 | next 132 | else 133 | puts "rebasing...".yellow 134 | end 135 | 136 | log(branch, remote) 137 | checkout(branch.name) 138 | rebase(remote) 139 | end 140 | end 141 | 142 | def repo 143 | @repo ||= get_repo 144 | end 145 | 146 | def get_repo 147 | repo_dir = `git rev-parse --show-toplevel`.chomp 148 | 149 | if $? == 0 150 | Dir.chdir repo_dir 151 | @repo = Grit::Repo.new(repo_dir) 152 | else 153 | raise GitError, "We don't seem to be in a git repository." 154 | end 155 | end 156 | 157 | def branches 158 | @branches ||= repo.branches.select { |b| remote_map.has_key?(b.name) }.sort_by { |b| b.name } 159 | end 160 | 161 | def remotes 162 | @remotes ||= remote_map.values.map { |r| r.name.split('/', 2).first }.uniq 163 | end 164 | 165 | def remote_map 166 | @remote_map ||= repo.branches.inject({}) { |map, branch| 167 | if remote = remote_for_branch(branch) 168 | map[branch.name] = remote 169 | end 170 | 171 | map 172 | } 173 | end 174 | 175 | def remote_for_branch(branch) 176 | remote_name = repo.config["branch.#{branch.name}.remote"] || "origin" 177 | remote_branch = repo.config["branch.#{branch.name}.merge"] || branch.name 178 | remote_branch.sub!(%r{^refs/heads/}, '') 179 | repo.remotes.find { |r| r.name == "#{remote_name}/#{remote_branch}" } 180 | end 181 | 182 | def with_stash 183 | stashed = false 184 | 185 | if change_count > 0 186 | puts "stashing #{change_count} changes".magenta 187 | repo.git.stash 188 | stashed = true 189 | end 190 | 191 | yield 192 | 193 | if stashed 194 | puts "unstashing".magenta 195 | repo.git.stash({}, "pop") 196 | end 197 | end 198 | 199 | def returning_to_current_branch 200 | unless repo.head.respond_to?(:name) 201 | puts "You're not currently on a branch. I'm exiting in case you're in the middle of something.".red 202 | return 203 | end 204 | 205 | branch_name = repo.head.name 206 | 207 | yield 208 | 209 | unless on_branch?(branch_name) 210 | puts "returning to #{branch_name}".magenta 211 | checkout(branch_name) 212 | end 213 | end 214 | 215 | def checkout(branch_name) 216 | output = repo.git.checkout({}, branch_name) 217 | 218 | unless on_branch?(branch_name) 219 | raise GitError.new("Failed to checkout #{branch_name}", output) 220 | end 221 | end 222 | 223 | def log(branch, remote) 224 | if log_hook = config("rebase.log-hook") 225 | system('sh', '-c', log_hook, 'git-up', branch.name, remote.name) 226 | end 227 | end 228 | 229 | def rebase(target_branch) 230 | current_branch = repo.head 231 | arguments = config("rebase.arguments") 232 | 233 | output, err = repo.git.sh("#{Grit::Git.git_binary} rebase #{arguments} #{target_branch.name}") 234 | 235 | unless on_branch?(current_branch.name) and is_fast_forward?(current_branch, target_branch) 236 | raise GitError.new("Failed to rebase #{current_branch.name} onto #{target_branch.name}", output+err) 237 | end 238 | end 239 | 240 | def check_bundler 241 | return unless use_bundler? 242 | 243 | begin 244 | require 'bundler' 245 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile') 246 | Gem.loaded_specs.clear 247 | Bundler.setup 248 | rescue Bundler::GemNotFound, Bundler::GitError 249 | puts 250 | print 'Gems are missing. '.yellow 251 | 252 | if config("bundler.autoinstall") == 'true' 253 | puts "Running `bundle install`.".yellow 254 | system "bundle", "install" 255 | else 256 | puts "You should `bundle install`.".yellow 257 | end 258 | end 259 | end 260 | 261 | def is_fast_forward?(a, b) 262 | merge_base(a.name, b.name) == b.commit.sha 263 | end 264 | 265 | def merge_base(a, b) 266 | repo.git.send("merge-base", {}, a, b).strip 267 | end 268 | 269 | def on_branch?(branch_name=nil) 270 | repo.head.respond_to?(:name) and repo.head.name == branch_name 271 | end 272 | 273 | class GitError < StandardError 274 | def initialize(message, output=nil) 275 | @msg = "#{message.red}" 276 | 277 | if output 278 | @msg << "\n" 279 | @msg << "Here's what Git said:".red 280 | @msg << "\n" 281 | @msg << output 282 | end 283 | end 284 | 285 | def message 286 | @msg 287 | end 288 | end 289 | 290 | private 291 | 292 | def use_bundler? 293 | use_bundler_config? and File.exists? 'Gemfile' 294 | end 295 | 296 | def use_bundler_config? 297 | if ENV.has_key?('GIT_UP_BUNDLER_CHECK') 298 | puts <<-EOS.yellow 299 | The GIT_UP_BUNDLER_CHECK environment variable is deprecated. 300 | You can now tell git-up to check (or not check) for missing 301 | gems on a per-project basis using git's config system. To 302 | set it globally, run this command anywhere: 303 | 304 | git config --global git-up.bundler.check true 305 | 306 | To set it within a project, run this command inside that 307 | project's directory: 308 | 309 | git config git-up.bundler.check true 310 | 311 | Replace 'true' with 'false' to disable checking. 312 | EOS 313 | end 314 | 315 | config("bundler.check") == 'true' || ENV['GIT_UP_BUNDLER_CHECK'] == 'true' 316 | end 317 | 318 | def prune? 319 | required_version = "1.6.6" 320 | config_value = config("fetch.prune") 321 | 322 | if git_version_at_least?(required_version) 323 | config_value != 'false' 324 | else 325 | if config_value == 'true' 326 | puts "Warning: fetch.prune is set to 'true' but your git version doesn't seem to support it (#{git_version} < #{required_version}). Defaulting to 'false'.".yellow 327 | end 328 | 329 | false 330 | end 331 | end 332 | 333 | def change_count 334 | @change_count ||= begin 335 | repo.git.status(:porcelain => true, :'untracked-files' => 'no').split("\n").count 336 | end 337 | end 338 | 339 | def config(key) 340 | repo.config["git-up.#{key}"] 341 | end 342 | 343 | def git_version_at_least?(required_version) 344 | (version_array(git_version) <=> version_array(required_version)) >= 0 345 | end 346 | 347 | def version_array(version_string) 348 | version_string.split('.').map { |s| s.to_i } 349 | end 350 | 351 | def git_version 352 | `git --version`[/\d+(\.\d+)+/] 353 | end 354 | end 355 | 356 | --------------------------------------------------------------------------------