Niro* by cho45

#5 Show branches that are changed only recently

Often, some projects using git have too many branches, but most branches are not updated in days. The old branches are very bother.

I wrote a script which shows only recent branches named 'git-branch-recent'.

#!/usr/bin/env ruby -Ku

require 'pathname'
require "optparse"

class GitRecentCommand
	Ref = Struct.new(:hash, :name, :time, :rtime, :author, :subject)

	def dot_git
		@dot_git ||= Pathname.new(`git rev-parse --git-dir`.chomp)
	end

	def self.run(argv)
		self.new.option(argv).run
	end

	def initialize(opts={})
		@opts = {
			:strict  => false,
			:max_num => 20,
		}.update(opts)
	end

	def option(argv)
		opts = @opts
		argv = argv.dup
		OptionParser.new do |parser|
			parser.instance_eval do
				self.banner = <<-EOB.gsub(/^\t+/, "")
					Usage: #{$0} [opts]

				EOB

				separator ""

				separator "Options:"
				on("-s", "--strict", "Running on strict mode (very heavy)") do |foreground|
					opts[:strict] = true
				end

				on("-n", "--number NUMBER", "Number branch to show") do |num|
					opts[:max_num] = num.to_i
				end

				parse!(argv)
			end
		end
		self
	end

	def run
		details = @opts[:strict] ? recent_branches_strict : recent_branches_fast
		details = details.sort_by {|ref| ref.time }.last(@opts[:max_num])

		remote_master = nil
		rtime_width = name_width = author_width = 0
		details.each do |ref|
			name_width    = ref.name.size   if ref.name.size   > name_width
			author_width  = ref.author.size if ref.author.size > author_width
			rtime_width   = ref.rtime.size  if ref.rtime.size  > rtime_width
			remote_master = ref.hash        if ref.name == 'origin/master'
		end

		details.each {|ref|
			ref.instance_eval {
				out = "\e[32m% -#{name_width}s\e[39m % #{rtime_width}s %s \e[31m% -#{author_width}s\e[39m %s" % [
					name,
					rtime,
					hash[/^.{7}/],
					author,
					subject
				]
				puts (hash == remote_master) ? "\e[7m#{out}\e[0m" : out
			}
		}
	end

	# search recent branches by file mtimes
	def recent_branches_fast
		refs = []
		refs.concat Pathname.glob(dot_git + 'refs/heads/**/*')
		refs.concat Pathname.glob(dot_git + 'refs/remotes/**/*')

		branches = refs.reject {|r| r.directory? }.sort_by {|r| r.mtime }.last(@opts[:max_num]).map {|r|
			ref = r.read.chomp
			if name = ref[/ref: (.+)/, 1]
				(dot_git + name).read.chomp
			else
				ref
			end
		}
		retrieve_branch_details(branches)
	end

	# search recent branches by retrieving whole branch information
	def recent_branches_strict
		branches = `git branch -a`.gsub!(/^\*?\s+|\(no branch\)\s*/, "").split(/\n/).map {|i|
			i.split(/ -> /)[0]
		}
		retrieve_branch_details(branches)
	end

	# retrieve branch details information from branch names
	def retrieve_branch_details(branches)
		details = []
		IO.popen("-", "r+") do |io|
			if io.nil?
				args = [ "show", "--pretty=format:%H\t%d\t%ct\t%cr\t%an\t%s", *branches ]
				args << "--"
				exec "git", *args
			else
				while l = io.gets
					next unless l =~ /^[a-z0-9]{40}/
					hash, refs, time, rtime, author, subject = * l.chomp.split(/\t/)
					refs.gsub!(/^\s*\(|\)\s*$/, '')

					refs.split(/\s*,\s*/).each do |ref|
						is_remote = ref[%r{refs/remotes}]
						ref.gsub!(%r{refs/(remotes|heads)/}, '')
						details.push Ref.new(hash, ref, time.to_i, rtime, author, subject)
					end
				end
			end
		end
		details
	end
end

GitRecentCommand.run(ARGV)

[[git]]

blog comments powered by Disqus
login