module GitHelpers::GitBranchInfos

more infos on branches

Public Instance Methods

ahead_behind(br1, br2) click to toggle source
# File lib/git_helpers/branch_infos.rb, line 4
def ahead_behind(br1, br2)
        with_dir do
                out=run_simple("git rev-list --left-right --count #{br1.shellescape}...#{br2.shellescape}", error: :quiet)
                out.match(/(\d+)\s+(\d+)/) do |m|
                        return m[1].to_i, m[2].to_i #br1 is ahead by m[1], behind by m[2] from br2
                end
                return 0, 0
        end
end
branch_infos(*branches, local: false, remote: false, tags: false, merged: nil, no_merged: nil) click to toggle source
# File lib/git_helpers/branch_infos.rb, line 14
def branch_infos(*branches, local: false, remote: false, tags: false, merged: nil, no_merged: nil)
        query = []
        query << "--merged=#{merged.shellescape}" if merged
        query << "--no_merged=#{no_merged.shellescape}" if no_merged
        query += branches.map {|b| name_branch(b)}
        query << 'refs/heads' if local
        query << 'refs/remotes' if remote
        query << 'refs/tags' if tags
        r={}
        format=%w(refname refname:short objecttype objectsize objectname upstream upstream:short upstream:track upstream:remotename upstream:remoteref push push:short push:track push:remotename push:remoteref HEAD symref)
        #Note push:remoteref is buggy (empty if no push refspec specified)
        #and push:track is upstream:track (cf my patch to the git mailing
        #list to correct that)
        out=run_simple("git for-each-ref --format '#{format.map {|f| "%(#{f})"}.join(';')}' #{query.shelljoin}", chomp: :lines)
        out.each do |l|
                infos=l.split(';')
                full_name=infos[0]
                infos=Hash[format.zip(infos)]

                infos[:name]=infos["refname:short"]
                infos[:head]=!(infos["HEAD"].empty? or infos["HEAD"]==" ")

                type=if full_name.start_with?("refs/heads/")
                                        :local
                                elsif full_name.start_with?("refs/remotes/")
                                        :remote
                                elsif full_name.start_with?("refs/tags/")
                                        :tags
                                end
                name = case type
                                when :local
                                        full_name.delete_prefix("refs/heads/")
                                when :remote
                                        full_name.delete_prefix("refs/remotes/")
                                when :tags
                                        full_name.delete_prefix("refs/tags/")
                                end
                infos[:type]=type
                infos[:name]=name

                infos[:upstream_ahead]=0
                infos[:upstream_behind]=0
                infos[:push_ahead]=0
                infos[:push_behind]=0
                track=infos["upstream:track"]
                track.match(/ahead (\d+)/) do |m|
                        infos[:upstream_ahead]=m[1].to_i
                end
                track.match(/behind (\d+)/) do |m|
                        infos[:upstream_behind]=m[1].to_i
                end

                ## git has a bug for push:track
                # ptrack=infos["push:track"]
                # ptrack.match(/ahead (\d+)/) do |m|
                #   infos[:push_ahead]=m[1].to_i
                # end
                # ptrack.match(/behind (\d+)/) do |m|
                #   infos[:push_behind]=m[1].to_i
                # end
                unless infos["push"].empty?
                        ahead, behind=ahead_behind(infos["refname"], infos["push"])
                        infos[:push_ahead]=ahead
                        infos[:push_behind]=behind
                end

                origin = infos["upstream:remotename"]
                unless origin.empty?
                        upstream_short=infos["upstream:short"]
                        infos["upstream:name"]=upstream_short.delete_prefix(origin+"/")
                end
                pushorigin = infos["push:remotename"]
                unless pushorigin.empty?
                        push_short=infos["push:short"]
                        if push_short.empty?
                                infos["push:name"]=infos["refname:short"]
                        else
                                infos["push:name"]= push_short.delete_prefix(pushorigin+"/")
                        end
                end

                r[full_name]=infos
        end
        r
end
format_branch_infos(infos, compare: nil, merged: nil, cherry: false, log: false) click to toggle source
# File lib/git_helpers/branch_infos.rb, line 100
def format_branch_infos(infos, compare: nil, merged: nil, cherry: false, log: false)
        # warning, here we pass the info values, ie infos should be a list
        infos.each do |i|
                name=i["refname:short"]
                upstream=i["upstream:short"]
                push=i["push:short"]
                color=:magenta
                if merged
                        color=:red #not merged
                        [*merged].each do |br|
                                ahead, _behind=ahead_behind(i["refname"], br)
                                if ahead==0
                                        color=:magenta
                                        break
                                end
                        end
                end
                r="#{i["HEAD"]}#{name.color(color)}"
                if compare
                        ahead, behind=ahead_behind(i["refname"], compare)
                        r << "↑#{ahead}" unless ahead==0
                        r << "↓#{behind}" unless behind==0
                end
                unless upstream.empty?
                        r <<  "  @{u}"
                        r << "=@{push}" if push==upstream
                        r << "=#{upstream.color(:yellow)}"
                        r << "↑#{i[:upstream_ahead]}" unless i[:upstream_ahead]==0
                        r << "↓#{i[:upstream_behind]}" unless i[:upstream_behind]==0
                end
                unless push.empty? or push == upstream
                        r << "  @{push}=#{push.color(:yellow)}"
                        r << "↑#{i[:push_ahead]}" unless i[:push_ahead]==0
                        r << "↓#{i[:push_behind]}" unless i[:push_behind]==0
                end
                if log
                        log_options=case log
                        when Hash
                                log.map {|k,v| "--#{k}=#{v.shellescape}"}.join(' ')
                        when String
                                log
                        else
                                ""
                        end
                        r << " → "+run_simple("git -c color.ui=always log --date=human --oneline --no-walk #{log_options} #{name}")
                end
                puts r
                if cherry #todo: add push cherry?
                        if upstream and i[:upstream_ahead] != 0 || i[:upstream_behind] != 0
                                ch=run_simple("git -c color.ui=always log --left-right --topo-order --oneline #{name}...#{upstream}")
                                ch.each_line do |l|
                                        puts "   #{l}"
                                end
                        end
                end
        end
end
name(branch='HEAD',**args) click to toggle source
# File lib/git_helpers/branch_infos.rb, line 161
def name(branch='HEAD',**args)
        self.branch(branch).name(**args)
end
name_branch(branch='HEAD',**args) click to toggle source
# File lib/git_helpers/branch_infos.rb, line 158
def name_branch(branch='HEAD',**args)
        self.branch(branch).full_name(**args)
end
recursive_upstream(*branches, local: true) click to toggle source

return all local upstreams of branches, recursively

# File lib/git_helpers/branch_infos.rb, line 166
def recursive_upstream(*branches, local: true)
        require 'tsort'
        each_node=lambda do |&b| branches.each(&b) end
        each_child=lambda do |br, &b|
                upstream=branch(br).upstream(short: false)
                upstreams=[]
                upstreams << upstream.to_s unless upstream.nil? or local && upstream.to_s.start_with?("refs/remotes/")
                upstreams.each(&b)
        end
        TSort.tsort(each_node, each_child)
end