public
Description: The Git TextMate Bundle
Homepage: http://tim.theenchanter.com/
Clone URL: git://github.com/timcharper/git-tmbundle.git
git-tmbundle / Support / lib / commands / branch.rb
100644 228 lines (190 sloc) 5.868 kb
1
2
3
4
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
class SCM::Git::Branch < SCM::Git::CommandProxyBase
  module BranchHelperMethods
    def shorten(name)
      name && name.gsub(/refs\/(heads|remotes)\//, "")
    end
    
    def format_name(name, format = :short)
      (format == :short || format == nil) ? shorten(name) : name
    end
  end
  
  include BranchHelperMethods
  
  def [](name)
    SCM::Git::Branch::BranchProxy.new(@base, self, name)
  end
  
  def create_and_switch(name)
    base.command("checkout", "-b", name)
  end
  
  def create(name, options = {})
    args = ["branch"]
    args << "--track" if options[:autotrack]
    args << name
    case
    when ! options[:source].blank?
      args << options[:source]
    when ! options[:tag].blank?
      args << options[:tag]
    when ! options[:revision].blank?
      args << options[:revision]
    end
    base.command(*args)
  end
  
  def switch(name)
    result = base.command("checkout", name)
    rescan_project
    result
  end
  
  def all_for_local_or_remote(which, options = {})
    list(which, options).map do |branch_params|
      BranchProxy.new(@base, self, branch_params)
    end
  end
  
  def all(which = :both, options = {})
    branches = []
    which = [:local, :remote] if which == :both
    [which].flatten.each do |side|
      branches.concat all_for_local_or_remote(side, options)
    end
    
    branches.compact
  end
  
  def list(which = :local, options= {})
    params = []
    case which
    when :local then params << "refs/heads"
    when :remote then params << "refs/remotes"
    end
    result = base.command("for-each-ref", *params).split("\n").map do |e|
      next unless /^([a-f0-9]{40})\s*commit\s*(.+)$/.match(e)
      {:ref => $1, :name => $2}
    end.compact
    
    if options[:remote]
      r_prefix = @base.remote[options[:remote]].remote_branch_prefix
      result.delete_if { |r| r[:name][0..(r_prefix.length-1)] != r_prefix }
    end
    result
  end
  
  def list_names(which = :local, options = {})
    list(which, options).map do |b|
      format_name(b[:name], options[:format])
    end
  end
  
  alias names list_names
  
  def current
    _current_name = current_name(:long)
    list(:local).each do |branch_params|
      return BranchProxy.new(@base, self, branch_params) if branch_params[:name] == _current_name
    end
    
    nil
  end
  
  def current_name(format = :short)
    return unless /^ref: (.+)$/.match(File.read(@base.path_for(".git/HEAD")))
    format_name($1, format)
  end
  
  def delete(name, options = {})
    branch_type = options[:branch_type] || (name.include?("/") ? :remote : :local)
    case branch_type.to_sym
    when :remote then delete_remote(name, options)
    when :local then delete_local(name, options)
    else
      raise "Unknown branch type: #{branch_type}"
    end
  end
  
  def delete_local(name, options = {})
    
    output = base.command("branch", options[:force] ? "-D" : "-d", name).strip
    outcome = case output
      when "Deleted branch #{name}."
        :success
      when /error.+(not a strict subset|not an ancestor of your current HEAD)/
        :unmerged
      else
        :unknown
      end
    {:outcome => outcome, :output => output, :branch => name }
  end
  
  def delete_remote(name, options = {})
    remote, branch = name.split("/")
    output = base.command("push", remote, ":#{branch}")
    outcome = case output
    when /refs\/heads\/#{branch}: .+\-\> deleted/, /\[deleted\]/
      :success
    when /error: dst refspec .+ does not match any existing ref on the remote and does not start with refs\/./
      :branch_not_found
    else
      :unknown
    end
    { :outcome => outcome, :output => output, :remote => remote, :branch => branch }
  end
  
  def compare_status(left, right)
    new_commits = Hash.new(0)
    result = @base.command("rev-list", "--left-right", "#{left}...#{right}").split("\n").each do |line|
      case line[0..0]
      when "<"
        new_commits[:left] += 1
      when ">"
        new_commits[:right] += 1
      end
    end
    
    case
    when new_commits[:left] == 0 && new_commits[:right] == 0
      :same
    when new_commits[:left] == 0 && new_commits[:right] >= 0
      :behind
    when new_commits[:left] >= 0 && new_commits[:right] == 0
      :ahead
    else
      :diverged
    end
  end
  
  class BranchProxy
    include BranchHelperMethods
    attr_reader :ref
    
    def initialize(base, parent, options = {})
      @base = base
      @parent = parent
      @name = options[:name]
      @ref = options[:ref]
    end
  
    def name(format = :short)
      format_name(@name, format)
    end
    
    def local?
      @local ||= (@name[0..10] == "refs/heads/")
    end
  
    def current?
      @parent.current_name(:long) == name(:long)
    end
  
    def remote?
      ! @local
    end
  
    def default?
      raise "implement me"
    end
    
    def remote(reload = false)
      remote_name(reload) && @base.remote[remote_name]
    end
    
    def remote_name(reload = false)
      @remote_name = nil if reload
      @remote_name ||= @base.config["branch.#{name}.remote"]
    end
  
    def remote_name=(value)
      @remote_name, @tracking_branch_name = nil
      @base.config["branch.#{name}.remote"] = value
    end
  
    def merge(reload = false)
      @merge = nil if (reload)
      @merge ||= @base.config["branch.#{name}.merge"]
    end
  
    def merge=(value)
      @merge, @tracking_branch_name = nil
      @base.config["branch.#{name}.merge"] = value
    end
    
    def tracking_branch_name(format = :short)
      @tracking_branch_name ||= (remote && merge && remote.remote_branch_name_for(merge, :long))
      format_name(@tracking_branch_name, format)
    end
    
    # tell if a branch
    def tracking_status
      return unless tracking_branch_name(:long)
      @parent.compare_status(name(:long), tracking_branch_name(:long))
    end
  end
end