Class: GitUp

Inherits:
Object
  • Object
show all
Defined in:
lib/git-up.rb,
lib/git-up/version.rb

Defined Under Namespace

Classes: GitError

Constant Summary collapse

VERSION =
"0.5.0"

Instance Method Summary collapse

Instance Method Details

#branchesObject



63
64
65
# File 'lib/git-up.rb', line 63

def branches
  @branches ||= repo.branches.select { |b| remote_map.has_key?(b.name) }
end

#check_bundlerObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/git-up.rb', line 142

def check_bundler
  return unless use_bundler?

  begin
    require 'bundler'
    ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile')
    Bundler.setup
  rescue Bundler::GemNotFound, Bundler::GitError
    puts
    print 'Gems are missing. '.yellow

    if config("bundler.autoinstall") == 'true'
      puts "Running `bundle install`.".yellow
      system "bundle", "install"
    else
      puts "You should `bundle install`.".yellow
    end
  end
end

#checkout(branch_name) ⇒ Object



124
125
126
127
128
129
130
# File 'lib/git-up.rb', line 124

def checkout(branch_name)
  output = repo.git.checkout({}, branch_name)

  unless on_branch?(branch_name)
    raise GitError.new("Failed to checkout #{branch_name}", output)
  end
end

#get_repoObject



53
54
55
56
57
58
59
60
61
# File 'lib/git-up.rb', line 53

def get_repo
  git_dir = `git rev-parse --git-dir`

  if $? == 0
    @repo = Grit::Repo.new(File.dirname(git_dir))
  else
    raise GitError, "We don't seem to be in a git repository."
  end
end

#is_fast_forward?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


162
163
164
# File 'lib/git-up.rb', line 162

def is_fast_forward?(a, b)
  merge_base(a.name, b.name) == b.commit.sha
end

#merge_base(a, b) ⇒ Object



166
167
168
# File 'lib/git-up.rb', line 166

def merge_base(a, b)
  repo.git.send("merge-base", {}, a, b).strip
end

#on_branch?(branch_name = nil) ⇒ Boolean

Returns:

  • (Boolean)


170
171
172
# File 'lib/git-up.rb', line 170

def on_branch?(branch_name=nil)
  repo.head.respond_to?(:name) and repo.head.name == branch_name
end

#rebase(target_branch) ⇒ Object



132
133
134
135
136
137
138
139
140
# File 'lib/git-up.rb', line 132

def rebase(target_branch)
  current_branch = repo.head

  output, err = repo.git.sh("#{Grit::Git.git_binary} rebase #{target_branch.name}")

  unless on_branch?(current_branch.name) and is_fast_forward?(current_branch, target_branch)
    raise GitError.new("Failed to rebase #{current_branch.name} onto #{target_branch.name}", output+err)
  end
end

#remote_for_branch(branch) ⇒ Object



81
82
83
84
85
86
# File 'lib/git-up.rb', line 81

def remote_for_branch(branch)
  remote_name   = repo.config["branch.#{branch.name}.remote"] || "origin"
  remote_branch = repo.config["branch.#{branch.name}.merge"] || branch.name
  remote_branch.sub!(%r{^refs/heads/}, '')
  repo.remotes.find { |r| r.name == "#{remote_name}/#{remote_branch}" }
end

#remote_mapObject



71
72
73
74
75
76
77
78
79
# File 'lib/git-up.rb', line 71

def remote_map
  @remote_map ||= repo.branches.inject({}) { |map, branch|
    if remote = remote_for_branch(branch)
      map[branch.name] = remote
    end

    map
  }
end

#remotesObject



67
68
69
# File 'lib/git-up.rb', line 67

def remotes
  @remotes ||= remote_map.values.map { |r| r.name.split('/', 2).first }.uniq
end

#repoObject



49
50
51
# File 'lib/git-up.rb', line 49

def repo
  @repo ||= get_repo
end

#returning_to_current_branchObject



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/git-up.rb', line 108

def returning_to_current_branch
  unless repo.head.respond_to?(:name)
    puts "You're not currently on a branch. I'm exiting in case you're in the middle of something.".red
    return
  end

  branch_name = repo.head.name

  yield

  unless on_branch?(branch_name)
    puts "returning to #{branch_name}".magenta
    checkout(branch_name)
  end
end

#runObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/git-up.rb', line 5

def run
  system('git', 'fetch', '--multiple', *remotes)
  raise GitError, "`git fetch` failed" unless $? == 0
  @remote_map = nil # flush cache after fetch

  with_stash do
    returning_to_current_branch do
      col_width = branches.map { |b| b.name.length }.max + 1

      branches.each do |branch|
        remote = remote_map[branch.name]

        print branch.name.ljust(col_width)

        if remote.commit.sha == branch.commit.sha
          puts "up to date".green
          next
        end

        base = merge_base(branch.name, remote.name)

        if base == remote.commit.sha
          puts "ahead of upstream".blue
          next
        end

        if base == branch.commit.sha
          puts "fast-forwarding...".yellow
          checkout(branch.name)
          rebase(remote)
        else
          puts "diverged".red
        end

      end
    end
  end

  check_bundler
rescue GitError => e
  puts e.message
  exit 1
end

#with_stashObject



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/git-up.rb', line 88

def with_stash
  stashed = false

  status = repo.status
  change_count = status.added.length + status.changed.length + status.deleted.length

  if change_count > 0
    puts "stashing #{change_count} changes".magenta
    repo.git.stash
    stashed = true
  end

  yield

  if stashed
    puts "unstashing".magenta
    repo.git.stash({}, "pop")
  end
end